Date: Thu, 22 Dec 2011 11:05:31 -0500 From: John Baldwin <jhb@freebsd.org> To: arch@freebsd.org Cc: Robert Watson <rwatson@freebsd.org> Subject: Post NOTE_ATTRIB EVFILT_VNODE events for extattr changes Message-ID: <201112221105.31746.jhb@freebsd.org>
next in thread | raw e-mail | index | archive | help
A co-worker noticed that there is currently no way to get an EVFILT_VNODE notification if the extended attributes for a given file change. I looked at OS X and found that it posts a NOTE_ATTRIB event (typically used for VOP_SETATTR()) for successful attempts to set or delete an extended attribute. I have a patch to do the same along with a test application for both OS X and FreeBSD. With this patch FreeBSD now has the same behavior as OS X. Index: kern/vnode_if.src =================================================================== --- kern/vnode_if.src (revision 228777) +++ kern/vnode_if.src (working copy) @@ -569,6 +569,7 @@ %% deleteextattr vp E E E +%! deleteextattr post vop_deleteextattr_post vop_deleteextattr { IN struct vnode *vp; @@ -580,6 +581,7 @@ %% setextattr vp E E E +%! setextattr post vop_setextattr_post vop_setextattr { IN struct vnode *vp; Index: kern/vfs_subr.c =================================================================== --- kern/vfs_subr.c (revision 228777) +++ kern/vfs_subr.c (working copy) @@ -4033,6 +4033,15 @@ } void +vop_deleteextattr_post(void *ap, int rc) +{ + struct vop_setattr_args *a = ap; + + if (!rc) + VFS_KNOTE_LOCKED(a->a_vp, NOTE_ATTRIB); +} + +void vop_link_post(void *ap, int rc) { struct vop_link_args *a = ap; @@ -4114,6 +4123,15 @@ } void +vop_setextattr_post(void *ap, int rc) +{ + struct vop_setattr_args *a = ap; + + if (!rc) + VFS_KNOTE_LOCKED(a->a_vp, NOTE_ATTRIB); +} + +void vop_symlink_post(void *ap, int rc) { struct vop_symlink_args *a = ap; Index: sys/vnode.h =================================================================== --- sys/vnode.h (revision 228777) +++ sys/vnode.h (working copy) @@ -705,6 +705,7 @@ /* These are called from within the actual VOPS. */ void vop_create_post(void *a, int rc); +void vop_deleteextattr_post(void *a, int rc); void vop_link_post(void *a, int rc); void vop_lock_pre(void *a); void vop_lock_post(void *a, int rc); @@ -717,6 +718,7 @@ void vop_rename_pre(void *a); void vop_rmdir_post(void *a, int rc); void vop_setattr_post(void *a, int rc); +void vop_setextattr_post(void *a, int rc); void vop_strategy_pre(void *a); void vop_symlink_post(void *a, int rc); void vop_unlock_post(void *a, int rc); /*- * Test to see if attempts to set or remove extended attributes * provoke a NOTE_ATTRIB vnode kevent. */ #ifdef __FreeBSD__ #include <sys/types.h> #include <sys/extattr.h> #endif #ifdef __APPLE__ #include <sys/xattr.h> #endif #include <sys/event.h> #include <sys/time.h> #include <err.h> #include <errno.h> #include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #ifdef __FreeBSD__ static ssize_t getea(int fd, const char *name, void *value, size_t size) { return (extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size)); } static int setea(int fd, const char *name, const void *value, size_t size) { ssize_t retval; retval = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size); if (retval < 0) return (-1); return (0); } static int delea(int fd, const char *name) { return (extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, name)); } static int listea(int fd, void *buf, size_t size) { return (extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, buf, size)); } static int compare_ealist(const char *name, void *buf, size_t size) { char *p; int i, len; i = 0; p = buf; if (size == 0) return (1); len = p[0]; p++; if (len != size - 1) return (1); if (strlen(name) != len) return (1); return (strncmp(p, name, len)); } #endif #ifdef __APPLE__ static ssize_t getea(int fd, const char *name, void *value, size_t size) { return (fgetxattr(fd, name, value, size, 0, 0)); } static int setea(int fd, const char *name, const void *value, size_t size) { return (fsetxattr(fd, name, value, size, 0, 0)); } static int delea(int fd, const char *name) { return (fremovexattr(fd, name, 0)); } static int listea(int fd, void *buf, size_t size) { return (flistxattr(fd, buf, size, 0)); } static int compare_ealist(const char *name, void *buf, size_t size) { if (size == 0) return (1); if (strlen(name) != size - 1) return (1); return (strncmp(buf, name, size)); } #endif #define EA_NAME "foo" char template[] = "/tmp/kevent_extattr.XXXXXX"; static int fd, kq; static void check_ea(const char *value, const char *desc) { ssize_t retval; size_t size; char *buf; retval = getea(fd, EA_NAME, NULL, 0); if (retval < 0) { if (errno == ENOATTR) { if (value != NULL) printf("Missing ea: %s\n", desc); return; } err(1, "getea"); } size = (size_t)retval; buf = malloc(size); retval = getea(fd, EA_NAME, buf, size); if (retval < 0) err(1, "getea"); if ((size_t)retval != size) errx(1, "getea: short read"); if (value == NULL) printf("Unexpected ea value %*s: %s\n", (int)size, buf, desc); else if (strncmp(value, buf, size) != 0) printf("Mismatched ea values %*s vs %s: %s\n", (int)size, buf, value, desc); free(buf); } static void check_ealist(const char *name, const char *desc) { ssize_t retval; size_t size; char *buf; retval = listea(fd, NULL, 0); if (retval < 0) err(1, "listea"); size = (size_t)retval; if (size == 0) { if (name != NULL) { printf("Missing ea: %s\n", desc); return; } return; } buf = malloc(size); retval = listea(fd, buf, size); if (retval < 0) err(1, "listea"); if ((size_t)retval != size) errx(1, "listea: short read"); if (name == NULL) printf("Unexpected ea: %s\n", desc); else if (compare_ealist(name, buf, size) != 0) printf("Mismatched ea list: %s\n", desc); free(buf); } static void check_event(bool expected, const char *desc) { struct timespec ts = { 0, 0 }; struct kevent ev; int retval; retval = kevent(kq, NULL, 0, &ev, 1, &ts); if (retval < 0) err(1, "kevent"); if (!expected) { if (retval != 0) printf("Unexpected kevent: %s\n", desc); } else { if (retval == 0) printf("Missing kevent: %s\n", desc); else if (ev.fflags != NOTE_ATTRIB) printf("Wrong flags (%x vs %x): %s\n", ev.fflags, NOTE_ATTRIB, desc); } } int main(int ac, char **av) { struct kevent ev; kq = kqueue(); if (kq < 0) err(1, "kqueue"); fd = mkstemp(template); if (fd < 0) err(1, "mkstemp"); if (unlink(template) < 0) err(1, "unlink"); EV_SET(&ev, fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, NOTE_ATTRIB, 0, 0); if (kevent(kq, &ev, 1, NULL, 0, NULL) < 0) err(1, "kevent(EV_ADD)"); check_event(false, "initial check"); check_ea(NULL, "initial check"); check_ealist(NULL, "initial check"); if (setea(fd, EA_NAME, "bar", sizeof("bar")) < 0) err(1, "setea(%s = \"bar\")", EA_NAME); check_event(true, "first set"); /* * The check_ea() reads the EA and should not have triggered * an event. */ check_ea("bar", "first set"); check_event(false, "getea"); /* * The ea list requests in this check should not trigger an * event either. */ check_ealist(EA_NAME, "first set"); check_event(false, "listea"); if (setea(fd, EA_NAME, "baz", sizeof("baz")) < 0) err(1, "setea(%s = \"baz\")", EA_NAME); check_event(true, "second set"); check_ea("baz", "second set"); check_ealist(EA_NAME, "second set"); if (delea(fd, EA_NAME) < 0) err(1, "delea(%s)", EA_NAME); check_event(true, "remove"); check_ea(NULL, "remove"); check_ealist(NULL, "remove"); close(fd); close(kq); return (0); } -- John Baldwin
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201112221105.31746.jhb>