From d209e0e238c4a12af79d5c0683aa22ad9371ab2f Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Sat, 12 Aug 2017 07:26:20 -0600 Subject: [PATCH] Add file missed in last commit. Fix naming in some configuration items. --- fs/procfs/Kconfig | 2 +- fs/procfs/fs_procfs.c | 1 - fs/procfs/fs_procfsproc.c | 2 +- net/procfs/net_procfs_route.c | 792 ++++++++++++++++++++++++++++++++++ 4 files changed, 794 insertions(+), 3 deletions(-) create mode 100644 net/procfs/net_procfs_route.c diff --git a/fs/procfs/Kconfig b/fs/procfs/Kconfig index de8cb8c44a..84d89ff21f 100644 --- a/fs/procfs/Kconfig +++ b/fs/procfs/Kconfig @@ -63,7 +63,7 @@ config FS_PROCFS_EXCLUDE_NET depends on NET default n -config FS_PROC_EXCLUDE_ROUTE +config FS_PROCFS_EXCLUDE_ROUTE bool "Exclude routing table" depends on !FS_PROCFS_EXCLUDE_NET && NET_ROUTE default n diff --git a/fs/procfs/fs_procfs.c b/fs/procfs/fs_procfs.c index b684e1825c..708f267f72 100644 --- a/fs/procfs/fs_procfs.c +++ b/fs/procfs/fs_procfs.c @@ -130,7 +130,6 @@ static const struct procfs_entry_s g_procfs_entries[] = #endif #if defined(CONFIG_FS_SMARTFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS) -//{ "fs/smartfs", &smartfs_procfsoperations, PROCFS_DIR_TYPE }, { "fs/smartfs**", &smartfs_procfsoperations, PROCFS_UNKOWN_TYPE }, #endif diff --git a/fs/procfs/fs_procfsproc.c b/fs/procfs/fs_procfsproc.c index 2cf0b7de0e..bcf1dba69b 100644 --- a/fs/procfs/fs_procfsproc.c +++ b/fs/procfs/fs_procfsproc.c @@ -1609,5 +1609,5 @@ static int proc_stat(const char *relpath, struct stat *buf) * Public Functions ****************************************************************************/ -#endif /* CONFIG_FS_PROC_EXCLUDE_PROCESS */ +#endif /* CONFIG_FS_PROCFS_EXCLUDE_PROCESS */ #endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */ diff --git a/net/procfs/net_procfs_route.c b/net/procfs/net_procfs_route.c new file mode 100644 index 0000000000..54fd94b610 --- /dev/null +++ b/net/procfs/net_procfs_route.c @@ -0,0 +1,792 @@ +/**************************************************************************** + * net/procfs/fs_procfs_route.c + * + * Copyright (C) 2017 Gregory Nutt. All rights reserved. + * Author: Gregory Nutt + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * 3. Neither the name NuttX nor the names of its contributors may be + * used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN + * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "route/route.h" + +#if !defined(CONFIG_DISABLE_MOUNTPOINT) && defined(CONFIG_FS_PROCFS) +#ifndef CONFIG_FS_PROCFS_EXCLUDE_ROUTE + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Determines the size of an intermediate buffer that must be large enough + * to handle the longest line generated by this logic. + */ + +#define STATUS_LINELEN 58 + +/* Directory entry indices */ + +#if defined(CONFIG_NET_IPv4) && defined(CONFIG_NET_IPv6) +# define IPv4_INDEX 0 +# define IPv6_INDEX 1 +#elif defined(CONFIG_NET_IPv4) +# define IPv4_INDEX 0 +#elif defined(CONFIG_NET_IPv6) +# define IPv6_INDEX 0 +#endif + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +/* This enumeration identifies all of the nodes that can be accessed via the + * procfs file system. + */ + +enum route_node_e +{ + PROC_ROUTE = 0 /* The top-level directory */ +#ifdef CONFIG_NET_IPv4 + , PROC_ROUTE_IPv4 /* IPv4 routing table */ +#endif +#ifdef CONFIG_NET_IPv6 + , PROC_ROUTE_IPv6 /* IPv4 routing table */ +#endif +}; + +/* This structure describes one open "file" */ + +struct route_file_s +{ + struct procfs_file_s base; /* Base open file structure */ + FAR const char *name; /* Terminal node segment name */ + uint8_t node; /* Type of node (see enum route_node_e) */ + char line[STATUS_LINELEN]; /* Pre-allocated buffer for formatted lines */ +}; + +/* This structure describes one open "directory" */ + +struct route_dir_s +{ + struct procfs_dir_priv_s base; /* Base directory private data */ + FAR const char *name; /* Terminal node segment name */ + uint8_t node; /* Type of node (see enum route_node_e) */ +}; + +/* The structure is used when traversing routing tables */ + +struct route_info_s +{ + FAR char *line; /* Intermediate line buffer pointer */ + FAR char *buffer; /* User buffer */ + size_t linelen; /* Size of the intermediate buffer */ + size_t buflen; /* Size of the user buffer */ + size_t remaining; /* Bytes remaining in user buffer */ + size_t totalsize; /* Accumulated size of the copy */ + off_t offset; /* Skip offset */ + int index; /* Routing table index */ +}; + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/* Helpers */ + +static void route_sprintf(FAR struct route_info_s *info, + FAR const char *fmt, ...); +#ifdef CONFIG_NET_IPv4 +static int route_ipv4_entry(FAR struct net_route_ipv4_s *route, + FAR void *arg); +#endif +#ifdef CONFIG_NET_IPv4 +static int route_ipv6_entry(FAR struct net_route_ipv6_s *route, + FAR void *arg); +#endif +#ifdef CONFIG_NET_IPv4 +static ssize_t route_ipv4_table(FAR struct route_file_s *procfile, + FAR char *buffer, size_t buflen, off_t offset); +#endif +#ifdef CONFIG_NET_IPv6 +static ssize_t route_ipv6_table(FAR struct route_file_s *procfile, + FAR char *buffer, size_t buflen, off_t offset); +#endif + +/* File system methods */ + +static int route_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode); +static int route_close(FAR struct file *filep); +static ssize_t route_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); + +static int route_dup(FAR const struct file *oldp, + FAR struct file *newp); + +static int route_opendir(const char *relpath, + FAR struct fs_dirent_s *dir); +static int route_closedir(FAR struct fs_dirent_s *dir); +static int route_readdir(FAR struct fs_dirent_s *dir); +static int route_rewinddir(FAR struct fs_dirent_s *dir); + +static int route_stat(FAR const char *relpath, FAR struct stat *buf); + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* See fs_mount.c -- this structure is explicitly externed there. + * We use the old-fashioned kind of initializers so that this will compile + * with any compiler. + */ + +const struct procfs_operations net_procfs_routeoperations = +{ + route_open, /* open */ + route_close, /* close */ + route_read, /* read */ + NULL, /* write */ + + route_dup, /* dup */ + + route_opendir, /* opendir */ + route_closedir, /* closedir */ + route_readdir, /* readdir */ + route_rewinddir, /* rewinddir */ + + route_stat /* stat */ +}; + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +/* Well-known paths */ + +static const char g_route_path[] = "net/route"; +#ifdef CONFIG_NET_IPv4 +static const char g_route_ipv4_path[] = "net/route/ipv4"; +#endif +#ifdef CONFIG_NET_IPv6 +static const char g_route_ipv6_path[] = "net/route/ipv6"; +#endif + +/* Subdirectory names */ + +#ifdef CONFIG_NET_IPv4 +static const char g_route_ipv4_subdir[] = "ipv4"; +#endif +#ifdef CONFIG_NET_IPv6 +static const char g_route_ipv6_subdir[] = "ipv6"; +#endif + + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: route_sprintf + * + * Description: + * Format: + * + * 11111111112222222222333 + * 12345678901234567890123456789012 + * SEQ TARGET NETMASK ROUTER + * nnnn. xxxxxxxx xxxxxxxx xxxxxxxx + * + ****************************************************************************/ + +static void route_sprintf(FAR struct route_info_s *info, + FAR const char *fmt, ...) +{ + size_t linesize; + size_t copysize; + va_list ap; + + /* Print the format and data to a line buffer */ + + va_start(ap, fmt); + linesize = vsnprintf(info->line, info->linelen, fmt, ap); + va_end(ap); + + /* Copy the line buffer to the user buffer */ + + copysize = procfs_memcpy(info->line, linesize, + info->buffer, info->remaining, + &info->offset); + + /* Update counts and pointers */ + + info->totalsize += copysize; + info->buffer += copysize; + info->remaining -= copysize; +} + +/**************************************************************************** + * Name: route_ipv4_entry + * + * Description: + * Format: + * + * 11111111112222222222333333333344444444444555 + * 12345678901234567890123456789012345678901234567890123 + * SEQ TARGET NETMASK ROUTER + * nnnn. xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +static int route_ipv4_entry(FAR struct net_route_ipv4_s *route, FAR void *arg) +{ + FAR struct route_info_s *info = (FAR struct route_info_s *)arg; + char target[INET_ADDRSTRLEN]; + char netmask[INET_ADDRSTRLEN]; + char router[INET_ADDRSTRLEN]; + + DEBUGASSERT(info != NULL); + + (void)inet_ntop(AF_INET, &route->target, target, INET_ADDRSTRLEN); + (void)inet_ntop(AF_INET, &route->netmask, netmask, INET_ADDRSTRLEN); + (void)inet_ntop(AF_INET, &route->router, router, INET_ADDRSTRLEN); + + info->index++; + route_sprintf(info, "%4u. %-16s%-16s%-16s\n", + info->index, target, netmask, router); + + return (info->totalsize >= info->buflen) ? 1 : 0; +} +#endif + +/**************************************************************************** + * Name: route_ipv6_entry + * + * Description: + * Format: + * + * 1111111111222222222233333333334444444444555 + * 1234567890123456789012345678901234567890123456789012 + * nnnn. target: xxxx:xxxx:xxxx:xxxxxxxx:xxxx:xxxx:xxxx + * netmask: xxxx:xxxx:xxxx:xxxxxxxx:xxxx:xxxx:xxxx + * router: xxxx:xxxx:xxxx:xxxxxxxx:xxxx:xxxx:xxxx + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +static int route_ipv6_entry(FAR struct net_route_ipv6_s *route, FAR void *arg) +{ + FAR struct route_info_s *info = (FAR struct route_info_s *)arg; + char addr[INET6_ADDRSTRLEN]; + + DEBUGASSERT(info != NULL); + + info->index++; + (void)inet_ntop(AF_INET6, route->target, addr, INET6_ADDRSTRLEN); + + route_sprintf(info, "%4u. TARGET %s\n", info->index, addr); + if (info->totalsize >= info->buflen) + { + return 1; + } + + (void)inet_ntop(AF_INET6, route->netmask, addr, INET6_ADDRSTRLEN); + route_sprintf(info, " NETMASK %s\n", addr); + if (info->totalsize >= info->buflen) + { + return 1; + } + + (void)inet_ntop(AF_INET6, route->router, addr, INET6_ADDRSTRLEN); + route_sprintf(info, " ROUTER %s\n", addr); + return (info->totalsize >= info->buflen) ? 1 : 0; +} +#endif + +/**************************************************************************** + * Name: route_ipv4_table + * + * Description: + * Format: + * + * 11111111112222222222333333333344444444444555 + * 12345678901234567890123456789012345678901234567890123 + * SEQ TARGET NETMASK ROUTER + * nnnn. xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx + * + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv4 +static ssize_t route_ipv4_table(FAR struct route_file_s *procfile, + FAR char *buffer, size_t buflen, off_t offset) +{ + struct route_info_s info; + + memset(&info, 0, sizeof(struct route_info_s)); + info.line = procfile->line; + info.buffer = buffer; + info.linelen = STATUS_LINELEN; + info.buflen = buflen; + info.remaining = buflen; + info.offset = offset; + + /* Generate the header */ + + route_sprintf(&info, "%-4s %-16s%-16s%-16s\n", + "SEQ", "TARGET", "NETMASK", "ROUTER"); + if (info.totalsize < info.buflen) + { + /* Generate each entry in the routing table */ + + (void)net_foreachroute_ipv4(route_ipv4_entry, &info); + } + + return info.totalsize; +} +#endif + +/**************************************************************************** + * Name: route_ipv6_table + ****************************************************************************/ + +#ifdef CONFIG_NET_IPv6 +static ssize_t route_ipv6_table(FAR struct route_file_s *procfile, + FAR char *buffer, size_t buflen, off_t offset) +{ + struct route_info_s info; + + memset(&info, 0, sizeof(struct route_info_s)); + info.line = procfile->line; + info.buffer = buffer; + info.linelen = STATUS_LINELEN; + info.buflen = buflen; + info.remaining = buflen; + info.offset = offset; + + /* Generate each entry in the routing table */ + + (void)net_foreachroute_ipv6(route_ipv6_entry, &info); + return info.totalsize; +} +#endif + +/**************************************************************************** + * Name: route_open + ****************************************************************************/ + +static int route_open(FAR struct file *filep, FAR const char *relpath, + int oflags, mode_t mode) +{ + FAR struct route_file_s *procfile; + FAR const char *name; + uint8_t node; + + finfo("Open '%s'\n", relpath); + + /* PROCFS is read-only. Any attempt to open with any kind of write + * access is not permitted. + * + * REVISIT: Write-able proc files could be quite useful. + */ + + if ((oflags & O_WRONLY) != 0 || (oflags & O_RDONLY) == 0) + { + ferr("ERROR: Only O_RDONLY supported\n"); + return -EACCES; + } + + /* There are only two possibilities */ + +#ifdef CONFIG_NET_IPv4 + if (strcmp(relpath, g_route_ipv4_path) == 0) + { + name = g_route_ipv4_subdir; + node = PROC_ROUTE_IPv4; + } + else +#endif +#ifdef CONFIG_NET_IPv6 + if (strcmp(relpath, g_route_ipv6_path) == 0) + { + name = g_route_ipv6_subdir; + node = PROC_ROUTE_IPv6; + } + else +#endif + { + ferr("ERROR: Invalid path \"%s\"\n", relpath); + return -ENOENT; + } + + /* Allocate a container to hold the task and node selection */ + + procfile = (FAR struct route_file_s *) + kmm_zalloc(sizeof(struct route_file_s)); + if (!procfile) + { + ferr("ERROR: Failed to allocate file container\n"); + return -ENOMEM; + } + + /* Initialize the file container */ + + procfile->name = name; /* Terminal node segment name */ + procfile->node = node; /* Type of node (see enum route_node_e) */ + + /* Save the index as the open-specific state in filep->f_priv */ + + filep->f_priv = (FAR void *)procfile; + return OK; +} + +/**************************************************************************** + * Name: route_close + ****************************************************************************/ + +static int route_close(FAR struct file *filep) +{ + FAR struct route_file_s *procfile; + + /* Recover our private data from the struct file instance */ + + procfile = (FAR struct route_file_s *)filep->f_priv; + DEBUGASSERT(procfile); + + /* Release the file container structure */ + + kmm_free(procfile); + filep->f_priv = NULL; + return OK; +} + +/**************************************************************************** + * Name: route_read + ****************************************************************************/ + +static ssize_t route_read(FAR struct file *filep, FAR char *buffer, + size_t buflen) +{ + FAR struct route_file_s *procfile; + ssize_t ret; + + finfo("buffer=%p buflen=%d\n", buffer, (int)buflen); + + /* Recover our private data from the struct file instance */ + + procfile = (FAR struct route_file_s *)filep->f_priv; + DEBUGASSERT(procfile); + + /* Provide the requested data */ + + switch (procfile->node) + { +#ifdef CONFIG_NET_IPv4 + case PROC_ROUTE_IPv4: /* IPv4 routing table */ + ret = route_ipv4_table(procfile, buffer, buflen, filep->f_pos); + break; +#endif + +#ifdef CONFIG_NET_IPv4 + case PROC_ROUTE_IPv6: /* IPv6 routing table */ + ret = route_ipv6_table(procfile, buffer, buflen, filep->f_pos); + break; +#endif + + default: + ret = -EINVAL; + break; + } + + /* Update the file offset */ + + if (ret > 0) + { + filep->f_pos += ret; + } + + return ret; +} + +/**************************************************************************** + * Name: route_dup + * + * Description: + * Duplicate open file data in the new file structure. + * + ****************************************************************************/ + +static int route_dup(FAR const struct file *oldp, FAR struct file *newp) +{ + FAR struct route_file_s *oldfile; + FAR struct route_file_s *newfile; + + finfo("Dup %p->%p\n", oldp, newp); + + /* Recover our private data from the old struct file instance */ + + oldfile = (FAR struct route_file_s *)oldp->f_priv; + DEBUGASSERT(oldfile); + + /* Allocate a new container to hold the task and node selection */ + + newfile = (FAR struct route_file_s *) + kmm_malloc(sizeof(struct route_file_s)); + if (!newfile) + { + ferr("ERROR: Failed to allocate file container\n"); + return -ENOMEM; + } + + /* The copy the file information from the old container to the new */ + + memcpy(newfile, oldfile, sizeof(struct route_file_s)); + + /* Save the new container in the new file structure */ + + newp->f_priv = (FAR void *)newfile; + return OK; +} + +/**************************************************************************** + * Name: route_opendir + * + * Description: + * Open a directory for read access + * + ****************************************************************************/ + +static int route_opendir(FAR const char *relpath, + FAR struct fs_dirent_s *dir) +{ + FAR struct route_dir_s *level2; + + finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL"); + DEBUGASSERT(relpath && dir && !dir->u.procfs); + + /* Check the relative path */ + + if (strcmp(relpath, g_route_path) != 0) + { +#ifdef CONFIG_NET_IPv4 + if (strcmp(relpath, g_route_ipv4_path) == 0) + { + return -ENOTDIR; + } +#endif +#ifdef CONFIG_NET_IPv6 + if (strcmp(relpath, g_route_ipv6_path) == 0) + { + return -ENOTDIR; + } +#endif + + return -ENOENT; + } + + level2 = (FAR struct route_dir_s *) + kmm_zalloc(sizeof(struct route_dir_s)); + if (!level2) + { + ferr("ERROR: Failed to allocate the directory structure\n"); + return -ENOMEM; + } + + /* This is a second level directory */ + + level2->base.level = 2; + level2->base.nentries = 2; + level2->name = ""; + level2->node = PROC_ROUTE; + dir->u.procfs = (FAR void *)level2; + return OK; +} + +/**************************************************************************** + * Name: route_closedir + * + * Description: Close the directory listing + * + ****************************************************************************/ + +static int route_closedir(FAR struct fs_dirent_s *dir) +{ + FAR struct route_dir_s *priv; + + DEBUGASSERT(dir && dir->u.procfs); + priv = dir->u.procfs; + + if (priv != NULL) + { + kmm_free(priv); + } + + dir->u.procfs = NULL; + return OK; +} + +/**************************************************************************** + * Name: route_readdir + * + * Description: Read the next directory entry + * + ****************************************************************************/ + +static int route_readdir(struct fs_dirent_s *dir) +{ + FAR struct route_dir_s *level2; + FAR const char *dname; + unsigned int index; + + DEBUGASSERT(dir != NULL && dir->u.procfs != NULL); + level2 = dir->u.procfs; + + /* The index determines which entry to return */ + + index = level2->base.index; +#ifdef CONFIG_NET_IPv4 + if (index == IPv4_INDEX) + { + dname = g_route_ipv4_subdir; + } + else +#endif +#ifdef CONFIG_NET_IPv6 + if (index == IPv6_INDEX) + { + dname = g_route_ipv6_subdir; + } + else +#endif + { + /* We signal the end of the directory by returning the special + * error -ENOENT + */ + + finfo("Entry %d: End of directory\n", index); + return -ENOENT; + } + + /* Save the filename and file type */ + + dir->fd_dir.d_type = DTYPE_FILE; + strncpy(dir->fd_dir.d_name, dname, NAME_MAX+1); + + /* Set up the next directory entry offset. NOTE that we could use the + * standard f_pos instead of our own private index. + */ + + level2->base.index = index + 1; + return OK; +} + +/**************************************************************************** + * Name: proc_rewindir + * + * Description: Reset directory read to the first entry + * + ****************************************************************************/ + +static int route_rewinddir(struct fs_dirent_s *dir) +{ + FAR struct route_dir_s *priv; + + DEBUGASSERT(dir && dir->u.procfs); + priv = dir->u.procfs; + + priv->base.index = 0; + return OK; +} + +/**************************************************************************** + * Name: route_stat + * + * Description: Return information about a file or directory + * + ****************************************************************************/ + +static int route_stat(const char *relpath, struct stat *buf) +{ + memset(buf, 0, sizeof(struct stat)); + + if (strcmp(relpath, g_route_path) == 0) + { + buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR; + } + else +#ifdef CONFIG_NET_IPv4 + if (strcmp(relpath, g_route_ipv4_path) == 0) + { + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + } + else +#endif +#ifdef CONFIG_NET_IPv6 +if (strcmp(relpath, g_route_ipv6_path) == 0) + { + buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR; + } +#endif + else + { + return -ENOENT; + } + + return OK; +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +#endif /* CONFIG_FS_PROCFS_EXCLUDE_ROUTE */ +#endif /* !CONFIG_DISABLE_MOUNTPOINT && CONFIG_FS_PROCFS */