diff --git a/fs/Kconfig b/fs/Kconfig index e27224adf0..3485a1878d 100644 --- a/fs/Kconfig +++ b/fs/Kconfig @@ -118,6 +118,7 @@ source "fs/mqueue/Kconfig" source "fs/shm/Kconfig" source "fs/mmap/Kconfig" source "fs/partition/Kconfig" +source "fs/notify/Kconfig" source "fs/fat/Kconfig" source "fs/nfs/Kconfig" source "fs/nxffs/Kconfig" diff --git a/fs/Makefile b/fs/Makefile index 29449b7983..d18a339973 100644 --- a/fs/Makefile +++ b/fs/Makefile @@ -59,6 +59,7 @@ include rpmsgfs/Make.defs include zipfs/Make.defs include mnemofs/Make.defs include v9fs/Make.defs +include notify/Make.defs endif CFLAGS += ${INCDIR_PREFIX}$(TOPDIR)$(DELIM)fs diff --git a/fs/driver/fs_registerblockdriver.c b/fs/driver/fs_registerblockdriver.c index 94a586a957..63bedb73f6 100644 --- a/fs/driver/fs_registerblockdriver.c +++ b/fs/driver/fs_registerblockdriver.c @@ -30,6 +30,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #ifndef CONFIG_DISABLE_MOUNTPOINT @@ -90,7 +91,11 @@ int register_blockdriver(FAR const char *path, node->u.i_bops = bops; node->i_private = priv; - ret = OK; + inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_create(path); +#endif + return OK; } inode_unlock(); diff --git a/fs/driver/fs_registerdriver.c b/fs/driver/fs_registerdriver.c index 1f3be6a4bc..b3db8b9bde 100644 --- a/fs/driver/fs_registerdriver.c +++ b/fs/driver/fs_registerdriver.c @@ -31,6 +31,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Public Functions @@ -89,7 +90,11 @@ int register_driver(FAR const char *path, node->u.i_ops = fops; node->i_private = priv; - ret = OK; + inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_create(path); +#endif + return OK; } inode_unlock(); diff --git a/fs/driver/fs_registermtddriver.c b/fs/driver/fs_registermtddriver.c index e9f6ef0616..8eb76b13d9 100644 --- a/fs/driver/fs_registermtddriver.c +++ b/fs/driver/fs_registermtddriver.c @@ -31,6 +31,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #if defined(CONFIG_MTD) && !defined(CONFIG_DISABLE_MOUNTPOINT) @@ -90,7 +91,11 @@ int register_mtddriver(FAR const char *path, FAR struct mtd_dev_s *mtd, node->u.i_mtd = mtd; node->i_private = priv; - ret = OK; + inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_create(path); +#endif + return OK; } inode_unlock(); diff --git a/fs/driver/fs_registerpipedriver.c b/fs/driver/fs_registerpipedriver.c index 031d4c66f8..ed5f48d8a1 100644 --- a/fs/driver/fs_registerpipedriver.c +++ b/fs/driver/fs_registerpipedriver.c @@ -30,6 +30,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #ifdef CONFIG_PIPES @@ -88,7 +89,11 @@ int register_pipedriver(FAR const char *path, node->u.i_ops = fops; node->i_private = priv; - ret = OK; + inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_create(path); +#endif + return OK; } inode_unlock(); diff --git a/fs/driver/fs_unregisterblockdriver.c b/fs/driver/fs_unregisterblockdriver.c index be697e1f96..73cd6982b8 100644 --- a/fs/driver/fs_unregisterblockdriver.c +++ b/fs/driver/fs_unregisterblockdriver.c @@ -27,6 +27,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Public Functions @@ -49,7 +50,12 @@ int unregister_blockdriver(FAR const char *path) { ret = inode_remove(path); inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(path); +#endif + return OK; } + inode_unlock(); return ret; } diff --git a/fs/driver/fs_unregisterdriver.c b/fs/driver/fs_unregisterdriver.c index 0b5d9b6c76..9b40c1f63f 100644 --- a/fs/driver/fs_unregisterdriver.c +++ b/fs/driver/fs_unregisterdriver.c @@ -27,6 +27,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Public Functions @@ -59,7 +60,12 @@ int unregister_driver(FAR const char *path) { ret = inode_remove(path); inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(path); +#endif + return OK; } + inode_unlock(); return ret; } diff --git a/fs/driver/fs_unregistermtddriver.c b/fs/driver/fs_unregistermtddriver.c index 3442e92cce..b99a46bc91 100644 --- a/fs/driver/fs_unregistermtddriver.c +++ b/fs/driver/fs_unregistermtddriver.c @@ -28,6 +28,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Public Functions @@ -50,7 +51,12 @@ int unregister_mtddriver(FAR const char *path) { ret = inode_remove(path); inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(path); +#endif + return OK; } + inode_unlock(); return ret; } diff --git a/fs/driver/fs_unregisterpipedriver.c b/fs/driver/fs_unregisterpipedriver.c index c8253ca693..f29b90ae58 100644 --- a/fs/driver/fs_unregisterpipedriver.c +++ b/fs/driver/fs_unregisterpipedriver.c @@ -27,6 +27,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #ifdef CONFIG_PIPES @@ -51,8 +52,13 @@ int unregister_pipedriver(FAR const char *path) { ret = inode_remove(path); inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(path); +#endif + return OK; } + inode_unlock(); return ret; } diff --git a/fs/fs_initialize.c b/fs/fs_initialize.c index c7285ee1ab..4424711196 100644 --- a/fs/fs_initialize.c +++ b/fs/fs_initialize.c @@ -26,6 +26,7 @@ #include #include +#include "notify/notify.h" #include "rpmsgfs/rpmsgfs.h" #include "inode/inode.h" #include "aio/aio.h" @@ -97,6 +98,10 @@ void fs_initialize(void) rpmsgfs_server_init(); #endif +#ifdef CONFIG_FS_NOTIFY + notify_initialize(); +#endif + register_reboot_notifier(&g_sync_nb); fs_trace_end(); } diff --git a/fs/mount/fs_mount.c b/fs/mount/fs_mount.c index 40c6b54182..b6804defe6 100644 --- a/fs/mount/fs_mount.c +++ b/fs/mount/fs_mount.c @@ -33,8 +33,9 @@ #include -#include "inode/inode.h" #include "driver/driver.h" +#include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Pre-processor Definitions @@ -494,6 +495,9 @@ int nx_mount(FAR const char *source, FAR const char *target, #ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS RELEASE_SEARCH(&desc); +#endif +#ifdef CONFIG_FS_NOTIFY + notify_create(target); #endif return OK; diff --git a/fs/mount/fs_umount2.c b/fs/mount/fs_umount2.c index e7b9206858..121be901a8 100644 --- a/fs/mount/fs_umount2.c +++ b/fs/mount/fs_umount2.c @@ -32,6 +32,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Public Functions @@ -184,6 +185,9 @@ int nx_umount2(FAR const char *target, unsigned int flags) } RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_unmount(target); +#endif return OK; /* A lot of goto's! But they make the error handling much simpler */ diff --git a/fs/mqueue/mq_open.c b/fs/mqueue/mq_open.c index a95a1575ab..7b30440de5 100644 --- a/fs/mqueue/mq_open.c +++ b/fs/mqueue/mq_open.c @@ -39,6 +39,7 @@ #include "inode/inode.h" #include "mqueue/mqueue.h" +#include "notify/notify.h" /**************************************************************************** * Private Functions Prototypes @@ -331,6 +332,9 @@ static int file_mq_vopen(FAR struct file *mq, FAR const char *mq_name, RELEASE_SEARCH(&desc); leave_critical_section(flags); +#ifdef CONFIG_FS_NOTIFY + notify_open(fullpath, oflags); +#endif return OK; errout_with_inode: diff --git a/fs/mqueue/mq_unlink.c b/fs/mqueue/mq_unlink.c index 55a2630243..5d205cfb0b 100644 --- a/fs/mqueue/mq_unlink.c +++ b/fs/mqueue/mq_unlink.c @@ -34,6 +34,7 @@ #include "inode/inode.h" #include "mqueue/mqueue.h" +#include "notify/notify.h" /**************************************************************************** * Private Functions @@ -173,6 +174,9 @@ int file_mq_unlink(FAR const char *mq_name) inode_unlock(); mq_inode_release(inode); RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(fullpath); +#endif return OK; errout_with_lock: diff --git a/fs/notify/Kconfig b/fs/notify/Kconfig new file mode 100644 index 0000000000..13fe9ce179 --- /dev/null +++ b/fs/notify/Kconfig @@ -0,0 +1,25 @@ +# +# For a description of the syntax of this configuration file, +# see the file kconfig-language.txt in the NuttX tools repository. +# + +config FS_NOTIFY + bool "FS Notify System" + default n + ---help--- + The Fsnotify System + +if FS_NOTIFY +config FSNOTIFY_BUCKET_SIZE + int "Dir hash bucket size" + default 64 + +config FSNOTIFY_MAX_EVENTS + int "Max events in one notify device" + default 1024 + +config FSNOTIFY_FD_POLLWAITERS + int "Max pollwaiters in one notify devcie" + default 2 + +endif # FS_NOTIFY diff --git a/fs/notify/Make.defs b/fs/notify/Make.defs new file mode 100644 index 0000000000..d61c4d84ec --- /dev/null +++ b/fs/notify/Make.defs @@ -0,0 +1,29 @@ +############################################################################ +# fs/notify/Make.defs +# +# 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. +# +############################################################################ + +# Include FSNOTIFY build support + + +ifeq ($(CONFIG_FS_NOTIFY),y) +CSRCS += inotify.c +endif + +DEPPATH += --dep-path notify +VPATH += :notify diff --git a/fs/notify/inotify.c b/fs/notify/inotify.c new file mode 100644 index 0000000000..dbfda950ed --- /dev/null +++ b/fs/notify/inotify.c @@ -0,0 +1,1356 @@ +/**************************************************************************** + * fs/notify/inotify.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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "inode/inode.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + + #define ROUND_UP(x, y) (((x) + (y) - 1) / (y) * (y)) + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +struct inotify_device_s +{ + mutex_t lock; /* Enforces device exclusive access */ + sem_t sem; /* Used to wait for poll events */ + struct list_node events; /* List of queued events */ + struct list_node watches; /* List of watches */ + int count; /* Reference count */ + uint32_t event_size; /* Size of the queue (bytes) */ + uint32_t event_count; /* Number of pending events */ + FAR struct pollfd *fds[CONFIG_FSNOTIFY_FD_POLLWAITERS]; +}; + +struct inotify_event_s +{ + struct list_node node; /* Entry in inotify_device's list */ + struct inotify_event event; /* The user-space event */ +}; + +struct inotify_watch_list_s +{ + struct list_node watches; + FAR char *path; +}; + +struct inotify_watch_s +{ + struct list_node d_node; /* Add to device list */ + struct list_node l_node; /* Add to node hash list */ + int wd; /* Watch descriptor */ + uint32_t mask; /* Event mask for this watch */ + FAR struct inotify_device_s *dev; /* Associated device */ + FAR struct inotify_watch_list_s *list; /* Associated watch list */ +}; + +/**************************************************************************** + * Private Functions Prototypes + ****************************************************************************/ + +static int inotify_open(FAR struct file *filep); +static int inotify_close(FAR struct file *filep); +static ssize_t inotify_read(FAR struct file *filep, FAR char *buffer, + size_t buflen); +static int inotify_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup); +static int inotify_ioctl(FAR struct file *filep, int cmd, + unsigned long arg); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct file_operations g_inotify_fops = +{ + inotify_open, /* open */ + inotify_close, /* close */ + inotify_read, /* read */ + NULL, /* write */ + NULL, /* seek */ + inotify_ioctl, /* ioctl */ + NULL, /* mmap */ + NULL, /* truncate */ + inotify_poll, /* poll */ +}; + +static struct inode g_inotify_inode = +{ + NULL, + NULL, + NULL, + 1, + FSNODEFLAG_TYPE_DRIVER, + { + &g_inotify_fops + } +}; + +static int g_inotify_event_cookie; +static int g_inotify_watch_cookie; +static struct hsearch_data g_inotify_hash; +static mutex_t g_inotify_hash_lock = NXMUTEX_INITIALIZER; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: inotify_alloc_event + * + * Description: + * Initialize a kernel event. + * Size of name is rounded up to sizeof(struct inotify_event). + ****************************************************************************/ + +static FAR struct inotify_event_s * +inotify_alloc_event(int wd, uint32_t mask, uint32_t cookie, + FAR const char *name) +{ + FAR struct inotify_event_s *event; + size_t len = 0; + + if (name != NULL) + { + len = ROUND_UP(strlen(name) + 1, sizeof(struct inotify_event)); + } + + event = kmm_malloc(sizeof(struct inotify_event_s) + len); + if (event == NULL) + { + return NULL; + } + + event->event.wd = wd; + event->event.mask = mask; + event->event.cookie = cookie; + event->event.len = len; + + if (name != NULL) + { + strcpy(event->event.name, name); + } + + return event; +} + +/**************************************************************************** + * Name: inotify_queue_event + * + * Description: + * Queue an event to the inotify device. + * + ****************************************************************************/ + +static void inotify_queue_event(FAR struct inotify_device_s *dev, int wd, + uint32_t mask, uint32_t cookie, + FAR const char *name) +{ + FAR struct inotify_event_s *event; + FAR struct inotify_event_s *last; + int semcnt; + + if (!list_is_empty(&dev->events)) + { + /* Drop this event if it is a dupe of the previous */ + + last = list_last_entry(&dev->events, + struct inotify_event_s, node); + if (last->event.mask == mask && last->event.wd == wd && + last->event.cookie == cookie && + ((name == NULL && last->event.len == 0) || + (name && last->event.len && !strcmp(name, last->event.name)))) + { + return; + } + } + + if (dev->event_count > CONFIG_FSNOTIFY_MAX_EVENTS) + { + ferr("Too many events queued\n"); + return; + } + + if (dev->event_count == CONFIG_FSNOTIFY_MAX_EVENTS) + { + event = inotify_alloc_event(-1, IN_Q_OVERFLOW, cookie, NULL); + } + else + { + event = inotify_alloc_event(wd, mask, cookie, name); + } + + if (event == NULL) + { + return; + } + + dev->event_count++; + dev->event_size += sizeof(struct inotify_event) + event->event.len; + list_add_tail(&dev->events, &event->node); + + poll_notify(dev->fds, CONFIG_FSNOTIFY_FD_POLLWAITERS, POLLIN); + + while (nxsem_get_value(&dev->sem, &semcnt) == 0 && semcnt <= 1) + { + nxsem_post(&dev->sem); + } +} + +/**************************************************************************** + * Name: inotify_remove_watch_no_event + * + * Description: + * Remove a watch from the inotify device without sending an event. + * + ****************************************************************************/ + +static void +inotify_remove_watch_no_event(FAR struct inotify_watch_s *watch) +{ + FAR struct inotify_watch_list_s *list = watch->list; + + list_delete(&watch->d_node); + list_delete(&watch->l_node); + kmm_free(watch); + + if (list_is_empty(&list->watches)) + { + ENTRY item; + item.key = list->path; + hsearch_r(item, DELETE, NULL, &g_inotify_hash); + } +} + +/**************************************************************************** + * Name: inotify_remove_watch + * + * Description: + * Remove a watch from the inotify device. + * + ****************************************************************************/ + +static void inotify_remove_watch(FAR struct inotify_device_s *dev, + FAR struct inotify_watch_s *watch) +{ + inotify_queue_event(dev, watch->wd, IN_IGNORED, 0, NULL); + inotify_remove_watch_no_event(watch); +} + +/**************************************************************************** + * Name: inotify_remove_event + * + * Description: + * Remove a kernel event from the inotify device. + * + ****************************************************************************/ + +static void inotify_remove_event(FAR struct inotify_device_s *dev, + FAR struct inotify_event_s *event) +{ + list_delete(&event->node); + dev->event_size -= sizeof(struct inotify_event) + event->event.len; + dev->event_count--; + kmm_free(event); +} + +/**************************************************************************** + * Name: inotify_alloc_device + * + * Description: + * Allocate a new inotify device. + * + ****************************************************************************/ + +static FAR struct inotify_device_s *inotify_alloc_device(void) +{ + FAR struct inotify_device_s *dev; + + dev = kmm_zalloc(sizeof(struct inotify_device_s)); + if (dev == NULL) + { + return dev; + } + + dev->count = 1; + nxmutex_init(&dev->lock); + nxsem_init(&dev->sem, 0, 0); + list_initialize(&dev->events); + list_initialize(&dev->watches); + return dev; +} + +/**************************************************************************** + * Name: inotify_open + * + * Description: + * Open the inotify device. + * + ****************************************************************************/ + +static int inotify_open(FAR struct file *filep) +{ + FAR struct inotify_device_s *dev = filep->f_priv; + + nxmutex_lock(&dev->lock); + dev->count++; + nxmutex_unlock(&dev->lock); + return OK; +} + +/**************************************************************************** + * Name: inotify_poll + * + * Description: + * Poll the inotify device. + * + ****************************************************************************/ + +static int inotify_poll(FAR struct file *filep, FAR struct pollfd *fds, + bool setup) +{ + FAR struct inotify_device_s *dev = filep->f_priv; + int ret = 0; + int i; + + nxmutex_lock(&dev->lock); + if (!setup) + { + FAR struct pollfd **slot = fds->priv; + *slot = NULL; + fds->priv = NULL; + goto out; + } + + for (i = 0; i < CONFIG_FSNOTIFY_FD_POLLWAITERS; i++) + { + if (dev->fds[i] == 0) + { + dev->fds[i] = fds; + fds->priv = &dev->fds[i]; + break; + } + } + + if (i >= CONFIG_FSNOTIFY_FD_POLLWAITERS) + { + fds->priv = NULL; + ret = -EBUSY; + goto out; + } + + if (!list_is_empty(&dev->events)) + { + poll_notify(dev->fds, CONFIG_FSNOTIFY_FD_POLLWAITERS, POLLIN); + } + +out: + nxmutex_unlock(&dev->lock); + return ret; +} + +/**************************************************************************** + * Name: inotify_ioctl + * + * Description: + * Perform an inotify ioctl. + * + ****************************************************************************/ + +static int inotify_ioctl(FAR struct file *filep, int cmd, unsigned long arg) +{ + FAR struct inotify_device_s *dev = filep->f_priv; + int ret = -ENOTTY; + + switch (cmd) + { + case FIONREAD: + { + FAR int *nbytes = (FAR int *)((uintptr_t)arg); + if (nbytes) + { + *nbytes = dev->event_size; + ret = OK; + } + } + break; + } + + return ret; +} + +/**************************************************************************** + * Name: inotify_read + * + * Description: + * Read from the event list of inotify device. + * + ****************************************************************************/ + +static ssize_t inotify_read(FAR struct file *filp, FAR char *buffer, + size_t len) +{ + FAR struct inotify_device_s *dev = filp->f_priv; + FAR char *start = buffer; + int ret = 0; + + if (len < sizeof(struct inotify_event) || buffer == NULL) + { + return -EINVAL; + } + + nxmutex_lock(&dev->lock); + while (len >= sizeof(struct inotify_event)) + { + if (list_is_empty(&dev->events)) + { + if (start != buffer || (filp->f_oflags & O_NONBLOCK) != 0) + { + break; + } + + nxmutex_unlock(&dev->lock); + ret = nxsem_wait_uninterruptible(&dev->sem); + nxmutex_lock(&dev->lock); + if (ret < 0) + { + break; + } + } + else + { + FAR struct inotify_event_s *event = + list_first_entry(&dev->events, struct inotify_event_s, node); + + size_t eventlen = sizeof(struct inotify_event) + event->event.len; + if (len < eventlen) + { + break; + } + + memcpy(buffer, &event->event, eventlen); + buffer += eventlen; + len -= eventlen; + inotify_remove_event(dev, event); + } + } + + nxmutex_unlock(&dev->lock); + if (start != buffer) + { + ret = buffer - start; + } + + return ret == 0 ? -EAGAIN : ret; +} + +/**************************************************************************** + * Name: inotify_close + * + * Description: + * Close the inotify device. + * + ****************************************************************************/ + +static int inotify_close(FAR struct file *filep) +{ + FAR struct inotify_device_s *dev = filep->f_priv; + + nxmutex_lock(&g_inotify_hash_lock); + nxmutex_lock(&dev->lock); + if (--dev->count > 0) + { + nxmutex_unlock(&dev->lock); + nxmutex_unlock(&g_inotify_hash_lock); + return OK; + } + + /* Destroy all of the watches on this device */ + + while (!list_is_empty(&dev->watches)) + { + FAR struct inotify_watch_s *watch; + watch = list_first_entry(&dev->watches, struct inotify_watch_s, + d_node); + inotify_remove_watch_no_event(watch); + } + + /* Destroy all of the events on this device */ + + while (!list_is_empty(&dev->events)) + { + FAR struct inotify_event_s *event; + event = list_first_entry(&dev->events, struct inotify_event_s, node); + inotify_remove_event(dev, event); + } + + nxmutex_unlock(&dev->lock); + nxmutex_unlock(&g_inotify_hash_lock); + nxmutex_destroy(&dev->lock); + nxsem_destroy(&dev->sem); + kmm_free(dev); + return OK; +} + +/**************************************************************************** + * Name: inotify_get_device_from_fd + * + * Description: + * Get the inotify device from the file descriptor. + * + ****************************************************************************/ + +static FAR struct inotify_device_s *inotify_get_device_from_fd(int fd) +{ + FAR struct file *filep; + + if (fs_getfilep(fd, &filep) < 0) + { + return NULL; + } + + if (filep == NULL || filep->f_inode != &g_inotify_inode) + { + return NULL; + } + + return filep->f_priv; +} + +/**************************************************************************** + * Name: inotify_get_watch_from_list + * + * Description: + * Get the inotify watch from the file node. + * + ****************************************************************************/ + +static FAR struct inotify_watch_s * +inotify_get_watch_from_list(FAR struct inotify_device_s *dev, + FAR struct inotify_watch_list_s *list) +{ + FAR struct inotify_watch_s *watch; + + list_for_every_entry(&list->watches, watch, struct inotify_watch_s, l_node) + { + if (watch->dev == dev) + { + return watch; + } + } + + return NULL; +} + +/**************************************************************************** + * Name: inotify_alloc_watch + * + * Description: + * Allocate a new inotify watch. + * + ****************************************************************************/ + +static FAR struct inotify_watch_s * +inotify_alloc_watch(FAR struct inotify_device_s *dev, + FAR struct inotify_watch_list_s *list, + uint32_t mask) +{ + FAR struct inotify_watch_s *watch; + + watch = kmm_zalloc(sizeof(struct inotify_watch_s)); + if (watch == NULL) + { + return NULL; + } + + watch->dev = dev; + watch->mask = mask; + watch->wd = ++g_inotify_watch_cookie; + watch->list = list; + list_add_tail(&dev->watches, &watch->d_node); + list_add_tail(&list->watches, &watch->l_node); + return watch; +} + +/**************************************************************************** + * Name: inotify_find_watch + * + * Description: + * Find the inotify watch from the watch descriptor. + * + ****************************************************************************/ + +static FAR struct inotify_watch_s * +inotify_find_watch(FAR struct inotify_device_s *dev, int wd) +{ + FAR struct inotify_watch_s *watch; + + list_for_every_entry(&dev->watches, watch, struct inotify_watch_s, d_node) + { + if (watch->wd == wd) + { + return watch; + } + } + + return NULL; +} + +/**************************************************************************** + * Name: inotify_free_watch_list + * + * Description: + * Release a reference to the inotify node. + * + ****************************************************************************/ + +static void inotify_free_watch_list(FAR struct inotify_watch_list_s *list) +{ + FAR struct inotify_watch_s *watch; + FAR struct inotify_device_s *dev; + bool last_iteration = false; + + do + { + if (list_is_singular(&list->watches)) + { + last_iteration = true; + } + + watch = list_first_entry(&list->watches, struct inotify_watch_s, + l_node); + dev = watch->dev; + nxmutex_lock(&dev->lock); + inotify_remove_watch(dev, watch); + nxmutex_unlock(&dev->lock); + } + while (!last_iteration); +} + +/**************************************************************************** + * Name: inotify_alloc_watch_list + * + * Description: + * Add the inotify node from the path. + * + ****************************************************************************/ + +static FAR struct inotify_watch_list_s * +inotify_alloc_watch_list(FAR const char *path) +{ + FAR struct inotify_watch_list_s *list; + FAR ENTRY *result; + ENTRY item; + + list = kmm_zalloc(sizeof(struct inotify_watch_list_s)); + if (list == NULL) + { + return NULL; + } + + list_initialize(&list->watches); + list->path = strdup(path); + if (list->path == NULL) + { + kmm_free(list); + return NULL; + } + + item.key = list->path; + item.data = list; + if (hsearch_r(item, ENTER, &result, &g_inotify_hash) == 0) + { + lib_free(list->path); + kmm_free(list); + return NULL; + } + + return list; +} + +/**************************************************************************** + * Name: inotify_get_watch_list + * + * Description: + * Get the watch list from the path. + * + ****************************************************************************/ + +static FAR struct inotify_watch_list_s * +inotify_get_watch_list(FAR const char *path) +{ + FAR ENTRY *result; + ENTRY item; + + item.key = (FAR char *)path; + if (hsearch_r(item, FIND, &result, &g_inotify_hash) == 0) + { + return NULL; + } + + return (FAR struct inotify_watch_list_s *)result->data; +} + +/**************************************************************************** + * Name: inotify_queue_watch_list_event + * + * Description: + * Queue an event to the watch list. + * + ****************************************************************************/ + +static void +inotify_queue_watch_list_event(FAR struct inotify_watch_list_s *list, + uint32_t mask, uint32_t cookie, + FAR const char *name) +{ + FAR struct inotify_watch_s *watch; + FAR struct inotify_watch_s *w_tmp; + + list_for_every_entry_safe(&list->watches, watch, w_tmp, + struct inotify_watch_s, l_node) + { + uint32_t watch_mask = watch->mask; + + if (watch_mask & mask) + { + FAR struct inotify_device_s *dev = watch->dev; + bool last_iteration = list_is_singular(&list->watches); + + nxmutex_lock(&dev->lock); + inotify_queue_event(dev, watch->wd, mask, cookie, name); + if (watch_mask & IN_ONESHOT) + { + inotify_remove_watch(dev, watch); + if (last_iteration) + { + nxmutex_unlock(&dev->lock); + return; + } + } + + nxmutex_unlock(&dev->lock); + } + } + + if (mask & IN_DELETE_SELF) + { + inotify_free_watch_list(list); + } +} + +/**************************************************************************** + * Name: inotify_queue_parent_event + * + * Description: + * Queue an event to the inotify inode. + * + ****************************************************************************/ + +static void inotify_queue_parent_event(FAR char *path, uint32_t mask, + uint32_t cookie) +{ + FAR struct inotify_watch_list_s *list; + FAR char *name; + + name = basename(path); + if (name == NULL) + { + return; + } + + *(name - 1) = '\0'; + list = inotify_get_watch_list(path); + if (list != NULL) + { + inotify_queue_watch_list_event(list, mask | IN_ISDIR, cookie, name); + } +} + +/**************************************************************************** + * Name: notify_queue_path_event + * + * Description: + * Send the notification by the path. + * + ****************************************************************************/ + +static void notify_queue_path_event(FAR const char *path, uint32_t mask) +{ + FAR struct inotify_watch_list_s *list; + FAR char *abspath; + uint32_t cookie = 0; + + abspath = lib_realpath(path, NULL, true); + if (abspath == NULL) + { + return; + } + + nxmutex_lock(&g_inotify_hash_lock); + if (mask & IN_MOVE) + { + if (mask & IN_MOVED_FROM) + { + ++g_inotify_event_cookie; + } + + cookie = g_inotify_event_cookie; + } + + list = inotify_get_watch_list(abspath); + inotify_queue_parent_event(abspath, mask, cookie); + if (list == NULL) + { + nxmutex_unlock(&g_inotify_hash_lock); + lib_free(abspath); + return; + } + + if (mask & IN_MOVED_FROM) + { + mask ^= IN_MOVED_FROM; + mask |= IN_MOVE_SELF; + } + + if (mask & IN_MOVED_TO) + { + mask ^= IN_MOVED_TO; + } + + if (mask & IN_DELETE) + { + mask ^= IN_DELETE; + mask |= IN_DELETE_SELF; + } + + if (mask != 0) + { + inotify_queue_watch_list_event(list, mask, cookie, NULL); + } + + nxmutex_unlock(&g_inotify_hash_lock); + lib_free(abspath); +} + +/**************************************************************************** + * Name: notify_queue_filep_event + * + * Description: + * Send the notification by the file pointer. + * + ****************************************************************************/ + +static inline void notify_queue_filep_event(FAR struct file *filep, + uint32_t mask) +{ + char path[PATH_MAX]; + int ret; + + ret = file_fcntl(filep, F_GETPATH, path); + if (ret < 0) + { + ferr("Failed to get path\n"); + return; + } + + if (filep->f_oflags & O_DIRECTORY) + { + mask |= IN_ISDIR; + } + + notify_queue_path_event(path, mask); +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: inotify_add_watch + * + * Description: + * Adds a new watch, or modifies an existing watch, for the file whose + * location is specified in pathname; The caller must have read permission + * for this file. The fd argument is a file descriptor referring to the + * inotify instance whose watch list is to be modified. The events to be + * monitored for pathname are specified in the mask bit-mask argument. + * + * Input Parameters: + * fd - The file descriptor associated with an instance of inotify. + * pathname - The path to the file to be monitored. + * mask - The bit mask of events to be monitored. + * + * Returned Value: + * On success, inotify_add_watch() returns a nonnegative watch descriptor. + * On error, -1 is returned and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_add_watch(int fd, FAR const char *pathname, uint32_t mask) +{ + FAR struct inotify_watch_list_s *list; + FAR struct inotify_watch_s *watch; + FAR struct inotify_watch_s *old; + FAR struct inotify_device_s *dev; + FAR char *abspath; + struct stat buf; + int ret; + + if ((mask & IN_ALL_EVENTS) == 0) + { + set_errno(EINVAL); + return ERROR; + } + + dev = inotify_get_device_from_fd(fd); + if (dev == NULL) + { + set_errno(EBADF); + return ERROR; + } + + abspath = lib_realpath(pathname, NULL, mask & IN_DONT_FOLLOW); + if (abspath == NULL) + { + return ERROR; + } + + ret = stat(abspath, &buf); + if (ret < 0) + { + goto out_free; + } + + if (!S_ISDIR(buf.st_mode) && (mask & IN_ONLYDIR)) + { + ret = -ENOTDIR; + goto out_free; + } + + nxmutex_lock(&g_inotify_hash_lock); + nxmutex_lock(&dev->lock); + list = inotify_get_watch_list(abspath); + if (list == NULL) + { + list = inotify_alloc_watch_list(abspath); + if (list == NULL) + { + ret = -ENOMEM; + goto out; + } + } + + old = inotify_get_watch_from_list(dev, list); + if (old != NULL) + { + if (mask & IN_MASK_CREATE) + { + ret = -EEXIST; + goto out; + } + else if (mask & IN_MASK_ADD) + { + old->mask |= mask; + } + else + { + old->mask = mask; + } + + ret = old->wd; + } + else + { + watch = inotify_alloc_watch(dev, list, mask); + if (watch == NULL && list_is_empty(&list->watches)) + { + ENTRY item; + item.key = list->path; + hsearch_r(item, DELETE, NULL, &g_inotify_hash); + ret = -ENOMEM; + goto out; + } + + ret = watch->wd; + } + +out: + nxmutex_unlock(&dev->lock); + nxmutex_unlock(&g_inotify_hash_lock); + +out_free: + lib_free(abspath); + if (ret < 0) + { + set_errno(-ret); + ret = ERROR; + } + + return ret; +} + +/**************************************************************************** + * Name: inotify_rm_watch + * + * Description: + * Removes the watch associated with the watch descriptor wd from the + * inotify instance associated with the file descriptor fd. + * + * Input Parameters: + * fd - The file descriptor associated with an instance of inotify. + * wd - The watch descriptor to be removed. + * + * Returned Value: + * On success, inotify_rm_watch() returns zero. On error, -1 is returned + * and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_rm_watch(int fd, int wd) +{ + FAR struct inotify_device_s *dev; + FAR struct inotify_watch_s *watch; + + dev = inotify_get_device_from_fd(fd); + if (dev == NULL) + { + set_errno(EBADF); + return ERROR; + } + + nxmutex_lock(&g_inotify_hash_lock); + nxmutex_lock(&dev->lock); + watch = inotify_find_watch(dev, wd); + if (watch == NULL) + { + nxmutex_unlock(&dev->lock); + nxmutex_unlock(&g_inotify_hash_lock); + set_errno(EINVAL); + return ERROR; + } + + inotify_remove_watch(dev, watch); + nxmutex_unlock(&dev->lock); + nxmutex_unlock(&g_inotify_hash_lock); + return OK; +} + +/**************************************************************************** + * Name: inotify_init1 + * + * Description: + * Initializes a new inotify instance and returns a file descriptor + * associated with a new inotify event queue. + * + * Input Parameters: + * flags - The following values are recognized in flags: + * IN_NONBLOCK - Set the O_NONBLOCK file status flag on the new open file + * description. Using this flag saves extra calls to fcntl(2) to achieve + * the same result. + * IN_CLOEXEC - Set the close-on-exec (FD_CLOEXEC) flag on the new file + * descriptor. See the description of the O_CLOEXEC flag in open(2) for + * reasons why this may be useful. + * + * Returned Value: + * On success, these system calls return a new file descriptor. + * On error, -1 is returned and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_init1(int flags) +{ + FAR struct inotify_device_s *dev; + int ret; + + if ((flags & ~(IN_NONBLOCK | IN_CLOEXEC)) != 0) + { + ret = -EINVAL; + goto exit_set_errno; + } + + dev = inotify_alloc_device(); + if (dev == NULL) + { + ferr("Failed to allocate inotify device\n"); + ret = -ENOMEM; + goto exit_set_errno; + } + + ret = file_allocate(&g_inotify_inode, O_RDOK | flags, + 0, dev, 0, true); + if (ret < 0) + { + ferr("Failed to allocate inotify fd\n"); + goto exit_with_dev; + } + + return ret; + +exit_with_dev: + nxmutex_destroy(&dev->lock); + nxsem_destroy(&dev->sem); + kmm_free(dev); +exit_set_errno: + set_errno(-ret); + return ERROR; +} + +/**************************************************************************** + * Name: inotify_init + * + * Description: + * Initializes a new inotify instance and returns a file descriptor + * associated with a new inotify event queue. + * + * Returned Value: + * On success, these system calls return a new file descriptor. + * On error, -1 is returned and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_init(void) +{ + return inotify_init1(0); +} + +/**************************************************************************** + * Name: notify_initialize + * + * Description: + * Initializes a new inotify root node. + * + ****************************************************************************/ + +void notify_initialize(void) +{ + int ret; + + ret = hcreate_r(CONFIG_FSNOTIFY_BUCKET_SIZE, &g_inotify_hash); + if (ret != 1) + { + ferr("Failed to create hash table\n"); + } +} + +/**************************************************************************** + * Name: notify_open + * + * Description: + * The hook is called when the file is opened. + * + ****************************************************************************/ + +void notify_open(FAR const char *path, int oflags) +{ + uint32_t mask = IN_OPEN; + + if (oflags & O_DIRECTORY) + { + mask |= IN_ISDIR; + } + + if (oflags & O_CREAT) + { + mask |= IN_CREATE; + } + + notify_queue_path_event(path, mask); +} + +/**************************************************************************** + * Name: notify_close + * + * Description: + * The hook is called when the file is closed. + * + ****************************************************************************/ + +void notify_close(FAR struct file *filep) +{ + if (filep->f_oflags & O_WROK) + { + notify_queue_filep_event(filep, IN_CLOSE_WRITE); + } + else + { + notify_queue_filep_event(filep, IN_CLOSE_NOWRITE); + } +} + +/**************************************************************************** + * Name: notify_close2 + * + * Description: + * The hook is called when the file is closed. + * + ****************************************************************************/ + +void notify_close2(FAR struct inode *inode) +{ + char path[PATH_MAX]; + if (inode_getpath(inode, path, PATH_MAX) >= 0) + { + notify_queue_path_event(path, IN_CLOSE_WRITE); + } +} + +/**************************************************************************** + * Name: notify_read + * + * Description: + * The hook is called when the file is read. + * + ****************************************************************************/ + +void notify_read(FAR struct file *filep) +{ + notify_queue_filep_event(filep, IN_ACCESS); +} + +/**************************************************************************** + * Name: notify_write + * + * Description: + * The hook is called when the file is written. + * + ****************************************************************************/ + +void notify_write(FAR struct file *filep) +{ + notify_queue_filep_event(filep, IN_MODIFY); +} + +/**************************************************************************** + * Name: notify_chstat + * + * Description: + * The hook is called when the file attribute is changed. + * + ****************************************************************************/ + +void notify_chstat(FAR struct file *filep) +{ + notify_queue_filep_event(filep, IN_ATTRIB); +} + +/**************************************************************************** + * Name: notify_unlink + * + * Description: + * The hook is called when the file is unlinked. + * + ****************************************************************************/ + +void notify_unlink(FAR const char *path) +{ + notify_queue_path_event(path, IN_DELETE); +} + +/**************************************************************************** + * Name: notify_unmount + * + * Description: + * The hook is called when the file system is unmounted. + * + ****************************************************************************/ + +void notify_unmount(FAR const char *path) +{ + notify_queue_path_event(path, IN_DELETE | IN_UNMOUNT); +} + +/**************************************************************************** + * Name: notify_mkdir + * + * Description: + * The hook is called when the directory is created. + * + ****************************************************************************/ + +void notify_mkdir(FAR const char *path) +{ + notify_queue_path_event(path, IN_CREATE | IN_ISDIR); +} + +/**************************************************************************** + * Name: notify_create + * + * Description: + * The hook is called symlink is created. + * + ****************************************************************************/ + +void notify_create(FAR const char *path) +{ + notify_queue_path_event(path, IN_CREATE); +} + +/**************************************************************************** + * Name: notify_rename + * + * Description: + * The hook is called when the file is moved. + * + ****************************************************************************/ + +void notify_rename(FAR const char *oldpath, bool oldisdir, + FAR const char *newpath, bool newisdir) +{ + uint32_t newmask = IN_MOVED_TO; + uint32_t oldmask = IN_MOVED_FROM; + + if (newisdir) + { + newmask |= IN_ISDIR; + } + + if (oldisdir) + { + oldmask |= IN_ISDIR; + } + + notify_queue_path_event(oldpath, oldmask); + notify_queue_path_event(newpath, newmask); +} diff --git a/fs/notify/notify.h b/fs/notify/notify.h new file mode 100644 index 0000000000..4a0fc11b02 --- /dev/null +++ b/fs/notify/notify.h @@ -0,0 +1,58 @@ +/**************************************************************************** + * fs/notify/notify.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __FS_NOTIFY_NOTIFY_H +#define __FS_NOTIFY_NOTIFY_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Type Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/* These are internal OS interface and are not available to applications */ + +void notify_open(FAR const char *path, int oflags); +void notify_close(FAR struct file *filep); +void notify_close2(FAR struct inode *inode); +void notify_read(FAR struct file *filep); +void notify_write(FAR struct file *filep); +void notify_chstat(FAR struct file *filep); +void notify_unlink(FAR const char *path); +void notify_unmount(FAR const char *path); +void notify_mkdir(FAR const char *path); +void notify_create(FAR const char *path); +void notify_rename(FAR const char *oldpath, bool oldisdir, + FAR const char *newpath, bool newisdir); +void notify_initialize(void); + +#endif diff --git a/fs/semaphore/sem_close.c b/fs/semaphore/sem_close.c index 90bc1f34b5..abfde41aad 100644 --- a/fs/semaphore/sem_close.c +++ b/fs/semaphore/sem_close.c @@ -33,6 +33,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #ifdef CONFIG_FS_NAMED_SEMAPHORES @@ -116,7 +117,9 @@ int nxsem_close(FAR sem_t *sem) */ inode_unlock(); - +#ifdef CONFIG_FS_NOTIFY + notify_close2(inode); +#endif DEBUGASSERT(inode->i_peer == NULL); inode_free(inode); return OK; diff --git a/fs/semaphore/sem_open.c b/fs/semaphore/sem_open.c index e21e237c1a..028b5e6817 100644 --- a/fs/semaphore/sem_open.c +++ b/fs/semaphore/sem_open.c @@ -40,6 +40,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #include "semaphore/semaphore.h" #ifdef CONFIG_FS_NAMED_SEMAPHORES @@ -222,6 +223,9 @@ int nxsem_open(FAR sem_t **sem, FAR const char *name, int oflags, ...) } RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_open(fullpath, oflags); +#endif return OK; errout_with_inode: diff --git a/fs/semaphore/sem_unlink.c b/fs/semaphore/sem_unlink.c index b90fef5268..5b2e5b3216 100644 --- a/fs/semaphore/sem_unlink.c +++ b/fs/semaphore/sem_unlink.c @@ -34,6 +34,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #include "semaphore/semaphore.h" /**************************************************************************** @@ -138,6 +139,9 @@ int nxsem_unlink(FAR const char *name) inode_unlock(); ret = nxsem_close(&inode->u.i_nsem->ns_sem); RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(fullpath); +#endif return ret; errout_with_lock: diff --git a/fs/shm/shm_open.c b/fs/shm/shm_open.c index 8f75d92fd5..59a96a82ee 100644 --- a/fs/shm/shm_open.c +++ b/fs/shm/shm_open.c @@ -29,6 +29,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" #include "shm/shmfs.h" /**************************************************************************** @@ -149,6 +150,13 @@ errout_with_sem: inode_unlock(); errout_with_search: RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + if (ret >= 0) + { + notify_open(fullpath, oflags); + } +#endif + return ret; } diff --git a/fs/shm/shm_unlink.c b/fs/shm/shm_unlink.c index 7fda33e9e6..20f64171c7 100644 --- a/fs/shm/shm_unlink.c +++ b/fs/shm/shm_unlink.c @@ -27,6 +27,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Private Functions @@ -140,6 +141,13 @@ errout_with_sem: inode_unlock(); errout_with_search: RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + if (ret >= 0) + { + notify_unlink(fullpath); + } +#endif + return ret; } diff --git a/fs/vfs/fs_close.c b/fs/vfs/fs_close.c index a5197e49f7..52924030df 100644 --- a/fs/vfs/fs_close.c +++ b/fs/vfs/fs_close.c @@ -32,6 +32,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" #include "vfs/lock.h" diff --git a/fs/vfs/fs_fchstat.c b/fs/vfs/fs_fchstat.c index 87503d0879..ddcf2af011 100644 --- a/fs/vfs/fs_fchstat.c +++ b/fs/vfs/fs_fchstat.c @@ -31,6 +31,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -191,6 +192,13 @@ int file_fchstat(FAR struct file *filep, FAR struct stat *buf, int flags) ret = inode_chstat(inode, buf, flags, 0); } +#ifdef CONFIG_FS_NOTIFY + if (ret >= 0) + { + notify_chstat(filep); + } +#endif + return ret; } diff --git a/fs/vfs/fs_mkdir.c b/fs/vfs/fs_mkdir.c index fbe71e89bc..a5464bdb9b 100644 --- a/fs/vfs/fs_mkdir.c +++ b/fs/vfs/fs_mkdir.c @@ -32,6 +32,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -165,6 +166,9 @@ int mkdir(const char *pathname, mode_t mode) /* Directory successfully created */ RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_mkdir(pathname); +#endif return OK; errout_with_inode: diff --git a/fs/vfs/fs_open.c b/fs/vfs/fs_open.c index 2913f0cf24..6b0b90564b 100644 --- a/fs/vfs/fs_open.c +++ b/fs/vfs/fs_open.c @@ -38,6 +38,7 @@ #include "inode/inode.h" #include "driver/driver.h" +#include "notify/notify.h" /**************************************************************************** * Private Functions @@ -196,7 +197,15 @@ static int file_vopen(FAR struct file *filep, FAR const char *path, /* Get the file structure of the opened character driver proxy */ - return block_proxy(filep, path, oflags); + ret = block_proxy(filep, path, oflags); +#ifdef CONFIG_FS_NOTIFY + if (ret >= 0) + { + notify_open(path, filep->f_oflags); + } +#endif + + return ret; } #endif @@ -255,6 +264,9 @@ static int file_vopen(FAR struct file *filep, FAR const char *path, } RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_open(path, filep->f_oflags); +#endif return OK; errout_with_inode: diff --git a/fs/vfs/fs_pseudofile.c b/fs/vfs/fs_pseudofile.c index f39f2e5162..a45d2875a8 100644 --- a/fs/vfs/fs_pseudofile.c +++ b/fs/vfs/fs_pseudofile.c @@ -37,6 +37,7 @@ #include #include "inode/inode.h" +#include "notify/notify.h" /**************************************************************************** * Private Types @@ -505,6 +506,9 @@ int pseudofile_create(FAR struct inode **node, FAR const char *path, (*node)->i_private = pf; inode_unlock(); +#ifdef CONFIG_FS_NOTIFY + notify_create(path); +#endif return 0; reserve_err: diff --git a/fs/vfs/fs_read.c b/fs/vfs/fs_read.c index ba5316d31a..0218158d67 100644 --- a/fs/vfs/fs_read.c +++ b/fs/vfs/fs_read.c @@ -33,6 +33,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -96,6 +97,13 @@ ssize_t file_read(FAR struct file *filep, FAR void *buf, size_t nbytes) /* Return the number of bytes read (or possibly an error code) */ +#ifdef CONFIG_FS_NOTIFY + if (ret > 0) + { + notify_read(filep); + } +#endif + return ret; } diff --git a/fs/vfs/fs_rename.c b/fs/vfs/fs_rename.c index 913fddfd06..ddbcfa2076 100644 --- a/fs/vfs/fs_rename.c +++ b/fs/vfs/fs_rename.c @@ -35,6 +35,7 @@ #include #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -67,6 +68,9 @@ static int pseudorename(FAR const char *oldpath, FAR struct inode *oldinode, struct inode_search_s newdesc; FAR struct inode *newinode; FAR char *subdir = NULL; +#ifdef CONFIG_FS_NOTIFY + bool isdir = INODE_IS_PSEUDODIR(oldinode); +#endif int ret; /* According to POSIX, any new inode at this path should be removed @@ -160,6 +164,9 @@ next_subdir: */ inode_remove(newpath); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(newpath); +#endif } inode_release(newinode); @@ -244,6 +251,13 @@ errout_with_lock: errout: RELEASE_SEARCH(&newdesc); +#ifdef CONFIG_FS_NOTIFY + if (ret >= 0) + { + notify_rename(oldpath, isdir, newpath, isdir); + } +#endif + if (subdir != NULL) { lib_free(subdir); @@ -270,6 +284,10 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, FAR struct inode *newinode; FAR const char *newrelpath; FAR char *subdir = NULL; +#ifdef CONFIG_FS_NOTIFY + bool newisdir = false; + bool oldisdir = false; +#endif int ret; DEBUGASSERT(oldinode->u.i_mops); @@ -338,13 +356,26 @@ static int mountptrename(FAR const char *oldpath, FAR struct inode *oldinode, { struct stat buf; +#ifdef CONFIG_FS_NOTIFY + ret = oldinode->u.i_mops->stat(oldinode, oldpath, &buf); + if (ret >= 0) + { + oldisdir = S_ISDIR(buf.st_mode); + } +#endif + next_subdir: ret = oldinode->u.i_mops->stat(oldinode, newrelpath, &buf); if (ret >= 0) { /* Is the directory entry a directory? */ +#ifdef CONFIG_FS_NOTIFY + newisdir = S_ISDIR(buf.st_mode); + if (newisdir) +#else if (S_ISDIR(buf.st_mode)) +#endif { FAR char *subdirname; @@ -417,6 +448,9 @@ next_subdir: */ oldinode->u.i_mops->unlink(oldinode, newrelpath); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(newrelpath); +#endif } } } @@ -438,6 +472,13 @@ errout_with_newsearch: lib_free(subdir); } +#ifdef CONFIG_FS_NOTIFY + if (ret >= 0) + { + notify_rename(oldpath, oldisdir, newpath, newisdir); + } +#endif + return ret; } #endif /* CONFIG_DISABLE_MOUNTPOINT */ diff --git a/fs/vfs/fs_rmdir.c b/fs/vfs/fs_rmdir.c index e4249a4e8a..d144d6c624 100644 --- a/fs/vfs/fs_rmdir.c +++ b/fs/vfs/fs_rmdir.c @@ -31,6 +31,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -165,6 +166,9 @@ int rmdir(FAR const char *pathname) inode_release(inode); RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(pathname); +#endif return OK; errout_with_inode: diff --git a/fs/vfs/fs_symlink.c b/fs/vfs/fs_symlink.c index 0ad492488d..9c6de4f994 100644 --- a/fs/vfs/fs_symlink.c +++ b/fs/vfs/fs_symlink.c @@ -35,6 +35,7 @@ #include #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -167,6 +168,9 @@ int symlink(FAR const char *path1, FAR const char *path2) /* Symbolic link successfully created */ RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_create(path2); +#endif return OK; errout_with_inode: diff --git a/fs/vfs/fs_unlink.c b/fs/vfs/fs_unlink.c index 4844803afc..b68b4da60d 100644 --- a/fs/vfs/fs_unlink.c +++ b/fs/vfs/fs_unlink.c @@ -31,6 +31,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -190,6 +191,9 @@ int nx_unlink(FAR const char *pathname) inode_release(inode); RELEASE_SEARCH(&desc); +#ifdef CONFIG_FS_NOTIFY + notify_unlink(pathname); +#endif return OK; #if !defined(CONFIG_DISABLE_MOUNTPOINT) || !defined(CONFIG_DISABLE_PSEUDOFS_OPERATIONS) diff --git a/fs/vfs/fs_write.c b/fs/vfs/fs_write.c index 3d1164d9e8..df3f203b1c 100644 --- a/fs/vfs/fs_write.c +++ b/fs/vfs/fs_write.c @@ -33,6 +33,7 @@ #include +#include "notify/notify.h" #include "inode/inode.h" /**************************************************************************** @@ -68,6 +69,7 @@ ssize_t file_write(FAR struct file *filep, FAR const void *buf, size_t nbytes) { FAR struct inode *inode; + ssize_t ret; /* Was this file opened for write access? */ @@ -86,7 +88,15 @@ ssize_t file_write(FAR struct file *filep, FAR const void *buf, /* Yes, then let the driver perform the write */ - return inode->u.i_ops->write(filep, buf, nbytes); + ret = inode->u.i_ops->write(filep, buf, nbytes); +#ifdef CONFIG_FS_NOTIFY + if (ret > 0) + { + notify_write(filep); + } +#endif + + return ret; } /**************************************************************************** diff --git a/include/sys/inotify.h b/include/sys/inotify.h new file mode 100644 index 0000000000..2fb1edcefe --- /dev/null +++ b/include/sys/inotify.h @@ -0,0 +1,169 @@ +/**************************************************************************** + * include/sys/inotify.h + * + * 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. + * + ****************************************************************************/ + +#ifndef __INCLUDE_SYS_INOTIFY_H +#define __INCLUDE_SYS_INOTIFY_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +#define IN_ACCESS 0x00000001 /* File was accessed */ +#define IN_MODIFY 0x00000002 /* File was modified */ +#define IN_ATTRIB 0x00000004 /* Metadata changed */ +#define IN_CLOSE_WRITE 0x00000008 /* Writtable file was closed */ +#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed */ +#define IN_OPEN 0x00000020 /* File was opened */ +#define IN_MOVED_FROM 0x00000040 /* File was moved from X */ +#define IN_MOVED_TO 0x00000080 /* File was moved to Y */ +#define IN_CREATE 0x00000100 /* Subfile was created */ +#define IN_DELETE 0x00000200 /* Subfile was deleted */ +#define IN_DELETE_SELF 0x00000400 /* Self was deleted */ +#define IN_MOVE_SELF 0x00000800 /* Self was moved */ + +#define IN_UNMOUNT 0x00002000 /* Backing fs was unmounted */ +#define IN_Q_OVERFLOW 0x00004000 /* Event queued overflowed */ +#define IN_IGNORED 0x00008000 /* File was ignored */ + +#define IN_ONLYDIR 0x01000000 /* Only watch the path if it is a directory. */ +#define IN_DONT_FOLLOW 0x02000000 /* Do not follow a sym link. */ +#define IN_EXCL_UNLINK 0x04000000 /* Exclude events on unlinked objects. */ +#define IN_MASK_CREATE 0x10000000 /* Only create watches. */ + +#define IN_MASK_ADD 0x20000000 /* Add to the mask of an already existing watch */ +#define IN_ISDIR 0x40000000 /* Event occurred against dir */ +#define IN_ONESHOT 0x80000000 /* Only send event once */ + +#define IN_CLOSE (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* Close */ +#define IN_MOVE (IN_MOVED_FROM | IN_MOVED_TO) /* Moves */ + +#define IN_ALL_EVENTS (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \ + IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | IN_MOVED_TO | \ + IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MOVE_SELF) + +#define IN_CLOEXEC O_CLOEXEC /* Set close_on_exec for the inotify file descriptor */ +#define IN_NONBLOCK O_NONBLOCK /* Set O_NONBLOCK for the inotify file descriptor */ + +/**************************************************************************** + * Type Definitions + ****************************************************************************/ + +struct inotify_event +{ + int wd; /* Watch descriptor */ + uint32_t mask; /* Mask describing event */ + uint32_t cookie; /* Unique cookie associating related events (for rename(2)) */ + uint32_t len; /* Size of name field */ + char name[0]; /* Stub for possible name */ +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: inotify_init + * + * Description: + * Initializes a new inotify instance and returns a file descriptor + * associated with a new inotify event queue. + * + * Returned Value: + * On success, these system calls return a new file descriptor. + * On error, -1 is returned and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_init(void); + +/**************************************************************************** + * Name: inotify_init1 + * + * Description: + * Initializes a new inotify instance and returns a file descriptor + * associated with a new inotify event queue. + * + * Input Parameters: + * flags - The following values are recognized in flags: + * IN_NONBLOCK - Set the O_NONBLOCK file status flag on the new open file + * description. Using this flag saves extra calls to fcntl(2) to achieve + * the same result. + * IN_CLOEXEC - Set the close-on-exec (FD_CLOEXEC) flag on the new file + * descriptor. See the description of the O_CLOEXEC flag in open(2) for + * reasons why this may be useful. + * + * Returned Value: + * On success, these system calls return a new file descriptor. + * On error, -1 is returned and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_init1(int flags); + +/**************************************************************************** + * Name: inotify_add_watch + * + * Description: + * Adds a new watch, or modifies an existing watch, for the file whose + * location is specified in pathname; the caller must have read permission + * for this file. The fd argument is a file descriptor referring to the + * inotify instance whose watch list is to be modified. The events to be + * monitored for pathname are specified in the mask bit-mask argument. + * + * Input Parameters: + * fd - The file descriptor associated with an instance of inotify. + * pathname - The path to the file to be monitored. + * mask - The bit mask of events to be monitored. + * + * Returned Value: + * On success, inotify_add_watch() returns a nonnegative watch descriptor. + * On error, -1 is returned and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_add_watch(int fd, FAR const char *pathname, uint32_t mask); + +/**************************************************************************** + * Name: inotify_rm_watch + * + * Description: + * Removes the watch associated with the watch descriptor wd from the + * inotify instance associated with the file descriptor fd. + * + * Input Parameters: + * fd - The file descriptor associated with an instance of inotify. + * wd - The watch descriptor to be removed. + * + * Returned Value: + * On success, inotify_rm_watch() returns zero. On error, -1 is returned + * and errno is set appropriately. + * + ****************************************************************************/ + +int inotify_rm_watch(int fd, int wd); + +#endif diff --git a/include/sys/syscall_lookup.h b/include/sys/syscall_lookup.h index ee5781030e..6da57cff18 100644 --- a/include/sys/syscall_lookup.h +++ b/include/sys/syscall_lookup.h @@ -286,6 +286,15 @@ SYSCALL_LOOKUP(munmap, 2) SYSCALL_LOOKUP(shm_unlink, 1) #endif +/* The following are defined if the file system notify is enabled */ + +#ifdef CONFIG_FS_NOTIFY + SYSCALL_LOOKUP(inotify_add_watch, 3) + SYSCALL_LOOKUP(inotify_init, 0) + SYSCALL_LOOKUP(inotify_init1, 1) + SYSCALL_LOOKUP(inotify_rm_watch, 2) +#endif + /* The following are defined if pthreads are enabled */ #ifndef CONFIG_DISABLE_PTHREAD diff --git a/syscall/syscall.csv b/syscall/syscall.csv index 6b23fcd604..e2a36223eb 100644 --- a/syscall/syscall.csv +++ b/syscall/syscall.csv @@ -50,6 +50,10 @@ "gettid","unistd.h","","pid_t" "gettimeofday","sys/time.h","","int","FAR struct timeval *","FAR struct timezone *" "getuid","unistd.h","defined(CONFIG_SCHED_USER_IDENTITY)","uid_t" +"inotify_add_watch","sys/inotify.h","defined(CONFIG_FS_NOTIFY)","int","int","FAR const char *","uint32_t" +"inotify_init","sys/inotify.h","defined(CONFIG_FS_NOTIFY)","int" +"inotify_init1","sys/inotify.h","defined(CONFIG_FS_NOTIFY)","int","int" +"inotify_rm_watch","sys/inotify.h","defined(CONFIG_FS_NOTIFY)","int","int","int" "insmod","nuttx/module.h","defined(CONFIG_MODULE)","FAR void *","FAR const char *","FAR const char *" "ioctl","sys/ioctl.h","","int","int","int","...","unsigned long" "kill","signal.h","","int","pid_t","int"