The Pedigree Project 0.1
xattr-contract-test/events.c
1#define _GNU_SOURCE
2#include <fcntl.h>
3#include <string.h>
4#include <time.h>
5#include <unistd.h>
6
7#include "contract.h"
8#include <sys/inotify.h>
9#include <sys/mman.h>
10#include <sys/stat.h>
11#include <sys/xattr.h>
12
13static int same_time(struct timespec first, struct timespec second) {
14 return first.tv_sec == second.tv_sec && first.tv_nsec == second.tv_nsec;
15}
16
17static int next_second(time_t previous) {
18 int64_t start = xa_now();
19 if (start < 0)
20 return -1;
21 do {
22 struct timespec now;
23 if (clock_gettime(CLOCK_REALTIME, &now))
24 return -1;
25 if (now.tv_sec > previous)
26 return 0;
27 const struct timespec pause = {0, 5000000};
28 nanosleep(&pause, NULL);
29 } while (xa_now() - start < 3000000000);
30 errno = ETIMEDOUT;
31 return -1;
32}
33
34static int event(int notify, int watch, int directory, int expected) {
35 if (notify < 0)
36 return 0;
37 struct inotify_event received;
38 errno = 0;
39 ssize_t length = read(notify, &received, sizeof(received));
40 if (!expected)
41 return length == -1 && errno == EAGAIN ? 0 : -1;
42 if (length != sizeof(received) || received.wd != watch || received.len || received.cookie ||
43 received.mask != (uint32_t)(IN_ATTRIB | (directory ? IN_ISDIR : 0)))
44 return -1;
45 errno = 0;
46 return read(notify, &received, sizeof(received)) == -1 && errno == EAGAIN ? 0 : -1;
47}
48
49static int metadata_case(int backend, int directory) {
50 int failed = 0, notify = -1, watch = -1;
51 struct xa_file file = {.fd = -1};
52 void* inaccessible = MAP_FAILED;
53 struct stat before, after;
54 static const unsigned char first[] = {0, 1, 0xff, 7};
55 static const unsigned char second[] = {9, 0, 2, 0xfe};
56 CHECK(!xa_create(&file, backend, directory));
57 inaccessible = mmap(NULL, xa_page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
58 CHECK(inaccessible != MAP_FAILED);
59 if (file.path[0]) {
60 notify = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
61 CHECK(notify >= 0);
62 watch = inotify_add_watch(notify, file.path, IN_ATTRIB | IN_MODIFY | IN_ACCESS);
63 CHECK(watch >= 0);
64 }
65 CHECK(!fsetxattr(file.fd, "user.event", first, sizeof(first), XATTR_CREATE));
66 CHECK(!event(notify, watch, directory, 1));
67 CHECK(!fstat(file.fd, &before));
68 // These filesystems expose second-resolution inode timestamps.
69 CHECK(!next_second(before.st_ctim.tv_sec));
70 CHECK(!fsetxattr(file.fd, "user.event", second, sizeof(second), XATTR_REPLACE));
71 CHECK(!event(notify, watch, directory, 1));
72 CHECK(!fstat(file.fd, &after));
73 CHECK(after.st_ctim.tv_sec > before.st_ctim.tv_sec);
74 CHECK(same_time(before.st_mtim, after.st_mtim) && same_time(before.st_atim, after.st_atim));
75 before = after;
76 errno = 0;
77 CHECK(fsetxattr(file.fd, "user.event", first, sizeof(first), XATTR_CREATE) == -1 &&
78 errno == EEXIST);
79 errno = 0;
80 CHECK(fsetxattr(file.fd, "user.event", inaccessible, 1, 0) == -1 && errno == EFAULT);
81 errno = 0;
82 CHECK(fremovexattr(file.fd, "user.absent") == -1 && errno == ENODATA);
83 CHECK(!xa_value(&file, XA_FD, "user.event", second, sizeof(second)));
84 const char* names[] = {"user.event"};
85 CHECK(!xa_names(&file, XA_FD, names, 1));
86 CHECK(!fstat(file.fd, &after));
87 CHECK(same_time(before.st_ctim, after.st_ctim) && same_time(before.st_mtim, after.st_mtim) &&
88 same_time(before.st_atim, after.st_atim));
89 CHECK(!event(notify, watch, directory, 0));
90 CHECK(!next_second(before.st_ctim.tv_sec));
91 CHECK(!fremovexattr(file.fd, "user.event"));
92 CHECK(!event(notify, watch, directory, 1));
93 CHECK(!fstat(file.fd, &after));
94 CHECK(after.st_ctim.tv_sec > before.st_ctim.tv_sec);
95 CHECK(same_time(before.st_mtim, after.st_mtim) && same_time(before.st_atim, after.st_atim));
96out:
97 if (notify >= 0)
98 close(notify);
99 if (inaccessible != MAP_FAILED)
100 munmap(inaccessible, xa_page);
101 xa_close(&file);
102 return failed;
103}
104
105int xa_events(void) {
106 for (int backend = XA_MEMFD; backend <= XA_EXT2; ++backend) {
107 if (metadata_case(backend, 0) || (backend != XA_MEMFD && metadata_case(backend, 1)))
108 return 1;
109 }
110 return 0;
111}