The Pedigree Project 0.1
fs.c
1#define _GNU_SOURCE
2
3/*
4 * Copyright (c) 2008-2014, Pedigree Developers
5 *
6 * Please see the CONTRIB file in the root of the source tree for a full
7 * list of contributors.
8 *
9 * Permission to use, copy, modify, and distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
20 */
21
22#include <errno.h>
23#include <fcntl.h>
24#include <stddef.h>
25#include <stdint.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
29#include <unistd.h>
30
31#include <sys/file.h>
32#include <sys/stat.h>
33#include <sys/syscall.h>
34#include <sys/uio.h>
35
36#ifndef AT_EMPTY_PATH
37#define AT_EMPTY_PATH 0x1000
38#endif
39
40extern void fail(void) __attribute__((noreturn));
41
42static void status(const char* s) {
43 puts(s);
44 fflush(stdout);
45}
46
47#define OK status("OK\n")
48
49static void test_positional_io(void) {
50 static const char initial[] = "abcdefghij";
51 static const char replacement[] = "XYZ";
52 static const char expected[] = "aXYZefghij";
53 char buffer[sizeof(initial)] = {0};
54
55 status("Testing positional file I/O... ");
56 int fd = open("/testing/positional-io", O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0666);
57 if (fd < 0 || write(fd, initial, sizeof(initial) - 1) != (ssize_t)(sizeof(initial) - 1) ||
58 lseek(fd, 3, SEEK_SET) != 3)
59 fail();
60
61 if (syscall(SYS_pread64, fd, buffer, 3, 6) != 3 || memcmp(buffer, "ghi", 3) ||
62 lseek(fd, 0, SEEK_CUR) != 3)
63 fail();
64 if (syscall(SYS_pwrite64, fd, replacement, sizeof(replacement) - 1, 1) !=
65 (ssize_t)(sizeof(replacement) - 1) ||
66 lseek(fd, 0, SEEK_CUR) != 3)
67 fail();
68
69 memset(buffer, 0, sizeof(buffer));
70 if (syscall(SYS_pread64, fd, buffer, sizeof(expected) - 1, 0) !=
71 (ssize_t)(sizeof(expected) - 1) ||
72 memcmp(buffer, expected, sizeof(expected) - 1))
73 fail();
74
75 errno = 0;
76 if (syscall(SYS_pread64, fd, buffer, 1, (off_t)-1) != -1 || errno != EINVAL)
77 fail();
78 errno = 0;
79 if (syscall(SYS_pwrite64, fd, buffer, 2, INT64_MAX) != -1 || errno != EINVAL)
80 fail();
81 errno = 0;
82 if (syscall(SYS_pread64, fd, buffer, (size_t)INT64_MAX + 1, 0) != -1 || errno != EINVAL)
83 fail();
84 errno = 0;
85 if (syscall(SYS_pwrite64, fd, (const void*)UINTPTR_MAX, 2, 0) != -1 || errno != EFAULT)
86 fail();
87
88 int pipefd[2];
89 if (pipe(pipefd))
90 fail();
91 errno = 0;
92 if (syscall(SYS_pread64, pipefd[0], buffer, 1, 0) != -1 || errno != ESPIPE)
93 fail();
94 errno = 0;
95 if (syscall(SYS_pwrite64, pipefd[1], buffer, 1, 0) != -1 || errno != ESPIPE)
96 fail();
97 if (close(pipefd[0]) || close(pipefd[1]))
98 fail();
99
100 int readOnly = open("/testing/positional-io", O_RDONLY);
101 int writeOnly = open("/testing/positional-io", O_WRONLY);
102 if (readOnly < 0 || writeOnly < 0)
103 fail();
104 errno = 0;
105 if (syscall(SYS_pwrite64, readOnly, buffer, 1, 0) != -1 || errno != EBADF)
106 fail();
107 errno = 0;
108 if (syscall(SYS_pread64, writeOnly, buffer, 1, 0) != -1 || errno != EBADF)
109 fail();
110
111 if (close(readOnly) || close(writeOnly) || close(fd) || unlink("/testing/positional-io"))
112 fail();
113 OK;
114}
115
116static void test_positional_vector_io(void) {
117 static const char initial[] = "abcdefghij";
118 static const char expected[] = "aXRS123hijQ";
119 char first[3] = {0};
120 char second[2] = {0};
121 char buffer[sizeof(expected)] = {0};
122 struct iovec read_vectors[2] = {
123 {first, 2},
124 {second, 1},
125 };
126 struct iovec xyz_vectors[2] = {
127 {(void*)"XY", 2},
128 {(void*)"Z", 1},
129 };
130 struct iovec numeric_vectors[2] = {
131 {(void*)"12", 2},
132 {(void*)"3", 1},
133 };
134 struct iovec one_vector = {(void*)"Q", 1};
135
136 status("Testing positional vector file I/O... ");
137 int fd = open("/testing/positional-vector-io", O_RDWR | O_CREAT | O_TRUNC | O_APPEND, 0666);
138 if (fd < 0 || write(fd, initial, sizeof(initial) - 1) != (ssize_t)(sizeof(initial) - 1) ||
139 lseek(fd, 3, SEEK_SET) != 3)
140 fail();
141
142 if (preadv(fd, read_vectors, 2, 6) != 3 || memcmp(first, "gh", 2) || second[0] != 'i' ||
143 lseek(fd, 0, SEEK_CUR) != 3)
144 fail();
145
146 // musl routes pwritev through pwritev2(RWF_NOAPPEND), which must override
147 // the description's O_APPEND without changing its current offset.
148 if (pwritev(fd, xyz_vectors, 2, 1) != 3 || lseek(fd, 0, SEEK_CUR) != 3)
149 fail();
150 if (syscall(SYS_pwritev, fd, numeric_vectors, 2, 4L, 0L) != 3 || lseek(fd, 0, SEEK_CUR) != 3)
151 fail();
152
153 // A raw pwritev2 flags=0 request retains Linux O_APPEND behavior; the
154 // RWF_NOAPPEND form below then writes at the explicit position.
155 if (syscall(SYS_pwritev2, fd, &one_vector, 1, 2L, 0L, 0) != 1 || lseek(fd, 0, SEEK_CUR) != 3)
156 fail();
157 one_vector.iov_base = (void*)"R";
158 if (syscall(SYS_pwritev2, fd, &one_vector, 1, 2L, 0L, RWF_NOAPPEND) != 1 ||
159 lseek(fd, 0, SEEK_CUR) != 3)
160 fail();
161
162 one_vector.iov_base = (void*)"S";
163 if (syscall(SYS_pwritev2, fd, &one_vector, 1, -1L, -1L, RWF_NOAPPEND) != 1 ||
164 lseek(fd, 0, SEEK_CUR) != 4)
165 fail();
166
167 memset(first, 0, sizeof(first));
168 struct iovec current_read = {first, 2};
169 if (syscall(SYS_preadv2, fd, &current_read, 1, -1L, -1L, 0) != 2 || memcmp(first, "12", 2) ||
170 lseek(fd, 0, SEEK_CUR) != 6)
171 fail();
172
173 memset(buffer, 0, sizeof(buffer));
174 if (preadv(fd, &(struct iovec){buffer, sizeof(expected) - 1}, 1, 0) !=
175 (ssize_t)(sizeof(expected) - 1) ||
176 memcmp(buffer, expected, sizeof(expected) - 1) || lseek(fd, 0, SEEK_CUR) != 6)
177 fail();
178
179 errno = 0;
180 if (syscall(SYS_preadv, fd, &current_read, 1, -1L, -1L) != -1 || errno != EINVAL)
181 fail();
182 errno = 0;
183 if (syscall(SYS_preadv2, fd, &current_read, 1, 0L, 0L, RWF_NOWAIT) != -1 || errno != EOPNOTSUPP)
184 fail();
185 errno = 0;
186 if (syscall(SYS_pwritev2, fd, &one_vector, 1, 0L, 0L, RWF_NOWAIT) != -1 || errno != EOPNOTSUPP)
187 fail();
188
189 int pipefd[2];
190 if (pipe(pipefd))
191 fail();
192 errno = 0;
193 if (preadv(pipefd[0], &current_read, 1, 0) != -1 || errno != ESPIPE)
194 fail();
195 errno = 0;
196 if (pwritev(pipefd[1], &one_vector, 1, 0) != -1 || errno != ESPIPE)
197 fail();
198
199 if (close(pipefd[0]) || close(pipefd[1]) || close(fd) || unlink("/testing/positional-vector-io"))
200 fail();
201 OK;
202}
203
204static void expect_lock_failure(int result, int expectedError) {
205 if (result != -1 || errno != expectedError)
206 fail();
207}
208
209static void test_advisory_locks(void) {
210 struct flock lock = {
211 .l_type = F_WRLCK,
212 .l_whence = SEEK_SET,
213 .l_start = 0,
214 .l_len = 0,
215 .l_pid = 123,
216 };
217
218 status("Testing advisory lock failure behavior... ");
219 int fd = open("/testing/advisory-locks", O_RDWR | O_CREAT | O_TRUNC, 0666);
220 if (fd < 0)
221 fail();
222
223 errno = 0;
224 expect_lock_failure(fcntl(fd, F_GETLK, &lock), ENOSYS);
225 if (lock.l_type != F_WRLCK || lock.l_whence != SEEK_SET || lock.l_start != 0 || lock.l_len != 0 ||
226 lock.l_pid != 123)
227 fail();
228 errno = 0;
229 expect_lock_failure(fcntl(fd, F_SETLK, &lock), ENOSYS);
230 errno = 0;
231 expect_lock_failure(fcntl(fd, F_SETLKW, &lock), ENOSYS);
232
233 errno = 0;
234 expect_lock_failure(fcntl(-1, F_GETLK, &lock), EBADF);
235 errno = 0;
236 expect_lock_failure(fcntl(-1, F_SETLK, &lock), EBADF);
237 errno = 0;
238 expect_lock_failure(fcntl(-1, F_SETLKW, &lock), EBADF);
239
240 errno = 0;
241 expect_lock_failure(flock(fd, LOCK_SH), ENOSYS);
242 errno = 0;
243 expect_lock_failure(flock(fd, LOCK_EX | LOCK_NB), ENOSYS);
244 errno = 0;
245 expect_lock_failure(flock(fd, LOCK_UN), ENOSYS);
246 errno = 0;
247 expect_lock_failure(flock(fd, LOCK_SH | LOCK_EX), EINVAL);
248 errno = 0;
249 expect_lock_failure(flock(-1, LOCK_SH | LOCK_EX), EINVAL);
250 errno = 0;
251 expect_lock_failure(flock(-1, LOCK_EX), EBADF);
252
253 errno = 0;
254 expect_lock_failure(lockf(fd, F_TEST, 0), ENOSYS);
255 errno = 0;
256 expect_lock_failure(lockf(fd, F_ULOCK, 0), ENOSYS);
257 errno = 0;
258 expect_lock_failure(lockf(fd, F_TLOCK, 0), ENOSYS);
259 errno = 0;
260 expect_lock_failure(lockf(fd, F_LOCK, 0), ENOSYS);
261 errno = 0;
262 expect_lock_failure(lockf(fd, -1, 0), EINVAL);
263
264 errno = 0;
265 expect_lock_failure(lockf(-1, F_TEST, 0), EBADF);
266 errno = 0;
267 expect_lock_failure(lockf(-1, F_ULOCK, 0), EBADF);
268 errno = 0;
269 expect_lock_failure(lockf(-1, F_TLOCK, 0), EBADF);
270 errno = 0;
271 expect_lock_failure(lockf(-1, F_LOCK, 0), EBADF);
272
273 if (close(fd) || unlink("/testing/advisory-locks"))
274 fail();
275 OK;
276}
277
278static void expect_access_failure(int result, int expected_error) {
279 if (result != -1 || errno != expected_error)
280 fail();
281}
282
283static void test_faccessat2(void) {
284 static const char path[] = "/testing/access-semantics";
285 static const char link_path[] = "/testing/access-link";
286
287 status("Testing faccessat2 semantics... ");
288 int fd = open(path, O_RDONLY | O_CREAT | O_TRUNC, 0400);
289 int dirfd = open("/testing", O_RDONLY | O_DIRECTORY);
290 if (fd < 0 || dirfd < 0)
291 fail();
292 if (chmod(path, 0400))
293 fail();
294
295 if (syscall(SYS_faccessat2, -1, path, F_OK, 0) ||
296 syscall(SYS_faccessat2, dirfd, "access-semantics", F_OK, 0))
297 fail();
298 errno = 0;
299 expect_access_failure(syscall(SYS_faccessat2, -1, "access-semantics", F_OK, 0), EBADF);
300 errno = 0;
301 expect_access_failure(syscall(SYS_faccessat2, fd, "access-semantics", F_OK, 0), ENOTDIR);
302 errno = 0;
303 expect_access_failure(syscall(SYS_faccessat2, -1, "/testing/access-missing", F_OK, 0), ENOENT);
304
305 errno = 0;
306 expect_access_failure(syscall(SYS_faccessat2, AT_FDCWD, path, 8, 0), EINVAL);
307 errno = 0;
308 expect_access_failure(syscall(SYS_faccessat2, AT_FDCWD, path, F_OK, 0x40000000), EINVAL);
309 errno = 0;
310 expect_access_failure(syscall(SYS_faccessat2, AT_FDCWD, (const char*)UINTPTR_MAX, F_OK, 0),
311 EFAULT);
312
313 errno = 0;
314 expect_access_failure(syscall(SYS_faccessat2, fd, "", F_OK, 0), ENOENT);
315 if (syscall(SYS_faccessat2, fd, "", F_OK, AT_EMPTY_PATH))
316 fail();
317 if (syscall(SYS_faccessat2, AT_FDCWD, "", F_OK, AT_EMPTY_PATH))
318 fail();
319 errno = 0;
320 expect_access_failure(syscall(SYS_faccessat2, -1, "", F_OK, AT_EMPTY_PATH), EBADF);
321
322 unlink(link_path);
323 if (symlink(path, link_path))
324 fail();
325
326 const uid_t original_real = getuid();
327 const uid_t original_effective = geteuid();
328 const uid_t alternate_real = original_effective == 123 ? 124 : 123;
329 if (syscall(SYS_setresuid, alternate_real, original_effective, (uid_t)-1))
330 fail();
331
332 errno = 0;
333 expect_access_failure(access(path, R_OK), EACCES);
334 errno = 0;
335 expect_access_failure(syscall(SYS_faccessat2, AT_FDCWD, path, R_OK, 0), EACCES);
336 if (syscall(SYS_faccessat2, AT_FDCWD, path, R_OK, AT_EACCESS))
337 fail();
338 errno = 0;
339 expect_access_failure(syscall(SYS_faccessat2, AT_FDCWD, path, X_OK, AT_EACCESS), EACCES);
340
341 errno = 0;
342 expect_access_failure(syscall(SYS_faccessat2, AT_FDCWD, link_path, R_OK, 0), EACCES);
343 if (syscall(SYS_faccessat2, AT_FDCWD, link_path, R_OK, AT_SYMLINK_NOFOLLOW) ||
344 syscall(SYS_faccessat2, AT_FDCWD, link_path, R_OK, AT_EACCESS))
345 fail();
346
347 if (syscall(SYS_setresuid, original_real, original_effective, (uid_t)-1))
348 fail();
349 if (close(dirfd) || close(fd) || unlink(link_path) || unlink(path))
350 fail();
351 OK;
352}
353
354void test_fs() {
355 int fd = -1;
356 int rc = 0;
357
358 srand(0);
359
360 printf("Testing filesystem...\n");
361
362 int urandom_fd = open("/dev/urandom", O_RDONLY);
363 if (urandom_fd < 0)
364 fail();
365
366 // fsck test directory - deleting a directory like we do below will result
367 // in us possibly missing "bad directory count" errors.
368 status("Creating directory for fsck test... ");
369 rc = mkdir("/fscktest", 0777);
370 if (rc)
371 fail();
372 OK;
373
374 // directory to be deleted later - shouldn't leave any cruft lying around
375 status("Creating directory for main test... ");
376 rc = mkdir("/testing", 0777);
377 if (rc)
378 fail();
379 OK;
380
381 test_positional_io();
382 test_positional_vector_io();
383 test_advisory_locks();
384 test_faccessat2();
385
386 // Create some files of varying sizes and destroy them.
387 status("Testing file creation... ");
388 for (size_t i = 0; i < 10; ++i) {
389 size_t sz = rand() % 8192;
390 if (!sz)
391 ++sz;
392 void* p = malloc(sz);
393 if (!p)
394 fail();
395 size_t bytesRead = 0;
396 while (bytesRead < sz) {
397 ssize_t n = read(urandom_fd, (char*)p + bytesRead, sz - bytesRead);
398 if (n < 0 && errno == EINTR)
399 continue;
400 if (n <= 0) {
401 free(p);
402 fail();
403 }
404 bytesRead += (size_t)n;
405 }
406
407 // Files that are expected to be deleted (might miss issues here in
408 // fsck after their deletion).
409 char fn[256];
410 sprintf(fn, "/testing/f%zu", i);
411 fd = open(fn, O_RDWR | O_CREAT, 0666);
412 if (fd < 0) {
413 free(p);
414 fail();
415 }
416 if (write(fd, p, sz) != (ssize_t)sz) {
417 close(fd);
418 free(p);
419 fail();
420 }
421 close(fd);
422
423 // Same deal for the fscktest directory. fsck will pick these up.
424 sprintf(fn, "/fscktest/f%zu", i);
425 fd = open(fn, O_RDWR | O_CREAT, 0666);
426 if (fd < 0) {
427 free(p);
428 fail();
429 }
430 if (write(fd, p, sz) != (ssize_t)sz) {
431 close(fd);
432 free(p);
433 fail();
434 }
435 close(fd);
436 free(p);
437 }
438 OK;
439
440 status("Testing file deletion... ");
441 for (size_t i = 0; i < 5; ++i) {
442 char fn[256];
443 sprintf(fn, "/testing/f%zu", i);
444 unlink(fn);
445 }
446 OK;
447
448 status("Testing a failed rmdir... ");
449 rc = rmdir("/testing");
450 if (rc == 0)
451 fail();
452 OK;
453
454 status("Testing further file deletion... ");
455 for (size_t i = 5; i < 10; ++i) {
456 char fn[256];
457 sprintf(fn, "/testing/f%zu", i);
458 unlink(fn);
459 }
460 OK;
461
462 status("Testing rmdir... ");
463 rc = rmdir("/testing");
464 if (rc)
465 fail();
466 close(urandom_fd);
467 OK;
468}