The Pedigree Project 0.1
rename-flags-contract-test/main.c
1#define _GNU_SOURCE
2#include <errno.h>
3#include <fcntl.h>
4#include <pthread.h>
5#include <stdio.h>
6#include <string.h>
7#include <unistd.h>
8
9#include <sys/stat.h>
10#include <sys/syscall.h>
11
12#define CHECK(condition) \
13 do { \
14 if (!(condition)) { \
15 fprintf(stderr, "RENAME-FLAGS-CONTRACT: line=%d errno=%d\n", __LINE__, errno); \
16 goto fail; \
17 } \
18 } while (0)
19
20struct race {
21 int directory;
22 const char* source;
23 pthread_barrier_t* barrier;
24 int result, error;
25};
26
27static void* rename_racer(void* opaque) {
28 struct race* race = opaque;
29 pthread_barrier_wait(race->barrier);
30 race->result =
31 renameat2(race->directory, race->source, race->directory, "winner", RENAME_NOREPLACE);
32 race->error = errno;
33 return NULL;
34}
35
36static int create_byte(int directory, const char* name, unsigned char value) {
37 int fd = openat(directory, name, O_CREAT | O_EXCL | O_RDWR, 0600);
38 if (fd < 0)
39 return -1;
40 int success = write(fd, &value, 1) == 1;
41 close(fd);
42 return success ? 0 : -1;
43}
44
45int main(int argc, char** argv) {
46 const char* parent = argc > 1 ? argv[1] : "/tmp";
47 char path[512], absolute[640];
48 snprintf(path, sizeof(path), "%s/rename-flags-%ld", parent, (long)getpid());
49 int directory = -1, retained = -1, result = 1;
50 struct stat source, target, status;
51 unsigned char byte = 0;
52 CHECK(mkdir(path, 0700) == 0);
53 directory = open(path, O_RDONLY | O_DIRECTORY);
54 CHECK(directory >= 0);
55 CHECK(create_byte(directory, "source", 31) == 0 && create_byte(directory, "target", 47) == 0);
56 CHECK(fstatat(directory, "source", &source, 0) == 0);
57 CHECK(fstatat(directory, "target", &target, 0) == 0);
58 retained = openat(directory, "source", O_RDONLY);
59 CHECK(retained >= 0);
60 errno = 0;
61 CHECK(renameat2(directory, "source", directory, "target", RENAME_NOREPLACE) == -1 &&
62 errno == EEXIST);
63 CHECK(fstatat(directory, "source", &status, 0) == 0 && status.st_ino == source.st_ino);
64 CHECK(fstatat(directory, "target", &status, 0) == 0 && status.st_ino == target.st_ino);
65 errno = 0;
66 CHECK(renameat2(directory, "source", directory, "source", RENAME_NOREPLACE) == -1 &&
67 errno == EEXIST);
68 if (argc > 2 && !strcmp(argv[2], "--hard-links")) {
69 CHECK(linkat(directory, "source", directory, "alias", 0) == 0);
70 errno = 0;
71 CHECK(renameat2(directory, "source", directory, "alias", RENAME_NOREPLACE) == -1 &&
72 errno == EEXIST);
73 CHECK(unlinkat(directory, "alias", 0) == 0);
74 }
75
76 CHECK(renameat2(directory, "source", directory, "moved", RENAME_NOREPLACE) == 0);
77 CHECK(fstatat(directory, "moved", &status, 0) == 0 && status.st_ino == source.st_ino);
78 CHECK(pread(retained, &byte, 1, 0) == 1 && byte == 31);
79 errno = 0;
80 CHECK(fstatat(directory, "source", &status, 0) == -1 && errno == ENOENT);
81 CHECK(syscall(SYS_renameat2, directory, "moved", directory, "target", 0) == 0);
82 CHECK(fstatat(directory, "target", &status, 0) == 0 && status.st_ino == source.st_ino);
83 snprintf(absolute, sizeof(absolute), "%s/absolute", path);
84 CHECK(renameat2(directory, "target", -1, absolute, RENAME_NOREPLACE) == 0);
85 CHECK(mkdirat(directory, "nested", 0700) == 0);
86 CHECK(renameat2(directory, "absolute", directory, "nested/file", RENAME_NOREPLACE) == 0);
87
88 errno = 0;
89 CHECK(renameat2(directory, "nested/file", directory, "other", RENAME_EXCHANGE) == -1 &&
90 errno == EOPNOTSUPP);
91 errno = 0;
92 CHECK(renameat2(directory, "nested/file", directory, "other", RENAME_WHITEOUT) == -1 &&
93 errno == EOPNOTSUPP);
94 errno = 0;
95 CHECK(renameat2(directory, "nested/file", directory, "other",
96 RENAME_EXCHANGE | RENAME_NOREPLACE) == -1 &&
97 errno == EINVAL);
98 errno = 0;
99 CHECK(renameat2(directory, "nested/file", directory, "other", 8) == -1 && errno == EINVAL);
100 errno = 0;
101 CHECK(renameat2(-1, "nested/file", directory, "other", RENAME_NOREPLACE) == -1 && errno == EBADF);
102 errno = 0;
103 CHECK(renameat2(directory, "", directory, "other", RENAME_NOREPLACE) == -1 && errno == ENOENT);
104
105 for (unsigned iteration = 0; iteration < 16; ++iteration) {
106 CHECK(create_byte(directory, "first", 61) == 0 && create_byte(directory, "second", 83) == 0);
107 pthread_barrier_t barrier;
108 pthread_t thread;
109 CHECK(pthread_barrier_init(&barrier, NULL, 2) == 0);
110 struct race races[2] = {{directory, "first", &barrier, -2, 0},
111 {directory, "second", &barrier, -2, 0}};
112 const int created = pthread_create(&thread, NULL, rename_racer, &races[0]);
113 if (created) {
114 pthread_barrier_destroy(&barrier);
115 errno = created;
116 CHECK(0);
117 }
118 rename_racer(&races[1]);
119 CHECK(pthread_join(thread, NULL) == 0);
120 CHECK(pthread_barrier_destroy(&barrier) == 0);
121 CHECK((races[0].result == 0 && races[1].result == -1 && races[1].error == EEXIST) ||
122 (races[1].result == 0 && races[0].result == -1 && races[0].error == EEXIST));
123 CHECK(unlinkat(directory, "winner", 0) == 0);
124 CHECK(unlinkat(directory, races[0].result ? "first" : "second", 0) == 0);
125 }
126 result = 0;
127
128fail:
129 if (retained >= 0)
130 close(retained);
131 if (directory >= 0) {
132 const char* names[] = {"source", "target", "moved", "absolute", "nested/file",
133 "other", "first", "second", "winner", "alias"};
134 for (unsigned i = 0; i < sizeof(names) / sizeof(names[0]); ++i)
135 unlinkat(directory, names[i], 0);
136 unlinkat(directory, "nested", AT_REMOVEDIR);
137 close(directory);
138 }
139 rmdir(path);
140 if (!result)
141 puts("RENAME-FLAGS-CONTRACT: PASS");
142 return result;
143}