Post NOTE_ATTRIB EVFILT_VNODE events for extattr changes

Robert N. M. Watson rwatson at freebsd.org
Thu Dec 22 23:19:32 UTC 2011


On 22 Dec 2011, at 16:05, John Baldwin wrote:

> 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.

This seems reasonable to me, although I've not tested the patch you attached myself.

I'm not familiar with the pre/post-vop hook mechanism, but I assume that this will fire when internal extended attribute I/O, for example triggered by ACL changes, occurs?

Robert

> 
> 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



More information about the freebsd-arch mailing list