nuttx/fs/notify/inotify.c
Shoukui Zhang 43223124ec vfs/file: add reference counting to prevent accidental close during reading writing...
Signed-off-by: Shoukui Zhang <zhangshoukui@xiaomi.com>
2024-09-17 12:01:53 +08:00

1512 lines
38 KiB
C

/****************************************************************************
* 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 <nuttx/config.h>
#include <nuttx/kmalloc.h>
#include <nuttx/list.h>
#include <nuttx/mutex.h>
#include <nuttx/fs/fs.h>
#include <nuttx/lib/lib.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <debug.h>
#include <poll.h>
#include <string.h>
#include <search.h>
#include <libgen.h>
#include "inode/inode.h"
#include "sched/sched.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_FS_NOTIFY_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 */
};
struct inotify_global_s
{
mutex_t lock; /* Enforces global exclusive access */
int event_cookie; /* Event cookie */
int watch_cookie; /* Watch cookie */
uint32_t read_count; /* Number of read events */
uint32_t write_count; /* Number of write events */
struct hsearch_data hash; /* Hash table for watch lists */
};
/****************************************************************************
* 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 struct inotify_global_s g_inotify =
{
.lock = NXMUTEX_INITIALIZER,
};
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: notify_add_count
*
* Description:
* Add the count.
*
****************************************************************************/
static void inotify_add_count(uint32_t mask)
{
g_inotify.read_count += (mask & IN_ACCESS) ? 1 : 0;
g_inotify.write_count += (mask & IN_MODIFY) ? 1 : 0;
}
/****************************************************************************
* Name: inotify_sub_count
*
* Description:
* Reduce the count.
*
****************************************************************************/
static void inotify_sub_count(uint32_t mask)
{
g_inotify.read_count -= (mask & IN_ACCESS) ? 1 : 0;
g_inotify.write_count -= (mask & IN_MODIFY) ? 1 : 0;
}
/****************************************************************************
* Name: notify_check_mask
*
* Description:
* Check if the count is valid.
*
****************************************************************************/
static int notify_check_mask(uint32_t mask)
{
return (((mask & IN_ACCESS) && g_inotify.read_count == 0) ||
((mask & IN_MODIFY) && g_inotify.write_count == 0)) ?
-EBADF : OK;
}
/****************************************************************************
* 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_FS_NOTIFY_MAX_EVENTS)
{
finfo("Too many events queued\n");
return;
}
if (dev->event_count == CONFIG_FS_NOTIFY_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_FS_NOTIFY_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);
inotify_sub_count(watch->mask);
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_FS_NOTIFY_FD_POLLWAITERS; i++)
{
if (dev->fds[i] == 0)
{
dev->fds[i] = fds;
fds->priv = &dev->fds[i];
break;
}
}
if (i >= CONFIG_FS_NOTIFY_FD_POLLWAITERS)
{
fds->priv = NULL;
ret = -EBUSY;
goto out;
}
if (!list_is_empty(&dev->events))
{
poll_notify(dev->fds, CONFIG_FS_NOTIFY_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.lock);
nxmutex_lock(&dev->lock);
if (--dev->count > 0)
{
nxmutex_unlock(&dev->lock);
nxmutex_unlock(&g_inotify.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.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)->f_inode != &g_inotify_inode)
{
fs_putfilep(*filep);
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 || name == path)
{
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;
FAR char *pathbuffer;
uint32_t cookie = 0;
pathbuffer = lib_get_pathbuffer();
if (pathbuffer == NULL)
{
return;
}
abspath = lib_realpath(path, pathbuffer, true);
if (abspath == NULL)
{
lib_put_pathbuffer(pathbuffer);
return;
}
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);
lib_put_pathbuffer(pathbuffer);
if (list == NULL)
{
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);
}
}
/****************************************************************************
* Name: notify_check_inode
*
* Description:
* Check if the inode is a mount point.
*
****************************************************************************/
static int notify_check_inode(FAR struct file *filep)
{
FAR struct tcb_s *tcb = this_task();
/* We only apply notify on mount points (f_inode won't be NULL). */
if ((tcb->flags & TCB_FLAG_SIGNAL_ACTION) ||
(!INODE_IS_MOUNTPT(filep->f_inode) &&
!INODE_IS_PSEUDODIR(filep->f_inode) &&
!INODE_IS_DRIVER(filep->f_inode)))
{
return -EBADF;
}
return OK;
}
/****************************************************************************
* 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)
{
FAR char *pathbuffer;
int ret;
ret = notify_check_inode(filep);
if (ret < 0)
{
return;
}
nxmutex_lock(&g_inotify.lock);
ret = notify_check_mask(mask);
nxmutex_unlock(&g_inotify.lock);
if (ret < 0)
{
return;
}
pathbuffer = lib_get_pathbuffer();
if (pathbuffer == NULL)
{
return;
}
ret = file_fcntl(filep, F_GETPATH, pathbuffer);
if (ret < 0)
{
lib_put_pathbuffer(pathbuffer);
return;
}
if (filep->f_oflags & O_DIRECTORY)
{
mask |= IN_ISDIR;
}
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(pathbuffer, mask);
lib_put_pathbuffer(pathbuffer);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* Name: notify_free_entry
*
* Description:
* Deallocate the hash entry.
*
****************************************************************************/
static void notify_free_entry(FAR ENTRY *entry)
{
/* Key is alloced by lib_malloc, value is alloced by kmm_malloc */
lib_free(entry->key);
kmm_free(entry->data);
}
/****************************************************************************
* 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 struct file *filep;
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, &filep);
if (dev == NULL)
{
set_errno(EBADF);
return ERROR;
}
abspath = lib_realpath(pathname, NULL, mask & IN_DONT_FOLLOW);
if (abspath == NULL)
{
fs_putfilep(filep);
return ERROR;
}
ret = nx_stat(abspath, &buf, 1);
if (ret < 0)
{
goto out_free;
}
if (!S_ISDIR(buf.st_mode) && (mask & IN_ONLYDIR))
{
ret = -ENOTDIR;
goto out_free;
}
nxmutex_lock(&g_inotify.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)
{
uint32_t tmpmask = old->mask;
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;
inotify_add_count(tmpmask ^ old->mask);
}
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;
inotify_add_count(mask);
}
out:
nxmutex_unlock(&dev->lock);
nxmutex_unlock(&g_inotify.lock);
out_free:
fs_putfilep(filep);
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;
FAR struct file *filep;
dev = inotify_get_device_from_fd(fd, &filep);
if (dev == NULL)
{
set_errno(EBADF);
return ERROR;
}
nxmutex_lock(&g_inotify.lock);
nxmutex_lock(&dev->lock);
watch = inotify_find_watch(dev, wd);
if (watch == NULL)
{
nxmutex_unlock(&dev->lock);
nxmutex_unlock(&g_inotify.lock);
fs_putfilep(filep);
set_errno(EINVAL);
return ERROR;
}
inotify_remove_watch(dev, watch);
nxmutex_unlock(&dev->lock);
nxmutex_unlock(&g_inotify.lock);
fs_putfilep(filep);
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;
g_inotify.hash.free_entry = notify_free_entry;
ret = hcreate_r(CONFIG_FS_NOTIFY_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;
}
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(path, mask);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* 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)
{
FAR char *pathbuffer;
pathbuffer = lib_get_pathbuffer();
if (pathbuffer == NULL)
{
return;
}
nxmutex_lock(&g_inotify.lock);
if (inode_getpath(inode, pathbuffer, PATH_MAX) >= 0)
{
notify_queue_path_event(pathbuffer, IN_CLOSE_WRITE);
}
lib_put_pathbuffer(pathbuffer);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* 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)
{
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(path, IN_DELETE);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* Name: notify_unmount
*
* Description:
* The hook is called when the file system is unmounted.
*
****************************************************************************/
void notify_unmount(FAR const char *path)
{
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(path, IN_DELETE | IN_UNMOUNT);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* Name: notify_mkdir
*
* Description:
* The hook is called when the directory is created.
*
****************************************************************************/
void notify_mkdir(FAR const char *path)
{
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(path, IN_CREATE | IN_ISDIR);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* Name: notify_create
*
* Description:
* The hook is called symlink is created.
*
****************************************************************************/
void notify_create(FAR const char *path)
{
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(path, IN_CREATE);
nxmutex_unlock(&g_inotify.lock);
}
/****************************************************************************
* 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;
}
nxmutex_lock(&g_inotify.lock);
notify_queue_path_event(oldpath, oldmask);
notify_queue_path_event(newpath, newmask);
nxmutex_unlock(&g_inotify.lock);
}