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