updated inotify.
Subsystems (7)
PATCH 1/1 updated inotify.
Updated inotify, against 2.6.13-rc1.
Only changes since the last post:
* Use kstrdup(). (Adrian Bunk)
* Fix unmount inode mishaps. (Anton Altaparmakov)
Anton's welcome patch fixes the only known inotify bug.
Andrew, can you replace the current 2.6-mm patch with this version?
And then I would like to look at submitting this to Linus.
Robert Love
inotify!
inotify is intended to correct the deficiencies of dnotify, particularly its inability to scale and its terrible user interface:
* dnotify requires the opening of one fd per each directory that you intend to watch. This quickly results in too many open files and pins removable media, preventing unmount.
* 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.
* dnotify's interface to user-space is awful. Signals?
inotify provides a more usable, simple, powerful solution to file change notification:
* inotify's interface is a device node, not SIGIO. You open a single fd to the device node, which is select()-able.
* inotify has an event that says "the filesystem that the item you were watching is on was unmounted."
* inotify can watch directories or files.
Inotify is currently used by Beagle (a desktop search infrastructure), Gamin (a FAM replacement), and other projects.
See Documentation/filesystems/inotify.txt.
Documentation/filesystems/inotify.txt | 155 +++++ fs/Kconfig | 13 fs/Makefile | 1 fs/attr.c | 33 - fs/compat.c | 12 fs/file_table.c | 3 fs/inode.c | 6 fs/inotify.c | 1011 ++++++++++++++++++++++++++++++++++ fs/namei.c | 30 - fs/nfsd/vfs.c | 6 fs/open.c | 3 fs/read_write.c | 15 fs/sysfs/file.c | 7 fs/xattr.c | 5 include/linux/fs.h | 6 include/linux/fsnotify.h | 248 ++++++++ include/linux/inotify.h | 124 ++++ include/linux/sched.h | 4 kernel/user.c | 4 19 files changed, 1622 insertions(+), 64 deletions(-)
Documentation/filesystems/inotify.txt +155 -0
@@ -0,0 +1,155 @@ + inotify + a powerful yet simple file change notification system + + + +Document started 15 Mar 2005 by Robert Love <[email protected]> + +(i) User Interface + +Inotify is controlled by a device node, /dev/inotify. If you do not use udev, +this device may need to be created manually. + +First step in using inotify is to open the device file: + + int dev_fd = open ("/dev/inotify", O_RDONLY); + +Change events are managed by "watches". A watch is an (object,mask) pair where +the object is a file or directory and the mask is a bitmask of one or more +inotify events that the application wishes to receive. See <linux/inotify.h> +for valid events. A watch is referenced by a watch descriptor, or wd. + +Watches are added via a file descriptor to the file. + +Watches on a directory will return events on any files inside of the directory. + +Adding a watch is simple, + + /* 'wd' represents the watch on fd with mask */ + struct inotify_request req = { fd, mask }; + int wd = ioctl (dev_fd, INOTIFY_WATCH, &req); + +You can add a large number of files via something like + + for each file to watch { + struct inotify_request req; + int file_fd; + + file_fd = open (file, O_RDONLY); + if (fd < 0) { + perror ("open"); + break; + } + + req.fd = file_fd; + req.mask = mask; + + wd = ioctl (dev_fd, INOTIFY_WATCH, &req); + + close (fd); + } + +You can update an existing watch in the same manner, by passing in a new mask. + +An existing watch is removed via the INOTIFY_IGNORE ioctl, for example + + ioctl (dev_fd, INOTIFY_IGNORE, wd); + +Events are provided in the form of an inotify_event structure that is read(2) +from /dev/inotify. The filename is of dynamic length and follows the struct. +It is of size len. The filename is padded with null bytes to ensure proper +alignment. This padding is reflected in len. + +You can slurp multiple events by passing a large buffer, for example + + size_t len = read (fd, buf, BUF_LEN); + +Will return as many events as are available and fit in BUF_LEN. + +/dev/inotify is also select()- and poll()-able. + +You can find the size of the current event queue via the FIONREAD ioctl. + +All watches are destroyed and cleaned up on close. + + +(ii) Internal Kernel Implementation + +Each open inotify device is associated with an inotify_device structure. + +Each watch is associated with an inotify_watch structure. Watches are chained +off of each associated device and each associated inode. + +See fs/inotify.c for the locking and lifetime rules. + + +(iii) Rationale + +Q: What is the design decision behind not tying the watch to the open fd of + the watched object? + +A: Watches are associated with an open inotify device, not an open file. + This solves the primary problem with dnotify: keeping the file open pins + the file and thus, worse, pins the mount. Dnotify is therefore infeasible + for use on a desktop system with removable media as the media cannot be + unmounted. + +Q: What is the design decision behind using an-fd-per-device as opposed to + an fd-per-watch? + +A: An fd-per-watch quickly consumes more file descriptors than are allowed, + more fd's than are feasible to manage, and more fd's than are optimally + select()-able. Yes, root can bump the per-process fd limit and yes, users + can use epoll, but requiring both is a silly and extraneous requirement. + A watch consumes less memory than an open file, separating the number + spaces is thus sensible. The current design is what user-space developers + want: Users open the device, once, and add n watches, requiring but one fd + and no twiddling with fd limits. Opening /dev/inotify two thousand times + is silly. If we can implement user-space's preferences cleanly--and we + can, the idr layer makes stuff like this trivial--then we should. + + There are other good arguments. With a single fd, there is a single + item to block on, which is mapped to a single queue of events. The single + fd returns all watch events and also any potential out-of-band data. If + every fd was a separate watch, + + - There would be no way to get event ordering. Events on file foo and + file bar would pop poll() on both fd's, but there would be no way to tell + which happened first. A single queue trivially gives you ordering. Such + ordering is crucial to existing applications such as Beagle. Imagine + "mv a b ; mv b a" events without ordering. + + - We'd have to maintain n fd's and n internal queues with state, + versus just one. It is a lot messier in the kernel. A single, linear + queue is the data structure that makes sense. + + - User-space developers prefer the current API. The Beagle guys, for + example, love it. Trust me, I asked. It is not a surprise: Who'd want + to manage and block on 1000 fd's via select? + + - You'd have to manage the fd's, as an example: Call close() when you + received a delete event. + + - No way to get out of band data. + + - 1024 is still too low. ;-) + + When you talk about designing a file change notification system that + scales to 1000s of directories, juggling 1000s of fd's just does not seem + the right interface. It is too heavy. + +Q: Why a device node? + +A: The poor user-space interface is the second biggest problem with dnotify. + Signals are a terrible, terrible interface for file notification. Or for + anything, for that matter. The idea solution, from all perspectives, is a + file descriptor-based one that allows basic file I/O and poll/select. + Obtaining the fd and managing the watches could have been done either via a + device file or a family of new system calls. We decided to implement a + device file because adding three or four new system calls that mirrored + open, close, and ioctl seemed silly. A character device makes sense from + user-space and was easy to implement inside of the kernel. + + Additionally, it _is_ possible to open /dev/inotify more than once and + juggle more than one queue and thus more than one associated fd. +
fs/attr.c +4 -29
@@ -10,7 +10,7 @@#include <linux/mm.h> #include <linux/string.h> #include <linux/smp_lock.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/fcntl.h> #include <linux/quotaops.h> #include <linux/security.h>
@@ -107,31 +107,8 @@ out:return error; } -EXPORT_SYMBOL (inode_setattr); -int setattr_mask (unsigned int ia_valid) -{ -unsigned long dn_mask =0 ; - -if (ia_valid &ATTR_UID ) - dn_mask |=DN_ATTRIB ; -if (ia_valid &ATTR_GID ) - dn_mask |=DN_ATTRIB ; -if (ia_valid &ATTR_SIZE ) - dn_mask |=DN_MODIFY ; -/* both times implies a utime(s) call */ -if ((ia_valid & (ATTR_ATIME |ATTR_MTIME )) == (ATTR_ATIME |ATTR_MTIME )) - dn_mask |=DN_ATTRIB ; -else if (ia_valid &ATTR_ATIME ) - dn_mask |=DN_ACCESS ; -else if (ia_valid &ATTR_MTIME ) - dn_mask |=DN_MODIFY ; -if (ia_valid &ATTR_MODE ) - dn_mask |=DN_ATTRIB ; -return dn_mask; -} -int notify_change (struct dentry * dentry,struct iattr * attr) {struct inode *inode = dentry->d_inode;
@@ -197,11 +174,9 @@if (ia_valid &ATTR_SIZE )up_write (&dentry->d_inode->i_alloc_sem); -if (!error) { -unsigned long dn_mask =setattr_mask (ia_valid); -if (dn_mask) -dnotify_parent (dentry, dn_mask); - } +if (!error) +fsnotify_change (dentry, ia_valid); +return error; }
fs/compat.c +8 -4
@@ -37,7 +37,7 @@#include <linux/ctype.h> #include <linux/module.h> #include <linux/dirent.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/highuid.h> #include <linux/sunrpc/svc.h> #include <linux/nfsd/nfsd.h>
@@ -1307,9 +1307,13 @@ out:if (iov != iovstack)kfree (iov); -if ((ret + (type ==READ )) >0 ) -dnotify_parent (file->f_dentry, - (type ==READ ) ?DN_ACCESS :DN_MODIFY ); +if ((ret + (type ==READ )) >0 ) { +struct dentry *dentry = file->f_dentry; +if (type ==READ ) +fsnotify_access (dentry); +else +fsnotify_modify (dentry); + }return ret; }
fs/file_table.c +3 -0
@@ -16,6 +16,7 @@#include <linux/eventpoll.h> #include <linux/mount.h> #include <linux/cdev.h> +#include <linux/fsnotify.h> /* sysctl tunables... */ struct files_stat_struct files_stat = {
@@ -126,6 +127,8 @@struct inode *inode = dentry->d_inode;might_sleep (); + +fsnotify_close (file);/* * The function eventpoll_release() should be the first called * in the file cleanup chain.
fs/inode.c +6 -0
@@ -21,6 +21,7 @@#include <linux/pagemap.h> #include <linux/cdev.h> #include <linux/bootmem.h> +#include <linux/inotify.h> /* * This is needed for the following functions:
@@ -202,6 +203,10 @@INIT_LIST_HEAD (&inode->i_data.i_mmap_nonlinear);spin_lock_init (&inode->i_lock);i_size_ordered_init (inode); +#ifdef CONFIG_INOTIFY +INIT_LIST_HEAD (&inode->inotify_watches); +sema_init (&inode->inotify_sem,1 ); +#endif }EXPORT_SYMBOL (inode_init_once);
@@ -346,6 +351,7 @@down (&iprune_sem);spin_lock (&inode_lock); +inotify_unmount_inodes (&sb->s_inodes); busy =invalidate_list (&sb->s_inodes, &throw_away);spin_unlock (&inode_lock);
fs/inotify.c +1011 -0
1,011 lines not on this page · raw message
fs/Kconfig +13 -0
@@ -356,6 +356,19 @@ If you don't know whether you need it, then you don't need it: answer N. +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 QUOTA bool "Quota support" help
fs/Makefile +1 -0
@@ -12,6 +12,7 @@ seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \ ioprio.o +obj-$(CONFIG_INOTIFY) += inotify.o obj-$(CONFIG_EPOLL) += eventpoll.o obj-$(CONFIG_COMPAT) += compat.o
fs/namei.c +16 -14
@@ -21,7 +21,7 @@#include <linux/namei.h> #include <linux/quotaops.h> #include <linux/pagemap.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/smp_lock.h> #include <linux/personality.h> #include <linux/security.h>
@@ -1312,7 +1312,7 @@DQUOT_INIT (dir); error = dir->i_op->create (dir, dentry, mode, nd);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, dentry->d_name.name);security_inode_post_create (dir, dentry, mode); }return error;
@@ -1637,7 +1637,7 @@DQUOT_INIT (dir); error = dir->i_op->mknod (dir, dentry, mode, dev);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, dentry->d_name.name);security_inode_post_mknod (dir, dentry, mode, dev); }return error;
@@ -1710,7 +1710,7 @@DQUOT_INIT (dir); error = dir->i_op->mkdir (dir, dentry, mode);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_mkdir (dir, dentry->d_name.name);security_inode_post_mkdir (dir,dentry, mode); }return error;
@@ -1801,7 +1801,7 @@ }up (&dentry->d_inode->i_sem);if (!error) { -inode_dir_notify (dir,DN_DELETE ); +fsnotify_rmdir (dentry, dentry->d_inode, dir);d_delete (dentry); }dput (dentry);
@@ -1874,9 +1874,10 @@/* We don't d_delete() NFS sillyrenamed files--they still exist. */ if (!error && !(dentry->d_flags &DCACHE_NFSFS_RENAMED )) { +fsnotify_unlink (dentry, dir);d_delete (dentry); -inode_dir_notify (dir,DN_DELETE ); } +return error; }
@@ -1950,7 +1951,7 @@DQUOT_INIT (dir); error = dir->i_op->symlink (dir, dentry, oldname);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, dentry->d_name.name);security_inode_post_symlink (dir, dentry, oldname); }return error;
@@ -2023,7 +2024,7 @@ error = dir->i_op->link (old_dentry, dir, new_dentry);up (&old_dentry->d_inode->i_sem);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, new_dentry->d_name.name);security_inode_post_link (old_dentry, dir, new_dentry); }return error;
@@ -2187,6 +2188,7 @@ {int error;int is_dir =S_ISDIR (old_dentry->d_inode->i_mode); +const char *old_name;if (old_dentry->d_inode == new_dentry->d_inode)return 0 ;
@@ -2208,18 +2210,18 @@DQUOT_INIT (old_dir);DQUOT_INIT (new_dir); + old_name =fsnotify_oldname_init (old_dentry->d_name.name); +if (is_dir) error =vfs_rename_dir (old_dir,old_dentry,new_dir,new_dentry);else error =vfs_rename_other (old_dir,old_dentry,new_dir,new_dentry);if (!error) { -if (old_dir == new_dir) -inode_dir_notify (old_dir,DN_RENAME ); -else { -inode_dir_notify (old_dir,DN_DELETE ); -inode_dir_notify (new_dir,DN_CREATE ); - } +const char *new_name = old_dentry->d_name.name; +fsnotify_move (old_dir, new_dir, old_name, new_name, is_dir); } +fsnotify_oldname_free (old_name); +return error; }
fs/nfsd/vfs.c +3 -3
@@ -45,7 +45,7 @@#endif /* CONFIG_NFSD_V3 */ #include <linux/nfsd/nfsfh.h> #include <linux/quotaops.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/posix_acl.h> #include <linux/posix_acl_xattr.h> #ifdef CONFIG_NFSD_V4
@@ -860,7 +860,7 @@ nfsdstats.io_read += err; *count = err; err =0 ; -dnotify_parent (file->f_dentry,DN_ACCESS ); +fsnotify_access (file->f_dentry); }else err =nfserrno (err); out:
@@ -916,7 +916,7 @@set_fs (oldfs);if (err >=0 ) { nfsdstats.io_write += cnt; -dnotify_parent (file->f_dentry,DN_MODIFY ); +fsnotify_modify (file->f_dentry); }/* clear setuid/setgid flag after write */
fs/open.c +2 -1
@@ -10,7 +10,7 @@#include <linux/file.h> #include <linux/smp_lock.h> #include <linux/quotaops.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/tty.h>
@@ -951,6 +951,7 @@put_unused_fd (fd); fd =PTR_ERR (f); }else { +fsnotify_open (f->f_dentry);fd_install (fd, f); } }
fs/read_write.c +9 -6
@@ -10,7 +10,7 @@#include <linux/file.h> #include <linux/uio.h> #include <linux/smp_lock.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/security.h> #include <linux/module.h> #include <linux/syscalls.h>
@@ -252,7 +252,7 @@else ret =do_sync_read (file, buf, count, pos);if (ret >0 ) { -dnotify_parent (file->f_dentry,DN_ACCESS ); +fsnotify_access (file->f_dentry); current->rchar += ret; } current->syscr++;
@@ -303,7 +303,7 @@else ret =do_sync_write (file, buf, count, pos);if (ret >0 ) { -dnotify_parent (file->f_dentry,DN_MODIFY ); +fsnotify_modify (file->f_dentry); current->wchar += ret; } current->syscw++;
@@ -539,9 +539,12 @@ out:if (iov != iovstack)kfree (iov); -if ((ret + (type ==READ )) >0 ) -dnotify_parent (file->f_dentry, - (type ==READ ) ?DN_ACCESS :DN_MODIFY ); +if ((ret + (type ==READ )) >0 ) { +if (type ==READ ) +fsnotify_access (file->f_dentry); +else +fsnotify_modify (file->f_dentry); + }return ret; Efault: ret = -EFAULT ;
fs/sysfs/file.c +2 -5
@@ -3,7 +3,7 @@*/ #include <linux/module.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/kobject.h> #include <linux/namei.h> #include <asm/uaccess.h>
@@ -391,9 +391,6 @@* sysfs_update_file - update the modified timestamp on an object attribute. * @kobj: object we're acting for. * @attr: attribute descriptor. -* -* Also call dnotify for the dentry, which lots of userspace programs -* use. */ int sysfs_update_file (struct kobject * kobj,const struct attribute * attr) {
@@ -408,7 +405,7 @@if (victim->d_inode && (victim->d_parent->d_inode == dir->d_inode)) { victim->d_inode->i_mtime =CURRENT_TIME ; -dnotify_parent (victim,DN_MODIFY ); +fsnotify_modify (victim);/** * Drop reference from initial sysfs_get_dentry().
fs/xattr.c +4 -1
@@ -16,6 +16,7 @@#include <linux/security.h> #include <linux/syscalls.h> #include <linux/module.h> +#include <linux/fsnotify.h> #include <asm/uaccess.h> /*
@@ -57,8 +58,10 @@if (error) goto out; error = d->d_inode->i_op->setxattr (d, kname, kvalue, size, flags); -if (!error) +if (!error) { +fsnotify_xattr (d);security_inode_post_setxattr (d, kname, kvalue, size, flags); + } out:up (&d->d_inode->i_sem); }
include/linux/fs.h +5 -1
@@ -474,6 +474,11 @@struct dnotify_struct *i_dnotify;/* for directory notifications */ #endif +#ifdef CONFIG_INOTIFY +struct list_head inotify_watches;/* watches on this inode */ +struct semaphore inotify_sem;/* protects the watches list */ +#endif +unsigned long i_state;unsigned long dirtied_when;/* jiffies of first dirtying */
@@ -1393,7 +1398,6 @@extern int do_remount_sb (struct super_block *sb,int flags,void *data,int force);extern sector_t bmap (struct inode *,sector_t ); -extern int setattr_mask (unsigned int );extern int notify_change (struct dentry *,struct iattr *);extern int permission (struct inode *,int ,struct nameidata *); extern int generic_permission(struct inode *, int,
include/linux/fsnotify.h +248 -0
248 lines not on this page · raw message
include/linux/inotify.h +124 -0
124 lines not on this page · raw message
include/linux/sched.h +4 -0
@@ -410,6 +410,10 @@atomic_t processes;/* How many processes does this user have? */ atomic_t files;/* How many open files does this user have? */ atomic_t sigpending;/* How many pending signals does this user have? */ +#ifdef CONFIG_INOTIFY +atomic_t inotify_watches;/* How many inotify watches does this user have? */ +atomic_t inotify_devs;/* How many inotify devs does this user have opened? */ +#endif /* protected by mq_lock */ unsigned long mq_bytes;/* How many bytes can be allocated to mqueue? */ unsigned long locked_shm;/* How many pages of mlocked shm ? */
kernel/user.c +4 -0
@@ -120,6 +120,10 @@atomic_set (&new->processes,0 );atomic_set (&new->files,0 );atomic_set (&new->sigpending,0 ); +#ifdef CONFIG_INOTIFY +atomic_set (&new->inotify_watches,0 ); +atomic_set (&new->inotify_devs,0 ); +#endif new->mq_bytes =0 ; new->locked_shm =0 ;
PATCH 1/1 updated inotify for 2.6.13-rc2.
Quoted 4 lines · Robert Love
Updated inotify, against 2.6.13-rc1.
Only changes since the last post:
Quoted 8 lines
* Use
kstrdup(). (Adrian Bunk)
* Fix unmount inode mishaps. (Anton Altaparmakov)Anton's welcome patch fixes the only known inotify bug.
Andrew, can you replace the current 2.6-mm patch with this version?
And then I would like to look at submitting this to Linus.
Rediff against 2.6.13-rc2.
Robert Love
inotify!
inotify is intended to correct the deficiencies of dnotify, particularly its inability to scale and its terrible user interface:
* dnotify requires the opening of one fd per each directory that you intend to watch. This quickly results in too many open files and pins removable media, preventing unmount.
* 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.
* dnotify's interface to user-space is awful. Signals?
inotify provides a more usable, simple, powerful solution to file change notification:
* inotify's interface is a device node, not SIGIO. You open a single fd to the device node, which is select()-able.
* inotify has an event that says "the filesystem that the item you were watching is on was unmounted."
* inotify can watch directories or files.
Inotify is currently used by Beagle (a desktop search infrastructure), Gamin (a FAM replacement), and other projects.
See Documentation/filesystems/inotify.txt.
Documentation/filesystems/inotify.txt | 155 +++++ fs/Kconfig | 13 fs/Makefile | 1 fs/attr.c | 33 - fs/compat.c | 12 fs/file_table.c | 3 fs/inode.c | 6 fs/inotify.c | 1011 ++++++++++++++++++++++++++++++++++ fs/namei.c | 30 - fs/nfsd/vfs.c | 6 fs/open.c | 3 fs/read_write.c | 15 fs/sysfs/file.c | 7 fs/xattr.c | 5 include/linux/fs.h | 6 include/linux/fsnotify.h | 248 ++++++++ include/linux/inotify.h | 124 ++++ include/linux/sched.h | 4 kernel/user.c | 4 19 files changed, 1622 insertions(+), 64 deletions(-)
Documentation/filesystems/inotify.txt +155 -0
@@ -0,0 +1,155 @@ + inotify + a powerful yet simple file change notification system + + + +Document started 15 Mar 2005 by Robert Love <[email protected]> + +(i) User Interface + +Inotify is controlled by a device node, /dev/inotify. If you do not use udev, +this device may need to be created manually. + +First step in using inotify is to open the device file: + + int dev_fd = open ("/dev/inotify", O_RDONLY); + +Change events are managed by "watches". A watch is an (object,mask) pair where +the object is a file or directory and the mask is a bitmask of one or more +inotify events that the application wishes to receive. See <linux/inotify.h> +for valid events. A watch is referenced by a watch descriptor, or wd. + +Watches are added via a file descriptor to the file. + +Watches on a directory will return events on any files inside of the directory. + +Adding a watch is simple, + + /* 'wd' represents the watch on fd with mask */ + struct inotify_request req = { fd, mask }; + int wd = ioctl (dev_fd, INOTIFY_WATCH, &req); + +You can add a large number of files via something like + + for each file to watch { + struct inotify_request req; + int file_fd; + + file_fd = open (file, O_RDONLY); + if (fd < 0) { + perror ("open"); + break; + } + + req.fd = file_fd; + req.mask = mask; + + wd = ioctl (dev_fd, INOTIFY_WATCH, &req); + + close (fd); + } + +You can update an existing watch in the same manner, by passing in a new mask. + +An existing watch is removed via the INOTIFY_IGNORE ioctl, for example + + ioctl (dev_fd, INOTIFY_IGNORE, wd); + +Events are provided in the form of an inotify_event structure that is read(2) +from /dev/inotify. The filename is of dynamic length and follows the struct. +It is of size len. The filename is padded with null bytes to ensure proper +alignment. This padding is reflected in len. + +You can slurp multiple events by passing a large buffer, for example + + size_t len = read (fd, buf, BUF_LEN); + +Will return as many events as are available and fit in BUF_LEN. + +/dev/inotify is also select()- and poll()-able. + +You can find the size of the current event queue via the FIONREAD ioctl. + +All watches are destroyed and cleaned up on close. + + +(ii) Internal Kernel Implementation + +Each open inotify device is associated with an inotify_device structure. + +Each watch is associated with an inotify_watch structure. Watches are chained +off of each associated device and each associated inode. + +See fs/inotify.c for the locking and lifetime rules. + + +(iii) Rationale + +Q: What is the design decision behind not tying the watch to the open fd of + the watched object? + +A: Watches are associated with an open inotify device, not an open file. + This solves the primary problem with dnotify: keeping the file open pins + the file and thus, worse, pins the mount. Dnotify is therefore infeasible + for use on a desktop system with removable media as the media cannot be + unmounted. + +Q: What is the design decision behind using an-fd-per-device as opposed to + an fd-per-watch? + +A: An fd-per-watch quickly consumes more file descriptors than are allowed, + more fd's than are feasible to manage, and more fd's than are optimally + select()-able. Yes, root can bump the per-process fd limit and yes, users + can use epoll, but requiring both is a silly and extraneous requirement. + A watch consumes less memory than an open file, separating the number + spaces is thus sensible. The current design is what user-space developers + want: Users open the device, once, and add n watches, requiring but one fd + and no twiddling with fd limits. Opening /dev/inotify two thousand times + is silly. If we can implement user-space's preferences cleanly--and we + can, the idr layer makes stuff like this trivial--then we should. + + There are other good arguments. With a single fd, there is a single + item to block on, which is mapped to a single queue of events. The single + fd returns all watch events and also any potential out-of-band data. If + every fd was a separate watch, + + - There would be no way to get event ordering. Events on file foo and + file bar would pop poll() on both fd's, but there would be no way to tell + which happened first. A single queue trivially gives you ordering. Such + ordering is crucial to existing applications such as Beagle. Imagine + "mv a b ; mv b a" events without ordering. + + - We'd have to maintain n fd's and n internal queues with state, + versus just one. It is a lot messier in the kernel. A single, linear + queue is the data structure that makes sense. + + - User-space developers prefer the current API. The Beagle guys, for + example, love it. Trust me, I asked. It is not a surprise: Who'd want + to manage and block on 1000 fd's via select? + + - You'd have to manage the fd's, as an example: Call close() when you + received a delete event. + + - No way to get out of band data. + + - 1024 is still too low. ;-) + + When you talk about designing a file change notification system that + scales to 1000s of directories, juggling 1000s of fd's just does not seem + the right interface. It is too heavy. + +Q: Why a device node? + +A: The poor user-space interface is the second biggest problem with dnotify. + Signals are a terrible, terrible interface for file notification. Or for + anything, for that matter. The idea solution, from all perspectives, is a + file descriptor-based one that allows basic file I/O and poll/select. + Obtaining the fd and managing the watches could have been done either via a + device file or a family of new system calls. We decided to implement a + device file because adding three or four new system calls that mirrored + open, close, and ioctl seemed silly. A character device makes sense from + user-space and was easy to implement inside of the kernel. + + Additionally, it _is_ possible to open /dev/inotify more than once and + juggle more than one queue and thus more than one associated fd. +
fs/attr.c +4 -29
@@ -10,7 +10,7 @@#include <linux/mm.h> #include <linux/string.h> #include <linux/smp_lock.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/fcntl.h> #include <linux/quotaops.h> #include <linux/security.h>
@@ -107,31 +107,8 @@ out:return error; } -EXPORT_SYMBOL (inode_setattr); -int setattr_mask (unsigned int ia_valid) -{ -unsigned long dn_mask =0 ; - -if (ia_valid &ATTR_UID ) - dn_mask |=DN_ATTRIB ; -if (ia_valid &ATTR_GID ) - dn_mask |=DN_ATTRIB ; -if (ia_valid &ATTR_SIZE ) - dn_mask |=DN_MODIFY ; -/* both times implies a utime(s) call */ -if ((ia_valid & (ATTR_ATIME |ATTR_MTIME )) == (ATTR_ATIME |ATTR_MTIME )) - dn_mask |=DN_ATTRIB ; -else if (ia_valid &ATTR_ATIME ) - dn_mask |=DN_ACCESS ; -else if (ia_valid &ATTR_MTIME ) - dn_mask |=DN_MODIFY ; -if (ia_valid &ATTR_MODE ) - dn_mask |=DN_ATTRIB ; -return dn_mask; -} -int notify_change (struct dentry * dentry,struct iattr * attr) {struct inode *inode = dentry->d_inode;
@@ -197,11 +174,9 @@if (ia_valid &ATTR_SIZE )up_write (&dentry->d_inode->i_alloc_sem); -if (!error) { -unsigned long dn_mask =setattr_mask (ia_valid); -if (dn_mask) -dnotify_parent (dentry, dn_mask); - } +if (!error) +fsnotify_change (dentry, ia_valid); +return error; }
fs/compat.c +8 -4
@@ -37,7 +37,7 @@#include <linux/ctype.h> #include <linux/module.h> #include <linux/dirent.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/highuid.h> #include <linux/sunrpc/svc.h> #include <linux/nfsd/nfsd.h>
@@ -1307,9 +1307,13 @@ out:if (iov != iovstack)kfree (iov); -if ((ret + (type ==READ )) >0 ) -dnotify_parent (file->f_dentry, - (type ==READ ) ?DN_ACCESS :DN_MODIFY ); +if ((ret + (type ==READ )) >0 ) { +struct dentry *dentry = file->f_dentry; +if (type ==READ ) +fsnotify_access (dentry); +else +fsnotify_modify (dentry); + }return ret; }
fs/file_table.c +3 -0
@@ -16,6 +16,7 @@#include <linux/eventpoll.h> #include <linux/mount.h> #include <linux/cdev.h> +#include <linux/fsnotify.h> /* sysctl tunables... */ struct files_stat_struct files_stat = {
@@ -126,6 +127,8 @@struct inode *inode = dentry->d_inode;might_sleep (); + +fsnotify_close (file);/* * The function eventpoll_release() should be the first called * in the file cleanup chain.
fs/inode.c +6 -0
@@ -21,6 +21,7 @@#include <linux/pagemap.h> #include <linux/cdev.h> #include <linux/bootmem.h> +#include <linux/inotify.h> /* * This is needed for the following functions:
@@ -202,6 +203,10 @@INIT_LIST_HEAD (&inode->i_data.i_mmap_nonlinear);spin_lock_init (&inode->i_lock);i_size_ordered_init (inode); +#ifdef CONFIG_INOTIFY +INIT_LIST_HEAD (&inode->inotify_watches); +sema_init (&inode->inotify_sem,1 ); +#endif }EXPORT_SYMBOL (inode_init_once);
@@ -346,6 +351,7 @@down (&iprune_sem);spin_lock (&inode_lock); +inotify_unmount_inodes (&sb->s_inodes); busy =invalidate_list (&sb->s_inodes, &throw_away);spin_unlock (&inode_lock);
fs/inotify.c +1011 -0
1,011 lines not on this page · raw message
fs/Kconfig +13 -0
@@ -356,6 +356,19 @@ If you don't know whether you need it, then you don't need it: answer N. +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 QUOTA bool "Quota support" help
fs/Makefile +1 -0
@@ -12,6 +12,7 @@ seq_file.o xattr.o libfs.o fs-writeback.o mpage.o direct-io.o \ ioprio.o +obj-$(CONFIG_INOTIFY) += inotify.o obj-$(CONFIG_EPOLL) += eventpoll.o obj-$(CONFIG_COMPAT) += compat.o
fs/namei.c +16 -14
@@ -21,7 +21,7 @@#include <linux/namei.h> #include <linux/quotaops.h> #include <linux/pagemap.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/smp_lock.h> #include <linux/personality.h> #include <linux/security.h>
@@ -1312,7 +1312,7 @@DQUOT_INIT (dir); error = dir->i_op->create (dir, dentry, mode, nd);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, dentry->d_name.name);security_inode_post_create (dir, dentry, mode); }return error;
@@ -1637,7 +1637,7 @@DQUOT_INIT (dir); error = dir->i_op->mknod (dir, dentry, mode, dev);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, dentry->d_name.name);security_inode_post_mknod (dir, dentry, mode, dev); }return error;
@@ -1710,7 +1710,7 @@DQUOT_INIT (dir); error = dir->i_op->mkdir (dir, dentry, mode);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_mkdir (dir, dentry->d_name.name);security_inode_post_mkdir (dir,dentry, mode); }return error;
@@ -1801,7 +1801,7 @@ }up (&dentry->d_inode->i_sem);if (!error) { -inode_dir_notify (dir,DN_DELETE ); +fsnotify_rmdir (dentry, dentry->d_inode, dir);d_delete (dentry); }dput (dentry);
@@ -1874,9 +1874,10 @@/* We don't d_delete() NFS sillyrenamed files--they still exist. */ if (!error && !(dentry->d_flags &DCACHE_NFSFS_RENAMED )) { +fsnotify_unlink (dentry, dir);d_delete (dentry); -inode_dir_notify (dir,DN_DELETE ); } +return error; }
@@ -1950,7 +1951,7 @@DQUOT_INIT (dir); error = dir->i_op->symlink (dir, dentry, oldname);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, dentry->d_name.name);security_inode_post_symlink (dir, dentry, oldname); }return error;
@@ -2023,7 +2024,7 @@ error = dir->i_op->link (old_dentry, dir, new_dentry);up (&old_dentry->d_inode->i_sem);if (!error) { -inode_dir_notify (dir,DN_CREATE ); +fsnotify_create (dir, new_dentry->d_name.name);security_inode_post_link (old_dentry, dir, new_dentry); }return error;
@@ -2187,6 +2188,7 @@ {int error;int is_dir =S_ISDIR (old_dentry->d_inode->i_mode); +const char *old_name;if (old_dentry->d_inode == new_dentry->d_inode)return 0 ;
@@ -2208,18 +2210,18 @@DQUOT_INIT (old_dir);DQUOT_INIT (new_dir); + old_name =fsnotify_oldname_init (old_dentry->d_name.name); +if (is_dir) error =vfs_rename_dir (old_dir,old_dentry,new_dir,new_dentry);else error =vfs_rename_other (old_dir,old_dentry,new_dir,new_dentry);if (!error) { -if (old_dir == new_dir) -inode_dir_notify (old_dir,DN_RENAME ); -else { -inode_dir_notify (old_dir,DN_DELETE ); -inode_dir_notify (new_dir,DN_CREATE ); - } +const char *new_name = old_dentry->d_name.name; +fsnotify_move (old_dir, new_dir, old_name, new_name, is_dir); } +fsnotify_oldname_free (old_name); +return error; }
fs/nfsd/vfs.c +3 -3
@@ -45,7 +45,7 @@#endif /* CONFIG_NFSD_V3 */ #include <linux/nfsd/nfsfh.h> #include <linux/quotaops.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/posix_acl.h> #include <linux/posix_acl_xattr.h> #ifdef CONFIG_NFSD_V4
@@ -860,7 +860,7 @@ nfsdstats.io_read += err; *count = err; err =0 ; -dnotify_parent (file->f_dentry,DN_ACCESS ); +fsnotify_access (file->f_dentry); }else err =nfserrno (err); out:
@@ -916,7 +916,7 @@set_fs (oldfs);if (err >=0 ) { nfsdstats.io_write += cnt; -dnotify_parent (file->f_dentry,DN_MODIFY ); +fsnotify_modify (file->f_dentry); }/* clear setuid/setgid flag after write */
fs/open.c +2 -1
@@ -10,7 +10,7 @@#include <linux/file.h> #include <linux/smp_lock.h> #include <linux/quotaops.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/module.h> #include <linux/slab.h> #include <linux/tty.h>
@@ -951,6 +951,7 @@put_unused_fd (fd); fd =PTR_ERR (f); }else { +fsnotify_open (f->f_dentry);fd_install (fd, f); } }
fs/read_write.c +9 -6
@@ -10,7 +10,7 @@#include <linux/file.h> #include <linux/uio.h> #include <linux/smp_lock.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/security.h> #include <linux/module.h> #include <linux/syscalls.h>
@@ -252,7 +252,7 @@else ret =do_sync_read (file, buf, count, pos);if (ret >0 ) { -dnotify_parent (file->f_dentry,DN_ACCESS ); +fsnotify_access (file->f_dentry); current->rchar += ret; } current->syscr++;
@@ -303,7 +303,7 @@else ret =do_sync_write (file, buf, count, pos);if (ret >0 ) { -dnotify_parent (file->f_dentry,DN_MODIFY ); +fsnotify_modify (file->f_dentry); current->wchar += ret; } current->syscw++;
@@ -539,9 +539,12 @@ out:if (iov != iovstack)kfree (iov); -if ((ret + (type ==READ )) >0 ) -dnotify_parent (file->f_dentry, - (type ==READ ) ?DN_ACCESS :DN_MODIFY ); +if ((ret + (type ==READ )) >0 ) { +if (type ==READ ) +fsnotify_access (file->f_dentry); +else +fsnotify_modify (file->f_dentry); + }return ret; Efault: ret = -EFAULT ;
fs/sysfs/file.c +2 -5
@@ -3,7 +3,7 @@*/ #include <linux/module.h> -#include <linux/dnotify.h> +#include <linux/fsnotify.h> #include <linux/kobject.h> #include <linux/namei.h> #include <asm/uaccess.h>
@@ -391,9 +391,6 @@* sysfs_update_file - update the modified timestamp on an object attribute. * @kobj: object we're acting for. * @attr: attribute descriptor. -* -* Also call dnotify for the dentry, which lots of userspace programs -* use. */ int sysfs_update_file (struct kobject * kobj,const struct attribute * attr) {
@@ -408,7 +405,7 @@if (victim->d_inode && (victim->d_parent->d_inode == dir->d_inode)) { victim->d_inode->i_mtime =CURRENT_TIME ; -dnotify_parent (victim,DN_MODIFY ); +fsnotify_modify (victim);/** * Drop reference from initial sysfs_get_dentry().
fs/xattr.c +4 -1
@@ -16,6 +16,7 @@#include <linux/security.h> #include <linux/syscalls.h> #include <linux/module.h> +#include <linux/fsnotify.h> #include <asm/uaccess.h> /*
@@ -57,8 +58,10 @@if (error) goto out; error = d->d_inode->i_op->setxattr (d, kname, kvalue, size, flags); -if (!error) +if (!error) { +fsnotify_xattr (d);security_inode_post_setxattr (d, kname, kvalue, size, flags); + } out:up (&d->d_inode->i_sem); }
include/linux/fs.h +5 -1
@@ -474,6 +474,11 @@struct dnotify_struct *i_dnotify;/* for directory notifications */ #endif +#ifdef CONFIG_INOTIFY +struct list_head inotify_watches;/* watches on this inode */ +struct semaphore inotify_sem;/* protects the watches list */ +#endif +unsigned long i_state;unsigned long dirtied_when;/* jiffies of first dirtying */
@@ -1393,7 +1398,6 @@extern int do_remount_sb (struct super_block *sb,int flags,void *data,int force);extern sector_t bmap (struct inode *,sector_t ); -extern int setattr_mask (unsigned int );extern int notify_change (struct dentry *,struct iattr *);extern int permission (struct inode *,int ,struct nameidata *); extern int generic_permission(struct inode *, int,
include/linux/fsnotify.h +248 -0
248 lines not on this page · raw message
include/linux/inotify.h +124 -0
124 lines not on this page · raw message
include/linux/sched.h +4 -0
@@ -410,6 +410,10 @@atomic_t processes;/* How many processes does this user have? */ atomic_t files;/* How many open files does this user have? */ atomic_t sigpending;/* How many pending signals does this user have? */ +#ifdef CONFIG_INOTIFY +atomic_t inotify_watches;/* How many inotify watches does this user have? */ +atomic_t inotify_devs;/* How many inotify devs does this user have opened? */ +#endif /* protected by mq_lock */ unsigned long mq_bytes;/* How many bytes can be allocated to mqueue? */ unsigned long locked_shm;/* How many pages of mlocked shm ? */
kernel/user.c +4 -0
@@ -120,6 +120,10 @@atomic_set (&new->processes,0 );atomic_set (&new->files,0 );atomic_set (&new->sigpending,0 ); +#ifdef CONFIG_INOTIFY +atomic_set (&new->inotify_watches,0 ); +atomic_set (&new->inotify_devs,0 ); +#endif new->mq_bytes =0 ; new->locked_shm =0 ;