Post NOTE_ATTRIB EVFILT_VNODE events for extattr changes

John Baldwin jhb at freebsd.org
Thu Dec 22 16:05:33 UTC 2011


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


More information about the freebsd-arch mailing list