inotify 0.10.0
Subsystems (3)
RFC PATCH 1/1 inotify 0.10.0
Hello,
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
--New in this version--
-code cleanup (rml)
-fix an OOPS when events where queued (me)
-cleaned up the events (me)
- remove IN_CREATE, IN_DELETED, IN_RENAME and IN_MOVE
- added IN_CREATE_SUBDIR, IN_CREATE_FILE, IN_DELETE_SUBDIR, IN_DELETE_FILE, IN_DELETE_SELF, IN_MOVED_FROM and IN_MOVED_TO
-added a cookie field to the event structure to pair MOVED_FROM/MOVED_TO -misc fixes (me)
-added description of inotify events to the release notes (me) -updated inotify-utils to work with this version (me)
John McCutchan
Release notes:
--Why Not dnotify and Why inotify (By Robert Love)--
Everyone seems quick to deride the blunder known as "dnotify" and applaud a
replacement, any replacement, man anything but that current mess, but in the
name of fairness I present my treatise on why dnotify is what one might call
not good:
* dnotify requires the opening of one fd per each directory that you intend to
watch.
o The file descriptor pins the directory, disallowing the backing device to be unmounted, which absolutely wrecks havoc with removable media.
o Watching many directories results in many open file descriptors, possibly hitting a per-process fd limit.
* dnotify is directory-based. You only learn about changes to directories.
Sure, a change to a file in a directory affects the directory, but you
are
then forced to keep a cache of stat structures around to compare
things in
order to find out which file.
* dnotify's interface to user-space is awful.
o dnotify uses signals to communicate with user-space.
o Specifically, dnotify uses SIGIO.
o But then you can pick a different signal! So by "signals," I really meant you need to use real-time signals if you want to queue the events.
* dnotify basically ignores any problems that would arise in the VFS from hard
links.
* Rumor is that the "d" in "dnotify" does not stand for "directory" but for
"suck."
A suitable replacement is "inotify." And now, my tract on what inotify brings
to the table:
* inotify's interface is a device node, not SIGIO.
o You open only a single fd, to the device node. No more pinning directories or opening a million file descriptors.
o Usage is nice: open the device, issue simple commands via ioctl(), and then block on the device. It returns events when, well, there are events to be returned.
o You can select() on the device node and so it integrates with main loops like coffee mixed with vanilla milkshake.
* inotify has an event that says "the filesystem that the item you were watching is on was unmounted" (this is particularly cool).
* inotify can watch directories or files.
* The "i" in inotify does not stand for "suck" but for "inode" -- the logical
choice since inotify is inode-based.
--COMPLEXITY--
I have been asked what the complexity of inotify is. Inotify has 2 path codes where complexity could be an issue:
Adding a watcher to a device
This code has to check if the inode is already being watched by the device, this is O(1) since the maximum number of devices is limited to 8.
Removing a watch from a device
This code has to do a search of all watches on the device to find the watch descriptor that is being asked to remove. This involves a linear search, but should not really be an issue because it is limited to 8192 entries. If this does turn in to a concern, I would replace the list of watches on the device with a sorted binary tree, so that the search could be done very quickly.
The calls to inotify from the VFS code has a complexity of O(1) so inotify does not affect the speed of VFS operations.
--MEMORY USAGE--
The inotify data structures are light weight:
inotify watch is 40 bytes
inotify device is 68 bytes
inotify event is 272 bytes
So assuming a device has 8192 watches, the structures are only going to consume 320KB of memory. With a maximum number of 8 devices allowed to exist at a time, this is still only 2.5 MB
Each device can also have 256 events queued at a time, which sums to 68KB per device. And only .5 MB if all devices are opened and have a full event queue.
So approximately 3 MB of memory are used in the rare case of everything open and full.
Each inotify watch pins the inode of a directory/file in memory, the size of an inode is different per file system but lets assume that it is 512 byes.
So assuming the maximum number of global watches are active, this would pin down 32 MB of inodes in the inode cache. Again not a problem on a modern system.
On smaller systems, the maximum watches / events could be lowered to provide a smaller foot print.
Keep in mind that this is an absolute worst case memory analysis. In reality it will most likely cost approximately 5MB.
--HOWTO USE--
Inotify is a character device that when opened offers 2 IOCTL's. (It actually has 4 but the other 2 are used for debugging)
INOTIFY_WATCH:
Which takes a path and event mask and returns a unique (to the instance of the driver) integer (wd [watch descriptor] from here on) that is a 1:1 mapping to the path passed. What happens is inotify gets the inode (and ref's the inode) for the path and adds a inotify_watcher structure to the inodes list of watchers. If this instance of the driver is already watching the path, the event mask will be updated and the original wd will be returned.
INOTIFY_IGNORE:
Which takes an integer (that you got from INOTIFY_WATCH) representing a wd that you are not interested in watching anymore. This will:
send an IGNORE event to the device
remove the inotify_watcher structure from the device and from the inode and unref the inode.
After you are watching 1 or more paths, you can read from the fd and get events. The events are struct inotify_event. If you are watching a directory and something happens to a file in the directory the event will contain the filename (just the filename not the full path).
-- EVENTS --IN_ACCESS - Sent when file is accessed.IN_MODIFY - Sent when file is modified.IN_ATTRIB - Sent when file is chmod'ed.IN_CLOSE - Sent when file is closedIN_OPEN - Sent when file is opened.IN_MOVED_FROM - Sent to the source folder of a move. IN_MOVED_TO - Sent to the destination folder of a move. IN_DELETE_SUBDIR - Sent when a sub directory is deleted. (When watching parent)IN_DELETE_FILE - Sent when a file is deleted. (When watching parent) IN_CREATE_SUBDIR - Sent when a sub directory is created. (When watching parent)IN_CREATE_FILE - Sent when a file is created. (When watching parent) IN_DELETE_SELF - Sent when file is deleted.IN_UNMOUNT - Sent when the filesystem is being unmounted. IN_Q_OVERFLOW - Sent when your event queue has over flowed.
The MOVED_FROM/MOVED_TO events are always sent in pairs. MOVED_FROM/MOVED_TO
is also sent when a file is renamed. The cookie field in the event pairs up MOVED_FROM/MOVED_TO events. These two events are not guaranteed to be successive in the event stream. You must rely on the cookie to pair them up. (Note, the cookie is not sent yet.)
If you aren't watching the source and destination folders in a MOVE. You will only get MOVED_TO or MOVED_FROM. In this case, MOVED_TO is equivelent to a CREATE and MOVED_FROM is equivelent to a DELETE.
--KERNEL CHANGES--
inotify char device driver.
Adding calls to inotify_inode_queue_event and inotify_dentry_parent_queue_event from VFS operations. Dnotify has the same function calls. The complexity of the VFS operations is not affected because inotify_*_queue_event is O(1).
Adding a call to inotify_super_block_umount fromgeneric_shutdown_superblock
inotify_super_block_umount consists of this:
find all of the inodes that are on the super block being shut down, sends each watcher on each inode the UNMOUNT and IGNORED event removes the watcher structures from each instance of the device driver and each inode.
unref's the inode.
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +1082 -0
@@ -0,0 +1,1082 @@ +/* +* Inode based directory notifications for Linux. +* +* Copyright (C) 2004 John McCutchan +* +* This program is free software; you can redistribute it and/or modify it +* under the terms of the GNU General Public License as published by the +* Free Software Foundation; either version 2, or (at your option) any +* later version. +* +* This program is distributed in the hope that it will be useful, but +* WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* General Public License for more details. +*/ + +/* TODO: +* rename inotify_watcher to inotify_watch +* need a way to connect MOVED_TO/MOVED_FROM events in user space +* use b-tree so looking up watch by WD is faster +*/ + +#include <linux/bitops.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/fs.h> +#include <linux/namei.h> +#include <linux/poll.h> +#include <linux/miscdevice.h> +#include <linux/device.h> +#include <linux/init.h> +#include <linux/types.h> +#include <linux/stddef.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/writeback.h> +#include <linux/inotify.h> + +#define INOTIFY_VERSION "0.10.0" + +#define MAX_INOTIFY_DEVS 8/* max open inotify devices */ +#define MAX_INOTIFY_DEV_WATCHERS 8192/* max total watches */ +#define MAX_INOTIFY_QUEUED_EVENTS 256/* max events queued on the dev*/ + +#define INOTIFY_DEV_TIMER_TIME (jiffies + (HZ/4)) + +static atomic_t watcher_count; +static kmem_cache_t *watcher_cache; +static kmem_cache_t *kevent_cache; + +/* For debugging */ +static int event_object_count; +static int watcher_object_count; +static int inode_ref_count; +static int inotify_debug_flags; +#define iprintk(f, str...) if (inotify_debug_flags & f) printk (KERN_ALERT str) + +/* +* struct inotify_device - represents an open instance of an inotify device +* +* For each inotify device, we need to keep track of the events queued on it, +* a list of the inodes that we are watching, and so on. +*/ +struct inotify_device { +unsigned long bitmask[MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG ]; +struct timer_list timer; +wait_queue_head_t wait; +struct list_head events; +struct list_head watchers; +spinlock_t lock; +unsigned int event_count; +int nr_watches; +}; + +struct inotify_watcher { +int wd;// watcher descriptor +unsigned long mask; +struct inode * inode; +struct inotify_device * dev; +struct list_head d_list;// device list +struct list_head i_list;// inode list +struct list_head u_list;// unmount list +}; +#define inotify_watcher_d_list (pos) list_entry((pos), struct inotify_watcher, d_list) +#define inotify_watcher_i_list (pos) list_entry((pos), struct inotify_watcher, i_list) +#define inotify_watcher_u_list (pos) list_entry((pos), struct inotify_watcher, u_list) + +/* +* A list of these is attached to each instance of the driver +* when the drivers read() gets called, this list is walked and +* all events that can fit in the buffer get delivered +*/ +struct inotify_kernel_event { +struct list_head list; +struct inotify_event event; +}; +#define list_to_inotify_kernel_event (pos) list_entry((pos), struct inotify_kernel_event, list) + +static int find_inode (const char __user *dirname,struct inode **inode) +{ +struct nameidata nd; +int error; + + error =__user_walk (dirname,LOOKUP_FOLLOW , &nd); +if (error) { +iprintk (INOTIFY_DEBUG_INODE ,"could not find inode\n" ); + goto out; + } + + *inode = nd.dentry->d_inode; +__iget (*inode); +iprintk (INOTIFY_DEBUG_INODE ,"ref'd inode\n" ); + inode_ref_count++; +path_release (&nd); +out: +return error; +} + +static void unref_inode (struct inode *inode) +{ + inode_ref_count--; +iprintk (INOTIFY_DEBUG_INODE ,"unref'd inode\n" ); +iput (inode); +} + +struct inotify_kernel_event *kernel_event (int wd,int mask, +const char *filename) +{ +struct inotify_kernel_event *kevent; + + kevent =kmem_cache_alloc (kevent_cache,GFP_ATOMIC ); +if (!kevent) { +iprintk (INOTIFY_DEBUG_ALLOC , +"failed to alloc kevent (%d,%d)\n" , wd, mask); + goto out; + } + +iprintk (INOTIFY_DEBUG_ALLOC ,"alloced kevent %p (%d,%d)\n" , + kevent, wd, mask); + + kevent->event.wd = wd; + kevent->event.mask = mask; +INIT_LIST_HEAD (&kevent->list); + +if (filename) { +iprintk (INOTIFY_DEBUG_FILEN , +"filename for event was %p %s\n" , filename, filename); +strncpy (kevent->event.filename, filename, +INOTIFY_FILENAME_MAX ); + kevent->event.filename[INOTIFY_FILENAME_MAX -1 ] ='\0' ; +iprintk (INOTIFY_DEBUG_FILEN , +"filename after copying %s\n" , kevent->event.filename); + }else { +iprintk (INOTIFY_DEBUG_FILEN ,"no filename for event\n" ); + kevent->event.filename[0 ] ='\0' ; + } + + event_object_count++; + +out: +return kevent; +} + +void delete_kernel_event (struct inotify_kernel_event *kevent) +{ +if (!kevent) +return ; + + event_object_count--; +INIT_LIST_HEAD (&kevent->list); + kevent->event.wd =-1 ; + kevent->event.mask =0 ; + +iprintk (INOTIFY_DEBUG_ALLOC ,"free'd kevent %p\n" , kevent); +kmem_cache_free (kevent_cache, kevent); +} + +#define inotify_dev_has_events (dev) (!list_empty(&dev->events)) +#define inotify_dev_get_event (dev) (list_to_inotify_kernel_event(dev->events.next)) +/* Does this events mask get sent to the watcher ? */ +#define event_and (event_mask,watchers_mask) ((event_mask == IN_UNMOUNT) || \ + (event_mask == IN_IGNORED) || \ + (event_mask & watchers_mask)) + +/* +* inotify_dev_queue_event - add a new event to the given device +* +* Caller must hold dev->lock. +*/ +static void inotify_dev_queue_event (struct inotify_device *dev, +struct inotify_watcher *watcher,int mask, +const char *filename) +{ +struct inotify_kernel_event *kevent; + +/* +* Check if the new event is a duplicate of the last event queued. +*/ +if (dev->event_count && +inotify_dev_get_event (dev)->event.mask == mask && +inotify_dev_get_event (dev)->event.wd == watcher->wd) { + +/* Check if the filenames match */ +if (!filename &&inotify_dev_get_event (dev)->event.filename[0 ] =='\0' ) +return ; +if (filename && !strcmp (inotify_dev_get_event (dev)->event.filename, filename)) +return ; + } +/* +* the queue has already overflowed and we have already sent the +* Q_OVERFLOW event +*/ +if (dev->event_count >MAX_INOTIFY_QUEUED_EVENTS ) { +iprintk (INOTIFY_DEBUG_EVENTS , +"event queue for %p overflowed\n" , dev); +return ; + } + +/* the queue has just overflowed and we need to notify user space */ +if (dev->event_count ==MAX_INOTIFY_QUEUED_EVENTS ) { + dev->event_count++; + kevent =kernel_event (-1 ,IN_Q_OVERFLOW ,NULL ); +iprintk (INOTIFY_DEBUG_EVENTS ,"sending IN_Q_OVERFLOW to %p\n" , + dev); + goto add_event_to_queue; + } + +if (!event_and (mask, watcher->inode->watchers_mask) || + !event_and (mask, watcher->mask)) +return ; + + dev->event_count++; + kevent =kernel_event (watcher->wd, mask, filename); + +add_event_to_queue: +if (!kevent) { +iprintk (INOTIFY_DEBUG_EVENTS ,"failed to queue event for %p" +" -- could not allocate kevent\n" , dev); + dev->event_count--; +return ; + } + +list_add_tail (&kevent->list, &dev->events); +iprintk (INOTIFY_DEBUG_EVENTS , +"queued event %x for %p\n" , kevent->event.mask, dev); +} + +/* +* inotify_dev_event_dequeue - destroy an event on the given device +* +* Caller must hold dev->lock. +*/ +static void inotify_dev_event_dequeue (struct inotify_device *dev) +{ +struct inotify_kernel_event *kevent; + +if (!inotify_dev_has_events (dev)) +return ; + + kevent =inotify_dev_get_event (dev); +list_del (&kevent->list); + dev->event_count--; +delete_kernel_event (kevent); + +iprintk (INOTIFY_DEBUG_EVENTS ,"dequeued event on %p\n" , dev); +} + +/* +* inotify_dev_get_wd - returns the next WD for use by the given dev +* +* Caller must hold dev->lock before calling. +*/ +static int inotify_dev_get_wd (struct inotify_device *dev) +{ +int wd; + +if (!dev || dev->nr_watches ==MAX_INOTIFY_DEV_WATCHERS ) +return -1 ; + + dev->nr_watches++; + wd =find_first_zero_bit (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS ); +set_bit (wd, dev->bitmask); + +return wd; +} + +/* +* inotify_dev_put_wd - release the given WD on the given device +* +* Caller must hold dev->lock. +*/ +static int inotify_dev_put_wd (struct inotify_device *dev,int wd) +{ +if (!dev || wd <0 ) +return -1 ; + + dev->nr_watches--; +clear_bit (wd, dev->bitmask); + +return 0 ; +} + +/* +* create_watcher - creates a watcher on the given device. +* +* Grabs dev->lock, so the caller must not hold it. +*/ +static struct inotify_watcher *create_watcher (struct inotify_device *dev, +int mask,struct inode *inode) +{ +struct inotify_watcher *watcher; + + watcher =kmem_cache_alloc (watcher_cache,GFP_KERNEL ); +if (!watcher) { +iprintk (INOTIFY_DEBUG_ALLOC , +"failed to allocate watcher (%p,%d)\n" , inode, mask); +return NULL ; + } + + watcher->wd =-1 ; + watcher->mask = mask; + watcher->inode = inode; + watcher->dev = dev; +INIT_LIST_HEAD (&watcher->d_list); +INIT_LIST_HEAD (&watcher->i_list); +INIT_LIST_HEAD (&watcher->u_list); + +spin_lock (&dev->lock); + watcher->wd =inotify_dev_get_wd (dev); +spin_unlock (&dev->lock); + +if (watcher->wd <0 ) { +iprintk (INOTIFY_DEBUG_ERRORS , +"Could not get wd for watcher %p\n" , watcher); +iprintk (INOTIFY_DEBUG_ALLOC ,"free'd watcher %p\n" , watcher); +kmem_cache_free (watcher_cache, watcher); +return NULL ; + } + + watcher_object_count++; +return watcher; +} + +/* +* delete_watcher - removes the given 'watcher' from the given 'dev' +* +* Caller must hold dev->lock. +*/ +static void delete_watcher (struct inotify_device *dev, +struct inotify_watcher *watcher) +{ +inotify_dev_put_wd (dev, watcher->wd); +iprintk (INOTIFY_DEBUG_ALLOC ,"free'd watcher %p\n" , watcher); +kmem_cache_free (watcher_cache, watcher); + watcher_object_count--; +} + +/* +* inotify_find_dev - find the watcher associated with the given inode and dev +* +* Caller must hold dev->lock. +*/ +static struct inotify_watcher *inode_find_dev (struct inode *inode, +struct inotify_device *dev) +{ +struct inotify_watcher *watcher; + +list_for_each_entry (watcher, &inode->watchers, i_list) { +if (watcher->dev == dev) +return watcher; + } + +return NULL ; +} + +static struct inotify_watcher *dev_find_wd (struct inotify_device *dev,int wd) +{ +struct inotify_watcher *watcher; + +list_for_each_entry (watcher, &dev->watchers, d_list) { +if (watcher->wd == wd) +return watcher; + } +return NULL ; +} + +static int inotify_dev_is_watching_inode (struct inotify_device *dev, +struct inode *inode) +{ +struct inotify_watcher *watcher; + +list_for_each_entry (watcher, &dev->watchers, d_list) { +if (watcher->inode == inode) +return 1 ; + } + +return 0 ; +} + +static int inotify_dev_add_watcher (struct inotify_device *dev, +struct inotify_watcher *watcher) +{ +int error; + + error =0 ; + +if (!dev || !watcher) { + error = -EINVAL ; + goto out; + } + +if (dev_find_wd (dev, watcher->wd)) { + error = -EINVAL ; + goto out; + } + +if (dev->nr_watches ==MAX_INOTIFY_DEV_WATCHERS ) { + error = -ENOSPC ; + goto out; + } + +list_add (&watcher->d_list, &dev->watchers); +out: +return error; +} + +/* +* inotify_dev_rm_watcher - remove the given watch from the given device +* +* Caller must hold dev->lock because we call inotify_dev_queue_event(). +*/ +static int inotify_dev_rm_watcher (struct inotify_device *dev, +struct inotify_watcher *watcher) +{ +int error; + + error = -EINVAL ; +if (watcher) { +inotify_dev_queue_event (dev, watcher,IN_IGNORED ,NULL ); +list_del (&watcher->d_list); + error =0 ; + } + +return error; +} + +void inode_update_watchers_mask (struct inode *inode) +{ +struct inotify_watcher *watcher; +unsigned long new_mask; + + new_mask =0 ; +list_for_each_entry (watcher, &inode->watchers, i_list) + new_mask |= watcher->mask; + + inode->watchers_mask = new_mask; +} + +/* +* inode_add_watcher - add a watcher to the given inode +* +* Callers must hold dev->lock, because we call inode_find_dev(). +*/ +static int inode_add_watcher (struct inode *inode, +struct inotify_watcher *watcher) +{ +if (!inode || !watcher ||inode_find_dev (inode, watcher->dev)) +return -EINVAL ; + +list_add (&watcher->i_list, &inode->watchers); + inode->watcher_count++; +inode_update_watchers_mask (inode); + +return 0 ; +} + +static int inode_rm_watcher (struct inode *inode, +struct inotify_watcher *watcher) +{ +if (!inode || !watcher) +return -EINVAL ; + +list_del (&watcher->i_list); + inode->watcher_count--; + +inode_update_watchers_mask (inode); + +return 0 ; +} + +/* Kernel API */ + +void inotify_inode_queue_event (struct inode *inode,unsigned long mask, +const char *filename) +{ +struct inotify_watcher *watcher; + +spin_lock (&inode->i_lock); + +list_for_each_entry (watcher, &inode->watchers, i_list) { +spin_lock (&watcher->dev->lock); +inotify_dev_queue_event (watcher->dev, watcher, mask, filename); +spin_unlock (&watcher->dev->lock); + } + +spin_unlock (&inode->i_lock); +} +EXPORT_SYMBOL_GPL (inotify_inode_queue_event); + +void inotify_inode_queue_event_pair (struct inode *inode1,unsigned long mask1, +const char *filename1, +struct inode *inode2,unsigned long mask2, +const char *filename2) +{ +struct inotify_watcher *watcher; + +spin_lock (&inode1->i_lock); +spin_lock (&inode2->i_lock); + +list_for_each_entry (watcher, &inode1->watchers, i_list) { +spin_lock (&watcher->dev->lock); +inotify_dev_queue_event (watcher->dev, watcher, + mask1, filename1); +spin_unlock (&watcher->dev->lock); + } + +list_for_each_entry (watcher, &inode2->watchers, i_list) { +spin_lock (&watcher->dev->lock); +inotify_dev_queue_event (watcher->dev, watcher, + mask2, filename2); +spin_unlock (&watcher->dev->lock); + } + +spin_unlock (&inode2->i_lock); +spin_unlock (&inode1->i_lock); +} +EXPORT_SYMBOL_GPL (inotify_inode_queue_event_pair); + +void inotify_dentry_parent_queue_event (struct dentry *dentry, +unsigned long mask, +const char *filename) +{ +struct dentry *parent; + +spin_lock (&dentry->d_lock); +dget (dentry->d_parent); + parent = dentry->d_parent; +inotify_inode_queue_event (parent->d_inode, mask, filename); +dput (parent); +spin_unlock (&dentry->d_lock); +} +EXPORT_SYMBOL_GPL (inotify_dentry_parent_queue_event); + +static void ignore_helper (struct inotify_watcher *watcher,int event) +{ +struct inotify_device *dev; +struct inode *inode; + +spin_lock (&watcher->dev->lock); +spin_lock (&watcher->inode->i_lock); + + inode = watcher->inode; + dev = watcher->dev; + +if (event) +inotify_dev_queue_event (dev, watcher, event,NULL ); + +inode_rm_watcher (inode, watcher); +inotify_dev_rm_watcher (watcher->dev, watcher); +list_del (&watcher->u_list); + +spin_unlock (&inode->i_lock); +delete_watcher (dev, watcher); +spin_unlock (&dev->lock); + +unref_inode (inode); +} + +static void process_umount_list (struct list_head *umount) { +struct inotify_watcher *watcher, *next; + +list_for_each_entry_safe (watcher, next, umount, u_list) +ignore_helper (watcher,IN_UNMOUNT ); +} + +static void build_umount_list (struct list_head *head,struct super_block *sb, +struct list_head *umount) +{ +struct inode * inode; + +list_for_each_entry (inode, head, i_list) { +struct inotify_watcher *watcher; + +if (inode->i_sb != sb) +continue ; + +spin_lock (&inode->i_lock); + +list_for_each_entry (watcher, &inode->watchers, i_list) +list_add (&watcher->u_list, umount); + +spin_unlock (&inode->i_lock); + } +} + +void inotify_super_block_umount (struct super_block *sb) +{ +struct list_head umount; + +INIT_LIST_HEAD (&umount); + +spin_lock (&inode_lock); +build_umount_list (&inode_in_use, sb, &umount); +spin_unlock (&inode_lock); + +process_umount_list (&umount); +} +EXPORT_SYMBOL_GPL (inotify_super_block_umount); + +void inotify_inode_is_dead (struct inode *inode) +{ +struct inotify_watcher *watcher, *next; + +list_for_each_entry_safe (watcher, next, &inode->watchers, i_list) { +ignore_helper (watcher,0 ); +} +EXPORT_SYMBOL_GPL (inotify_inode_is_dead); + +/* The driver interface is implemented below */ + +static unsigned int inotify_poll (struct file *file,poll_table *wait) +{ +struct inotify_device *dev; + + dev = file->private_data; + +poll_wait (file, &dev->wait, wait); + +if (inotify_dev_has_events (dev)) +return POLLIN |POLLRDNORM ; + +return 0 ; +} + +#define MAX_EVENTS_AT_ONCE 20 +static ssize_t inotify_read (struct file *file,__user char *buf, +size_t count,loff_t *pos) +{ +size_t out; +struct inotify_event *eventbuf; +struct inotify_kernel_event *kevent; +struct inotify_device *dev; +char *obuf; +int err; +int events; +int event_count; + +DECLARE_WAITQUEUE (wait, current); + + out = -ENOMEM ; + eventbuf =kmalloc (sizeof (struct inotify_event ) *MAX_EVENTS_AT_ONCE , +GFP_KERNEL ); +if (!eventbuf) + goto out; + + events =0 ; + out =0 ; + err =0 ; + obuf = buf; + + dev = file->private_data; + +/* We only hand out full inotify events */ +if (count <sizeof (struct inotify_event )) { + out = -EINVAL ; + goto out; + } + + events = count /sizeof (struct inotify_event ); +if (events >MAX_EVENTS_AT_ONCE ) + events =MAX_EVENTS_AT_ONCE ; + +if (!inotify_dev_has_events (dev)) { +if (file->f_flags &O_NONBLOCK ) { + out = -EAGAIN ; + goto out; + } + } + +spin_lock (&dev->lock); + +add_wait_queue (&dev->wait, &wait); +repeat: +if (signal_pending (current)) { +spin_unlock (&dev->lock); + out = -ERESTARTSYS ; +set_current_state (TASK_RUNNING ); +remove_wait_queue (&dev->wait, &wait); + goto out; + } +set_current_state (TASK_INTERRUPTIBLE ); +if (!inotify_dev_has_events (dev)) { +spin_unlock (&dev->lock); +schedule (); +spin_lock (&dev->lock); + goto repeat; + } + +set_current_state (TASK_RUNNING ); +remove_wait_queue (&dev->wait, &wait); + + err = !access_ok (VERIFY_WRITE , (void *)buf, +sizeof (struct inotify_event )); + +if (err) { + out = -EFAULT ; + goto out; + } + +/* Copy all the events we can to the event buffer */ +for (event_count =0 ; event_count < events; event_count++) { + kevent =inotify_dev_get_event (dev); + eventbuf[event_count] = kevent->event; +inotify_dev_event_dequeue (dev); + } + +spin_unlock (&dev->lock); + +/* Send the event buffer to user space */ + err =copy_to_user (buf, eventbuf, + events *sizeof (struct inotify_event )); + + buf +=sizeof (struct inotify_event ) * events; + + out = buf - obuf; + +out: +kfree (eventbuf); +return out; +} + +static void inotify_dev_timer (unsigned long data) +{ +struct inotify_device *dev; + +if (!data) +return ; + dev = (struct inotify_device *) data; + +/* reset the timer */ +mod_timer (&dev->timer,INOTIFY_DEV_TIMER_TIME ); + +/* wake up anyone blocked on the device */ +if (inotify_dev_has_events (dev)) +wake_up_interruptible (&dev->wait); +} + +static int inotify_open (struct inode *inode,struct file *file) +{ +struct inotify_device *dev; + +if (atomic_read (&watcher_count) ==MAX_INOTIFY_DEVS ) +return -ENODEV ; + +atomic_inc (&watcher_count); + + dev =kmalloc (sizeof (struct inotify_device ),GFP_KERNEL ); +if (!dev) +return -ENOMEM ; + +memset (dev->bitmask,0 , +sizeof (unsigned long ) *MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG ); + +INIT_LIST_HEAD (&dev->events); +INIT_LIST_HEAD (&dev->watchers); +init_timer (&dev->timer); +init_waitqueue_head (&dev->wait); + + dev->event_count =0 ; + dev->nr_watches =0 ; + dev->lock =SPIN_LOCK_UNLOCKED ; + + file->private_data = dev; + + dev->timer.data = (unsigned long ) dev; + dev->timer.function = inotify_dev_timer; + dev->timer.expires =INOTIFY_DEV_TIMER_TIME ; + +add_timer (&dev->timer); + +printk (KERN_ALERT "inotify device opened\n" ); + +return 0 ; +} + +static void inotify_release_all_watchers (struct inotify_device *dev) +{ +struct inotify_watcher *watcher,*next; + +list_for_each_entry_safe (watcher, next, &dev->watchers, d_list) +ignore_helper (watcher,0 ); +} + +/* +* inotify_release_all_events - destroy all of the events on a given device +*/ +static void inotify_release_all_events (struct inotify_device *dev) +{ +spin_lock (&dev->lock); +while (inotify_dev_has_events (dev)) +inotify_dev_event_dequeue (dev); +spin_unlock (&dev->lock); +} + + +static int inotify_release (struct inode *inode,struct file *file) +{ +if (file->private_data) { +struct inotify_device *dev; + + dev = (struct inotify_device *) file->private_data; +del_timer_sync (&dev->timer); +inotify_release_all_watchers (dev); +inotify_release_all_events (dev); +kfree (dev); + } + +printk (KERN_ALERT "inotify device released\n" ); + +atomic_dec (&watcher_count); +return 0 ; +} + +static int inotify_watch (struct inotify_device *dev, +struct inotify_watch_request *request) +{ +int err; +struct inode *inode; +struct inotify_watcher *watcher; + err =0 ; + + err =find_inode (request->dirname, &inode); +if (err) + goto exit; + +if (!S_ISDIR (inode->i_mode)) +iprintk (INOTIFY_DEBUG_ERRORS ,"watching file\n" ); + +spin_lock (&dev->lock); +spin_lock (&inode->i_lock); + +/* +* This handles the case of re-adding a directory we are already +* watching, we just update the mask and return 0 +*/ +if (inotify_dev_is_watching_inode (dev, inode)) { +struct inotify_watcher *owatcher;/* the old watcher */ + +iprintk (INOTIFY_DEBUG_ERRORS , +"adjusting event mask for inode %p\n" , inode); + + owatcher =inode_find_dev (inode, dev); + owatcher->mask = request->mask; +inode_update_watchers_mask (inode); +spin_unlock (&inode->i_lock); +spin_unlock (&dev->lock); +unref_inode (inode); + +return 0 ; + } + +spin_unlock (&inode->i_lock); +spin_unlock (&dev->lock); + + watcher =create_watcher (dev, request->mask, inode); +if (!watcher) { +unref_inode (inode); +return -ENOSPC ; + } + +spin_lock (&dev->lock); +spin_lock (&inode->i_lock); + +/* We can't add anymore watchers to this device */ +if (inotify_dev_add_watcher (dev, watcher) == -ENOSPC ) { +iprintk (INOTIFY_DEBUG_ERRORS , +"can't add watcher dev is full\n" ); +spin_unlock (&inode->i_lock); +delete_watcher (dev, watcher); +spin_unlock (&dev->lock); + +unref_inode (inode); +return -ENOSPC ; + } + +inode_add_watcher (inode, watcher); + +/* we keep a reference on the inode */ +if (!err) + err = watcher->wd; + +spin_unlock (&inode->i_lock); +spin_unlock (&dev->lock); +exit: +return err; +} + +static int inotify_ignore (struct inotify_device *dev,int wd) +{ +struct inotify_watcher *watcher; + + watcher =dev_find_wd (dev, wd); +if (!watcher) +return -EINVAL ; +ignore_helper (watcher,0 ); + +return 0 ; +} + +static void inotify_print_stats (struct inotify_device *dev) +{ +int sizeof_inotify_watcher; +int sizeof_inotify_device; +int sizeof_inotify_kernel_event; + + sizeof_inotify_watcher =sizeof (struct inotify_watcher ); + sizeof_inotify_device =sizeof (struct inotify_device ); + sizeof_inotify_kernel_event =sizeof (struct inotify_kernel_event ); + +printk (KERN_ALERT "GLOBAL INOTIFY STATS\n" ); +printk (KERN_ALERT "watcher_count = %d\n" ,atomic_read (&watcher_count)); +printk (KERN_ALERT "event_object_count = %d\n" , event_object_count); +printk (KERN_ALERT "watcher_object_count = %d\n" , watcher_object_count); +printk (KERN_ALERT "inode_ref_count = %d\n" , inode_ref_count); + +printk (KERN_ALERT "sizeof(struct inotify_watcher) = %d\n" , + sizeof_inotify_watcher); +printk (KERN_ALERT "sizeof(struct inotify_device) = %d\n" , + sizeof_inotify_device); +printk (KERN_ALERT "sizeof(struct inotify_kernel_event) = %d\n" , + sizeof_inotify_kernel_event); + +spin_lock (&dev->lock); + +printk (KERN_ALERT "inotify device: %p\n" , dev); +printk (KERN_ALERT "inotify event_count: %u\n" , dev->event_count); +printk (KERN_ALERT "inotify watch_count: %d\n" , dev->nr_watches); + +spin_unlock (&dev->lock); +} + +static int inotify_ioctl (struct inode *ip,struct file *fp, +unsigned int cmd,unsigned long arg) { +int err; +struct inotify_device *dev; +struct inotify_watch_request request; +int wd; + + dev = fp->private_data; + err =0 ; + +if (_IOC_TYPE (cmd) !=INOTIFY_IOCTL_MAGIC ) +return -EINVAL ; +if (_IOC_NR (cmd) >INOTIFY_IOCTL_MAXNR ) +return -EINVAL ; + +if (_IOC_DIR (cmd) & _IOC_READ) + err = !access_ok (VERIFY_READ , (void *) arg,_IOC_SIZE (cmd)); + +if (err) + err = -EFAULT ; + goto out; + +if (_IOC_DIR (cmd) & _IOC_WRITE) + err = !access_ok (VERIFY_WRITE , (void *)arg,_IOC_SIZE (cmd)); + +if (err) { + err = -EFAULT ; + goto out; + } + + err = -EINVAL ; + +switch (cmd) { +case INOTIFY_WATCH : +iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_WATCH ioctl\n" ); +if (copy_from_user (&request, (void *) arg, +sizeof (struct inotify_watch_request ))) { + err = -EFAULT ; + goto out; + } + + err =inotify_watch (dev, &request); +break ; +case INOTIFY_IGNORE : +iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_IGNORE ioctl\n" ); +if (copy_from_user (&wd, (void *)arg,sizeof (int ))) { + + err = -EFAULT ; + goto out; + } + + err =inotify_ignore (dev, wd); +break ; +case INOTIFY_STATS : +iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_STATS ioctl\n" ); +inotify_print_stats (dev); + err =0 ; +break ; +case INOTIFY_SETDEBUG : +iprintk (INOTIFY_DEBUG_ERRORS , +"INOTIFY_SETDEBUG ioctl\n" ); +if (copy_from_user (&inotify_debug_flags, (void *) arg, +sizeof (int ))) { + err = -EFAULT ; + goto out; + } +break ; + } + +out: +return err; +} + +static struct file_operations inotify_fops = { + .owner =THIS_MODULE , + .poll = inotify_poll, + .read = inotify_read, + .open = inotify_open, + .release = inotify_release, + .ioctl = inotify_ioctl, +}; + +struct miscdevice inotify_device = { + .minor =MISC_DYNAMIC_MINOR , + .name ="inotify" , + .fops = &inotify_fops, +}; + + +static int __init inotify_init (void ) +{ +int ret; + + ret =misc_register (&inotify_device); +if (ret) + goto out; + + inotify_debug_flags =INOTIFY_DEBUG_NONE ; + + watcher_cache =kmem_cache_create ("watcher_cache" , +sizeof (struct inotify_watcher ),0 ,SLAB_PANIC , +NULL ,NULL ); + + kevent_cache =kmem_cache_create ("kevent_cache" , +sizeof (struct inotify_kernel_event ),0 , +SLAB_PANIC ,NULL ,NULL ); + +printk (KERN_ALERT "inotify %s minor=%d\n" ,INOTIFY_VERSION , + inotify_device.minor); +out: +return ret; +} + +static void inotify_exit (void ) +{ +kmem_cache_destroy (kevent_cache); +kmem_cache_destroy (watcher_cache); +misc_deregister (&inotify_device); +printk (KERN_ALERT "inotify shutdown ec=%d wc=%d ic=%d\n" , + event_object_count, watcher_object_count, inode_ref_count); +} + +MODULE_AUTHOR ("John McCutchan <[email protected]>" ); +MODULE_DESCRIPTION ("Inode event driver" ); +MODULE_LICENSE ("GPL" ); + +module_init (inotify_init); +module_exit (inotify_exit);
include/linux/inotify.h renamed from linux/include/linux/inotify.h +100 -0
100 lines not on this page · raw message
include/linux/fs.h renamed from linux/include/linux/fs.h +4 -0
@@ -458,6 +458,10 @@unsigned long i_dnotify_mask;/* Directory notify events */ struct dnotify_struct *i_dnotify;/* for directory notifications */ +struct list_head watchers; +unsigned long watchers_mask; +int watcher_count; +unsigned long i_state;unsigned long dirtied_when;/* jiffies of first dirtying */
fs/super.c renamed from linux/fs/super.c +2 -0
@@ -36,6 +36,7 @@#include <linux/writeback.h> /* for the emergency remount stuff */ #include <linux/idr.h> #include <asm/uaccess.h> +#include <linux/inotify.h> void get_filesystem (struct file_system_type *fs);
@@ -204,6 +205,7 @@if (root) { sb->s_root =NULL ; +inotify_super_block_umount (sb);shrink_dcache_parent (root);shrink_dcache_anon (&sb->s_anon);dput (root);
fs/read_write.c renamed from linux/fs/read_write.c +14 -3
@@ -11,6 +11,7 @@#include <linux/uio.h> #include <linux/smp_lock.h> #include <linux/dnotify.h> +#include <linux/inotify.h> #include <linux/security.h> #include <linux/module.h>
@@ -216,8 +217,11 @@ ret = file->f_op->read (file, buf, count, pos); else ret =do_sync_read (file, buf, count, pos); -if (ret >0 ) +if (ret >0 ) {dnotify_parent (file->f_dentry,DN_ACCESS ); +inotify_dentry_parent_queue_event (file->f_dentry,IN_ACCESS , file->f_dentry->d_name.name); +inotify_inode_queue_event (file->f_dentry->d_inode,IN_ACCESS ,NULL ); + } } }
@@ -260,8 +264,11 @@ ret = file->f_op->write (file, buf, count, pos); else ret =do_sync_write (file, buf, count, pos); -if (ret >0 ) +if (ret >0 ) {dnotify_parent (file->f_dentry,DN_MODIFY ); +inotify_dentry_parent_queue_event (file->f_dentry,IN_MODIFY , file->f_dentry->d_name.name); +inotify_inode_queue_event (file->f_dentry->d_inode,IN_MODIFY ,NULL ); + } } }
@@ -493,9 +500,13 @@ out:if (iov != iovstack)kfree (iov); -if ((ret + (type ==READ )) >0 ) +if ((ret + (type ==READ )) >0 ) {dnotify_parent (file->f_dentry, (type ==READ ) ?DN_ACCESS :DN_MODIFY ); +inotify_dentry_parent_queue_event (file->f_dentry, + (type ==READ ) ?IN_ACCESS :IN_MODIFY , file->f_dentry->d_name.name); +inotify_inode_queue_event (file->f_dentry->d_inode, (type ==READ ) ?IN_ACCESS :IN_MODIFY ,NULL ); + }return ret; }
fs/open.c renamed from linux/fs/open.c +5 -0
@@ -11,6 +11,7 @@#include <linux/smp_lock.h> #include <linux/quotaops.h> #include <linux/dnotify.h> +#include <linux/inotify.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/tty.h>
@@ -955,6 +956,8 @@ error =PTR_ERR (f);if (IS_ERR (f)) goto out_error; +inotify_inode_queue_event (f->f_dentry->d_inode,IN_OPEN ,NULL ); +inotify_dentry_parent_queue_event (f->f_dentry,IN_OPEN , f->f_dentry->d_name.name);fd_install (fd, f); } out:
@@ -1034,6 +1037,8 @@FD_CLR (fd, files->close_on_exec);__put_unused_fd (files, fd);spin_unlock (&files->file_lock); +inotify_dentry_parent_queue_event (filp->f_dentry,IN_CLOSE , filp->f_dentry->d_name.name); +inotify_inode_queue_event (filp->f_dentry->d_inode,IN_CLOSE ,NULL );return filp_close (filp, files); out_unlock:
fs/namei.c renamed from linux/fs/namei.c +18 -3
@@ -22,6 +22,7 @@#include <linux/quotaops.h> #include <linux/pagemap.h> #include <linux/dnotify.h> +#include <linux/inotify.h> #include <linux/smp_lock.h> #include <linux/personality.h> #include <linux/security.h>
@@ -1221,6 +1222,7 @@ error = dir->i_op->create (dir, dentry, mode, nd);if (!error) {inode_dir_notify (dir,DN_CREATE ); +inotify_inode_queue_event (dir,IN_CREATE_FILE , dentry->d_name.name);security_inode_post_create (dir, dentry, mode); }return error;
@@ -1535,6 +1537,7 @@ error = dir->i_op->mknod (dir, dentry, mode, dev);if (!error) {inode_dir_notify (dir,DN_CREATE ); +inotify_inode_queue_event (dir,IN_CREATE_FILE , dentry->d_name.name);security_inode_post_mknod (dir, dentry, mode, dev); }return error;
@@ -1608,6 +1611,7 @@ error = dir->i_op->mkdir (dir, dentry, mode);if (!error) {inode_dir_notify (dir,DN_CREATE ); +inotify_inode_queue_event (dir,IN_CREATE_SUBDIR , dentry->d_name.name);security_inode_post_mkdir (dir,dentry, mode); }return error;
@@ -1703,6 +1707,9 @@up (&dentry->d_inode->i_sem);if (!error) {inode_dir_notify (dir,DN_DELETE ); +inotify_inode_queue_event (dir,IN_DELETE_SUBDIR , dentry->d_name.name); +inotify_inode_queue_event (dentry->d_inode,IN_DELETE_SELF ,NULL ); +inotify_inode_is_dead (dentry->d_inode);d_delete (dentry); }dput (dentry);
@@ -1775,8 +1782,11 @@/* We don't d_delete() NFS sillyrenamed files--they still exist. */ if (!error && !(dentry->d_flags &DCACHE_NFSFS_RENAMED )) { -d_delete (dentry);inode_dir_notify (dir,DN_DELETE ); +inotify_inode_queue_event (dir,IN_DELETE_FILE , dentry->d_name.name); +inotify_inode_queue_event (dentry->d_inode,IN_DELETE_SELF ,NULL ); +inotify_inode_is_dead (dentry->d_inode); +d_delete (dentry); }return error; }
@@ -1853,6 +1863,7 @@ error = dir->i_op->symlink (dir, dentry, oldname);if (!error) {inode_dir_notify (dir,DN_CREATE ); +inotify_inode_queue_event (dir,IN_CREATE_FILE , dentry->d_name.name);security_inode_post_symlink (dir, dentry, oldname); }return error;
@@ -1926,6 +1937,7 @@up (&old_dentry->d_inode->i_sem);if (!error) {inode_dir_notify (dir,DN_CREATE ); +inotify_inode_queue_event (dir,IN_CREATE_FILE , new_dentry->d_name.name);security_inode_post_link (old_dentry, dir, new_dentry); }return error;
@@ -2115,12 +2127,15 @@else error =vfs_rename_other (old_dir,old_dentry,new_dir,new_dentry);if (!error) { -if (old_dir == new_dir) +if (old_dir == new_dir) {inode_dir_notify (old_dir,DN_RENAME ); -else { + }else {inode_dir_notify (old_dir,DN_DELETE );inode_dir_notify (new_dir,DN_CREATE ); } + +inotify_inode_queue_event (old_dir,IN_MOVED_FROM , old_dentry->d_name.name); +inotify_inode_queue_event (new_dir,IN_MOVED_TO , new_dentry->d_name.name); }return error; }
fs/inode.c renamed from linux/fs/inode.c +1 -0
@@ -114,6 +114,7 @@if (inode) {struct address_space *const mapping = &inode->i_data; +INIT_LIST_HEAD (&inode->watchers); inode->i_sb = sb; inode->i_blkbits = sb->s_blocksize_bits; inode->i_flags =0 ;
fs/attr.c renamed from linux/fs/attr.c +31 -1
@@ -11,6 +11,7 @@#include <linux/string.h> #include <linux/smp_lock.h> #include <linux/dnotify.h> +#include <linux/inotify.h> #include <linux/fcntl.h> #include <linux/quotaops.h> #include <linux/security.h>
@@ -128,6 +129,29 @@return dn_mask; } +int setattr_mask_inotify (unsigned int ia_valid) +{ +unsigned long dn_mask =0 ; + +if (ia_valid &ATTR_UID ) + dn_mask |=IN_ATTRIB ; +if (ia_valid &ATTR_GID ) + dn_mask |=IN_ATTRIB ; +if (ia_valid &ATTR_SIZE ) + dn_mask |=IN_MODIFY ; +/* both times implies a utime(s) call */ +if ((ia_valid & (ATTR_ATIME |ATTR_MTIME )) == (ATTR_ATIME |ATTR_MTIME )) + dn_mask |=IN_ATTRIB ; +else if (ia_valid &ATTR_ATIME ) + dn_mask |=IN_ACCESS ; +else if (ia_valid &ATTR_MTIME ) + dn_mask |=IN_MODIFY ; +if (ia_valid &ATTR_MODE ) + dn_mask |=IN_ATTRIB ; +return dn_mask; +} + +int notify_change (struct dentry * dentry,struct iattr * attr) {struct inode *inode = dentry->d_inode;
@@ -185,8 +209,14 @@ }if (!error) {unsigned long dn_mask =setattr_mask (ia_valid); -if (dn_mask) +unsigned long in_mask =setattr_mask_inotify (ia_valid); +if (dn_mask) {dnotify_parent (dentry, dn_mask); + } +if (in_mask) { +inotify_inode_queue_event (dentry->d_inode, in_mask,NULL ); +inotify_dentry_parent_queue_event (dentry, in_mask, dentry->d_name.name); + } }return error; }
drivers/char/Makefile renamed from linux/drivers/char/Makefile +1 -1
@@ -7,7 +7,7 @@ # FONTMAPFILE = cp437.uni -obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o pty.o misc.o +obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o pty.o misc.o inotify.o obj-$(CONFIG_VT) += vt_ioctl.o vc_screen.o consolemap.o \ consolemap_deftbl.o selection.o keyboard.o
Quoted 2 lines · John McCutchan
Announcing the release of inotify 0.10.0.
I can't say I'm thrilled by the locking.
Quoted 1 line
Attached is a patch to 2.6.8.1.
Please raise patches against current kernels from ftp://ftp.kernel.org/pub/linux/kernel/v2.6/snapshots. This kernel is six weeks old.
Quoted 1 line
+#define
INOTIFY_VERSION"0.10.0"
You should plan to remove this - it becomes meainingless once the code is merged up, and nobody ever updates it as they patch things.
drivers/char/inotify.c line 46
Quoted 3 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +3
-0+#define MAX_INOTIFY_DEVS 8/* max open inotify devices */ +#define MAX_INOTIFY_DEV_WATCHERS 8192/* max total watches */ +#define MAX_INOTIFY_QUEUED_EVENTS 256/* max events queued on the dev*/
hmm.
+#define INOTIFY_DEV_TIMER_TIME (jiffies + (HZ/4))
ick. Don't hide the logic in a #define.
static inline arm_dev_timer(struct inotify_device *dev)
{
mod_timer(&dev->timer, jiffies + HZ/4);
}is nicer.
drivers/char/inotify.c line 57
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +4
-0+/* For debugging */ +static int event_object_count; +static int watcher_object_count; +static int inode_ref_count;
OK. These are accessed racily. Either make them atomic_t's or remove them.
Quoted 15 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +15
-0+struct inotify_device { +unsigned long bitmask[MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG ]; +struct timer_list timer; +wait_queue_head_t wait; +struct list_head events; +struct list_head watchers; +spinlock_t lock; +unsigned int event_count; +int nr_watches; +}; + +struct inotify_watcher { +int wd;// watcher descriptor +unsigned long mask; +struct inode * inode;
drivers/char/inotify.c line 99
Quoted 10 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+struct inotify_device * dev; +struct list_head d_list;// device list +struct list_head i_list;// inode list +struct list_head u_list;// unmount list +}; +struct inotify_kernel_event { +struct list_head list; +struct inotify_event event; +};
Would be nice to add commentary documenting what lock protects the various fields.
Quoted 1 line
+static int
find_inode(const char __user *dirname, struct inode **inode)
This can just return an inode*, or an IS_ERR() errno, I think?
drivers/char/inotify.c line 134
Quoted 6 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +6
-0+struct inotify_kernel_event *kernel_event (int wd,int mask, +const char *filename) +{ +struct inotify_kernel_event *kevent; + + kevent =kmem_cache_alloc (kevent_cache,GFP_ATOMIC );
Try to rearrange things so the allocation mode here can become GFP_KERNEL.
Are there ever enough of these objects in flight to justify a standalone slab cache?
drivers/char/inotify.c line 316
Quoted 1 line
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +1
-0+ watcher =kmem_cache_alloc (watcher_cache,GFP_KERNEL );
Ditto.
drivers/char/inotify.c line 501
Quoted 6 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +6
-0+void inotify_inode_queue_event (struct inode *inode,unsigned long mask, +const char *filename) +{ +struct inotify_watcher *watcher; + +spin_lock (&inode->i_lock);
i_lock is documented as an "innermost" lock.
Review
Quoted 2 lines
(quoted diff) +2
-0+ list_for_each_entry(watcher, &inode->watchers, i_list) { + spin_lock(&watcher->dev->lock);
But here, dev->lock is nesting inside it. Not necessarily a bug per-se, but it changes and complicates kernel locking rules, and invalidates commentary elsewhere.
If possible, please try to avoid using i_lock. Use i_sem instead.
drivers/char/inotify.c line 521
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+void inotify_inode_queue_event_pair (struct inode *inode1,unsigned long mask1, +const char *filename1, +struct inode *inode2,unsigned long mask2, +const char *filename2) +{ +struct inotify_watcher *watcher; + +spin_lock (&inode1->i_lock); +spin_lock (&inode2->i_lock);
A bug, I think. What happens if another CPU comes in and tries to take these two locks in the opposite order?
The same problem applies if you switch to i_sem. The standard fix is to take the lowest-addressed lock first. See d_move() for an example.
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +4
-0+ +void inotify_dentry_parent_queue_event (struct dentry *dentry, +unsigned long mask, +const char *filename)
drivers/char/inotify.c line 554
Quoted 10 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +10
-0+{ +struct dentry *parent; + +spin_lock (&dentry->d_lock); +dget (dentry->d_parent); + parent = dentry->d_parent; +inotify_inode_queue_event (parent->d_inode, mask, filename); +dput (parent); +spin_unlock (&dentry->d_lock); +}
Does d_lock need to be held across the inotify_inode_queue_event() call?
If (hopefully) not, simply use dget_parent() in here.
drivers/char/inotify.c line 563
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+EXPORT_SYMBOL_GPL (inotify_dentry_parent_queue_event); + +static void ignore_helper (struct inotify_watcher *watcher,int event) +{ +struct inotify_device *dev; +struct inode *inode; + +spin_lock (&watcher->dev->lock); +spin_lock (&watcher->inode->i_lock);
Bug. Elsewhere, i_lock nests outside dev->lock.
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+static void build_umount_list (struct list_head *head,struct super_block *sb, +struct list_head *umount) +{ +struct inode * inode; + +list_for_each_entry (inode, head, i_list) { +struct inotify_watcher *watcher; + +if (inode->i_sb != sb)
drivers/char/inotify.c line 607
Quoted 10 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +10
-0+continue ; + +spin_lock (&inode->i_lock); + +list_for_each_entry (watcher, &inode->watchers, i_list) +list_add (&watcher->u_list, umount); + +spin_unlock (&inode->i_lock); + } +}
I'll be merging invalidate_inodes-speedup.patch once the 2.6.10 stream opens. That will make the above code simpler, faster and quite different.
drivers/char/inotify.c line 630
Quoted 8 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +8
-0+void inotify_inode_is_dead (struct inode *inode) +{ +struct inotify_watcher *watcher, *next; + +list_for_each_entry_safe (watcher, next, &inode->watchers, i_list) { +ignore_helper (watcher,0 ); +} +EXPORT_SYMBOL_GPL (inotify_inode_is_dead);
This needs locking, I think. Or a comment explaining why it does not.
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+add_wait_queue (&dev->wait, &wait); +repeat: +if (signal_pending (current)) { +spin_unlock (&dev->lock); + out = -ERESTARTSYS ; +set_current_state (TASK_RUNNING ); +remove_wait_queue (&dev->wait, &wait); + goto out; + }
drivers/char/inotify.c line 713
Quoted 10 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +10
-0+set_current_state (TASK_INTERRUPTIBLE ); +if (!inotify_dev_has_events (dev)) { +spin_unlock (&dev->lock); +schedule (); +spin_lock (&dev->lock); + goto repeat; + } + +set_current_state (TASK_RUNNING ); +remove_wait_queue (&dev->wait, &wait);
The above seems a bit clumsy.
drivers/char/inotify.c line 722
Quoted 8 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +8
-0+ err = !access_ok (VERIFY_WRITE , (void *)buf, +sizeof (struct inotify_event )); + +if (err) { + out = -EFAULT ; + goto out; + } +
The above is unneeded. copy_to_user() will check.
Quoted 11 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +11
-0+/* Copy all the events we can to the event buffer */ +for (event_count =0 ; event_count < events; event_count++) { + kevent =inotify_dev_get_event (dev); + eventbuf[event_count] = kevent->event; +inotify_dev_event_dequeue (dev); + } + +spin_unlock (&dev->lock); + +/* Send the event buffer to user space */ + err = copy_to_user(buf, eventbuf,
drivers/char/inotify.c line 743
Quoted 10 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +10
-0+ events * sizeof(struct inotify_event)); + + buf +=sizeof (struct inotify_event ) * events; + + out = buf - obuf; + +out: +kfree (eventbuf); +return out; +}
Except the copy_to_user() return value is being ignored!
I don't think you need event_buf at all. Just copy one event at a time to userspace. copy_to_user() is efficient. Using an intermediate buffer like this consumes extra CPU dcache and may even be slower.
Probably slower, given that the temp buffer is kmalloced.
Quoted 5 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +5
-0+static int inotify_open (struct inode *inode,struct file *file) +{ +struct inotify_device *dev; + +if (atomic_read (&watcher_count) ==MAX_INOTIFY_DEVS )
drivers/char/inotify.c line 775
Quoted 10 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +10
-0+return -ENODEV ; + +atomic_inc (&watcher_count); + + dev =kmalloc (sizeof (struct inotify_device ),GFP_KERNEL ); +if (!dev) +return -ENOMEM ; + +memset (dev->bitmask,0 , +sizeof (unsigned long ) *MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG );
What purpose does this bitmask serve, anyway??
drivers/char/inotify.c line 792
Quoted 1 line
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +1
-0+add_timer (&dev->timer);
And what does the timer do? (You surely explained the feature earlier, but I was asleep, sorry).
drivers/char/inotify.c line 805
Quoted 7 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +7
-0+static void inotify_release_all_watchers (struct inotify_device *dev) +{ +struct inotify_watcher *watcher,*next; + +list_for_each_entry_safe (watcher, next, &dev->watchers, d_list) +ignore_helper (watcher,0 ); +}
Locking?
drivers/char/inotify.c line 821
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +4
-0+ +static int inotify_release (struct inode *inode,struct file *file) +{ +if (file->private_data) {
Why test ->private_data here?
If it indeed needs testing here, shouldn't it be zeroed out as well, with appropriate locking?
drivers/char/inotify.c line 824
Quoted 3 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +3
-0+struct inotify_device *dev; + + dev = (struct inotify_device *) file->private_data;
Please don't typecast when assigning to and from void*'s
drivers/char/inotify.c line 833
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+del_timer_sync (&dev->timer); +inotify_release_all_watchers (dev); +inotify_release_all_events (dev); +kfree (dev); + } + +printk (KERN_ALERT "inotify device released\n" ); + +atomic_dec (&watcher_count);
If file->private_data was zero, we shouldn't have decremented this?
+static int inotify_watch(struct inotify_device *dev,
+ struct inotify_watch_request *request)
drivers/char/inotify.c line 853
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +3
-0+{ +spin_lock (&dev->lock); +spin_lock (&inode->i_lock);
Lock ranking.
drivers/char/inotify.c line 956
Quoted 2 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +2
-0+static int inotify_ioctl (struct inode *ip,struct file *fp, +unsigned int cmd,unsigned long arg) {
An errant brace!
drivers/char/inotify.c line 976
Quoted 8 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +8
-0+ +if (_IOC_DIR (cmd) & _IOC_READ) + err = !access_ok (VERIFY_READ , (void *) arg,_IOC_SIZE (cmd)); + +if (err) + err = -EFAULT ; + goto out; +
eh? The above is missing braces, and cannot possibly have worked.
drivers/char/inotify.c line 983
Quoted 7 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +7
-0+if (_IOC_DIR (cmd) & _IOC_WRITE) + err = !access_ok (VERIFY_WRITE , (void *)arg,_IOC_SIZE (cmd)); + +if (err) { + err = -EFAULT ; + goto out; + }
Why are these access_ok() checks here? I think they can (should) go away.
drivers/char/inotify.c line 988
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +4
-0+ err = -EINVAL ; + +switch (cmd) { +case INOTIFY_WATCH :
We often do:
switch (cmd) {
case INOTIFY_WATCH:to save a tabstop.
drivers/char/inotify.c line 1041
Quoted 5 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +5
-0+struct miscdevice inotify_device = { + .minor =MISC_DYNAMIC_MINOR , + .name ="inotify" , + .fops = &inotify_fops, +};
Please update devices.txt
Quoted 8 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +8
-0+/* this size could limit things, since technically we could need PATH_MAX */ +#define INOTIFY_FILENAME_MAX 256 + +/* +* struct inotify_event - structure read from the inotify device for each event +* +* When you are watching a directory, you will receive the filename for events +* such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ...
include/linux/inotify.h line 31
Quoted 10 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +10
-0+* +* Note: When reading from the device you must provide a buffer that is a +* multiple of sizeof(struct inotify_event) +*/ +struct inotify_event { +int wd; +int mask; +int cookie; +char filename[INOTIFY_FILENAME_MAX ]; +};
yeah, that's not very nice. Better to kmalloc the pathname.
Quoted 4 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +4
-0+/* Adds event to all watchers on inode that are interested in mask */ +void inotify_inode_queue_event (struct inode *inode,unsigned long mask, +const char *filename); +
include/linux/inotify.h line 97
Quoted 10 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +10
-0+/* Same as above but uses dentry's inode */ +void inotify_dentry_parent_queue_event (struct dentry *dentry, +unsigned long mask,const char *filename); + +/* This will remove all watchers from all inodes on the superblock */ +void inotify_super_block_umount (struct super_block *sb); + +/* Call this when an inode is dead, and inotify should ignore it */ +void inotify_inode_is_dead (struct inode *inode); +
Please add CONFIG_INOTIFY and make all this:
include/linux/fs.h line 467
Quoted 13 lines
include/linux/fs.h renamed from linux/include/linux/fs.h +4
-0@@ -458,6 +458,10 @@unsigned long i_dnotify_mask;/* Directory notify events */ struct dnotify_struct *i_dnotify;/* for directory notifications */ +struct list_head watchers; +unsigned long watchers_mask; +int watcher_count; +unsigned long i_state;unsigned long dirtied_when;/* jiffies of first dirtying */
and this:
include/linux/fs.h line 225
Quoted 11 lines
include/linux/fs.h renamed from linux/include/linux/fs.h +4
-1@@ -216,8 +217,11 @@ ret = file->f_op->read (file, buf, count, pos); else ret =do_sync_read (file, buf, count, pos); -if (ret >0 ) +if (ret >0 ) {dnotify_parent (file->f_dentry,DN_ACCESS ); +inotify_dentry_parent_queue_event (file->f_dentry,IN_ACCESS , file->f_dentry->d_name.name); +inotify_inode_queue_event (file->f_dentry->d_inode,IN_ACCESS ,NULL ); + } }
go away if the user doesn't want inotify. And remember to test with CONFIG_INOTIFY=n!
include/linux/fs.h line 226
Quoted 1 line
include/linux/fs.h renamed from linux/include/linux/fs.h +1
-0+INIT_LIST_HEAD (&inode->watchers);
Please review the entire patch and ensure that all macros and function calls have no space between the identifier and the opening parenthesis.
Can't say that I'm thrilled that it uses an ioctl.
What are the security policies?
Am I allowed to monitor an inode for which I do not have read permission? If so, can I use that to pin files which root wants to delete, thus causing the disk to fill up?
Quoted 3 lines · Andrew Morton
Please raise patches against current kernels from ftp://ftp.kernel.org/pub/linux/kernel/v2.6/snapshots. This kernel is six weeks old.
I have patches against newer kernels at
http://www.kernel.org/pub/linux/kernel/people/rml/inotify
Anyhow, the tidal flood of commentary is appreciated. I have addressed all of your issues ...
Quoted 4 lines
Quoted 1 line
+#define
INOTIFY_VERSION"0.10.0"You should plan to remove this - it becomes meainingless once the code is merged up, and nobody ever updates it as they patch things.
Agreed. Patch sent.
Quoted 10 lines
+#define
INOTIFY_DEV_TIMER_TIME(jiffies + (HZ/4))ick. Don't hide the logic in a #define.
static inline
arm_dev_timer(struct inotify_device *dev){ mod_timer(&dev->timer, jiffies + HZ/4); }is nicer.
It is used in more than one place, but OK. Patch sent.
Quoted 6 lines
drivers/char/inotify.c line 57
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +4
-0+/* For debugging */ +static int event_object_count; +static int watcher_object_count; +static int inode_ref_count;OK. These are accessed racily. Either make them atomic_t's or remove them.
They are just statistics. I'd prefer to remove them entirely (I don't personally think that the debugging code, statistics, etc. should go into the mainline kernel).
Quoted 3 lines
Quoted 1 line
+static int
find_inode(const char __user *dirname, struct inode **inode)This can just return an inode*, or an
IS_ERR()errno, I think?
Yes, it can. Done, patch sent.
Quoted 8 lines
drivers/char/inotify.c line 134
Quoted 6 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +6
-0+struct inotify_kernel_event *kernel_event (int wd,int mask, +const char *filename) +{ +struct inotify_kernel_event *kevent; + + kevent =kmem_cache_alloc (kevent_cache,GFP_ATOMIC );Try to rearrange things so the allocation mode here can become
GFP_KERNEL.
Hrmph.
Quoted 2 lines
Are there ever enough of these objects in flight to justify a standalone slab cache?
Yes. There are up to 256*8 possible events and (more importantly than the net number, I think) they come and go constantly.
Quoted 1 line
drivers/char/inotify.c line 316
Quoted 1 line
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +1
-0+ watcher =kmem_cache_alloc (watcher_cache,GFP_KERNEL );
Yes. There can be a lot of watches... one of the intended uses of this is automatic indexing of a user's homedir (think Apple Spotlight). Alan Cox has also mentioned virus checking, etc.
There are structures are _not_ used as much, and we do not slab cache them.
Quoted 6 lines
i_lockis documented as an "innermost" lock.But here,
dev->lockis nesting inside it. Not necessarily a bug per-se, but it changes and complicates kernel locking rules, and invalidates commentary elsewhere.
Quoted 7 lines
If possible, please try to avoid using
i_lock. Usei_seminstead.A bug, I think. What happens if another CPU comes in and tries to take these two locks in the opposite order?
The same problem applies if you switch to
i_sem. The standard fix is to take the lowest-addressed lock first. Seed_move()for an example.
I'll work on the locking.
Quoted 2 lines
I'll be merging invalidate_inodes-speedup.patch once the 2.6.10 stream opens. That will make the above code simpler, faster and quite different.
Sweet.
Quoted 13 lines
Quoted 13 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +13
-0+add_wait_queue (&dev->wait, &wait); +repeat: +if (signal_pending (current)) { +spin_unlock (&dev->lock); + out = -ERESTARTSYS ; +set_current_state (TASK_RUNNING ); +remove_wait_queue (&dev->wait, &wait); + goto out; + } +set_current_state (TASK_INTERRUPTIBLE ); +if (!inotify_dev_has_events (dev)) { +spin_unlock (&dev->lock); +schedule ();
Quoted 8 lines
drivers/char/inotify.c line 713
Quoted 6 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +6
-0+spin_lock (&dev->lock); + goto repeat; + } + +set_current_state (TASK_RUNNING ); +remove_wait_queue (&dev->wait, &wait);The above seems a bit clumsy.
John is reworking inotify_read(), which should take care of the various issues you raised here.
Quoted 9 lines
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+static int inotify_open (struct inode *inode,struct file *file) +{ +struct inotify_device *dev; + +if (atomic_read (&watcher_count) ==MAX_INOTIFY_DEVS ) +return -ENODEV ; + +atomic_inc (&watcher_count); +
Quoted 8 lines
drivers/char/inotify.c line 775
Quoted 6 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +6
-0+ dev =kmalloc (sizeof (struct inotify_device ),GFP_KERNEL ); +if (!dev) +return -ENOMEM ; + +memset (dev->bitmask,0 , +sizeof (unsigned long ) *MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG );What purpose does this bitmask serve, anyway??
Bitmask of allocated/unallocated watcher descriptors.
Patch sent to add a comment. Also sent a patch to use the bitmap.h functions instead of this open-coded memset().
Quoted 10 lines
drivers/char/inotify.c line 805
Quoted 7 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +7
-0+static void inotify_release_all_watchers (struct inotify_device *dev) +{ +struct inotify_watcher *watcher,*next; + +list_for_each_entry_safe (watcher, next, &dev->watchers, d_list) +ignore_helper (watcher,0 ); +}Locking?
Quoted 6 lines
drivers/char/inotify.c line 821
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +4
-0+ +static int inotify_release (struct inode *inode,struct file *file) +{ +if (file->private_data) {Why test ->private_data here?
I'm not sure why. I don't think we ought to - release functions are only called once, when the last ref on the file dies. I asked John and sent a patch to remove it.
Quoted 8 lines
If it indeed needs testing here, shouldn't it be zeroed out as well, with appropriate locking?
drivers/char/inotify.c line 824
Quoted 3 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +3
-0+struct inotify_device *dev; + + dev = (struct inotify_device *) file->private_data;Please don't typecast when assigning to and from void*'s
Nod. Patch sent.
Quoted 11 lines
drivers/char/inotify.c line 833
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +9
-0+del_timer_sync (&dev->timer); +inotify_release_all_watchers (dev); +inotify_release_all_events (dev); +kfree (dev); + } + +printk (KERN_ALERT "inotify device released\n" ); + +atomic_dec (&watcher_count);If
file->private_datawas zero, we shouldn't have decremented this?
I don't think we should test file->private_data at all, so after fixing that, this is fixed.
Quoted 9 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +2
-0+static int inotify_watch (struct inotify_device *dev, +struct inotify_watch_request *request)drivers/char/inotify.c line 853
Quoted 4 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +3
-0+{ +spin_lock (&dev->lock); +spin_lock (&inode->i_lock);Lock ranking.
Quoted 4 lines
drivers/char/inotify.c line 956
Quoted 2 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +2
-0+static int inotify_ioctl (struct inode *ip,struct file *fp, +unsigned int cmd,unsigned long arg) {An errant brace!
Patch sent.
Quoted 10 lines
drivers/char/inotify.c line 976
Quoted 8 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +8
-0+ +if (_IOC_DIR (cmd) & _IOC_READ) + err = !access_ok (VERIFY_READ , (void *) arg,_IOC_SIZE (cmd)); + +if (err) + err = -EFAULT ; + goto out; +eh? The above is missing braces, and cannot possibly have worked.
That is my fault, just introduced in the latest revision.
Quoted 9 lines
drivers/char/inotify.c line 983
Quoted 7 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +7
-0+if (_IOC_DIR (cmd) & _IOC_WRITE) + err = !access_ok (VERIFY_WRITE , (void *)arg,_IOC_SIZE (cmd)); + +if (err) { + err = -EFAULT ; + goto out; + }Why are these
access_ok()checks here? I think they can (should) go away.
They should. We should just use copy_{to,from}_user. Will fix.
Quoted 6 lines
We often do:
switch (cmd) { case INOTIFY_WATCH:to save a tabstop.
Yah. I thought I included that in my coding style cleanup. Patch sent.
Quoted 7 lines
drivers/char/inotify.c line 1041
Quoted 5 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +5
-0+struct miscdevice inotify_device = { + .minor =MISC_DYNAMIC_MINOR , + .name ="inotify" , + .fops = &inotify_fops, +};Please update devices.txt
Documentation/devices.txt doesn't really have provisions for dynamically allocated devices, which this is.
We _could_ take a fixed minor...
Quoted 8 lines
include/linux/inotify.h line 31
Quoted 6 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +6
-0+struct inotify_event { +int wd; +int mask; +int cookie; +char filename[INOTIFY_FILENAME_MAX ]; +};yeah, that's not very nice. Better to kmalloc the pathname.
That is the structure that we communicate with to user-space.
We could kmalloc() filename, but it makes the user-space use a bit more complicated (and right now it is trivial and wonderfully simple).
We've been debating the pros and cons.
Quoted 6 lines
Please add
CONFIG_INOTIFYand make all this:[...]
go away if the user doesn't want inotify. And remember to test with
CONFIG_INOTIFY=n!
Done. Patch sent.
Quoted 4 lines
fs/inode.c line 117
Quoted 1 line
fs/inode.c renamed from linux/fs/inode.c +1
-0+INIT_LIST_HEAD (&inode->watchers);Please review the entire patch and ensure that all macros and function calls have no space between the identifier and the opening parenthesis.
I thought I caught them all - guess not, patch sent.
Thanks for the feedback.
Robert Love
Quoted 7 lines · Robert Love
Quoted 5 lines
drivers/char/inotify.c line 775
Quoted 3 lines
drivers/char/inotify.c renamed from linux/drivers/char/inotify.c +2
-0+memset (dev->bitmask,0 , +sizeof (unsigned long ) *MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG );What purpose does this bitmask serve, anyway??
Bitmask of allocated/unallocated watcher descriptors.
Can you expand on that? Why do we need such a bitmap?
Would an idr tree be more appropriate?
Quoted 11 lines
We _could_ take a fixed minor...
Quoted 8 lines
include/linux/inotify.h line 31
Quoted 6 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +6
-0+struct inotify_event { +int wd; +int mask; +int cookie; +char filename[INOTIFY_FILENAME_MAX ]; +};yeah, that's not very nice. Better to kmalloc the pathname.
Quoted 1 line
That is the structure that we communicate with to user-space.
In that case it looks rather 64-bit-unfriendly. A 32-bit compiler will lay that structure out differently from a 64-bit compiler. Or not. Hard to say. Perhaps something more defensive is needed here.
One other thing: the patch adds 16 bytes to struct inode, for a feature which many users and most inodes will not use. Unfortunate.
Is it possible to redesign things so that those four new fields are in a standalone struct which points at the managed inode? Joined at the hip like journal_head and buffer_head?
Bonus marks for not having a backpointer from the inode to the new struct ;)
(Still wondering what those timers are doing in there, btw)
Quoted 1 line · Andrew Morton
Can you expand on that? Why do we need such a bitmap?
It is a unique cookie that identifies the exact object being watched (e.g. /home/rml or /etc/fstab). I suspect John introduced it in lieu of the (device,inode) tuple when Al bitched, which makes sense. Because we have only a single fd (this is one of the problems with dnotify, the 1:1 relation between objects and file descriptors consumed) we need some other object to identify each watched object.
So John introduced watcher descriptors. This bitmask keeps track of which descriptors are used versus unused.
Quoted 1 line
Would an idr tree be more appropriate?
Quite possibly. I was originally thinking that idr's were too heavy, but if we can make the wd <-> inotify_watcher relation then they make perfect sense.
I'll look at making the conversion.
Quoted 3 lines
In that case it looks rather 64-bit-unfriendly. A 32-bit compiler will lay that structure out differently from a 64-bit compiler. Or not. Hard to say. Perhaps something more defensive is needed here.
Well, no, since all known architectures are everything-is-32bit or LP64, as far as I know. And padding would be the same.
And even if not, the only problem would be with 64-bit architectures and a 32-bit user-space.
Nonetheless, we should probably make the three int types be s32 or u32's, eh? I will submit a patch.
Quoted 6 lines
One other thing: the patch adds 16 bytes to struct inode, for a feature which many users and most inodes will not use. Unfortunate.
Is it possible to redesign things so that those four new fields are in a standalone struct which points at the managed inode? Joined at the hip like
journal_headandbuffer_head?
We could probably get away with a single word-sized variable in the inode structure.
Quoted 1 line
Bonus marks for not having a backpointer from the inode to the new struct ;)
Don't push your luck. ;-)
In school, I always felt the bonus was just showing off, what with the perfect score on the normal assignment. But I will investigate.
Quoted 1 line
(Still wondering what those timers are doing in there, btw)
John? I see what the timer does, but I am wondering why a timer _has_ to do it?
Thanks for the review, Andrew.
Robert Love
Quoted 14 lines · Robert Love
Quoted 1 line · Andrew Morton
Can you expand on that? Why do we need such a bitmap?
It is a unique cookie that identifies the exact object being watched (e.g.
/home/rmlor/etc/fstab). I suspect John introduced it in lieu of the (device,inode) tuple when Al bitched, which makes sense. Because we have only a single fd (this is one of the problems with dnotify, the 1:1 relation between objects and file descriptors consumed) we need some other object to identify each watched object.So John introduced watcher descriptors. This bitmask keeps track of which descriptors are used versus unused.
Quoted 7 lines
Quoted 1 line
Would an idr tree be more appropriate?
Quite possibly. I was originally thinking that idr's were too heavy, but if we can make the wd <->
inotify_watcherrelation then they make perfect sense.I'll look at making the conversion.
I only first heard about idr in last weeks LWN, I thought they might be useful.
Quoted 17 lines
Quoted 4 lines
In that case it looks rather 64-bit-unfriendly. A 32-bit compiler will lay that structure out differently from a 64-bit compiler. Or not. Hard to say. Perhaps something more defensive is needed here.
Well, no, since all known architectures are everything-is-32bit or LP64, as far as I know. And padding would be the same.
And even if not, the only problem would be with 64-bit architectures and a 32-bit user-space.
Nonetheless, we should probably make the three int types be s32 or u32's, eh? I will submit a patch.
Quoted 3 lines
One other thing: the patch adds 16 bytes to struct inode, for a feature which many users and most inodes will not use. Unfortunate.
Quoted 7 lines
Quoted 3 lines
Is it possible to redesign things so that those four new fields are in a standalone struct which points at the managed inode? Joined at the hip like
journal_headandbuffer_head?We could probably get away with a single word-sized variable in the inode structure.
Yep, we could toss everything in to a structure and only have a pointer to it from the inode.
Quoted 11 lines
Quoted 1 line
Bonus marks for not having a backpointer from the inode to the new struct ;)
Don't push your luck. ;-)
In school, I always felt the bonus was just showing off, what with the perfect score on the normal assignment. But I will investigate.
Quoted 1 line
(Still wondering what those timers are doing in there, btw)
John? I see what the timer does, but I am wondering why a timer _has_ to do it?
We need a timer to wake up any processes blocking on a read() call. The reason it has to be a timer is because the code paths that get run when an event is queued are not safe places to wake up blocked processes (But I a kernel amateur so I am probably wrong).
John
Quoted 4 lines · John McCutchan
We need a timer to wake up any processes blocking on a
read()call. The reason it has to be a timer is because the code paths that get run when an event is queued are not safe places to wake up blocked processes (But I a kernel amateur so I am probably wrong).
We probably don't need the timer. wake_up_interruptible() does not sleep; we can call it from anywhere. Heck, timers are more atomic than where we probably need to wake stuff up from anyhow.
But it is not easy to tell where that place is, because it looks like the timer just runs every 250ms? That is no good.
I suspect that we can remove all of the timer stuff and just do
/* wake up! you are going to miss the bus! */ wake_up_interruptible(&dev->wait);
after
list_add_tail(&kevent->list, &dev->events);
in inotify_dev_queue_event().
Robert Love
Quoted 9 lines · Robert Love
Quoted 8 lines
include/linux/inotify.h line 31
Quoted 6 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +6
-0+struct inotify_event { +int wd; +int mask; +int cookie; +char filename[INOTIFY_FILENAME_MAX ]; +};yeah, that's not very nice. Better to kmalloc the pathname.
Quoted 6 lines
That is the structure that we communicate with to user-space.
We could
kmalloc()filename, but it makes the user-space use a bit more complicated (and right now it is trivial and wonderfully simple).We've been debating the pros and cons.
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length. It makes userspace double-check whether the filename extends all the way to the boundary of the structure, and if so, then go back to the disk to try to guess what the kernel really meant to say.
My way, doing a kmalloc of either the entire structure + filename length
+ NUL, or just the filename + NUL makes userspace infinitesimally trickier to write. There's more effort in just getting the error handling correct in either version.
The current way viewed by userspace, stolen shamelessly from the beagle source:
/* FIXME: We need to check for errors, etc. */
remaining = sizeof (struct inotify_event);
event_buffer = (char *) &event;
while (remaining > 0) {
num_bytes = read (fd, event_buffer, remaining);
event_buffer += num_bytes;
remaining -= num_bytes;
}My way (where struct inotify_event is declared with a char filename[0] at the end):
if ((n=read(fd, buf, sizeof buf)) > 0) {
unsigned offset=0;
while (n > 0) {
// inotify guarantees complete events
unsigned len = sizeof(struct inotify_event);
dispatch((struct inotify_event *) &buf[offset]);
len += strlen(buf[offset + len]) + 1;
n -= len;
offset += len;
}
}Doesn't seem all that tricky.
I'm willing to believe that I'm missing something. Help me see what.
Ray
Quoted 4 lines · Ray Lee
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length.
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
Quoted 3 lines · Andrew Morton
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
For passing in the data, that would work. Wouldn't you still need a name or path when getting data back though?
Chris
Quoted 8 lines · Chris Friesen
Quoted 3 lines · Andrew Morton
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
For passing in the data, that would work. Wouldn't you still need a name or path when getting data back though?
Does Andrew mean an fd on the thing being watched?
That is what we are trying to fix with dnotify: the open fd's are pin the device and prevent unmount, making notification on removable devices impossible. Such a 1:1 relationship also opens way too many fd's.
Robert Love
Quoted 7 lines · Robert Love
Quoted 6 lines · Chris Friesen
Quoted 3 lines · Andrew Morton
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
Quoted 8 lines
Quoted 2 lines
For passing in the data, that would work. Wouldn't you still need a name or path when getting data back though?
Does Andrew mean an fd on the thing being watched?
That is what we are trying to fix with dnotify: the open fd's are pin the device and prevent unmount, making notification on removable devices impossible.
That's why he said to close the fd right after the syscall. But yeah, for a case of someone wanting to watch their 1700 directories underneath ~/, thems a lot of open calls.
Quoted 1 line
Such a 1:1 relationship also opens way too many fd's.
...I'm not sure I follow. If you're talking about the IN_CREATE and IN_DELETE events available when watching a parent directory, then I don't think anything would change. IOW, why not do an open(2) on the directory in question, and pass that fd in?
Regardless, Andrew's point still stands. What do we want the permission semantics to be? One would think that a normal user account should not be able to watch the contents of some other user's 0600 directories, for example. open(2) already does all the correct checks. We should inherit that work if at all possible.
Another benefit of passing in an fd, by the way, would be to make it easier to make a write(2) interface to inotify, and get rid of the ioctl one.
~ ~
As Chris points out, we still need a way to pass the name or path back to userspace when an event occurs, which is the interface I was harping on a few messages back.
It seems we're trying to recreate a variant struct dirent for communicating changes to userspace. Perhaps we can learn something from already trodden ground? Just sayin'.
Ray
Quoted 28 lines · Ray Lee
Quoted 7 lines · Robert Love
Quoted 6 lines · Chris Friesen
Quoted 3 lines · Andrew Morton
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
Quoted 8 lines
Quoted 2 lines
For passing in the data, that would work. Wouldn't you still need a name or path when getting data back though?
Does Andrew mean an fd on the thing being watched?
That is what we are trying to fix with dnotify: the open fd's are pin the device and prevent unmount, making notification on removable devices impossible.
That's why he said to close the fd right after the syscall. But yeah, for a case of someone wanting to watch their 1700 directories underneath ~/, thems a lot of open calls.
Quoted 1 line
Such a 1:1 relationship also opens way too many fd's.
...I'm not sure I follow. If you're talking about the
IN_CREATEandIN_DELETEevents available when watching a parent directory, then I don't think anything would change. IOW, why not do an open(2) on the directory in question, and pass that fd in?
Quoted 5 lines
Regardless, Andrew's point still stands. What do we want the permission semantics to be? One would think that a normal user account should not be able to watch the contents of some other user's 0600 directories, for example. open(2) already does all the correct checks. We should inherit that work if at all possible.
Yes we should, but I think the inotify interface would be cleaner if we just factored out this permission code and called it from open() and from the inotify code.
Quoted 5 lines
Another benefit of passing in an fd, by the way, would be to make it easier to make a write(2) interface to inotify, and get rid of the ioctl one.
I don't see how passing directories/files to inotify by fd not filename, makes providing a write(2) interface to inotify any easier. To me they are mutually exclusive. When you open up /dev/inotify, you get an fd, you read events from it. We could provide write on that fd instead of the ioctl() interface.
Quoted 9 lines
~ ~
As Chris points out, we still need a way to pass the name or path back to userspace when an event occurs, which is the interface I was harping on a few messages back.
It seems we're trying to recreate a variant struct dirent for communicating changes to userspace. Perhaps we can learn something from already trodden ground? Just sayin'.
Yes the current method of passing the name back to user space is definitely sub par. But I don't think passing a full path to user space is reasonable, as that would require walking the dirent tree for every event. Really the best we can provide user space is the filename/dirname (relative to the directory you are currently watching).
John
Quoted 35 lines · John McCutchan
Quoted 28 lines · Ray Lee
Quoted 7 lines · Robert Love
Quoted 6 lines · Chris Friesen
Quoted 3 lines · Andrew Morton
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
Quoted 8 lines
Quoted 2 lines
For passing in the data, that would work. Wouldn't you still need a name or path when getting data back though?
Does Andrew mean an fd on the thing being watched?
That is what we are trying to fix with dnotify: the open fd's are pin the device and prevent unmount, making notification on removable devices impossible.
That's why he said to close the fd right after the syscall. But yeah, for a case of someone wanting to watch their 1700 directories underneath ~/, thems a lot of open calls.
Quoted 1 line
Such a 1:1 relationship also opens way too many fd's.
...I'm not sure I follow. If you're talking about the
IN_CREATEandIN_DELETEevents available when watching a parent directory, then I don't think anything would change. IOW, why not do an open(2) on the directory in question, and pass that fd in?Quoted 5 lines
Regardless, Andrew's point still stands. What do we want the permission semantics to be? One would think that a normal user account should not be able to watch the contents of some other user's 0600 directories, for example. open(2) already does all the correct checks. We should inherit that work if at all possible.
Quoted 3 lines
Yes we should, but I think the inotify interface would be cleaner if we just factored out this permission code and called it from
open()and from the inotify code.
I don't know enough to have an opinion on this. I'll leave that up to actual arbiters of taste.
Quoted 8 lines
Quoted 5 lines
Another benefit of passing in an fd, by the way, would be to make it easier to make a write(2) interface to inotify, and get rid of the ioctl one.
I don't see how passing directories/files to inotify by fd not filename, makes providing a write(2) interface to inotify any easier.
There seemed to be some unwillingness to deal with variable length items being passed back and forth, to and fro userspace. Placing an fd in the watch structure would make it fixed size, rather than variable length. Frankly, I find either as easy to deal with.
Quoted 1 line
To me they are mutually exclusive.
To me as well. Robert seems to have issues with making the userspace interface as simple as possible, hence my suggestion. I'm happy to withdraw it.
Quoted 3 lines
When you open up
/dev/inotify, you get an fd,
you read events from it. We could provide write on that fd instead of theioctl()interface.
I agree completely.
Quoted 8 lines
Quoted 7 lines
As Chris points out, we still need a way to pass the name or path back to userspace when an event occurs, which is the interface I was harping on a few messages back.
It seems we're trying to recreate a variant struct dirent for communicating changes to userspace. Perhaps we can learn something from already trodden ground? Just sayin'.
Quoted 5 lines
Yes the current method of passing the name back to user space is definitely sub par. But I don't think passing a full path to user space is reasonable, as that would require walking the dirent tree for every event. Really the best we can provide user space is the filename/dirname (relative to the directory you are currently watching).
Apologies, I was unclear. I should not have said that we would pass the full path back. In fact, we can't, as the same directory may be mounted in multiple places. I knew this, but I blame lack of sleep for my slip.
Ray
Quoted 11 lines · John McCutchan
Quoted 10 lines
~ ~
As Chris points out, we still need a way to pass the name or path back to userspace when an event occurs, which is the interface I was harping on a few messages back.
It seems we're trying to recreate a variant struct dirent for communicating changes to userspace. Perhaps we can learn something from already trodden ground? Just sayin'.
Quoted 5 lines
Yes the current method of passing the name back to user space is definitely sub par. But I don't think passing a full path to user space is reasonable, as that would require walking the dirent tree for every event. Really the best we can provide user space is the filename/dirname (relative to the directory you are currently watching).
Userspace requests that the kernel start monitoring an fd. The kernel returns an integer cookie which corresponds to that monitor.
When an event occurs, the kernel returns the event identifier and the cookie to userspace.
Userspace then does a lookup to work out what pathname corresponds to the cookie.
Or is it the case that you expect a single monitor on /etc will return "/etc/passwd" if someone modified that file, or "/etc/hosts" if someone modified that file? If so, perhaps we should take that feature away and require that userspace rescan the directory?
Because passing pathnames into and back from the kernel from this manner is really not a nice design.
A halfway point might be to return {cookie-of-/etc,EVENT_MODIFY,"hosts"} to a monitor on the /etc directory.
Quoted 18 lines · Andrew Morton
Quoted 11 lines · John McCutchan
Quoted 10 lines
~ ~
As Chris points out, we still need a way to pass the name or path back to userspace when an event occurs, which is the interface I was harping on a few messages back.
It seems we're trying to recreate a variant struct dirent for communicating changes to userspace. Perhaps we can learn something from already trodden ground? Just sayin'.
Quoted 5 lines
Yes the current method of passing the name back to user space is definitely sub par. But I don't think passing a full path to user space is reasonable, as that would require walking the dirent tree for every event. Really the best we can provide user space is the filename/dirname (relative to the directory you are currently watching).
Quoted 8 lines
Userspace requests that the kernel start monitoring an fd. The kernel returns an integer cookie which corresponds to that monitor.
When an event occurs, the kernel returns the event identifier and the cookie to userspace.
Userspace then does a lookup to work out what pathname corresponds to the cookie.
That is what inotify does. Except user space asks the kernel to monitor a particular path (not an fd).
Quoted 7 lines
Or is it the case that you expect a single monitor on /etc will return "
/etc/passwd" if someone modified that file, or "/etc/hosts" if someone modified that file? If so, perhaps we should take that feature away and require that userspace rescan the directory?
Yes, this is the case that we are discussing. Rescanning the directory is a waste, if the kernel can cheaply tell userspace what happened.
Quoted 8 lines
Because passing pathnames into and back from the kernel from this manner is really not a nice design.
A halfway point might be to return {cookie-of-/etc,EVENT_MODIFY,"hosts"} to a monitor on the /etc directory.
This is what the current inotify code does. You get an event with the filename relative to your watch descriptor (cookie).
John
Quoted 3 lines · Andrew Morton
Or is it the case that you expect a single monitor on /etc will return "
/etc/passwd" if someone modified that file, or "/etc/hosts" if someone modified that file?
A monitor on "/etc" will return "hosts" if hosts is modified. Which I think is OK--we don't pass the entire path, nor do we want to if we could do it easily, for numerous reasons ..
Quoted 2 lines
If so, perhaps we should take that feature away and require that userspace rescan the directory?
This is one of the issues with dnotify we want to fix. And to do generic file notification, it is not just rescanning the directory but caching file modify timestamps (say, keep all of the stat structures in memory) and then re-stat'ing everything and comparing. Ugh.
Quoted 2 lines
Because passing pathnames into and back from the kernel from this manner is really not a nice design.
Agreed.
Quoted 2 lines
A halfway point might be to return {cookie-of-/etc,EVENT_MODIFY,"hosts"} to a monitor on the /etc directory.
This is what inotify does. ;-)
Everything is relative to the object being watched.
Robert Love
Quoted 3 lines · Robert
A monitor on "/etc" will return "hosts" if hosts is modified. Which I think is OK--we don't pass the entire path, nor do we want to if we could do it easily, for numerous reasons ..
How about returning the inode number?
Notice that this can be converted to the name without using stat, using the d_ino field in the struct dirent returned from an opendir/readdir loop.
Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373Quoted 8 lines · Paul Jackson
Quoted 3 lines · Robert
A monitor on "/etc" will return "hosts" if hosts is modified. Which I think is OK--we don't pass the entire path, nor do we want to if we could do it easily, for numerous reasons ..
How about returning the inode number?
Then I think you need the filesystem as well.
Chris
Chris replied to pj:
Quoted 3 lines
Quoted 1 line
How about returning the inode number?
Then I think you need the filesystem as well.
They already had that, with the integer cookie representing the directory ("/etc", in the example), as was a bit obvious, or else returning the relative path ("hosts" in the example) wouldn't have worked either.
That is, I was suggesting returning:
{cookie-of-/etc,EVENT_MODIFY,inode-number-of-hosts-file}instead of:
{cookie-of-/etc,EVENT_MODIFY,"hosts"}Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373Quoted 10 lines · Paul Jackson
Quoted 3 lines · Robert
A monitor on "/etc" will return "hosts" if hosts is modified. Which I think is OK--we don't pass the entire path, nor do we want to if we could do it easily, for numerous reasons ..
How about returning the inode number?
Notice that this can be converted to the name without using stat, using the
d_inofield in the struct dirent returned from an opendir/readdir loop.
This changes an O(1) process to O(N), where N is the size of the directory. That's one of the issues with dnotify that they're trying to fix.
Bottom line, the kernel has that information available to it, and cheaply. Forcing userspace to rederive that information for the advantage of not spewing filenames out of the kernel is a false economy, when viewed from the entire system.
Ray
Quoted 1 line
This changes an O(1) process to O(N),
At the microlevel, yes. But:
1) If one takes as the "unit of measurement" the number of system calls made, it's more like O(N/128), given that one might average 128 directory entries per getdents() call.
2) This can be cached, with user code mapping inode->d_ino to d_name, and then the cached name checked with a single stat(2) call to ensure it wasn't stale.
Be leary of micro optimizations imposing a poorer design, especially across major API boundaries. Simply waving "O(N)" in our face may not be adequately persuasive. You might need a compelling performance analysis, showing that you can only meet critical goals by passing the name. Such analysis may already be intuitively obvious to you. If it's already earlier on this thread, don't hesitate to tell me where to go back and read it. But right now I am unaware of any such compelling need.
And there is a long history of pain in Unix dealing with variable length return values. Much better to deal with that entirely in user space code under your control, than to have to align kernel and glibc code in addition to your code, to get something fixed. More often than not, you will end up with faster code when you control the details, than if you have to align heaven and earth to make changes.
Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373Quoted 3 lines · Paul Jackson
Quoted 1 line
This changes an O(1) process to O(N),
At the microlevel, yes. But:
Apologies, I wasn't trying to shut down the conversation with a magic O(N) wand.
Quoted 4 lines
1) If one takes as the "unit of measurement" the number of system calls made, it's more like O(N/128), given that one might average 128 directory entries per
getdents()call.
O(N/128) = O(N), but you knew that already. My fully arbitrary units, however, were in terms of what needs to be done from userspace to recreate the information that the kernel already had, and that means basically scanning a directory of N items. Sure, it's less than N syscalls, but userspace is still going to ask the CPU to do N comparisons across N items, with all the implications that comes along with that. (To be explicit: Syscalls, memory usage for caching, blowing the processor cache by visiting lots of useless items, and speed of just CPU time for the comparisons.)
Quoted 3 lines
2) This can be cached, with user code mapping
inode->d_inotod_name, and then the cached name checked with a single stat(2) call to ensure it wasn't stale.
Yeah, well, dnotify asks userspace to do much the same thing. This is marginally better, in that we'd know what we're looking for once we see it. However, dnotify has shown that forcing userspace to cache the entire contents of all the directories it wishes to watch doesn't scale well. That's one of the core problems they're trying to address, and passing an inode number back would reintroduce it.
Quoted 2 lines
Be leary of micro optimizations imposing a poorer design, especially across major API boundaries.
Yes.
Quoted 1 line
Simply waving "O(N)" in our face may not be adequately persuasive.
It shouldn't be persuasive on its own. It should be coupled with the fact that dnotify already forces an O(N) workload per directory onto userspace, and that has shown itself to not work well.
Quoted 2 lines
And there is a long history of pain in Unix dealing with variable length return values.
Y'know, I keep hearing this, but I'm afraid I'm just too dense to get it. How about we get it right once, in the kernel and userspace, and we make a library out of it so that luser space authors like myself don't have to reinvent the wheel? (getdents(2) seems to work somehow.) I'll even offer to write the code once I can sit down and spend time with the kernel again, if no one else is willing to touch this.
Quoted 5 lines
Much better to deal with that entirely in user space
code under your control, than to have to align kernel and glibc code in addition to your code, to get something fixed. More often than not, you will end up with faster code when you control the details, than if you have to align heaven and earth to make changes.
Sure, there are designs that have a natural fit with the system, and others that don't. Much like trying to express a trig function in terms of polynomials. Not a good fit, and you get a massive series to try to approximate it, if you'll forgive a calculus analogy. Anyway, the kernel *has* that information already, so we're not shoving the work somewhere else just to make userspace simpler somehow (which is always a specious argument).
I think I understand your point. What I'd suggest is reading Robert's "Why inotify?" that's posted along with the inotify patches (see
http://lkml.org/lkml/2004/9/28/201 for example) to get a small feel for what issues are trying to be addressed.
Off to do honest work,
Ray
Quoted 3 lines · Ray
However, dnotify has shown that forcing userspace to cache the entire contents of all the directories it wishes to watch doesn't scale well.
The dnotify cache isn't just an optional performance tweak, caching inode to name. It's an essential tracking of much of the stat information of any file of interest, in order to have _any_ way of detecting which file changed.
The inotify cache could just have the last few most recently used entries or some such - it's just a space vs time performance tradeoff.
So passing back an inode number doesn't come close to reintroducing the forced tracking of all the interesting stat data of every file.
Quoted 2 lines
dnotify already forces an O(N) workload per directory onto userspace, and that has shown itself to not work well.
Just because there exists an O(N) solution that has been shown to fail doesn't mean all O(N) solutions fail. If the constants change enough, then the actual numbers (how many changes per minute, how many compute cycles and memory bytes, what's the likely cache hit rate for a given size cache, ...) need to be re-examined. I see no evidence of that re-examination -- am I missing something?
Quoted 1 line
getdents(2) seems to work somehow.
Yeah ... but we had to walk up hill, both ways, through 9 foot snow drifts, for years, summer and winter, to get there ;).
Quoted 1 line
the kernel *has* that information already, ...
Frustrating, isn't it. The information is right there, and we're trying to keep you from getting to it.
Whatever ...
Quoted 1 line
Off to do honest work,
Good idea. Good luck with this. I rest my case, such as it be.
Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373Okay, now I'm sure I'm not communicating well. Please let me try again, and I'll see if I can do better this time.
Quoted 9 lines · Paul Jackson
Quoted 3 lines · Ray
However, dnotify has shown that forcing userspace to cache the entire contents of all the directories it wishes to watch doesn't scale well.
The dnotify cache isn't just an optional performance tweak, caching inode to name. It's an essential tracking of much of the stat information of any file of interest, in order to have _any_ way of detecting which file changed.
Agreed.
Quoted 2 lines
The inotify cache could just have the last few most recently used entries or some such - it's just a space vs time performance tradeoff.
Not the case.
Quoted 2 lines
So passing back an inode number doesn't come close to reintroducing the forced tracking of all the interesting stat data of every file.
It certainly forces userspace to track all file names and inodes, at least. Userspace wishes to know about deletes and renames. Unless it caches everything, it won't know what was deleted, or what got renamed. For that matter, passing back the inode number might also cause confusion for hardlinked files in the same directory, though I'd have to think about that for a bit to be sure.
Perhaps I'm missing something. I haven't missed anything yet today, which means I'm overdue.
Quoted 5 lines
Quoted 2 lines
dnotify already forces an O(N) workload per directory onto userspace, and that has shown itself to not work well.
Just because there exists an O(N) solution that has been shown to fail doesn't mean all O(N) solutions fail.
<shrug> Okay, I'll agree with you in theory. This is in fact true for several classes of multiplication algorithms, for example. (Who's gonna do an FFT multiply of two two-digit numbers, I ask; no one.) So, yeah, I get your point. Honest.
Quoted 5 lines
If the constants change enough,
then the actual numbers (how many changes per minute, how many compute cycles and memory bytes, what's the likely cache hit rate for a given size cache, ...) need to be re-examined. I see no evidence of that re-examination -- am I missing something?
This is one of the parts I'm not communicating well. For any 'event' that inotify wants to relay, the kernel knows the inode and the name. You're saying pass the inode number, as it's smaller and makes for an easier and higher speed interface to get changes to userspace (if I understand you correctly).
I'm saying, sure, if you look at that corner of the problem, you're right. That's ignoring how this gets used, however, where userspace ultimately wants to know the name of the file that's changed. So, by your method, we scan the entire directory, looking for the inode that matches via the getdents call.
So, the kernel ended up sending the name of the changed file *anyway*, but userspace had to ask for it. And, oh, the kernel sent the name of every other file in that directory too. (Okay, maybe half, but then again maybe not if you want to catch hardlinked files.)
Quoted 6 lines
Quoted 1 line
the kernel *has* that information already, ...
Frustrating, isn't it. The information is right there, and we're trying to keep you from getting to it.
Whatever ...
No, that's not my point at all. I'm not saying Big Bad Kernel Developers won't let me have my candy. I'm saying that if the kernel has the information already, and we're not passing it to userspace, and userspace *needs* that information, then all we've done is guarantee another long set of syscalls while it tries to pull the directory contents to match up item for item against its cache of previously known file states.
Or, in other words, I don't see how tossing away needed information is going to make anything more efficient in this case. Userspace, like a bulldog, will be coming back knocking on the kernel's door to get that information one way or another.
~ ~
I've been avoiding saying this, as this really will be a bit more complex than even my suggestions, but perhaps everyone would be happier if we crammed all this through a netlink socket instead. Got me.
Ray
Quoted 6 lines · Ray
Quoted 2 lines
So passing back an inode number doesn't come close to reintroducing the forced tracking of all the interesting stat data of every file.
It certainly forces userspace to track all file names and inodes, at least. Userspace wishes to know about deletes and renames. Unless it caches everything, it won't know what was deleted, or what got renamed.
Aha ... you finally got through my thick skull. Congratulations ;).
If file "foo" goes away, and if the kernel only reports that inode 314159 was unlinked from a given directory, unless some user code previously remembered that inode 314159 was accessible from the directory entry named "foo", you can't tell the user that "foo" disappeared.
Do you have the same problem with directories, if some cookie, not the full pathname, is passed back after an 'rmdir(2)' event? Or is it just that it's less onerous to track all the directories, because there's fewer of them?
Quoted 3 lines
You're saying pass the inode number, as it's smaller and makes for an easier and higher speed interface to get changes to userspace (if I understand you correctly).
That was the idea, yes. Most of it anyway. That and striving to keep the API the kernel presents to the user minimal and orthogonal.
Quoted 5 lines
I'm saying that if the kernel has the information already, and we're not passing it to userspace, and userspace *needs* that information, then all we've done is guarantee another long set of syscalls while it tries to pull the directory contents to match up item for item against its cache of previously known file states.
If there is a way, any way, for user code to get something from the kernel, then I don't mind dragging my feet on providing other ways, until I see a good reason. It's always worth a bit of effort to keep kernel API's minimal and orthogonal.
Your "delete and rename" point above seems now like a good reason.
Quoted 3 lines
I've been avoiding saying this, as this really will be a bit more complex than even my suggestions, but perhaps everyone would be happier if we crammed all this through a netlink socket instead. Got me.
No comment ;).
Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373Quoted 5 lines · Paul Jackson
If file "foo" goes away, and if the kernel only reports that inode 314159 was unlinked from a given directory, unless some user code previously remembered that inode 314159 was accessible from the directory entry named "foo", you can't tell the user that "foo" disappeared.
Right. Follow the same argument for renames, as well. ("Well, what did it *used* be to named?") Short of caching the entire contents, there's no way to know if all that's returned is an inode.
Quoted 4 lines
Do you have the same problem with directories, if some cookie, not the full pathname, is passed back after an 'rmdir(2)' event? Or is it just that it's less onerous to track all the directories, because there's fewer of them?
In the case of a directory that's being monitored going away, userspace has already asked for tracking events for that directory explicitly, and has to keep track of the cookie ("watch descriptor") to directory name mapping (or some context, at least) already. That's the only way it knows the correct context for the events.
For example, I ask for monitoring of two different directories on my system, both with a final path component of "etc". Since the kernel can't pass back the full path (cf. mount --bind, or consider an watch/chdir/watch sequence, if you believe the kernel should cache the full path passed to it from userspace), it instead needs to pass back something unique for that directory. The name isn't, and the full pathname isn't available to the kernel. So, the watch descriptor, being an arbitrary but unique cookie, steps in.
Quoted 6 lines
Quoted 3 lines
You're saying pass the inode number, as it's smaller and makes for an easier and higher speed interface to get changes to userspace (if I understand you correctly).
That was the idea, yes. Most of it anyway. That and striving to keep the API the kernel presents to the user minimal and orthogonal.
<Nod> That's actually a stronger argument with me than the micro- optimization. Orthogonal interfaces allow for more uses. However, I can find no way to clean up the interface any more than what's been suggested so far.
Quoted 3 lines
If there is a way, any way, for user code to get something from the kernel, then I don't mind dragging my feet on providing other ways, until I see a good reason.
Fair enough.
Quoted 2 lines
It's always worth a bit of effort to keep
kernel API's minimal and orthogonal.
Agreed.
Quoted 1 line
Your "delete and rename" point above seems now like a good reason.
It would bring inotify almost down to the level of usefulness of dnotify, which would be painful.
Ray
Quoted 10 lines · Andrew Morton
Quoted 4 lines · Ray Lee
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length.
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
I don't think moving inotify to a syscall based interface is worth it.
First off, on startup, this would require about 2k open() calls, followed by 2k syscalls to inotify. Not as nice as just 2k ioctl() calls.
The character device interface right now suits it perfectly. If we used syscalls we would need to provide a syscall that gives user space a FD that it can read events on, then more 2 more syscalls to provide the watch and ignore functionality. Switching to the syscall interface would also require implementing the idea of the inotify device instance without the assistance of the char device subsystem. If the ioctl() based interface is so bad, we could change it to a write() based interface.
John
Quoted 2 lines · John McCutchan
If the
ioctl()based interface is so bad, we could change it to awrite()based interface.
What baffles me is that a write() interface is infinitely more type unsafe, arbitrary, and uncontrolled than an ioctl() one.
Robert Love
Quoted 7 lines · Robert Love
Quoted 2 lines · John McCutchan
If the
ioctl()based interface is so bad, we could change it to awrite()based interface.What baffles me is that a
write()interface is infinitely more type unsafe, arbitrary, and uncontrolled than anioctl()one.
The word "Huh?" comes to mind, for some reason.
It's just as easy to pass crap via ioctl() as write(). Regardless, I'm not the Arbiter Of Taste on this topic. I'm merely following what I believe to be the prevailing (<cough>Linus</cough>) sentiments are regarding ioctl()s.
Well, that, and I personally find write(fd, &x, sizeof x) a cleaner interface. My personal preference is of little importance on this topic, however.
Ray
Quoted 1 line · Ray Lee
It's just as easy to pass crap via
ioctl()aswrite().
The ioctl() at least forces you to break up your calls into request and argument pairs. And as the requests are inotify-based preprocessor defines, at least that half of the equation is impossible to mess up.
In other words, ioctl() is slightly more structured than write(), which is utterly non-structured.
Robert Love
Quoted 12 lines · John McCutchan
Quoted 10 lines · Andrew Morton
Quoted 4 lines · Ray Lee
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length.
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
Quoted 4 lines
I don't think moving inotify to a syscall based interface is worth it.
First off, on startup, this would require about 2k
open()calls, followed by 2k syscalls to inotify.
And then 2k close() calls.
Quoted 1 line
Not as nice as just 2k
ioctl()calls.
<shrug> Syscalls aren't free, but they aren't the end of the world.
Quoted 3 lines
The character device interface right now suits it perfectly. If we used syscalls we would need to provide a syscall that gives user space a FD that it can read events on,
Again, apologies, I should know better than to write email on short sleep. All I was suggesting was that we pass in an fd that comes from open(), and that we should look at replacing the ioctl with write(). I like it as a character device, honest.
Ray
Quoted 8 lines · John McCutchan
Quoted 6 lines · Andrew Morton
Quoted 4 lines · Ray Lee
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length.
Quoted 6 lines
Quoted 4 lines
Why don't you pass a file descriptor into the syscall instead of a pathname? You can then take a ref on the inode and userspace can close the file. That gets you permission checking for free.
I don't think moving inotify to a syscall based interface is worth it.
That wasn't my point.
What I'm trying to get away from is this passing of full pathnames into and out of the kernel, whether by syscall or ioctl. It is a poor interface and, less importantly, is slow.
And it is slow on the common (notify) path! It's worth adding additional setup overhead if we can make he event delivery faster, no?
Quoted 3 lines
First off, on startup, this would require about 2k
open()calls, followed by 2k syscalls to inotify. Not as nice as just 2kioctl()calls.
There's probably not a lot of difference, even if all those pathnames and inodes are in cache.
Quoted 15 lines · Ray Lee
Quoted 14 lines · Robert Love
Quoted 8 lines
include/linux/inotify.h line 31
Quoted 6 lines
include/linux/inotify.h renamed from linux/include/linux/inotify.h +6
-0+struct inotify_event { +int wd; +int mask; +int cookie; +char filename[INOTIFY_FILENAME_MAX ]; +};yeah, that's not very nice. Better to kmalloc the pathname.
That is the structure that we communicate with to user-space.
We could
kmalloc()filename, but it makes the user-space use a bit more complicated (and right now it is trivial and wonderfully simple).
Quoted 8 lines
Quoted 1 line
We've been debating the pros and cons.
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length. It makes userspace double-check whether the filename extends all the way to the boundary of the structure, and if so, then go back to the disk to try to guess what the kernel really meant to say.
I thought that filenames where limited to 256 characters? That was the idea behind the 256 character limit.
John
Quoted 10 lines · John McCutchan
Quoted 6 lines · Ray Lee
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length. It makes userspace double-check whether the filename extends all the way to the boundary of the structure, and if so, then go back to the disk to try to guess what the kernel really meant to say.
I thought that filenames where limited to 256 characters? That was the idea behind the 256 character limit.
I thought so too, as linux/limits.h claims:
#define NAME_MAX 255 /* # chars in a file name */
Quoted 3 lines · But Robert earlier
Technically speaking, a single filename can be as large as PATH_MAX-1. The comment is just a warning, though, to explain the dreary theoretical side of the world.
...where PATH_MAX is 4096.
So, got me. I believe there is some minor confusion going on.
Ray
Quoted 1 line · Ray Lee
So, got me. I believe there is some minor confusion going on.
I think you are both just talking about different things. John was confused about what you were saying, I think.
Right now we limit the filename returned to 256 characters.
And file names can be as large as PATH_MAX-1 (minus one for the root slash).
Robert Love
Quoted 18 lines · Ray Lee
Quoted 10 lines · John McCutchan
Quoted 6 lines · Ray Lee
The current way pads out the structure unnecessarily, and still doesn't handle the really long filenames, by your admission. It incurs extra syscalls, as few filenames are really 256 characters in length. It makes userspace double-check whether the filename extends all the way to the boundary of the structure, and if so, then go back to the disk to try to guess what the kernel really meant to say.
I thought that filenames where limited to 256 characters? That was the idea behind the 256 character limit.
I thought so too, as
linux/limits.hclaims:#define
NAME_MAX255 /* # chars in a file name */But Robert earlier said:
Quoted 7 lines
Quoted 3 lines
Technically speaking, a single filename can be as large as PATH_MAX-1. The comment is just a warning, though, to explain the dreary theoretical side of the world.
...where
PATH_MAXis 4096.So, got me. I believe there is some minor confusion going on.
A quick test of 'echo "" > XXXX...XXX' the filename seems to be limited to 256.
John
Quoted 2 lines · John McCutchan
A quick test of 'echo "" > XXXX...XXX' the filename seems to be limited to 256.
I think ext3 limits filenames to 255 or 256 characters. Other filesystems probably have other limits.
The POSIX absolute maximum is PATH_MAX for the entire path, which means that a legal filename could theoretically be PATH_MAX-1 (a file in the root directory). But maybe in practice this is never an issue.
We still have the issue where 256 is much larger than the average file.
But, as I have said before, I am _for_ keeping the thing static, although I acknowledge all the points about going dynamic.
Robert Love
Quoted 3 lines · Robert Love
The POSIX absolute maximum is
PATH_MAXfor the entire path, which means that a legal filename could theoretically be PATH_MAX-1 (a file in the root directory). But maybe in practice this is never an issue.
Ah, okay.
Quoted 1 line
We still have the issue where 256 is much larger than the average file.
Yes.
Quoted 2 lines
But, as I have said before, I am _for_ keeping the thing static, although I acknowledge all the points about going dynamic.
I'm afraid I'm not seeing the complexity argument. Do you have other concerns regarding dynamic lengths?
Ray
Quoted 2 lines · Ray Lee
I'm afraid I'm not seeing the complexity argument. Do you have other concerns regarding dynamic lengths?
You _do_ see the complexity, you just don't that think it matters.
It forces more complexity on user-space (as least as much as you have shown) and even more in the kernel (due partly to the current very simple event code in place that needs the buffer to be a multiple of the struct size, etc.).
Personally, I don't know if it matters. John and I have both said that we will look into going to a dynamic filename.
Also, I am sure that John will take a patch, if you are so inclined.
Robert Love
Re: [gamin] [RFC][PATCH] inotify 0.10.0 [u]
In reply to John McCutchanOn Sun, 2004-09-26 at 22:02 -0400, John McCutchan wrote:
Hi,
Quoted 4 lines
-cleaned up the events (me)
- removeIN_CREATE,IN_DELETED,IN_RENAMEandIN_MOVE
- addedIN_CREATE_SUBDIR,IN_CREATE_FILE,IN_DELETE_SUBDIR,IN_DELETE_FILE,IN_DELETE_SELF,IN_MOVED_FROMandIN_MOVED_TO
I have just looked quickly at the inotify backend this weekend, so not really clued up yet, so excuse the stupid question: Does this mean inotify is now up to par with dnotify/poll feature wise? Or should we still look at getting inotify backend to use poll?
Signature · 1 line
Martin Schlemmer
Re: [gamin] [RFC][PATCH] inotify 0.10.0 [u]
Quoted 4 lines · Martin Schlemmer [c]
I have just looked quickly at the inotify backend this weekend, so not really clued up yet, so excuse the stupid question: Does this mean inotify is now up to par with dnotify/poll feature wise? Or should we still look at getting inotify backend to use poll?
inotify supports poll in the sense that you can poll() or select() on /dev/inotify.
dnotify does not support poll() at all (it is signal based), so your question is kind of confusing.
I cannot think of any features that dnotify has that inotify does not.
Robert Love
Re: [gamin] [RFC][PATCH] inotify 0.10.0 [u]
Quoted 7 lines · Robert Love
Quoted 4 lines · Martin Schlemmer [c]
I have just looked quickly at the inotify backend this weekend, so not really clued up yet, so excuse the stupid question: Does this mean inotify is now up to par with dnotify/poll feature wise? Or should we still look at getting inotify backend to use poll?
Quoted 6 lines
inotify supports poll in the sense that you can
poll()orselect()on/dev/inotify.dnotify does not support
poll()at all (it is signal based), so your question is kind of confusing.
(Sorry for double posting - keep forgetting with what email addy I am
subscribed to this list)
Right, but using gamin, dnotify+poll support some things that inotify do not support. Basically the dnotify backend also use the poll backend to enhance it to be able to monitor some events that dnotify by itself cannot (Daniel will be the better person to ask here). Here is a small snippit from another thread:
----
Quoted 12 lines
Quoted 12 lines
Quoted 11 lines
Quoted 8 lines
inotify should be able to fallback to poll too, otherwise it's
Quoted 2 lines
a bug in the inotify back-end, think for example for NFS resources.
Hmm - well it does not use poll. I will see if I can have a look at at dnotify backend and cook something up. I assume John never got to it, or you added the poll support to dnotify after inotify was added?
I added it after, yes.
Quoted 7 lines
Quoted 2 lines
Ok, thanks.
Actually, initially the inotify backend did support using the poll backend as well. I got rid of it because at the time inotify provided everything we needed. Now gamin supports watching things that inotify can't, like directories that don't exist.
----
Thanks,
Signature · 1 line
Martin Schlemmer
Re: [gamin] [RFC][PATCH] inotify 0.10.0 [u]
Quoted 5 lines · Martin Schlemmer [c]
Right, but using gamin, dnotify+poll support some things that inotify do not support. Basically the dnotify backend also use the poll backend to enhance it to be able to monitor some events that dnotify by itself cannot (Daniel will be the better person to ask here). Here is a small snippit from another thread:
I am thinking that we have two different definitions of poll here, sorry.
I guess you mean poll as in some sort of Gamin backend type?
Anyhow, I guess this is a Gamin question, not a inotify kernel-side question.
Robert Love
Re: [gamin] [RFC][PATCH] inotify 0.10.0 [u]
Quoted 8 lines · Robert Love
Quoted 5 lines · Martin Schlemmer [c]
Right, but using gamin, dnotify+poll support some things that inotify do not support. Basically the dnotify backend also use the poll backend to enhance it to be able to monitor some events that dnotify by itself cannot (Daniel will be the better person to ask here). Here is a small snippit from another thread:
Quoted 5 lines
I am thinking that we have two different definitions of poll here, sorry.
I guess you mean poll as in some sort of Gamin backend type?
Yip.
Quoted 3 lines
Anyhow, I guess this is a Gamin question, not a inotify kernel-side question.
Sorry - we talked about this during last week, and I did not think to trim the CC list. I will do so from now on.
Regards,
Signature · 1 line
Martin Schlemmer
Re: [gamin] [RFC][PATCH] inotify 0.10.0 [u]
In reply to John McCutchanOn Sun, 2004-09-26 at 22:02 -0400, John McCutchan wrote:
Hi,
Quoted 4 lines
-cleaned up the events (me)
- removeIN_CREATE,IN_DELETED,IN_RENAMEandIN_MOVE
- addedIN_CREATE_SUBDIR,IN_CREATE_FILE,IN_DELETE_SUBDIR,IN_DELETE_FILE,IN_DELETE_SELF,IN_MOVED_FROMandIN_MOVED_TO
I have just looked quickly at the inotify backend this weekend, so not really clued up yet, so excuse the stupid question: Does this mean inotify is now up to par with dnotify/poll feature wise? Or should we still look at getting inotify backend to use poll?
Signature · 1 line
Martin Schlemmer
On Sun, 2004-09-26 at 22:02 -0400, John McCutchan wrote:
Hi, John.
Quoted 2 lines
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
Attached patch addresses a few misc. issues that Andrew raised:
- Get rid of INOTIFY_VERSION. I agree with Andrew, once inotify is in the kernel, version information like this is worthless and thus shunned.
- Just open code INOTIFY_DEV_TIMER_TIME.
- Add comment re 'bitmask'
- Don't typecast when assigning from a void *.
- Misc. and minor coding style fixes
Best,
Robert Love
drivers/char/inotify.c | 85 +++++++++++++++++++++++-------------------------- 1 files changed, 40 insertions(+), 45 deletions(-)
drivers/char/inotify.c +40 -45
@@ -39,14 +39,10 @@#include <linux/writeback.h> #include <linux/inotify.h> -#define INOTIFY_VERSION "0.10.0" -#define MAX_INOTIFY_DEVS 8/* max open inotify devices */ #define MAX_INOTIFY_DEV_WATCHERS 8192/* max total watches */ #define MAX_INOTIFY_QUEUED_EVENTS 256/* max events queued on the dev*/ -#define INOTIFY_DEV_TIMER_TIME (jiffies + (HZ/4)) -static atomic_t watcher_count;static kmem_cache_t *watcher_cache;static kmem_cache_t *kevent_cache;
@@ -63,6 +59,9 @@* * For each inotify device, we need to keep track of the events queued on it, * a list of the inodes that we are watching, and so on. +* +* 'bitmask' holds one bit for each possible watcher descriptor: a set bit +* implies that the given WD is valid, unset implies it is not. */ struct inotify_device {unsigned long bitmask[MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG ];
@@ -751,7 +750,7 @@ dev = (struct inotify_device *) data;/* reset the timer */ -mod_timer (&dev->timer,INOTIFY_DEV_TIMER_TIME ); +mod_timer (&dev->timer, jiffies + (HZ /4 ));/* wake up anyone blocked on the device */ if (inotify_dev_has_events (dev))
@@ -787,7 +786,7 @@ dev->timer.data = (unsigned long ) dev; dev->timer.function = inotify_dev_timer; - dev->timer.expires =INOTIFY_DEV_TIMER_TIME ; + dev->timer.expires = jiffies + (HZ /4 );add_timer (&dev->timer);
@@ -821,7 +820,7 @@if (file->private_data) {struct inotify_device *dev; - dev = (struct inotify_device *) file->private_data; + dev = file->private_data;del_timer_sync (&dev->timer);inotify_release_all_watchers (dev);inotify_release_all_events (dev);
@@ -953,7 +952,8 @@ }static int inotify_ioctl (struct inode *ip,struct file *fp, -unsigned int cmd,unsigned long arg) { +unsigned int cmd,unsigned long arg) +{int err;struct inotify_device *dev;struct inotify_watch_request request;
@@ -985,40 +985,37 @@ err = -EINVAL ;switch (cmd) { -case INOTIFY_WATCH : -iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_WATCH ioctl\n" ); -if (copy_from_user (&request, (void *) arg, -sizeof (struct inotify_watch_request ))) { - err = -EFAULT ; - goto out; - } - - err =inotify_watch (dev, &request); -break ; -case INOTIFY_IGNORE : -iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_IGNORE ioctl\n" ); -if (copy_from_user (&wd, (void *)arg,sizeof (int ))) { - - err = -EFAULT ; - goto out; - } - - err =inotify_ignore (dev, wd); -break ; -case INOTIFY_STATS : -iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_STATS ioctl\n" ); -inotify_print_stats (dev); - err =0 ; -break ; -case INOTIFY_SETDEBUG : -iprintk (INOTIFY_DEBUG_ERRORS , -"INOTIFY_SETDEBUG ioctl\n" ); -if (copy_from_user (&inotify_debug_flags, (void *) arg, -sizeof (int ))) { - err = -EFAULT ; - goto out; - } -break ; +case INOTIFY_WATCH : +iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_WATCH ioctl\n" ); +if (copy_from_user (&request, (void *) arg, +sizeof (struct inotify_watch_request ))) { + err = -EFAULT ; + goto out; + } + err =inotify_watch (dev, &request); +break ; +case INOTIFY_IGNORE : +iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_IGNORE ioctl\n" ); +if (copy_from_user (&wd, (void *)arg,sizeof (int ))) { + err = -EFAULT ; + goto out; + } + err =inotify_ignore (dev, wd); +break ; +case INOTIFY_STATS : +iprintk (INOTIFY_DEBUG_ERRORS ,"INOTIFY_STATS ioctl\n" ); +inotify_print_stats (dev); + err =0 ; +break ; +case INOTIFY_SETDEBUG : +iprintk (INOTIFY_DEBUG_ERRORS , +"INOTIFY_SETDEBUG ioctl\n" ); +if (copy_from_user (&inotify_debug_flags, (void *) arg, +sizeof (int ))) { + err = -EFAULT ; + goto out; + } +break ; } out:
@@ -1040,7 +1037,6 @@ .fops = &inotify_fops, }; -static int __init inotify_init (void ) {int ret;
@@ -1059,8 +1055,7 @@sizeof (struct inotify_kernel_event ),0 , SLAB_PANIC, NULL, NULL); -printk (KERN_ALERT "inotify %s minor=%d\n" ,INOTIFY_VERSION , - inotify_device.minor); +printk (KERN_INFO "inotify init: minor=%d\n" , inotify_device.minor); out:return ret; }
Quoted 1 line
unsigned long bitmask[MAX_INOTIFY_DEV_WATCHERS/BITS_PER_LONG];
This assumes that MAX_INOTIFY_DEV_WATCHERS is an integral multiple of BITS_PER_LONG, otherwise, the last word will be missing.
Perhaps this would this better be written as:
DECLARE_BITMAP(bitmask, MAX_INOTIFY_DEV_WATCHERS);
and the clearing of it in the original patch:
Review
Quoted 2 lines
(quoted diff) +2
-0+ memset(dev->bitmask, 0, + sizeof(unsigned long) * MAX_INOTIFY_DEV_WATCHERS / BITS_PER_LONG);
might better be written as:
CLEAR_BITMAP(dev->bitmask, MAX_INOTIFY_DEV_WATCHERS);
Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373patch] inotify: use bitmap.h functions
Quoted 4 lines · Paul Jackson
Quoted 1 line
unsigned long bitmask[MAX_INOTIFY_DEV_WATCHERS/BITS_PER_LONG];This assumes that
MAX_INOTIFY_DEV_WATCHERSis an integral multiple ofBITS_PER_LONG, otherwise, the last word will be missing.
Yah. Since we defined MAX_INOTIFY_DEV_WATCHERS, I presumed to be able to ensure it was a multiple (e.g. just keep it a power of two) ...
Quoted 3 lines
Perhaps this would this better be written as:
DECLARE_BITMAP(bitmask, MAX_INOTIFY_DEV_WATCHERS);
... but this is indeed cleaner.
Quoted 8 lines
and the clearing of it in the original patch:
Review
Quoted 2 lines
(quoted diff) +2
-0+ memset(dev->bitmask, 0, + sizeof(unsigned long) * MAX_INOTIFY_DEV_WATCHERS / BITS_PER_LONG);might better be written as:
CLEAR_BITMAP(dev->bitmask, MAX_INOTIFY_DEV_WATCHERS);
I think you mean bitmap_zero(), but yah. Agreed.
John, here is a patch to use the bitmap.h functions to manipulate the bitmap.
Robert Love
Use <linux/bitmap.h> bitmap functions
drivers/char/inotify.c | 8 ++++---- 1 files changed, 4 insertions(+), 4 deletions(-)
drivers/char/inotify.c +4 -4
@@ -21,6 +21,7 @@*/ #include <linux/bitops.h> +#include <linux/bitmap.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h>
@@ -64,7 +65,7 @@* implies that the given WD is valid, unset implies it is not. */ struct inotify_device { -unsigned long bitmask[MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG ]; + DECLARE_BITMAP(bitmask, MAX_INOTIFY_DEV_WATCHERS);struct timer_list timer;wait_queue_head_t wait;struct list_head events;
@@ -286,7 +287,7 @@ dev->nr_watches++; wd =find_first_zero_bit (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS ); -set_bit (wd, dev->bitmask); +set_bit (wd, dev->bitmask);return wd; }
@@ -775,8 +776,7 @@if (!dev)return -ENOMEM ; -memset (dev->bitmask,0 , -sizeof (unsigned long ) *MAX_INOTIFY_DEV_WATCHERS /BITS_PER_LONG ); +bitmap_zero (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS );INIT_LIST_HEAD (&dev->events);INIT_LIST_HEAD (&dev->watchers);
Re: patch] inotify: use bitmap.h functions
Quoted 4 lines
I think you mean
bitmap_zero(), but yah. Agreed.John, here is a patch to use the
bitmap.hfunctions to manipulate the bitmap.
thanks
Signature · 3 lines
I won't rest till it's the best ...
Programmer, Linux Scalability
Paul Jackson <[email protected]> 1.650.933.1373PATCH 1/1 inotify: make it configurable
In reply to John McCutchanQuoted 2 lines · John McCutchan
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
Hi, John.
Attached patch makes inotify configurable via the boolean CONFIG_INOTIFY. We cannot allow CONFIG_INOTIFY=m without playing with function pointers, since the inotify (as with dnotify) code is called from generic code.
Compile tested both on and off.
Also removed some module stuff that was unneeded, since we are not a module.
A nice TODO would be to make dnotify a compile option, since any modern desktop using Gamin would not need nor want dnotify. I will look at that later.
Rock,
Robert Love
add CONFIG_INOTIFY
drivers/char/Kconfig | 13 ++++++++++++ drivers/char/Makefile | 3 +- drivers/char/inotify.c | 14 ------------- fs/attr.c | 13 ++++++------ fs/inode.c | 4 ++- include/linux/fs.h | 2 + include/linux/inotify.h | 50 ++++++++++++++++++++++++++++++++---------------- 7 files changed, 61 insertions(+), 38 deletions(-)
drivers/char/inotify.c +0 -14
@@ -1060,18 +1060,4 @@return ret; } -static void inotify_exit (void ) -{ -kmem_cache_destroy (kevent_cache); -kmem_cache_destroy (watcher_cache); -misc_deregister (&inotify_device); -printk (KERN_ALERT "inotify shutdown ec=%d wc=%d ic=%d\n" , - event_object_count, watcher_object_count, inode_ref_count); -} - -MODULE_AUTHOR ("John McCutchan <[email protected]>" ); -MODULE_DESCRIPTION ("Inode event driver" ); -MODULE_LICENSE ("GPL" ); -module_init (inotify_init); -module_exit (inotify_exit);
drivers/char/Kconfig +13 -0
@@ -62,6 +62,19 @@ depends on VT && !S390 && !UM default y +config INOTIFY + bool "Inotify file change notification support" + default y + ---help--- + Say Y here to enable inotify support and the /dev/inotify character + device. Inotify is a file change notification system and a + replacement for dnotify. Inotify fixes numerous shortcomings in + dnotify and introduces several new features. It allows monitoring + of both files and directories via a single open fd. Multiple file + events are supported. + + If unsure, say Y. + config SERIAL_NONSTANDARD bool "Non-standard serial port support" ---help---
drivers/char/Makefile +2 -1
@@ -7,11 +7,12 @@ # FONTMAPFILE = cp437.uni -obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o inotify.o +obj-y += mem.o random.o tty_io.o n_tty.o tty_ioctl.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-y += misc.o +obj-$(CONFIG_INOTIFY) += inotify.o obj-$(CONFIG_VT) += vt_ioctl.o vc_screen.o consolemap.o \ consolemap_deftbl.o selection.o keyboard.o obj-$(CONFIG_HW_CONSOLE) += vt.o defkeymap.o
fs/attr.c +7 -6
@@ -129,7 +129,7 @@return dn_mask; } -int setattr_mask_inotify (unsigned int ia_valid) +static int setattr_mask_inotify (unsigned int ia_valid) {unsigned long dn_mask =0 ;
@@ -151,7 +151,6 @@return dn_mask; } -int notify_change (struct dentry * dentry,struct iattr * attr) {struct inode *inode = dentry->d_inode;
@@ -210,12 +209,14 @@if (!error) {unsigned long dn_mask =setattr_mask (ia_valid);unsigned long in_mask =setattr_mask_inotify (ia_valid); -if (dn_mask) { + +if (dn_mask)dnotify_parent (dentry, dn_mask); - }if (in_mask) { -inotify_inode_queue_event (dentry->d_inode, in_mask,NULL ); -inotify_dentry_parent_queue_event (dentry, in_mask, dentry->d_name.name); +inotify_inode_queue_event (dentry->d_inode, in_mask, +NULL ); +inotify_dentry_parent_queue_event (dentry, in_mask, + dentry->d_name.name); } }return error;
fs/inode.c +3 -1
@@ -114,7 +114,9 @@if (inode) {struct address_space *const mapping = &inode->i_data; -INIT_LIST_HEAD (&inode->watchers); +#ifdef CONFIG_INOTIFY +INIT_LIST_HEAD (&inode->watchers); +#endif inode->i_sb = sb; inode->i_blkbits = sb->s_blocksize_bits; inode->i_flags =0 ;
include/linux/fs.h +2 -0
@@ -462,9 +462,11 @@unsigned long i_dnotify_mask;/* Directory notify events */ struct dnotify_struct *i_dnotify;/* for directory notifications */ +#ifdef CONFIG_INOTIFY struct list_head watchers;unsigned long watchers_mask;int watcher_count; +#endif unsigned long i_state;unsigned long dirtied_when;/* jiffies of first dirtying */
include/linux/inotify.h +34 -16
74 lines not on this page · raw message
PATCH 1/1 inotify: doh
In reply to John McCutchanQuoted 2 lines · John McCutchan
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
Don't even ask.
And definitely don't laugh.
Robert Love
drivers/char/inotify.c | 2 +- 1 files changed, 1 insertion(+), 1 deletion(-)
drivers/char/inotify.c +1 -1
@@ -623,7 +623,7 @@ {struct inotify_watcher *watcher, *next; -list_for_each_entry_safe (watcher, next, &inode->watchers, i_list) { +list_for_each_entry_safe (watcher, next, &inode->watchers, i_list)ignore_helper (watcher,0 ); }EXPORT_SYMBOL_GPL (inotify_inode_is_dead);
Quoted 2 lines · John McCutchan
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
John,
find_inode() can just return an inode directly, so we don't need the second argument and the double indirection.
FYI, errors can be encoded in pointers via ERR_PTR(), checked via IS_ERR (), and retrieved via PTR_ERR().
Patch is on top of your patch and all of my previous patches.
Robert Love
fix_inode() bits
drivers/char/inotify.c | 19 +++++++++++++------ 1 files changed, 13 insertions(+), 6 deletions(-)
drivers/char/inotify.c +13 -6
@@ -98,24 +98,29 @@ };#define list_to_inotify_kernel_event (pos) list_entry((pos), struct inotify_kernel_event, list) -static int find_inode (const char __user *dirname,struct inode **inode) +/* +* find_inode - resolve a user-given path to a specific inode and iget() it +*/ +static struct inode *find_inode (const char __user *dirname) { +struct inode *inode;struct nameidata nd;int error; error =__user_walk (dirname,LOOKUP_FOLLOW , &nd);if (error) {iprintk (INOTIFY_DEBUG_INODE ,"could not find inode\n" ); + inode =ERR_PTR (error); goto out; } - *inode = nd.dentry->d_inode; -__iget (*inode); + inode = nd.dentry->d_inode; +__iget (inode);iprintk (INOTIFY_DEBUG_INODE ,"ref'd inode\n" ); inode_ref_count++;path_release (&nd); out: -return error; +return inode; }static void unref_inode (struct inode *inode)
@@ -841,9 +846,11 @@struct inotify_watcher *watcher; err =0 ; - err =find_inode (request->dirname, &inode); -if (err) + inode =find_inode (request->dirname); +if (IS_ERR (inode)) { + err =PTR_ERR (inode); goto exit; + }if (!S_ISDIR (inode->i_mode))iprintk (INOTIFY_DEBUG_ERRORS ,"watching file\n" );
PATCH 1/1 inotify: don't check private_data
In reply to John McCutchanQuoted 2 lines · John McCutchan
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
Hey, John.
Any reason we check file->private_data in inotify_release()?
I don't think there is. Following patch removes the check.
Robert Love
No need to check file->private_data
drivers/char/inotify.c | 14 ++++++-------- 1 files changed, 6 insertions(+), 8 deletions(-)
drivers/char/inotify.c +6 -8
@@ -822,15 +822,13 @@static int inotify_release (struct inode *inode,struct file *file) { -if (file->private_data) { -struct inotify_device *dev; +struct inotify_device *dev; - dev = file->private_data; -del_timer_sync (&dev->timer); -inotify_release_all_watchers (dev); -inotify_release_all_events (dev); -kfree (dev); - } + dev = file->private_data; +del_timer_sync (&dev->timer); +inotify_release_all_watchers (dev); +inotify_release_all_events (dev); +kfree (dev);printk (KERN_ALERT "inotify device released\n" );
PATCH 1/1 inotify: silly fix
In reply to John McCutchanOn Sun, 2004-09-26 at 22:02 -0400, John McCutchan wrote:
John,
Quoted 2 lines
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
John, as Andrew pointed out, we are missing some braces in an inopportune location. I think we need to refactor this code entirely, to remove the access_ok() checks and use copy_{to,from}_user(), but we can take this in the meantime since the patch is dead without it.
I'll take paper, please. One brown paper bag right over my head.
Robert Love
ARE WE SERIOUS?
drivers/char/inotify.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletion(-)
drivers/char/inotify.c +2 -1
@@ -970,9 +970,10 @@if (_IOC_DIR (cmd) & _IOC_READ) err = !access_ok (VERIFY_READ , (void *) arg,_IOC_SIZE (cmd)); -if (err) +if (err) { err = -EFAULT ; goto out; + }if (_IOC_DIR (cmd) & _IOC_WRITE) err = !access_ok (VERIFY_WRITE , (void *)arg,_IOC_SIZE (cmd));
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1
John McCutchan wrote:
| | --Why Not dnotify and Why inotify (By Robert Love)-- |
| * inotify has an event that says "the filesystem that the item you were
| watching is on was unmounted" (this is particularly cool).
| +++ linux/fs/super.c 2004-09-18 02:24:33.000000000 -0400
| @@ -36,6 +36,7 @@
| #include <linux/writeback.h> /* for the emergency remount stuff */
| #include <linux/idr.h>
| #include <asm/uaccess.h>
| +#include <linux/inotify.h>
|
|
| void get_filesystem(struct file_system_type *fs);
| @@ -204,6 +205,7 @@
|
| if (root) {
| sb->s_root = NULL;
| + inotify_super_block_umount (sb);
| shrink_dcache_parent(root);
| shrink_dcache_anon(&sb->s_anon);
| dput(root);This doesn't seem right. generic_shutdown_super is only called when the last instance of a super is released. If a system were to have a filesystem mounted in two locations (for instance, by creating a new namespace), then the umount and ignore would not get propagated when one is unmounted.
How about an approach that somehow referenced vfsmounts (without having a reference count proper)? That way you could queue messages in umount_tree and do_umount..
- --
Mike Waychison
Sun Microsystems, Inc.
1 (650) 352-5299 voice
1 (416) 202-8336 voice
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NOTICE: The opinions expressed in this email are held by me, and may not represent the views of Sun Microsystems, Inc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.5 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org
iD8DBQFBWaGHdQs4kOxk3/MRAr22AJ0SHDUiIXKRKE/TBFmTYBL5J7KD9gCbBoso DYRg+SjnO8urCKLmDbehvkM=
=5fMW
-----END PGP SIGNATURE-----
Quoted 35 lines · Mike Waychison
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1John McCutchan wrote:
| | --Why Not dnotify and Why inotify (By Robert Love)-- || * inotify has an event that says "the filesystem that the item you were
| watching is on was unmounted" (this is particularly cool).| +++ linux/fs/super.c 2004-09-18 02:24:33.000000000 -0400 | @@ -36,6 +36,7 @@ | #include <linux/writeback.h> /* for the emergency remount stuff */ | #include <linux/idr.h> | #include <asm/uaccess.h> | +#include <linux/inotify.h> | | | void get_filesystem(struct file_system_type *fs); | @@ -204,6 +205,7 @@ | | if (root) { | sb->s_root = NULL; | + inotify_super_block_umount (sb); | shrink_dcache_parent(root); | shrink_dcache_anon(&sb->s_anon); | dput(root);This doesn't seem right.
generic_shutdown_superis only called when the last instance of a super is released. If a system were to have a filesystem mounted in two locations (for instance, by creating a new namespace), then the umount and ignore would not get propagated when one is unmounted.
Quoted 3 lines
How about an approach that somehow referenced vfsmounts (without having a reference count proper)? That way you could queue messages in
umount_treeanddo_umount..
I was not aware of this subtlety. You are right, we should make sure events are sent for every unmount, not just the last.
John
PATCH 1/1 inotify: remove timer
In reply to John McCutchanOn Sun, 2004-09-26 at 22:02 -0400, John McCutchan wrote:
John,
Quoted 2 lines
Announcing the release of inotify 0.10.0.
Attached is a patch to 2.6.8.1.
Attached patch removes dev->timer and instead wakes up waiting tasks in inotify_dev_queue_event().
I'd like you to go over it, make sure this works the same and makes sense. It needs a good testing before merged.
Robert Love
Remove dev->timer
drivers/char/inotify.c | 32 +++++++------------------------- 1 files changed, 7 insertions(+), 25 deletions(-)
drivers/char/inotify.c +7 -25
@@ -63,10 +63,14 @@* * 'bitmask' holds one bit for each possible watcher descriptor: a set bit * implies that the given WD is valid, unset implies it is not. +* +* This structure is protected by 'lock'. Lock ordering: +* dev->lock +* dev->wait->lock +* FIXME: Define lock ordering wrt inode and dentry locking! */ struct inotify_device { DECLARE_BITMAP(bitmask, MAX_INOTIFY_DEV_WATCHERS); -struct timer_list timer;wait_queue_head_t wait;struct list_head events;struct list_head watchers;
@@ -248,9 +252,11 @@return ; } +/* queue the event and wake up anyone waiting */ list_add_tail (&kevent->list, &dev->events);iprintk (INOTIFY_DEBUG_EVENTS ,"queued event %x for %p\n" , kevent->event.mask, dev); +wake_up_interruptible (&dev->wait); }/*
@@ -747,22 +753,6 @@return out; } -static void inotify_dev_timer (unsigned long data) -{ -struct inotify_device *dev; - -if (!data) -return ; - dev = (struct inotify_device *) data; - -/* reset the timer */ -mod_timer (&dev->timer, jiffies + (HZ /4 )); - -/* wake up anyone blocked on the device */ -if (inotify_dev_has_events (dev)) -wake_up_interruptible (&dev->wait); -} -static int inotify_open (struct inode *inode,struct file *file) {struct inotify_device *dev;
@@ -780,7 +770,6 @@INIT_LIST_HEAD (&dev->events);INIT_LIST_HEAD (&dev->watchers); -init_timer (&dev->timer);init_waitqueue_head (&dev->wait); dev->event_count =0 ;
@@ -789,12 +778,6 @@ file->private_data = dev; - dev->timer.data = (unsigned long ) dev; - dev->timer.function = inotify_dev_timer; - dev->timer.expires = jiffies + (HZ /4 ); - -add_timer (&dev->timer); -printk (KERN_ALERT "inotify device opened\n" );return 0 ;
@@ -825,7 +808,6 @@struct inotify_device *dev; dev = file->private_data; -del_timer_sync (&dev->timer);inotify_release_all_watchers (dev);inotify_release_all_events (dev);kfree (dev);
PATCH 1/1 inotify: use the idr layer
In reply to John McCutchanOK, I told John I would post this ASAP, as soon as I finished testing it, but I got backed up, so here it is without much testing. It does compile fine.
This patch removes our current bitmap-based allocation system and replaces it with the idr layer. The idr layer allows us to use a radix tree, rooted at each device instance, to trivially map between watcher descriptors and watcher structures. In idr terminology, the watcher descriptor is the id and the watcher structure is the pointer.
Allocating a new watcher descriptor and associating it with a given watcher structure is done in the same place as before, inotify_dev_get_wd(). The code for doing this is a bit weird. The idr layer's interface leaves a bit to be desired.
The function dev_find_wd() is used to map from a given watcher descriptor to a watcher structure. This used to be our least scalable
function: O(n), but at a small fixed n, so you could call it O(1). Now it is O(lg n); n is still fixed, so you can still call it O(1).
I also cleaned up some locking and added some comments.
Like I said, I have not tested this, probably won't until tomorrow, but here it is to play with earlier if anyone so chooses. The idr layer is rather nice for this sort of thing.
Patch is on top of all of my previous patches.
Robert Love
idr layer support for inotify
drivers/char/inotify.c | 93 +++++++++++++++++++++++++++---------------------- 1 files changed, 53 insertions(+), 40 deletions(-)
drivers/char/inotify.c +53 -40
@@ -17,15 +17,14 @@/* TODO: * rename inotify_watcher to inotify_watch * need a way to connect MOVED_TO/MOVED_FROM events in user space -* use b-tree so looking up watch by WD is faster */ #include <linux/bitops.h> -#include <linux/bitmap.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/spinlock.h> +#include <linux/idr.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/namei.h>
@@ -61,22 +60,19 @@* For each inotify device, we need to keep track of the events queued on it, * a list of the inodes that we are watching, and so on. * -* 'bitmask' holds one bit for each possible watcher descriptor: a set bit -* implies that the given WD is valid, unset implies it is not. -* * This structure is protected by 'lock'. Lock ordering: * dev->lock * dev->wait->lock * FIXME: Define lock ordering wrt inode and dentry locking! */ struct inotify_device { - DECLARE_BITMAP(bitmask, MAX_INOTIFY_DEV_WATCHERS);wait_queue_head_t wait; +struct idr idr;struct list_head events;struct list_head watchers;spinlock_t lock;unsigned int event_count; -int nr_watches; +unsigned int nr_watches; };struct inotify_watcher {
@@ -179,10 +175,6 @@return ; event_object_count--; -INIT_LIST_HEAD (&kevent->list); - kevent->event.wd =-1 ; - kevent->event.mask =0 ; -iprintk (INOTIFY_DEBUG_ALLOC ,"free'd kevent %p\n" , kevent);kmem_cache_free (kevent_cache, kevent); }
@@ -282,20 +274,30 @@/* * inotify_dev_get_wd - returns the next WD for use by the given dev * -* Caller must hold dev->lock before calling. +* This function sleeps. */ -static int inotify_dev_get_wd (struct inotify_device *dev) +static int inotify_dev_get_wd (struct inotify_device *dev, +struct inotify_watcher *watcher) { -int wd; +int ret; -if (!dev || dev->nr_watches ==MAX_INOTIFY_DEV_WATCHERS ) +if (dev->nr_watches ==MAX_INOTIFY_DEV_WATCHERS )return -1 ; +repeat: +if (!idr_pre_get (&dev->idr,GFP_KERNEL )) +return -1 ; + +spin_lock (&dev->lock); + ret =idr_get_new (&dev->idr, watcher, &watcher->wd); +spin_lock (&dev->lock); +if (ret == -EAGAIN )/* more memory is required, try again */ + goto repeat; +if (ret == -ENOSPC )/* the idr is full! */ +return -1 ; dev->nr_watches++; - wd =find_first_zero_bit (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS ); -set_bit (wd, dev->bitmask); -return wd; +return 0 ; }/*
@@ -309,7 +311,7 @@return -1 ; dev->nr_watches--; -clear_bit (wd, dev->bitmask); +idr_remove (&dev->idr, wd);return 0 ; }
@@ -331,7 +333,6 @@return NULL ; } - watcher->wd =-1 ; watcher->mask = mask; watcher->inode = inode; watcher->dev = dev;
@@ -339,11 +340,7 @@INIT_LIST_HEAD (&watcher->i_list);INIT_LIST_HEAD (&watcher->u_list); -spin_lock (&dev->lock); - watcher->wd =inotify_dev_get_wd (dev); -spin_unlock (&dev->lock); - -if (watcher->wd <0 ) { +if (inotify_dev_get_wd (dev, watcher)) {iprintk (INOTIFY_DEBUG_ERRORS ,"Could not get wd for watcher %p\n" , watcher);iprintk (INOTIFY_DEBUG_ALLOC ,"free'd watcher %p\n" , watcher);
@@ -387,15 +384,18 @@return NULL ; } -static struct inotify_watcher *dev_find_wd (struct inotify_device *dev,int wd) +/* +* dev_find_wd - given a (dev,wd) pair, returns the matching inotify_watcher +* +* Returns the results of looking up (dev,wd) in the idr layer. NULL is +* returned on error. +* +* The caller must hold dev->lock. +*/ +static inline struct inotify_watcher *dev_find_wd (struct inotify_device *dev, +int wd) { -struct inotify_watcher *watcher; - -list_for_each_entry (watcher, &dev->watchers, d_list) { -if (watcher->wd == wd) -return watcher; - } -return NULL ; +return idr_find (&dev->idr, wd); } static int inotify_dev_is_watching_inode(struct inotify_device *dev,
@@ -411,6 +411,11 @@return 0 ; } +/* +* inotify_dev_add_watcher - add the given watcher to the given device instance +* +* Caller must hold dev->lock. +*/ static int inotify_dev_add_watcher (struct inotify_device *dev,struct inotify_watcher *watcher) {
@@ -570,11 +575,11 @@struct inotify_device *dev;struct inode *inode; -spin_lock (&watcher->dev->lock); -spin_lock (&watcher->inode->i_lock); - - inode = watcher->inode; dev = watcher->dev; + inode = watcher->inode; + +spin_lock (&dev->lock); +spin_lock (&inode->i_lock);if (event)inotify_dev_queue_event (dev, watcher, event,NULL );
@@ -590,7 +595,8 @@unref_inode (inode); } -static void process_umount_list (struct list_head *umount) { +static void process_umount_list (struct list_head *umount) +{struct inotify_watcher *watcher, *next;list_for_each_entry_safe (watcher, next, umount, u_list)
@@ -598,7 +604,7 @@ }static void build_umount_list (struct list_head *head,struct super_block *sb, -struct list_head *umount) +struct list_head *umount) {struct inode * inode;
@@ -766,7 +772,7 @@if (!dev)return -ENOMEM ; -bitmap_zero (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS ); +idr_init (&dev->idr);INIT_LIST_HEAD (&dev->events);INIT_LIST_HEAD (&dev->watchers);
@@ -898,9 +904,16 @@ {struct inotify_watcher *watcher; +/* +* FIXME: Silly to grab dev->lock here and then drop it, when +* ignore_helper() grabs it anyway a few lines down. +*/ +spin_lock (&dev->lock); watcher =dev_find_wd (dev, wd); +spin_unlock (&dev->lock);if (!watcher)return -EINVAL ; +ignore_helper (watcher,0 );return 0 ;
Re: [patch] inotify: use the idr layer
+ spin_lock(&dev->lock); + ret = idr_get_new(&dev->idr, watcher, &watcher->wd); + spin_lock(&dev->lock);
I think you mean spin_unlock on the third line? Other than that I think it should work.
John
Quoted 30 lines · Robert Love
OK, I told John I would post this ASAP, as soon as I finished testing it, but I got backed up, so here it is without much testing. It does compile fine.
This patch removes our current bitmap-based allocation system and replaces it with the idr layer. The idr layer allows us to use a radix tree, rooted at each device instance, to trivially map between watcher descriptors and watcher structures. In idr terminology, the watcher descriptor is the id and the watcher structure is the pointer.
Allocating a new watcher descriptor and associating it with a given watcher structure is done in the same place as before,
inotify_dev_get_wd(). The code for doing this is a bit weird. The idr layer's interface leaves a bit to be desired.The function
dev_find_wd()is used to map from a given watcher descriptor to a watcher structure. This used to be our least scalable
function: O(n), but at a small fixed n, so you could call it O(1). Now it is O(lg n); n is still fixed, so you can still call it O(1).I also cleaned up some locking and added some comments.
Like I said, I have not tested this, probably won't until tomorrow, but here it is to play with earlier if anyone so chooses. The idr layer is rather nice for this sort of thing.
Patch is on top of all of my previous patches.
Robert Love
Re: [patch] inotify: use the idr layer
Quoted 6 lines · John McCutchan
(quoted diff) +3
-0+ spin_lock(&dev->lock); + ret = idr_get_new(&dev->idr, watcher, &watcher->wd); + spin_lock(&dev->lock);I think you mean
spin_unlockon the third line? Other than that I think it should work.
Ech, I have an updated patch.
Robert Love
idr layer support for inotify
drivers/char/inotify.c | 93 +++++++++++++++++++++++++++---------------------- 1 files changed, 53 insertions(+), 40 deletions(-)
drivers/char/inotify.c +53 -40
@@ -17,15 +17,14 @@/* TODO: * rename inotify_watcher to inotify_watch * need a way to connect MOVED_TO/MOVED_FROM events in user space -* use b-tree so looking up watch by WD is faster */ #include <linux/bitops.h> -#include <linux/bitmap.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> #include <linux/spinlock.h> +#include <linux/idr.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/namei.h>
@@ -61,22 +60,19 @@* For each inotify device, we need to keep track of the events queued on it, * a list of the inodes that we are watching, and so on. * -* 'bitmask' holds one bit for each possible watcher descriptor: a set bit -* implies that the given WD is valid, unset implies it is not. -* * This structure is protected by 'lock'. Lock ordering: * dev->lock * dev->wait->lock * FIXME: Define lock ordering wrt inode and dentry locking! */ struct inotify_device { - DECLARE_BITMAP(bitmask, MAX_INOTIFY_DEV_WATCHERS);wait_queue_head_t wait; +struct idr idr;struct list_head events;struct list_head watchers;spinlock_t lock;unsigned int event_count; -int nr_watches; +unsigned int nr_watches; };struct inotify_watcher {
@@ -179,10 +175,6 @@return ; event_object_count--; -INIT_LIST_HEAD (&kevent->list); - kevent->event.wd =-1 ; - kevent->event.mask =0 ; -iprintk (INOTIFY_DEBUG_ALLOC ,"free'd kevent %p\n" , kevent);kmem_cache_free (kevent_cache, kevent); }
@@ -282,20 +274,30 @@/* * inotify_dev_get_wd - returns the next WD for use by the given dev * -* Caller must hold dev->lock before calling. +* This function sleeps. */ -static int inotify_dev_get_wd (struct inotify_device *dev) +static int inotify_dev_get_wd (struct inotify_device *dev, +struct inotify_watcher *watcher) { -int wd; +int ret; -if (!dev || dev->nr_watches ==MAX_INOTIFY_DEV_WATCHERS ) +if (dev->nr_watches ==MAX_INOTIFY_DEV_WATCHERS )return -1 ; +repeat: +if (!idr_pre_get (&dev->idr,GFP_KERNEL )) +return -1 ; + +spin_lock (&dev->lock); + ret =idr_get_new (&dev->idr, watcher, &watcher->wd); +spin_unlock (&dev->lock); +if (ret == -EAGAIN )/* more memory is required, try again */ + goto repeat; +if (ret == -ENOSPC )/* the idr is full! */ +return -1 ; dev->nr_watches++; - wd =find_first_zero_bit (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS ); -set_bit (wd, dev->bitmask); -return wd; +return 0 ; }/*
@@ -309,7 +311,7 @@return -1 ; dev->nr_watches--; -clear_bit (wd, dev->bitmask); +idr_remove (&dev->idr, wd);return 0 ; }
@@ -331,7 +333,6 @@return NULL ; } - watcher->wd =-1 ; watcher->mask = mask; watcher->inode = inode; watcher->dev = dev;
@@ -339,11 +340,7 @@INIT_LIST_HEAD (&watcher->i_list);INIT_LIST_HEAD (&watcher->u_list); -spin_lock (&dev->lock); - watcher->wd =inotify_dev_get_wd (dev); -spin_unlock (&dev->lock); - -if (watcher->wd <0 ) { +if (inotify_dev_get_wd (dev, watcher)) {iprintk (INOTIFY_DEBUG_ERRORS ,"Could not get wd for watcher %p\n" , watcher);iprintk (INOTIFY_DEBUG_ALLOC ,"free'd watcher %p\n" , watcher);
@@ -387,15 +384,18 @@return NULL ; } -static struct inotify_watcher *dev_find_wd (struct inotify_device *dev,int wd) +/* +* dev_find_wd - given a (dev,wd) pair, returns the matching inotify_watcher +* +* Returns the results of looking up (dev,wd) in the idr layer. NULL is +* returned on error. +* +* The caller must hold dev->lock. +*/ +static inline struct inotify_watcher *dev_find_wd (struct inotify_device *dev, +int wd) { -struct inotify_watcher *watcher; - -list_for_each_entry (watcher, &dev->watchers, d_list) { -if (watcher->wd == wd) -return watcher; - } -return NULL ; +return idr_find (&dev->idr, wd); } static int inotify_dev_is_watching_inode(struct inotify_device *dev,
@@ -411,6 +411,11 @@return 0 ; } +/* +* inotify_dev_add_watcher - add the given watcher to the given device instance +* +* Caller must hold dev->lock. +*/ static int inotify_dev_add_watcher (struct inotify_device *dev,struct inotify_watcher *watcher) {
@@ -570,11 +575,11 @@struct inotify_device *dev;struct inode *inode; -spin_lock (&watcher->dev->lock); -spin_lock (&watcher->inode->i_lock); - - inode = watcher->inode; dev = watcher->dev; + inode = watcher->inode; + +spin_lock (&dev->lock); +spin_lock (&inode->i_lock);if (event)inotify_dev_queue_event (dev, watcher, event,NULL );
@@ -590,7 +595,8 @@unref_inode (inode); } -static void process_umount_list (struct list_head *umount) { +static void process_umount_list (struct list_head *umount) +{struct inotify_watcher *watcher, *next;list_for_each_entry_safe (watcher, next, umount, u_list)
@@ -598,7 +604,7 @@ }static void build_umount_list (struct list_head *head,struct super_block *sb, -struct list_head *umount) +struct list_head *umount) {struct inode * inode;
@@ -766,7 +772,7 @@if (!dev)return -ENOMEM ; -bitmap_zero (dev->bitmask,MAX_INOTIFY_DEV_WATCHERS ); +idr_init (&dev->idr);INIT_LIST_HEAD (&dev->events);INIT_LIST_HEAD (&dev->watchers);
@@ -898,9 +904,16 @@ {struct inotify_watcher *watcher; +/* +* FIXME: Silly to grab dev->lock here and then drop it, when +* ignore_helper() grabs it anyway a few lines down. +*/ +spin_lock (&dev->lock); watcher =dev_find_wd (dev, wd); +spin_unlock (&dev->lock);if (!watcher)return -EINVAL ; +ignore_helper (watcher,0 );return 0 ;