--- zzzz-none-000/linux-4.4.60/fs/notify/fsnotify.c 2017-04-08 07:53:53.000000000 +0000 +++ wasp-540e-714/linux-4.4.60/fs/notify/fsnotify.c 2019-07-03 09:21:34.000000000 +0000 @@ -41,6 +41,31 @@ fsnotify_clear_marks_by_mount(mnt); } +#if defined(CONFIG_FSNOTIFY_RECURSIVE) +static void __fsnotify_update_child_dentry_flags_recursivly(struct dentry *dentry) +{ + struct dentry *child; + int watched_recursivly = 0; + + if (dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY) + watched_recursivly = 1; + + list_for_each_entry(child, &dentry->d_subdirs, d_child) { + if (!child->d_inode) + continue; + + spin_lock_nested(&child->d_lock, DENTRY_D_LOCK_NESTED); + if (watched_recursivly) + child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY; + else + child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY; + + __fsnotify_update_child_dentry_flags_recursivly(child); + spin_unlock(&child->d_lock); + } +} +#endif /* CONFIG_FSNOTIFY_RECURSIVE */ + /* * Given an inode, first check if we care what happens to our children. Inotify * and dnotify both tell their parents about events. If we care about any event @@ -52,12 +77,18 @@ { struct dentry *alias; int watched; +#if defined(CONFIG_FSNOTIFY_RECURSIVE) + int watched_recursivly; +#endif /* CONFIG_FSNOTIFY_RECURSIVE */ if (!S_ISDIR(inode->i_mode)) return; /* determine if the children should tell inode about their events */ watched = fsnotify_inode_watches_children(inode); +#if defined(CONFIG_FSNOTIFY_RECURSIVE) + watched_recursivly = fsnotify_inode_watches_children_recursivly(inode); +#endif /* CONFIG_FSNOTIFY_RECURSIVE */ spin_lock(&inode->i_lock); /* run all of the dentries associated with this inode. Since this is a @@ -65,6 +96,11 @@ hlist_for_each_entry(alias, &inode->i_dentry, d_u.d_alias) { struct dentry *child; +#if defined(CONFIG_FSNOTIFY_RECURSIVE) + if (alias->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY) + watched_recursivly = 1; +#endif /* CONFIG_FSNOTIFY_RECURSIVE */ + /* run all of the children of the original inode and fix their * d_flags to indicate parental interest (their parent is the * original inode) */ @@ -78,6 +114,15 @@ child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED; else child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED; + +#if defined(CONFIG_FSNOTIFY_RECURSIVE) + if (watched_recursivly) + child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY; + else + child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY; + + __fsnotify_update_child_dentry_flags_recursivly(child); +#endif /* CONFIG_FSNOTIFY_RECURSIVE */ spin_unlock(&child->d_lock); } spin_unlock(&alias->d_lock); @@ -104,16 +149,20 @@ if (unlikely(!fsnotify_inode_watches_children(p_inode))) __fsnotify_update_child_dentry_flags(p_inode); else if (p_inode->i_fsnotify_mask & mask) { + struct name_snapshot name; + /* we are notifying a parent so come up with the new mask which * specifies these are events which came from a child. */ mask |= FS_EVENT_ON_CHILD; + take_dentry_name_snapshot(&name, dentry); if (path) ret = fsnotify(p_inode, mask, path, FSNOTIFY_EVENT_PATH, - dentry->d_name.name, 0); + name.name, 0); else ret = fsnotify(p_inode, mask, dentry->d_inode, FSNOTIFY_EVENT_INODE, - dentry->d_name.name, 0); + name.name, 0); + release_dentry_name_snapshot(&name); } dput(parent); @@ -122,6 +171,174 @@ } EXPORT_SYMBOL_GPL(__fsnotify_parent); +#if defined(CONFIG_FSNOTIFY_RECURSIVE) +/** + * Formates a relative path beginning at an entry with the given name up to top. + * + * @param first_parent The first parent of the name + * @param top the top-most dentry to be included + * @param name the name of the last component + * @return a kmalloc'd string representing the path + */ +static char *alloc_relative_path(struct dentry *first_parent, struct dentry *top, const char *name) +{ + struct dentry *parent; + char *path; + size_t size; + + size = strlen(name) + 1 /* \0 */; + + /* + * First we go upwards the tree to calculate the needed size. + */ + parent = dget(first_parent); + while (parent != top) { + struct dentry *old_parent = parent; + + size += parent->d_name.len + 1 /* / */; + + parent = dget_parent(parent); + dput(old_parent); + } + dput(parent); + + path = kmalloc(size, GFP_ATOMIC); + if (!path) { + path = ERR_PTR(-ENOMEM); + goto out; + } + + size -= strlen(name) + 1; + memcpy(path + size, name, strlen(name) + 1); + + /* + * Secondly go upwards again and fill in path backwards + */ + parent = dget(first_parent); + while (parent != top) { + struct dentry *old_parent = parent; + + size -= 1; + path[size] = '/'; + + size -= parent->d_name.len; + memcpy(path + size, parent->d_name.name, parent->d_name.len); + + parent = dget_parent(parent); + dput(old_parent); + } + dput(parent); + +out: + return path; +} + +/** + * Notifies the parent interested. + * + * Walkes the dentry tree upwards to find interested parents and forward the event to them. + * + * DOES NOT cross fs boundaries + * DOES ONLY notifies the first parent + * + * @param path path for the event + * @param dentry the dentry for the event + * @param first_parent the parent of dentry, if it is different because dentry has been moved + * @param name the name of dentry, if it is different because dentry has been renamed + * @param mask the event mask to pass + * @param cookie the cookie to pass + * @return 0 on success, !=0 otherwise + */ +int __fsnotify_parent_recursive(struct path *path, struct dentry *dentry, struct dentry *first_parent, const char *name, __u32 mask, __u32 cookie) +{ + struct dentry *parent; + struct inode *p_inode; + int ret = 0; + + if (!dentry) + dentry = path->dentry; + + /* + * No one above us in the hierarchy is interested in our events + */ + if (!(dentry->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY)) + return 0; + + /* + * The event is not allowed to propagate upwards + */ + if (!(mask & FS_EVENTS_POSS_ON_CHILD_RECURSIVLY)) + return 0; + + if (!first_parent) + first_parent = dget_parent(dentry); + else + first_parent = dget(first_parent); + + if (!name) + name = dentry->d_name.name; + + rcu_read_lock(); + parent = dget(first_parent); + while (parent->d_flags & DCACHE_FSNOTIFY_PARENT_WATCHED_RECURSIVLY + || (parent->d_inode && fsnotify_inode_watches_children_recursivly(parent->d_inode))) { + struct dentry *old_parent = parent; + + if (parent->d_inode && fsnotify_inode_watches_children_recursivly(parent->d_inode)) { + p_inode = parent->d_inode; + + if (parent != first_parent && (p_inode->i_fsnotify_mask & mask)) { + char *rel_path = alloc_relative_path(first_parent, parent, name); + + /* + * Could not create a relative path, silently skip it. + */ + if (IS_ERR(rel_path)) + break; + + /* + * We are notifying a parent so come up with the new mask which + * specifies these are events which came from a child. + */ + mask |= FS_EVENT_ON_CHILD_RECURSIVLY; + + rcu_read_unlock(); + if (path) + ret = fsnotify(p_inode, mask, path, FSNOTIFY_EVENT_PATH, + rel_path, cookie); + else + ret = fsnotify(p_inode, mask, dentry->d_inode, FSNOTIFY_EVENT_INODE, + rel_path, cookie); + + rcu_read_lock(); + kfree(rel_path); + /* + * To avoid inconsitencies by temporarily releasing the rcu_read_lock + * we'll only notify the first parent for now. + */ + break; + } + } + + /* + * A root dentry is it's own parent, so break here + */ + if (IS_ROOT(parent)) + break; + + parent = dget_parent(parent); + dput(old_parent); + } + dput(parent); + rcu_read_unlock(); + + dput(first_parent); + + return ret; +} +EXPORT_SYMBOL_GPL(__fsnotify_parent_recursive); +#endif /* CONFIG_FSNOTIFY_RECURSIVE */ + static int send_to_group(struct inode *to_tell, struct fsnotify_mark *inode_mark, struct fsnotify_mark *vfsmount_mark,