f44a31c337
This is a memory monitoring interface implemented with reference to Linux's PSI (Pressure Stall Information), which can send notifications when the system's remaining memory is below the threshold. The following example code sets two different thresholds. When the system memory is below 10MB, a notification is triggered. When the system memory is below 20 MB, a notification (POLLPRI event) is triggered every 1s. ``` int main(int argc, FAR char *argv[]) { struct pollfd fds[2]; int ret; if (argc == 2) { char *ptr = malloc(1024*1024*atoi(argv[1])); printf("Allocating %d MB\n", atoi(argv[1])); ptr[0] = 0; return 0; } fds[0].fd = open("/proc/pressure/memory", O_RDWR); fds[1].fd = open("/proc/pressure/memory", O_RDWR); fds[0].events = POLLPRI; fds[1].events = POLLPRI; dprintf(fds[0].fd, "%llu -1", 1024LLU*1024 * 10); dprintf(fds[1].fd, "%llu 1000000", 1024LLU*1024 * 20); while (1) { ret = poll(fds, 2, -1); if (ret > 0) { printf("Memory pressure: POLLPRI, %d\n", ret); } } return 0; } ``` https://docs.kernel.org/accounting/psi.html Signed-off-by: yinshengkai <yinshengkai@xiaomi.com>
1245 lines
37 KiB
C
1245 lines
37 KiB
C
/****************************************************************************
|
|
* fs/procfs/fs_procfs.c
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed with
|
|
* this work for additional information regarding copyright ownership. The
|
|
* ASF licenses this file to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance with the
|
|
* License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
* License for the specific language governing permissions and limitations
|
|
* under the License.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/statfs.h>
|
|
#include <sys/stat.h>
|
|
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <fnmatch.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <debug.h>
|
|
|
|
#include <nuttx/irq.h>
|
|
#include <nuttx/arch.h>
|
|
#include <nuttx/sched.h>
|
|
#include <nuttx/kmalloc.h>
|
|
#include <nuttx/fs/fs.h>
|
|
#include <nuttx/fs/procfs.h>
|
|
|
|
#include "mount/mount.h"
|
|
|
|
/****************************************************************************
|
|
* External Definitions
|
|
****************************************************************************/
|
|
|
|
extern const struct procfs_operations g_clk_operations;
|
|
extern const struct procfs_operations g_cpuinfo_operations;
|
|
extern const struct procfs_operations g_cpuload_operations;
|
|
extern const struct procfs_operations g_critmon_operations;
|
|
extern const struct procfs_operations g_fdt_operations;
|
|
extern const struct procfs_operations g_iobinfo_operations;
|
|
extern const struct procfs_operations g_irq_operations;
|
|
extern const struct procfs_operations g_meminfo_operations;
|
|
extern const struct procfs_operations g_memdump_operations;
|
|
extern const struct procfs_operations g_mempool_operations;
|
|
extern const struct procfs_operations g_module_operations;
|
|
extern const struct procfs_operations g_pm_operations;
|
|
extern const struct procfs_operations g_proc_operations;
|
|
extern const struct procfs_operations g_tcbinfo_operations;
|
|
extern const struct procfs_operations g_uptime_operations;
|
|
extern const struct procfs_operations g_version_operations;
|
|
extern const struct procfs_operations g_pressure_operations;
|
|
|
|
/* This is not good. These are implemented in other sub-systems. Having to
|
|
* deal with them here is not a good coupling. What is really needed is a
|
|
* run-time procfs registration system vs. a build time, fixed procfs
|
|
* configuration.
|
|
*/
|
|
|
|
extern const struct procfs_operations g_mount_operations;
|
|
extern const struct procfs_operations g_net_operations;
|
|
extern const struct procfs_operations g_netroute_operations;
|
|
extern const struct procfs_operations g_part_operations;
|
|
extern const struct procfs_operations g_smartfs_operations;
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
/* Table of all known / pre-registered procfs handlers / participants. */
|
|
|
|
#ifdef CONFIG_FS_PROCFS_REGISTER
|
|
static const struct procfs_entry_s g_base_entries[] =
|
|
#else
|
|
static const struct procfs_entry_s g_procfs_entries[] =
|
|
#endif
|
|
{
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
|
|
{ "[0-9]*/**", &g_proc_operations, PROCFS_UNKOWN_TYPE },
|
|
{ "[0-9]*", &g_proc_operations, PROCFS_DIR_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_CLK) && !defined(CONFIG_FS_PROCFS_EXCLUDE_CLK)
|
|
{ "clk", &g_clk_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_HAVE_CPUINFO) && !defined(CONFIG_FS_PROCFS_EXCLUDE_CPUINFO)
|
|
{ "cpuinfo", &g_cpuinfo_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if !defined(CONFIG_SCHED_CPULOAD_NONE) && \
|
|
!defined(CONFIG_FS_PROCFS_EXCLUDE_CPULOAD)
|
|
{ "cpuload", &g_cpuload_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifdef CONFIG_SCHED_CRITMONITOR
|
|
{ "critmon", &g_critmon_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_DEVICE_TREE) && !defined(CONFIG_FS_PROCFS_EXCLUDE_FDT)
|
|
{ "fdt", &g_fdt_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_BLOCKS
|
|
{ "fs/blocks", &g_mount_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_MOUNT
|
|
{ "fs/mount", &g_mount_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_FS_SMARTFS) && !defined(CONFIG_FS_PROCFS_EXCLUDE_SMARTFS)
|
|
{ "fs/smartfs**", &g_smartfs_operations, PROCFS_UNKOWN_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_USAGE
|
|
{ "fs/usage", &g_mount_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_MM_IOB) && !defined(CONFIG_FS_PROCFS_EXCLUDE_IOBINFO)
|
|
{ "iobinfo", &g_iobinfo_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifdef CONFIG_SCHED_IRQMONITOR
|
|
{ "irqs", &g_irq_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_MEMINFO
|
|
# ifndef CONFIG_FS_PROCFS_EXCLUDE_MEMDUMP
|
|
{ "memdump", &g_memdump_operations, PROCFS_FILE_TYPE },
|
|
# endif
|
|
{ "meminfo", &g_meminfo_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_MM_HEAP_MEMPOOL) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MEMPOOL)
|
|
{ "mempool", &g_mempool_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_MODULE) && !defined(CONFIG_FS_PROCFS_EXCLUDE_MODULE)
|
|
{ "modules", &g_module_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_NET) && !defined(CONFIG_FS_PROCFS_EXCLUDE_NET)
|
|
{ "net", &g_net_operations, PROCFS_DIR_TYPE },
|
|
# if defined(CONFIG_NET_ROUTE) && !defined(CONFIG_FS_PROCFS_EXCLUDE_ROUTE)
|
|
{ "net/route", &g_netroute_operations, PROCFS_DIR_TYPE },
|
|
{ "net/route/**", &g_netroute_operations, PROCFS_UNKOWN_TYPE },
|
|
# endif
|
|
{ "net/**", &g_net_operations, PROCFS_UNKOWN_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_MTD_PARTITION) && !defined(CONFIG_FS_PROCFS_EXCLUDE_PARTITIONS)
|
|
{ "partitions", &g_part_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_PM) && defined(CONFIG_PM_PROCFS)
|
|
{ "pm", &g_pm_operations, PROCFS_DIR_TYPE },
|
|
{ "pm/**", &g_pm_operations, PROCFS_UNKOWN_TYPE },
|
|
#endif
|
|
|
|
#ifdef CONFIG_FS_PROCFS_INCLUDE_PRESSURE
|
|
{ "pressure", &g_pressure_operations, PROCFS_DIR_TYPE },
|
|
{ "pressure/**", &g_pressure_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
|
|
{ "self", &g_proc_operations, PROCFS_DIR_TYPE },
|
|
{ "self/**", &g_proc_operations, PROCFS_UNKOWN_TYPE },
|
|
#endif
|
|
|
|
#if defined(CONFIG_ARCH_HAVE_TCBINFO) && !defined(CONFIG_FS_PROCFS_EXCLUDE_TCBINFO)
|
|
{ "tcbinfo", &g_tcbinfo_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_UPTIME
|
|
{ "uptime", &g_uptime_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_VERSION
|
|
{ "version", &g_version_operations, PROCFS_FILE_TYPE },
|
|
#endif
|
|
};
|
|
|
|
#ifdef CONFIG_FS_PROCFS_REGISTER
|
|
static const uint8_t g_base_entrycount = sizeof(g_base_entries) /
|
|
sizeof(struct procfs_entry_s);
|
|
|
|
static FAR struct procfs_entry_s *g_procfs_entries;
|
|
static uint8_t g_procfs_entrycount;
|
|
#else
|
|
static const uint8_t g_procfs_entrycount = sizeof(g_procfs_entries) /
|
|
sizeof(struct procfs_entry_s);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
/* File system methods */
|
|
|
|
static int procfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode);
|
|
static int procfs_close(FAR struct file *filep);
|
|
static ssize_t procfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen);
|
|
static ssize_t procfs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen);
|
|
static int procfs_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup);
|
|
static int procfs_ioctl(FAR struct file *filep, int cmd,
|
|
unsigned long arg);
|
|
|
|
static int procfs_dup(FAR const struct file *oldp,
|
|
FAR struct file *newp);
|
|
static int procfs_fstat(FAR const struct file *filep,
|
|
FAR struct stat *buf);
|
|
|
|
static int procfs_opendir(FAR struct inode *mountpt,
|
|
FAR const char *relpath, FAR struct fs_dirent_s **dir);
|
|
static int procfs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
static int procfs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir, FAR struct dirent *entry);
|
|
static int procfs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir);
|
|
|
|
static int procfs_bind(FAR struct inode *blkdriver,
|
|
FAR const void *data, FAR void **handle);
|
|
static int procfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
|
|
unsigned int flags);
|
|
static int procfs_statfs(FAR struct inode *mountpt,
|
|
FAR struct statfs *buf);
|
|
|
|
static int procfs_stat(FAR struct inode *mountpt,
|
|
FAR const char *relpath, FAR struct stat *buf);
|
|
|
|
/* Initialization */
|
|
|
|
#ifdef CONFIG_FS_PROCFS_REGISTER
|
|
static int procfs_initialize(void);
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* 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 mountpt_operations g_procfs_operations =
|
|
{
|
|
procfs_open, /* open */
|
|
procfs_close, /* close */
|
|
procfs_read, /* read */
|
|
procfs_write, /* write */
|
|
NULL, /* seek */
|
|
procfs_ioctl, /* ioctl */
|
|
NULL, /* mmap */
|
|
NULL, /* truncate */
|
|
procfs_poll, /* poll */
|
|
|
|
NULL, /* sync */
|
|
procfs_dup, /* dup */
|
|
procfs_fstat, /* fstat */
|
|
NULL, /* fchstat */
|
|
|
|
procfs_opendir, /* opendir */
|
|
procfs_closedir, /* closedir */
|
|
procfs_readdir, /* readdir */
|
|
procfs_rewinddir, /* rewinddir */
|
|
|
|
procfs_bind, /* bind */
|
|
procfs_unbind, /* unbind */
|
|
procfs_statfs, /* statfs */
|
|
|
|
NULL, /* unlink */
|
|
NULL, /* mkdir */
|
|
NULL, /* rmdir */
|
|
NULL, /* rename */
|
|
procfs_stat, /* stat */
|
|
NULL /* chstat */
|
|
};
|
|
|
|
/* Level 0 contains the directory of active tasks in addition to other
|
|
* statically registered entries with custom handlers. This structure
|
|
* contains a snapshot of the active tasks when the directory is first
|
|
* opened.
|
|
*/
|
|
|
|
struct procfs_level0_s
|
|
{
|
|
struct procfs_dir_priv_s base; /* Base struct for ProcFS dir */
|
|
|
|
/* Our private data */
|
|
|
|
uint8_t lastlen; /* length of last reported static dir */
|
|
pid_t pid[CONFIG_FS_PROCFS_MAX_TASKS]; /* Snapshot of all active task IDs */
|
|
FAR const char *lastread; /* Pointer to last static dir read */
|
|
};
|
|
|
|
/* Level 1 is an internal virtual directory (such as /proc/fs) which
|
|
* will contain one or more additional static entries based on the
|
|
* configuration.
|
|
*/
|
|
|
|
struct procfs_level1_s
|
|
{
|
|
struct procfs_dir_priv_s base; /* Base struct for ProcFS dir */
|
|
|
|
/* Our private data */
|
|
|
|
uint8_t lastlen; /* length of last reported static dir */
|
|
uint8_t subdirlen; /* Length of the subdir search */
|
|
uint16_t firstindex; /* Index of 1st entry matching this subdir */
|
|
FAR const char *lastread; /* Pointer to last static dir read */
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_enum
|
|
****************************************************************************/
|
|
|
|
static void procfs_enum(FAR struct tcb_s *tcb, FAR void *arg)
|
|
{
|
|
FAR struct procfs_level0_s *dir = (FAR struct procfs_level0_s *)arg;
|
|
int index;
|
|
|
|
DEBUGASSERT(dir);
|
|
|
|
/* Add the PID to the list */
|
|
|
|
index = dir->base.nentries;
|
|
if (index >= CONFIG_FS_PROCFS_MAX_TASKS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dir->pid[index] = tcb->pid;
|
|
dir->base.nentries = index + 1;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_sort_pid
|
|
****************************************************************************/
|
|
|
|
static void procfs_sort_pid(FAR struct procfs_level0_s *level0)
|
|
{
|
|
pid_t pid;
|
|
int i;
|
|
int j;
|
|
|
|
/* Sort the process id by Bubble.
|
|
* FIXME: improve searching algorithm.
|
|
*/
|
|
|
|
for (i = 0; i < level0->base.nentries; i++)
|
|
{
|
|
for (j = 0; j < level0->base.nentries - 1 - i; j++)
|
|
{
|
|
if (level0->pid[j] > level0->pid[j + 1])
|
|
{
|
|
pid = level0->pid[j];
|
|
level0->pid[j] = level0->pid[j + 1];
|
|
level0->pid[j + 1] = pid;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_open
|
|
****************************************************************************/
|
|
|
|
static int procfs_open(FAR struct file *filep, FAR const char *relpath,
|
|
int oflags, mode_t mode)
|
|
{
|
|
int x;
|
|
int ret = -ENOENT;
|
|
|
|
finfo("Open '%s'\n", relpath);
|
|
|
|
/* Perform the stat based on the procfs_entry operations */
|
|
|
|
for (x = 0; x < g_procfs_entrycount; x++)
|
|
{
|
|
/* Test if the path matches this entry's specification */
|
|
|
|
if (fnmatch(g_procfs_entries[x].pathpattern, relpath, 0) == 0)
|
|
{
|
|
/* Match found! Stat using this procfs entry */
|
|
|
|
DEBUGASSERT(g_procfs_entries[x].ops &&
|
|
g_procfs_entries[x].ops->open);
|
|
|
|
ret = g_procfs_entries[x].ops->open(filep, relpath, oflags, mode);
|
|
if (ret == OK)
|
|
{
|
|
DEBUGASSERT(filep->f_priv);
|
|
|
|
((FAR struct procfs_file_s *)filep->f_priv)->procfsentry =
|
|
&g_procfs_entries[x];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_close
|
|
****************************************************************************/
|
|
|
|
static int procfs_close(FAR struct file *filep)
|
|
{
|
|
FAR struct procfs_file_s *attr;
|
|
int ret = OK;
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
attr = (FAR struct procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(attr);
|
|
|
|
/* Release the file attributes structure */
|
|
|
|
if (attr->procfsentry->ops->close != NULL)
|
|
{
|
|
ret = attr->procfsentry->ops->close(filep);
|
|
}
|
|
else
|
|
{
|
|
kmm_free(attr);
|
|
}
|
|
|
|
filep->f_priv = NULL;
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_read
|
|
****************************************************************************/
|
|
|
|
static ssize_t procfs_read(FAR struct file *filep, FAR char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct procfs_file_s *handler;
|
|
|
|
finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
handler = (FAR struct procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(handler);
|
|
|
|
/* Call the handler's read routine */
|
|
|
|
return handler->procfsentry->ops->read(filep, buffer, buflen);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_write
|
|
****************************************************************************/
|
|
|
|
static ssize_t procfs_write(FAR struct file *filep, FAR const char *buffer,
|
|
size_t buflen)
|
|
{
|
|
FAR struct procfs_file_s *handler;
|
|
|
|
finfo("buffer=%p buflen=%d\n", buffer, (int)buflen);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
handler = (FAR struct procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(handler);
|
|
|
|
/* Call the handler's write routine */
|
|
|
|
if (handler->procfsentry->ops->write)
|
|
{
|
|
return handler->procfsentry->ops->write(filep, buffer, buflen);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int procfs_poll(FAR struct file *filep, FAR struct pollfd *fds,
|
|
bool setup)
|
|
{
|
|
FAR struct procfs_file_s *handler;
|
|
|
|
finfo("fds=%p setup=%d\n", fds, setup);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
handler = (FAR struct procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(handler);
|
|
|
|
/* Call the handler's poll routine */
|
|
|
|
if (handler->procfsentry->ops->poll)
|
|
{
|
|
return handler->procfsentry->ops->poll(filep, fds, setup);
|
|
}
|
|
|
|
return -ENOSYS;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_ioctl
|
|
****************************************************************************/
|
|
|
|
static int procfs_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
|
|
{
|
|
finfo("cmd: %d arg: %08lx\n", cmd, arg);
|
|
|
|
/* No IOCTL commands supported */
|
|
|
|
return -ENOTTY;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_dup
|
|
*
|
|
* Description:
|
|
* Duplicate open file data in the new file structure.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_dup(FAR const struct file *oldp, FAR struct file *newp)
|
|
{
|
|
FAR struct procfs_file_s *oldattr;
|
|
|
|
finfo("Dup %p->%p\n", oldp, newp);
|
|
|
|
/* Recover our private data from the old struct file instance */
|
|
|
|
oldattr = (FAR struct procfs_file_s *)oldp->f_priv;
|
|
DEBUGASSERT(oldattr);
|
|
|
|
/* Allow lower-level handler do the dup to get it's extra data */
|
|
|
|
return oldattr->procfsentry->ops->dup(oldp, newp);
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_fstat
|
|
*
|
|
* Description:
|
|
* Obtain information about an open file associated with the file
|
|
* descriptor 'fd', and will write it to the area pointed to by 'buf'.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_fstat(FAR const struct file *filep, FAR struct stat *buf)
|
|
{
|
|
FAR struct procfs_file_s *handler;
|
|
|
|
finfo("buf=%p\n", buf);
|
|
|
|
/* Recover our private data from the struct file instance */
|
|
|
|
handler = (FAR struct procfs_file_s *)filep->f_priv;
|
|
DEBUGASSERT(handler);
|
|
|
|
/* The procfs file system contains only directory and data file entries.
|
|
* Since the file has been opened, we know that this is a data file and,
|
|
* at a minimum, readable.
|
|
*/
|
|
|
|
memset(buf, 0, sizeof(struct stat));
|
|
buf->st_mode = S_IFREG | S_IROTH | S_IRGRP | S_IRUSR;
|
|
|
|
/* If the write method is provided, then let's also claim that the file is
|
|
* writable.
|
|
*/
|
|
|
|
if (handler->procfsentry->ops->write != NULL)
|
|
{
|
|
buf->st_mode |= S_IWOTH | S_IWGRP | S_IWUSR;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_opendir
|
|
*
|
|
* Description:
|
|
* Open a directory for read access
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_opendir(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct fs_dirent_s **dir)
|
|
{
|
|
FAR struct procfs_level0_s *level0;
|
|
|
|
finfo("relpath: \"%s\"\n", relpath ? relpath : "NULL");
|
|
DEBUGASSERT(mountpt && dir && relpath);
|
|
|
|
/* The relative must be either:
|
|
*
|
|
* "" - The top level directory of task/thread IDs
|
|
* "<pid>" - The sub-directory of task/thread attributes
|
|
*/
|
|
|
|
if (!relpath || relpath[0] == '\0')
|
|
{
|
|
/* The path refers to the top level directory. Allocate the level0
|
|
* dirent structure.
|
|
*/
|
|
|
|
level0 = (FAR struct procfs_level0_s *)
|
|
kmm_zalloc(sizeof(struct procfs_level0_s));
|
|
if (!level0)
|
|
{
|
|
ferr("ERROR: Failed to allocate the level0 directory structure\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* Take a snapshot of all currently active tasks. Any new tasks
|
|
* added between the opendir() and closedir() call will not be
|
|
* visible.
|
|
*
|
|
* NOTE that interrupts must be disabled throughout the traversal.
|
|
*/
|
|
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
|
|
nxsched_foreach(procfs_enum, level0);
|
|
procfs_sort_pid(level0);
|
|
#else
|
|
level0->base.index = 0;
|
|
level0->base.nentries = 0;
|
|
#endif
|
|
|
|
/* Initialize lastread entries */
|
|
|
|
level0->lastread = "";
|
|
level0->lastlen = 0;
|
|
level0->base.procfsentry = NULL;
|
|
|
|
*dir = (FAR struct fs_dirent_s *)level0;
|
|
}
|
|
else
|
|
{
|
|
int x;
|
|
int ret;
|
|
int len = strlen(relpath);
|
|
|
|
/* Search the static array of procfs_entries */
|
|
|
|
for (x = 0; x < g_procfs_entrycount; x++)
|
|
{
|
|
/* Test if the path matches this entry's specification */
|
|
|
|
if (fnmatch(g_procfs_entries[x].pathpattern, relpath, 0) == 0)
|
|
{
|
|
/* Match found! Call the handler's opendir routine. If
|
|
* successful, this opendir routine will create an entry
|
|
* derived from struct procfs_dir_priv_s as dir.
|
|
*/
|
|
|
|
DEBUGASSERT(g_procfs_entries[x].ops != NULL &&
|
|
g_procfs_entries[x].ops->opendir != NULL);
|
|
|
|
ret = g_procfs_entries[x].ops->opendir(relpath, dir);
|
|
if (ret == OK)
|
|
{
|
|
FAR struct procfs_dir_priv_s *dirpriv;
|
|
|
|
DEBUGASSERT(*dir);
|
|
|
|
/* Set the procfs_entry handler */
|
|
|
|
dirpriv = (FAR struct procfs_dir_priv_s *)(*dir);
|
|
dirpriv->procfsentry = &g_procfs_entries[x];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Test for a sub-string match (e.g. "ls /proc/fs") */
|
|
|
|
else if (strncmp(g_procfs_entries[x].pathpattern, relpath,
|
|
len) == 0)
|
|
{
|
|
FAR struct procfs_level1_s *level1;
|
|
|
|
/* Doing an intermediate directory search */
|
|
|
|
/* The path refers to the top level directory. Allocate
|
|
* the level1 dirent structure.
|
|
*/
|
|
|
|
level1 = (FAR struct procfs_level1_s *)
|
|
kmm_zalloc(sizeof(struct procfs_level1_s));
|
|
if (!level1)
|
|
{
|
|
ferr("ERROR: Failed to allocate the level0 directory "
|
|
"structure\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
level1->base.level = 1;
|
|
level1->base.index = x;
|
|
level1->firstindex = x;
|
|
level1->subdirlen = len;
|
|
level1->lastread = "";
|
|
level1->lastlen = 0;
|
|
level1->base.procfsentry = NULL;
|
|
|
|
*dir = (FAR struct fs_dirent_s *)level1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (x == g_procfs_entrycount)
|
|
{
|
|
return -ENOENT;
|
|
}
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_closedir
|
|
*
|
|
* Description: Close the directory listing
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_closedir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
DEBUGASSERT(mountpt && dir);
|
|
kmm_free(dir);
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_readdir
|
|
*
|
|
* Description: Read the next directory entry
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_readdir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir,
|
|
FAR struct dirent *entry)
|
|
{
|
|
FAR const struct procfs_entry_s *pentry = NULL;
|
|
FAR struct procfs_dir_priv_s *priv;
|
|
FAR struct procfs_level0_s *level0;
|
|
FAR const char *name = NULL;
|
|
unsigned int index;
|
|
int ret = -ENOENT;
|
|
|
|
DEBUGASSERT(mountpt && dir);
|
|
priv = (FAR struct procfs_dir_priv_s *)dir;
|
|
|
|
/* Are we reading the 1st directory level with dynamic PID and static
|
|
* entries?
|
|
*/
|
|
|
|
if (priv->level == 0)
|
|
{
|
|
level0 = (FAR struct procfs_level0_s *)priv;
|
|
|
|
/* Have we reached the end of the PID information */
|
|
|
|
index = priv->index;
|
|
if (index >= priv->nentries)
|
|
{
|
|
index -= priv->nentries;
|
|
|
|
/* We must report the next static entry ... no more PID entries.
|
|
* skip any entries with wildcards in the first segment of the
|
|
* directory name.
|
|
*/
|
|
|
|
while (index < g_procfs_entrycount)
|
|
{
|
|
pentry = &g_procfs_entries[index];
|
|
name = pentry->pathpattern;
|
|
|
|
while (*name != '/' && *name != '\0')
|
|
{
|
|
if (*name == '*' || *name == '[' || *name == '?')
|
|
{
|
|
/* Wildcard found. Skip this entry */
|
|
|
|
index++;
|
|
name = NULL;
|
|
break;
|
|
}
|
|
|
|
name++;
|
|
}
|
|
|
|
/* Test if we skipped this entry */
|
|
|
|
if (name != NULL)
|
|
{
|
|
/* This entry is okay to report. Test if it has a
|
|
* duplicate first level name as the one we just reported.
|
|
* This could happen in the event of procfs_entry_s such
|
|
* as:
|
|
*
|
|
* fs/smartfs
|
|
* fs/nfs
|
|
* fs/nxffs
|
|
*/
|
|
|
|
name = g_procfs_entries[index].pathpattern;
|
|
if (!level0->lastlen ||
|
|
strncmp(name, level0->lastread, level0->lastlen) != 0)
|
|
{
|
|
/* Not a duplicate, return the first segment of this
|
|
* entry
|
|
*/
|
|
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* Skip this entry ... duplicate 1st level name found */
|
|
|
|
index++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Test if we are at the end of the directory */
|
|
|
|
if (index < g_procfs_entrycount)
|
|
{
|
|
/* Report the next static entry */
|
|
|
|
level0->lastlen = strcspn(name, "/");
|
|
level0->lastread = name;
|
|
strlcpy(entry->d_name, name, level0->lastlen + 1);
|
|
|
|
/* If the entry is a directory type OR if the reported name is
|
|
* only a sub-string of the entry (meaning that it contains
|
|
* '/'), then report this entry as a directory.
|
|
*/
|
|
|
|
if (pentry->type == PROCFS_DIR_TYPE ||
|
|
level0->lastlen != strlen(name))
|
|
{
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
entry->d_type = DTYPE_FILE;
|
|
}
|
|
|
|
/* Advance to next entry for the next read */
|
|
|
|
priv->index = priv->nentries + index;
|
|
ret = OK;
|
|
}
|
|
}
|
|
#ifndef CONFIG_FS_PROCFS_EXCLUDE_PROCESS
|
|
else
|
|
{
|
|
/* Verify that the pid still refers to an active task/thread */
|
|
|
|
pid_t pid = level0->pid[index];
|
|
FAR struct tcb_s *tcb = nxsched_get_tcb(pid);
|
|
if (!tcb)
|
|
{
|
|
ferr("ERROR: PID %d is no longer valid\n", pid);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Save the filename=pid and file type=directory */
|
|
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
procfs_snprintf(entry->d_name, NAME_MAX + 1, "%d", pid);
|
|
|
|
/* Set up the next directory entry offset. NOTE that we could use
|
|
* the standard f_pos instead of our own private index.
|
|
*/
|
|
|
|
level0->base.index = index + 1;
|
|
ret = OK;
|
|
}
|
|
#endif /* CONFIG_FS_PROCFS_EXCLUDE_PROCESS */
|
|
}
|
|
|
|
/* Are we reading an intermediate subdirectory? */
|
|
|
|
else if (priv->level > 0 && priv->procfsentry == NULL)
|
|
{
|
|
FAR struct procfs_level1_s *level1;
|
|
|
|
level1 = (FAR struct procfs_level1_s *)priv;
|
|
|
|
/* Test if this entry matches. We assume all entries of the same
|
|
* subdirectory are listed in order in the procfs_entry array.
|
|
*/
|
|
|
|
if (level1->base.index < g_procfs_entrycount &&
|
|
level1->firstindex < g_procfs_entrycount &&
|
|
strncmp(g_procfs_entries[level1->base.index].pathpattern,
|
|
g_procfs_entries[level1->firstindex].pathpattern,
|
|
level1->subdirlen) == 0)
|
|
{
|
|
/* This entry matches. Report the subdir entry */
|
|
|
|
name = &g_procfs_entries[level1->base.index].
|
|
pathpattern[level1->subdirlen + 1];
|
|
level1->lastlen = strcspn(name, "/");
|
|
level1->lastread = name;
|
|
strlcpy(entry->d_name, name, level1->lastlen + 1);
|
|
|
|
/* Some of the search entries contain '**' wildcards. When we
|
|
* report the entry name, we must remove this wildcard search
|
|
* specifier.
|
|
*/
|
|
|
|
while (entry->d_name[level1->lastlen - 1] == '*')
|
|
{
|
|
level1->lastlen--;
|
|
}
|
|
|
|
entry->d_name[level1->lastlen] = '\0';
|
|
|
|
if (name[level1->lastlen] == '/')
|
|
{
|
|
entry->d_type = DTYPE_DIRECTORY;
|
|
}
|
|
else
|
|
{
|
|
entry->d_type = DTYPE_FILE;
|
|
}
|
|
|
|
level1->base.index++;
|
|
ret = OK;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We are performing a directory search of one of the subdirectories
|
|
* and we must let the handler perform the read.
|
|
*/
|
|
|
|
DEBUGASSERT(priv->procfsentry && priv->procfsentry->ops->readdir);
|
|
ret = priv->procfsentry->ops->readdir(dir, entry);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_rewindir
|
|
*
|
|
* Description: Reset directory read to the first entry
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_rewinddir(FAR struct inode *mountpt,
|
|
FAR struct fs_dirent_s *dir)
|
|
{
|
|
FAR struct procfs_dir_priv_s *priv;
|
|
|
|
DEBUGASSERT(mountpt && dir);
|
|
priv = (FAR struct procfs_dir_priv_s *)dir;
|
|
|
|
if (priv->level > 0 && priv->procfsentry == NULL)
|
|
{
|
|
priv->index = ((FAR struct procfs_level1_s *)priv)->firstindex;
|
|
}
|
|
else
|
|
{
|
|
priv->index = 0;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_bind
|
|
*
|
|
* Description: This implements a portion of the mount operation. This
|
|
* function allocates and initializes the mountpoint private data and
|
|
* binds the block driver inode to the filesystem private data. The final
|
|
* binding of the private data (containing the block driver) to the
|
|
* mountpoint is performed by mount().
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_bind(FAR struct inode *blkdriver, FAR const void *data,
|
|
FAR void **handle)
|
|
{
|
|
#ifdef CONFIG_FS_PROCFS_REGISTER
|
|
/* Make sure that we are properly initialized */
|
|
|
|
procfs_initialize();
|
|
#endif
|
|
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_unbind
|
|
*
|
|
* Description: This implements the filesystem portion of the umount
|
|
* operation.
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_unbind(FAR void *handle, FAR struct inode **blkdriver,
|
|
unsigned int flags)
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_statfs
|
|
*
|
|
* Description: Return filesystem statistics
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_statfs(FAR struct inode *mountpt, FAR struct statfs *buf)
|
|
{
|
|
/* Fill in the statfs info */
|
|
|
|
buf->f_type = PROCFS_MAGIC;
|
|
buf->f_bsize = 0;
|
|
buf->f_blocks = 0;
|
|
buf->f_bfree = 0;
|
|
buf->f_bavail = 0;
|
|
buf->f_namelen = NAME_MAX;
|
|
return OK;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_stat
|
|
*
|
|
* Description: Return information about a file or directory
|
|
*
|
|
****************************************************************************/
|
|
|
|
static int procfs_stat(FAR struct inode *mountpt, FAR const char *relpath,
|
|
FAR struct stat *buf)
|
|
{
|
|
int ret = -ENOENT;
|
|
|
|
/* Three path forms are accepted:
|
|
*
|
|
* "" - The relative path refers to the top level directory
|
|
* "<pid>" - If <pid> refers to a currently active task/thread, then it
|
|
* is a directory
|
|
* "<pid>/<attr>" - If <attr> is a recognized attribute then, then it
|
|
* is a file.
|
|
*/
|
|
|
|
memset(buf, 0, sizeof(struct stat));
|
|
if (!relpath || relpath[0] == '\0')
|
|
{
|
|
/* The path refers to the top level directory.
|
|
* It's a read-only directory.
|
|
*/
|
|
|
|
buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
|
|
ret = OK;
|
|
}
|
|
else
|
|
{
|
|
int x;
|
|
int len = strlen(relpath);
|
|
|
|
/* Perform the stat based on the procfs_entry operations */
|
|
|
|
for (x = 0; x < g_procfs_entrycount; x++)
|
|
{
|
|
/* Test if the path matches this entry's specification */
|
|
|
|
if (fnmatch(g_procfs_entries[x].pathpattern, relpath, 0) == 0)
|
|
{
|
|
/* Match found! Stat using this procfs entry */
|
|
|
|
DEBUGASSERT(g_procfs_entries[x].ops &&
|
|
g_procfs_entries[x].ops->stat);
|
|
|
|
return g_procfs_entries[x].ops->stat(relpath, buf);
|
|
}
|
|
|
|
/* Test for an internal subdirectory stat */
|
|
|
|
else if (strncmp(g_procfs_entries[x].pathpattern, relpath,
|
|
len) == 0)
|
|
{
|
|
/* It's an internal subdirectory */
|
|
|
|
buf->st_mode = S_IFDIR | S_IROTH | S_IRGRP | S_IRUSR;
|
|
ret = OK;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_initialize
|
|
*
|
|
* Description:
|
|
* Configure the initial set of entries in the procfs file system.
|
|
*
|
|
* Input Parameters:
|
|
* None
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_FS_PROCFS_REGISTER
|
|
int procfs_initialize(void)
|
|
{
|
|
/* Are we already initialized? */
|
|
|
|
if (g_procfs_entries == NULL)
|
|
{
|
|
/* No.. allocate a modifiable list of entries */
|
|
|
|
g_procfs_entries = (FAR struct procfs_entry_s *)
|
|
kmm_malloc(sizeof(g_base_entries));
|
|
if (g_procfs_entries == NULL)
|
|
{
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/* And copy the fixed entries into the allocated array */
|
|
|
|
memcpy(g_procfs_entries, g_base_entries, sizeof(g_base_entries));
|
|
g_procfs_entrycount = g_base_entrycount;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Name: procfs_register
|
|
*
|
|
* Description:
|
|
* Add a new entry to the procfs file system.
|
|
*
|
|
* NOTE: This function should be called *prior* to mounting the procfs
|
|
* file system to prevent concurrency problems with the modification of
|
|
* the procfs data set while it is in use.
|
|
*
|
|
* Input Parameters:
|
|
* entry - Describes the entry to be registered.
|
|
*
|
|
* Returned Value:
|
|
* Zero (OK) on success; a negated errno value on failure
|
|
*
|
|
****************************************************************************/
|
|
|
|
#ifdef CONFIG_FS_PROCFS_REGISTER
|
|
int procfs_register(FAR const struct procfs_entry_s *entry)
|
|
{
|
|
FAR struct procfs_entry_s *newtable;
|
|
unsigned int newcount;
|
|
size_t newsize;
|
|
int ret = -ENOMEM;
|
|
|
|
/* Make sure that we are properly initialized */
|
|
|
|
procfs_initialize();
|
|
|
|
/* realloc the table of procfs entries.
|
|
*
|
|
* REVISIT: This reallocation may free memory previously used for the
|
|
* procfs entry table. If that table were actively in use, then that
|
|
* could cause procfs logic to use a stale memory pointer! We avoid that
|
|
* problem by requiring that the procfs file be unmounted when the new
|
|
* entry is added. That requirement, however, is not enforced explicitly.
|
|
*
|
|
* Locking the scheduler as done below is insufficient. As would be just
|
|
* marking the entries as volatile.
|
|
*/
|
|
|
|
newcount = g_procfs_entrycount + 1;
|
|
newsize = newcount * sizeof(struct procfs_entry_s);
|
|
|
|
newtable = (FAR struct procfs_entry_s *)
|
|
kmm_realloc(g_procfs_entries, newsize);
|
|
if (newtable != NULL)
|
|
{
|
|
/* Copy the new entry at the end of the reallocated table */
|
|
|
|
memcpy(&newtable[g_procfs_entrycount], entry,
|
|
sizeof(struct procfs_entry_s));
|
|
|
|
/* Instantiate the reallocated table */
|
|
|
|
g_procfs_entries = newtable;
|
|
g_procfs_entrycount = newcount;
|
|
ret = OK;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
#endif
|