/****************************************************************************
 * fs/vfs/fs_inotify.c
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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"
#include "fs_heap.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 DEFINE_PER_CPU_BMP(struct inode, g_inotify_inode) =
{
  NULL,
  NULL,
  NULL,
  1,
  FSNODEFLAG_TYPE_DRIVER,
  {
    &g_inotify_fops
  }
};
#define g_inotify_inode this_cpu_var_bmp(g_inotify_inode)

static DEFINE_PER_CPU_BMP(struct inotify_global_s, g_inotify) =
{
  .lock = NXMUTEX_INITIALIZER,
};
#define g_inotify this_cpu_var_bmp(g_inotify)

/****************************************************************************
 * 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 = fs_heap_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);

  if (nxsem_get_value(&dev->sem, &semcnt) >= 0)
    {
      while (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);
  fs_heap_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--;
  fs_heap_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 = fs_heap_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);
  fs_heap_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 (file_get(fd, filep) < 0)
    {
      return NULL;
    }

  if ((*filep)->f_inode != &g_inotify_inode)
    {
      file_put(*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 = fs_heap_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 = fs_heap_zalloc(sizeof(struct inotify_watch_list_s));
  if (list == NULL)
    {
      return NULL;
    }

  list_initialize(&list->watches);
  list->path = fs_heap_strdup(path);
  if (list->path == NULL)
    {
      fs_heap_free(list);
      return NULL;
    }

  item.key = list->path;
  item.data = list;
  if (hsearch_r(item, ENTER, &result, &g_inotify.hash) == 0)
    {
      fs_heap_free(list->path);
      fs_heap_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_tempbuffer(PATH_MAX);
  if (pathbuffer == NULL)
    {
      return;
    }

  abspath = lib_realpath(path, pathbuffer, true);
  if (abspath == NULL)
    {
      lib_put_tempbuffer(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_tempbuffer(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 ((atomic_read(&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_tempbuffer(PATH_MAX);
  if (pathbuffer == NULL)
    {
      return;
    }

  ret = file_fcntl(filep, F_GETPATH, pathbuffer);
  if (ret < 0)
    {
      lib_put_tempbuffer(pathbuffer);
      return;
    }

  if (filep->f_oflags & O_DIRECTORY)
    {
      mask |= IN_ISDIR;
    }

  nxmutex_lock(&g_inotify.lock);
  notify_queue_path_event(pathbuffer, mask);
  lib_put_tempbuffer(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 fs_heap_malloc */

  fs_heap_free(entry->key);
  fs_heap_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)
    {
      file_put(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:
  file_put(filep);
  fs_heap_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);
      file_put(filep);
      set_errno(EINVAL);
      return ERROR;
    }

  inotify_remove_watch(dev, watch);
  nxmutex_unlock(&dev->lock);
  nxmutex_unlock(&g_inotify.lock);
  file_put(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_from_inode(&g_inotify_inode, O_RDOK | flags,
                                 0, dev, 0);
  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);
  fs_heap_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 const char *path, int oflags)
{
  if (oflags & O_WROK)
    {
      notify_queue_path_event(path, IN_CLOSE_WRITE);
    }
  else
    {
      notify_queue_path_event(path, 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_tempbuffer(PATH_MAX);
  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_tempbuffer(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);
}