/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* TODO: * get more events sent to the queue * rename * move * unmount * check that any inodes we are asked to watch are actually directories * write user space test app * change umount code */ #define MAX_WATCHER_COUNT 8 /* We only support 8 watchers */ #define MAX_WATCH_COUNT 128 /* A watcher can only be watching 128 inodes */ static int watcher_count = 0; struct inotify_watch { struct list_head list; struct inode * inode; }; #define list_to_inotify_watch(pos) list_entry((pos),struct inotify_watch,list) /* A list of these structures is attached to each inode that is being watched. * * each item in the list represents a unique watcher. * * it tell us what events we are looking at and who is watching the inode. * */ struct inotify_inode_watcher { struct list_head list; unsigned long mask; struct inode * inode; void * private_data; // the driver instance }; #define list_to_inotify_inode_watcher(pos) list_entry((pos), struct inofiy_inode_watcher, 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_event { struct list_head list; unsigned long i_no; unsigned long i_dev; unsigned long mask; }; #define list_to_inotify_event(pos) list_entry((pos), struct inotify_event, list) /* For each inotify device we need to keep a list of events queued on it * aswell as a list of inodes that we are watching */ struct inotify_device { struct list_head * events; struct list_head * watch; atomic_t watch_count; char read_state; wait_queue_head_t wait; }; static struct inotify_inode_watcher *inotify_inode_find_watcher (struct inotify_device *dev, struct inode *inode) { struct inotify_inode_watcher *watcher; list_for_each_entry (watcher, &inode->watchers, list) { /* This watcher is from this device */ if (watcher->private_data == dev) { return watcher; } } return NULL; } static void inotify_inode_add_watcher (struct inotify_device *dev, struct inode *inode, unsigned long mask) { struct inotify_inode_watcher *watcher; watcher = inotify_inode_find_watcher (dev, inode); /* Check if we are already watching this inode */ if (NULL != watcher) { watcher->mask = mask; } else { watcher = kmalloc (sizeof (struct inotify_inode_watcher), GFP_KERNEL); watcher->private_data = dev; watcher->inode = inode; watcher->mask = mask; atomic_inc (&inode->watcher_count); list_add (&watcher->list, &inode->watchers); } inotify_inode_update_mask (inode); } static void inotify_inode_rm_watcher (struct inotify_device *dev, struct inode *inode) { struct inotify_inode_watcher *watcher; watcher = inotify_inode_find_watcher (dev, inode); if (NULL != watcher) { list_del(&watcher->list); kfree (watcher); atomic_dec (&inode->watcher_count); } } static struct inotify_watch *inotify_dev_find_watcher (struct inotify_device *dev, struct inode *inode) { struct inotify_watch *watch; list_for_each_entry (watch, dev->watch, list) { if (watch->inode == inode) { return watch; } } return NULL; } static void inotify_dev_add_watcher (struct inotify_device *dev, struct inode *inode) { struct inotify_watch *watch; watch = inotify_dev_find_watcher (dev, inode); if (watch == NULL) { watch = kmalloc (sizeof (struct inotify_watch), GFP_KERNEL); watch->inode = inode; atomic_inc (&dev->watch_count); list_add (&watch->list, dev->watch); } } static void inotify_dev_rm_watcher (struct inotify_device *dev, struct inode *inode) { struct inotify_watch *watch; watch = inotify_dev_find_watcher (dev, inode); if (NULL != watch) { list_del (&watch->list); kfree (watch); atomic_dec (&dev->watch_count); } } static char inotify_dev_has_events (struct inotify_device *dev) { return dev->events && !list_empty(dev->events); } static void inotify_dev_event_dequeue (struct inotify_device *dev) { struct inotify_event *event; if (list_empty (dev->events)) { return; } /* Get the first entry from the event queue */ event = list_to_inotify_event (dev->events->next); list_del (&event->list); kfree (event); } static void inotify_dev_event_queue (struct inotify_device *dev, unsigned long i_no, unsigned long i_dev, unsigned long mask) { struct inotify_event *event; event = kmalloc (sizeof (struct inotify_event), GFP_KERNEL); event->i_no = i_no; event->i_dev = i_dev; event->mask = mask; list_add_tail (&event->list, dev->events); } /* Kernel API */ void inotify_inode_queue_event (struct inode *inode, unsigned long mask) { struct inotify_inode_watcher *watcher; /* If this inode isn't interested in the event */ if (!(inode->watchers_mask & mask)) { return; } list_for_each_entry (watcher, &inode->watchers, list) { if (watcher->mask & mask) { inotify_dev_event_queue (watcher->private_data, inode->i_ino, new_encode_dev (inode->i_sb->s_dev), mask); } } } EXPORT_SYMBOL_GPL(inotify_inode_queue_event); /* Based on dnotify_parent -- locking may be broken */ void inotify_dentry_queue_event (struct dentry *dentry, unsigned long mask) { struct dentry *parent; spin_lock(&dentry->d_lock); parent = dentry->d_parent; dget (parent); inotify_inode_queue_event (parent->d_inode, mask); dput (parent); spin_unlock(&dentry->d_lock); } EXPORT_SYMBOL_GPL(inotify_dentry_queue_event); void inotify_inode_update_mask (struct inode *inode) { struct inotify_inode_watcher *watcher; unsigned long new_mask; new_mask = 0; list_for_each_entry (watcher, &inode->watchers, list) { new_mask |= watcher->mask; } inode->watchers_mask = new_mask; } EXPORT_SYMBOL_GPL(inotify_inode_update_mask); /* The driver interface is implemented below */ #define INOTIFY_READ_STATE_INODE 0 #define INOTIFY_READ_STATE_DEV 1 #define INOTIFY_READ_STATE_MASK 2 static ssize_t inotify_read(struct file *file, char *buf, size_t count, loff_t *pos) { struct inotify_device *dev; size_t out; out = 0; dev = file->private_data; if (!inotify_dev_has_events (dev)) { return 0; } while (out < count && inotify_dev_has_events (dev)) { struct inotify_event *event; event = list_to_inotify_event (dev->events->next); switch (dev->read_state) { case INOTIFY_READ_STATE_INODE: if (put_user (event->i_no, buf)) return -EFAULT; buf += sizeof (event->i_no); dev->read_state++; break; case INOTIFY_READ_STATE_DEV: if (put_user (event->i_dev, buf)) return -EFAULT; buf += sizeof (event->i_dev); dev->read_state++; break; case INOTIFY_READ_STATE_MASK: if (put_user (event->mask, buf)) return -EFAULT; buf += sizeof (event->mask); dev->read_state = INOTIFY_READ_STATE_INODE; /* We have finished delivering the complete event, so dequeue it */ inotify_dev_event_dequeue (dev); break; default: /* BUG */ break; } out++; } return out; } 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; } static int inotify_open(struct inode *inode, struct file *file) { struct inotify_device *dev; if (watcher_count == MAX_WATCHER_COUNT) return -ENODEV; watcher_count++; dev = kmalloc (sizeof (struct inotify_device), GFP_KERNEL); dev->events = kmalloc (sizeof (struct list_head), GFP_KERNEL); INIT_LIST_HEAD(dev->events); dev->watch = kmalloc (sizeof (struct list_head), GFP_KERNEL); INIT_LIST_HEAD(dev->watch); atomic_set(&dev->watch_count, 0); dev->read_state = INOTIFY_READ_STATE_INODE; init_waitqueue_head (&dev->wait); file->private_data = dev; return 0; } static void inotify_release_all_watchers (struct inotify_device *dev) { struct inotify_watch *watch; list_for_each_entry (watch, dev->watch, list) { /* Remove the watcher from the inode */ inotify_inode_rm_watcher (dev, watch->inode); /* Remove the inode from the device */ inotify_dev_rm_watcher (dev, watch->inode); } } static void inotify_release_all_events (struct inotify_device *dev) { while (inotify_dev_has_events (dev)) { inotify_dev_event_dequeue (dev); } } 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; inotify_release_all_watchers (dev); kfree (dev->watch); inotify_release_all_events (dev); kfree (dev->events); kfree (dev); } watcher_count--; return 0; } static struct inode *inotify_find_inode (struct inotify_packet *packet) { struct super_block *s; struct inode *inode; s = user_get_super (new_decode_dev(packet->i_dev)); if (!s) { return NULL; } inode = ilookup (s, packet->i_no); drop_super(s); if (!inode) { return NULL; } /* ilookup takes a reference on the inode, release it */ iput (inode); return inode; } static int inotify_watch(struct inotify_device *dev, struct inotify_packet *packet) { int err; struct inode *inode; err = 0; inode = inotify_find_inode (packet); if (!inode) { err = -ENOENT; goto out; } inotify_inode_add_watcher (dev, inode, packet->mask); inotify_dev_add_watcher (dev, inode); out: return err; } static int inotify_ignore(struct inotify_device *dev, struct inotify_packet *packet) { int err; struct inode *inode; err = 0; inode = inotify_find_inode (packet); if (!inode) { err = -ENOENT; goto out; } inotify_inode_rm_watcher (dev, inode); inotify_dev_rm_watcher (dev, inode); out: return err; } static int inotify_ioctl(struct inode *ip, struct file *fp, unsigned int cmd, unsigned long arg) { int err; struct inotify_device *dev; struct inotify_packet *packet; 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_WRITE, (void *)arg, _IOC_SIZE(cmd)); if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok (VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT; packet = kmalloc (sizeof (struct inotify_packet), GFP_KERNEL); if (copy_from_user (packet, (void *)arg, sizeof (struct inotify_packet))) { err = -EFAULT; goto out; } err = -EINVAL; switch (cmd) { case INOTIFY_WATCH: printk(KERN_ALERT "inotify WATCH: %ld %ld %ld\n", packet->i_no, packet->i_dev, packet->mask); err = inotify_watch (dev, packet); break; case INOTIFY_IGNORE: printk(KERN_ALERT "inotify IGNORE: %ld %ld\n", packet->i_no, packet->i_dev); err = inotify_ignore (dev, packet); break; } out: kfree (packet); return err; } static struct file_operations inotify_fops = { .owner = THIS_MODULE, .read = inotify_read, .poll = inotify_poll, .open = inotify_open, .release = inotify_release, .ioctl = inotify_ioctl, }; struct miscdevice inotify_device = { .minor = INOTIFY_MINOR, .name = "inotify", .fops = &inotify_fops, }; static int __init inotify_init (void) { int ret; ret = misc_register (&inotify_device); if (ret) { goto out; } ret = 0; printk(KERN_ALERT "inotify 0.1 startup\n"); out: return ret; } static void inotify_exit (void) { misc_deregister (&inotify_device); printk(KERN_ALERT "inotify 0.1 shutdown\n"); } MODULE_AUTHOR("John McCutchan "); MODULE_DESCRIPTION("Inode event driver"); MODULE_LICENSE("GPL"); module_init (inotify_init); module_exit (inotify_exit);