]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/test/test-fs-util.c
0610416d3704e72f58848887ca8895b440f20c49
[thirdparty/systemd.git] / src / test / test-fs-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2
3 #include <unistd.h>
4
5 #include "alloc-util.h"
6 #include "fd-util.h"
7 #include "fs-util.h"
8 #include "id128-util.h"
9 #include "macro.h"
10 #include "mkdir.h"
11 #include "path-util.h"
12 #include "rm-rf.h"
13 #include "stdio-util.h"
14 #include "string-util.h"
15 #include "strv.h"
16 #include "tests.h"
17 #include "tmpfile-util.h"
18 #include "umask-util.h"
19 #include "user-util.h"
20 #include "util.h"
21 #include "virt.h"
22
23 static const char *arg_test_dir = NULL;
24
25 static void test_chase_symlinks(void) {
26 _cleanup_free_ char *result = NULL;
27 char *temp;
28 const char *top, *p, *pslash, *q, *qslash;
29 struct stat st;
30 int r, pfd;
31
32 log_info("/* %s */", __func__);
33
34 temp = strjoina(arg_test_dir ?: "/tmp", "/test-chase.XXXXXX");
35 assert_se(mkdtemp(temp));
36
37 top = strjoina(temp, "/top");
38 assert_se(mkdir(top, 0700) >= 0);
39
40 p = strjoina(top, "/dot");
41 if (symlink(".", p) < 0) {
42 assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
43 log_tests_skipped_errno(errno, "symlink() not possible");
44 goto cleanup;
45 };
46
47 p = strjoina(top, "/dotdot");
48 assert_se(symlink("..", p) >= 0);
49
50 p = strjoina(top, "/dotdota");
51 assert_se(symlink("../a", p) >= 0);
52
53 p = strjoina(temp, "/a");
54 assert_se(symlink("b", p) >= 0);
55
56 p = strjoina(temp, "/b");
57 assert_se(symlink("/usr", p) >= 0);
58
59 p = strjoina(temp, "/start");
60 assert_se(symlink("top/dot/dotdota", p) >= 0);
61
62 /* Paths that use symlinks underneath the "root" */
63
64 r = chase_symlinks(p, NULL, 0, &result);
65 assert_se(r > 0);
66 assert_se(path_equal(result, "/usr"));
67 result = mfree(result);
68
69 pslash = strjoina(p, "/");
70 r = chase_symlinks(pslash, NULL, 0, &result);
71 assert_se(r > 0);
72 assert_se(path_equal(result, "/usr/"));
73 result = mfree(result);
74
75 r = chase_symlinks(p, temp, 0, &result);
76 assert_se(r == -ENOENT);
77
78 r = chase_symlinks(pslash, temp, 0, &result);
79 assert_se(r == -ENOENT);
80
81 q = strjoina(temp, "/usr");
82
83 r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result);
84 assert_se(r == 0);
85 assert_se(path_equal(result, q));
86 result = mfree(result);
87
88 qslash = strjoina(q, "/");
89
90 r = chase_symlinks(pslash, temp, CHASE_NONEXISTENT, &result);
91 assert_se(r == 0);
92 assert_se(path_equal(result, qslash));
93 result = mfree(result);
94
95 assert_se(mkdir(q, 0700) >= 0);
96
97 r = chase_symlinks(p, temp, 0, &result);
98 assert_se(r > 0);
99 assert_se(path_equal(result, q));
100 result = mfree(result);
101
102 r = chase_symlinks(pslash, temp, 0, &result);
103 assert_se(r > 0);
104 assert_se(path_equal(result, qslash));
105 result = mfree(result);
106
107 p = strjoina(temp, "/slash");
108 assert_se(symlink("/", p) >= 0);
109
110 r = chase_symlinks(p, NULL, 0, &result);
111 assert_se(r > 0);
112 assert_se(path_equal(result, "/"));
113 result = mfree(result);
114
115 r = chase_symlinks(p, temp, 0, &result);
116 assert_se(r > 0);
117 assert_se(path_equal(result, temp));
118 result = mfree(result);
119
120 /* Paths that would "escape" outside of the "root" */
121
122 p = strjoina(temp, "/6dots");
123 assert_se(symlink("../../..", p) >= 0);
124
125 r = chase_symlinks(p, temp, 0, &result);
126 assert_se(r > 0 && path_equal(result, temp));
127 result = mfree(result);
128
129 p = strjoina(temp, "/6dotsusr");
130 assert_se(symlink("../../../usr", p) >= 0);
131
132 r = chase_symlinks(p, temp, 0, &result);
133 assert_se(r > 0 && path_equal(result, q));
134 result = mfree(result);
135
136 p = strjoina(temp, "/top/8dotsusr");
137 assert_se(symlink("../../../../usr", p) >= 0);
138
139 r = chase_symlinks(p, temp, 0, &result);
140 assert_se(r > 0 && path_equal(result, q));
141 result = mfree(result);
142
143 /* Paths that contain repeated slashes */
144
145 p = strjoina(temp, "/slashslash");
146 assert_se(symlink("///usr///", p) >= 0);
147
148 r = chase_symlinks(p, NULL, 0, &result);
149 assert_se(r > 0);
150 assert_se(path_equal(result, "/usr"));
151 result = mfree(result);
152
153 r = chase_symlinks(p, temp, 0, &result);
154 assert_se(r > 0);
155 assert_se(path_equal(result, q));
156 result = mfree(result);
157
158 /* Paths underneath the "root" with different UIDs while using CHASE_SAFE */
159
160 if (geteuid() == 0) {
161 p = strjoina(temp, "/user");
162 assert_se(mkdir(p, 0755) >= 0);
163 assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
164
165 q = strjoina(temp, "/user/root");
166 assert_se(mkdir(q, 0755) >= 0);
167
168 p = strjoina(q, "/link");
169 assert_se(symlink("/", p) >= 0);
170
171 /* Fail when user-owned directories contain root-owned subdirectories. */
172 r = chase_symlinks(p, temp, CHASE_SAFE, &result);
173 assert_se(r == -ENOLINK);
174 result = mfree(result);
175
176 /* Allow this when the user-owned directories are all in the "root". */
177 r = chase_symlinks(p, q, CHASE_SAFE, &result);
178 assert_se(r > 0);
179 result = mfree(result);
180 }
181
182 /* Paths using . */
183
184 r = chase_symlinks("/etc/./.././", NULL, 0, &result);
185 assert_se(r > 0);
186 assert_se(path_equal(result, "/"));
187 result = mfree(result);
188
189 r = chase_symlinks("/etc/./.././", "/etc", 0, &result);
190 assert_se(r > 0 && path_equal(result, "/etc"));
191 result = mfree(result);
192
193 r = chase_symlinks("/../.././//../../etc", NULL, 0, &result);
194 assert_se(r > 0);
195 assert_se(streq(result, "/etc"));
196 result = mfree(result);
197
198 r = chase_symlinks("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result);
199 assert_se(r == 0);
200 assert_se(streq(result, "/test-chase.fsldajfl"));
201 result = mfree(result);
202
203 r = chase_symlinks("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result);
204 assert_se(r > 0);
205 assert_se(streq(result, "/etc"));
206 result = mfree(result);
207
208 r = chase_symlinks("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result);
209 assert_se(r == 0);
210 assert_se(streq(result, "/test-chase.fsldajfl"));
211 result = mfree(result);
212
213 r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result);
214 assert_se(r == -ENOTDIR);
215 result = mfree(result);
216
217 /* Path that loops back to self */
218
219 p = strjoina(temp, "/recursive-symlink");
220 assert_se(symlink("recursive-symlink", p) >= 0);
221 r = chase_symlinks(p, NULL, 0, &result);
222 assert_se(r == -ELOOP);
223
224 /* Path which doesn't exist */
225
226 p = strjoina(temp, "/idontexist");
227 r = chase_symlinks(p, NULL, 0, &result);
228 assert_se(r == -ENOENT);
229
230 r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
231 assert_se(r == 0);
232 assert_se(path_equal(result, p));
233 result = mfree(result);
234
235 p = strjoina(temp, "/idontexist/meneither");
236 r = chase_symlinks(p, NULL, 0, &result);
237 assert_se(r == -ENOENT);
238
239 r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
240 assert_se(r == 0);
241 assert_se(path_equal(result, p));
242 result = mfree(result);
243
244 /* Path which doesn't exist, but contains weird stuff */
245
246 p = strjoina(temp, "/idontexist/..");
247 r = chase_symlinks(p, NULL, 0, &result);
248 assert_se(r == -ENOENT);
249
250 r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
251 assert_se(r == -ENOENT);
252
253 p = strjoina(temp, "/target");
254 q = strjoina(temp, "/top");
255 assert_se(symlink(q, p) >= 0);
256 p = strjoina(temp, "/target/idontexist");
257 r = chase_symlinks(p, NULL, 0, &result);
258 assert_se(r == -ENOENT);
259
260 if (geteuid() == 0) {
261 p = strjoina(temp, "/priv1");
262 assert_se(mkdir(p, 0755) >= 0);
263
264 q = strjoina(p, "/priv2");
265 assert_se(mkdir(q, 0755) >= 0);
266
267 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
268
269 assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
270 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
271
272 assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
273 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
274
275 assert_se(chown(q, 0, 0) >= 0);
276 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -ENOLINK);
277
278 assert_se(rmdir(q) >= 0);
279 assert_se(symlink("/etc/passwd", q) >= 0);
280 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -ENOLINK);
281
282 assert_se(chown(p, 0, 0) >= 0);
283 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
284 }
285
286 p = strjoina(temp, "/machine-id-test");
287 assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
288
289 pfd = chase_symlinks(p, NULL, CHASE_OPEN, NULL);
290 if (pfd != -ENOENT) {
291 _cleanup_close_ int fd = -1;
292 sd_id128_t a, b;
293
294 assert_se(pfd >= 0);
295
296 fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
297 assert_se(fd >= 0);
298 safe_close(pfd);
299
300 assert_se(id128_read_fd(fd, ID128_PLAIN, &a) >= 0);
301 assert_se(sd_id128_get_machine(&b) >= 0);
302 assert_se(sd_id128_equal(a, b));
303 }
304
305 /* Test CHASE_NOFOLLOW */
306
307 p = strjoina(temp, "/target");
308 q = strjoina(temp, "/symlink");
309 assert_se(symlink(p, q) >= 0);
310 pfd = chase_symlinks(q, NULL, CHASE_OPEN|CHASE_NOFOLLOW, &result);
311 assert_se(pfd > 0);
312 assert_se(path_equal(result, q));
313 assert_se(fstat(pfd, &st) >= 0);
314 assert_se(S_ISLNK(st.st_mode));
315 result = mfree(result);
316
317 /* s1 -> s2 -> nonexistent */
318 q = strjoina(temp, "/s1");
319 assert_se(symlink("s2", q) >= 0);
320 p = strjoina(temp, "/s2");
321 assert_se(symlink("nonexistent", p) >= 0);
322 pfd = chase_symlinks(q, NULL, CHASE_OPEN|CHASE_NOFOLLOW, &result);
323 assert_se(pfd > 0);
324 assert_se(path_equal(result, q));
325 assert_se(fstat(pfd, &st) >= 0);
326 assert_se(S_ISLNK(st.st_mode));
327 result = mfree(result);
328
329 /* Test CHASE_ONE */
330
331 p = strjoina(temp, "/start");
332 r = chase_symlinks(p, NULL, CHASE_STEP, &result);
333 assert_se(r == 0);
334 p = strjoina(temp, "/top/dot/dotdota");
335 assert_se(streq(p, result));
336 result = mfree(result);
337
338 r = chase_symlinks(p, NULL, CHASE_STEP, &result);
339 assert_se(r == 0);
340 p = strjoina(temp, "/top/./dotdota");
341 assert_se(streq(p, result));
342 result = mfree(result);
343
344 r = chase_symlinks(p, NULL, CHASE_STEP, &result);
345 assert_se(r == 0);
346 p = strjoina(temp, "/top/../a");
347 assert_se(streq(p, result));
348 result = mfree(result);
349
350 r = chase_symlinks(p, NULL, CHASE_STEP, &result);
351 assert_se(r == 0);
352 p = strjoina(temp, "/a");
353 assert_se(streq(p, result));
354 result = mfree(result);
355
356 r = chase_symlinks(p, NULL, CHASE_STEP, &result);
357 assert_se(r == 0);
358 p = strjoina(temp, "/b");
359 assert_se(streq(p, result));
360 result = mfree(result);
361
362 r = chase_symlinks(p, NULL, CHASE_STEP, &result);
363 assert_se(r == 0);
364 assert_se(streq("/usr", result));
365 result = mfree(result);
366
367 r = chase_symlinks("/usr", NULL, CHASE_STEP, &result);
368 assert_se(r > 0);
369 assert_se(streq("/usr", result));
370 result = mfree(result);
371
372 cleanup:
373 assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
374 }
375
376 static void test_unlink_noerrno(void) {
377 char *name;
378 int fd;
379
380 log_info("/* %s */", __func__);
381
382 name = strjoina(arg_test_dir ?: "/tmp", "/test-close_nointr.XXXXXX");
383 fd = mkostemp_safe(name);
384 assert_se(fd >= 0);
385 assert_se(close_nointr(fd) >= 0);
386
387 {
388 PROTECT_ERRNO;
389 errno = 42;
390 assert_se(unlink_noerrno(name) >= 0);
391 assert_se(errno == 42);
392 assert_se(unlink_noerrno(name) < 0);
393 assert_se(errno == 42);
394 }
395 }
396
397 static void test_readlink_and_make_absolute(void) {
398 const char *tempdir, *name, *name2, *name_alias;
399 _cleanup_free_ char *r1 = NULL, *r2 = NULL, *pwd = NULL;
400
401 log_info("/* %s */", __func__);
402
403 tempdir = strjoina(arg_test_dir ?: "/tmp", "/test-readlink_and_make_absolute");
404 name = strjoina(tempdir, "/original");
405 name2 = "test-readlink_and_make_absolute/original";
406 name_alias = strjoina(arg_test_dir ?: "/tmp", "/test-readlink_and_make_absolute-alias");
407
408 assert_se(mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0);
409 assert_se(touch(name) >= 0);
410
411 if (symlink(name, name_alias) < 0) {
412 assert_se(IN_SET(errno, EINVAL, ENOSYS, ENOTTY, EPERM));
413 log_tests_skipped_errno(errno, "symlink() not possible");
414 } else {
415 assert_se(readlink_and_make_absolute(name_alias, &r1) >= 0);
416 assert_se(streq(r1, name));
417 assert_se(unlink(name_alias) >= 0);
418
419 assert_se(safe_getcwd(&pwd) >= 0);
420
421 assert_se(chdir(tempdir) >= 0);
422 assert_se(symlink(name2, name_alias) >= 0);
423 assert_se(readlink_and_make_absolute(name_alias, &r2) >= 0);
424 assert_se(streq(r2, name));
425 assert_se(unlink(name_alias) >= 0);
426
427 assert_se(chdir(pwd) >= 0);
428 }
429
430 assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
431 }
432
433 static void test_get_files_in_directory(void) {
434 _cleanup_strv_free_ char **l = NULL, **t = NULL;
435
436 assert_se(get_files_in_directory(arg_test_dir ?: "/tmp", &l) >= 0);
437 assert_se(get_files_in_directory(".", &t) >= 0);
438 assert_se(get_files_in_directory(".", NULL) >= 0);
439 }
440
441 static void test_var_tmp(void) {
442 _cleanup_free_ char *tmpdir_backup = NULL, *temp_backup = NULL, *tmp_backup = NULL;
443 const char *tmp_dir = NULL, *t;
444
445 log_info("/* %s */", __func__);
446
447 t = getenv("TMPDIR");
448 if (t) {
449 tmpdir_backup = strdup(t);
450 assert_se(tmpdir_backup);
451 }
452
453 t = getenv("TEMP");
454 if (t) {
455 temp_backup = strdup(t);
456 assert_se(temp_backup);
457 }
458
459 t = getenv("TMP");
460 if (t) {
461 tmp_backup = strdup(t);
462 assert_se(tmp_backup);
463 }
464
465 assert_se(unsetenv("TMPDIR") >= 0);
466 assert_se(unsetenv("TEMP") >= 0);
467 assert_se(unsetenv("TMP") >= 0);
468
469 assert_se(var_tmp_dir(&tmp_dir) >= 0);
470 assert_se(streq(tmp_dir, "/var/tmp"));
471
472 assert_se(setenv("TMPDIR", "/tmp", true) >= 0);
473 assert_se(streq(getenv("TMPDIR"), "/tmp"));
474
475 assert_se(var_tmp_dir(&tmp_dir) >= 0);
476 assert_se(streq(tmp_dir, "/tmp"));
477
478 assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0);
479 assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88"));
480
481 assert_se(var_tmp_dir(&tmp_dir) >= 0);
482 assert_se(streq(tmp_dir, "/var/tmp"));
483
484 if (tmpdir_backup) {
485 assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0);
486 assert_se(streq(getenv("TMPDIR"), tmpdir_backup));
487 }
488
489 if (temp_backup) {
490 assert_se(setenv("TEMP", temp_backup, true) >= 0);
491 assert_se(streq(getenv("TEMP"), temp_backup));
492 }
493
494 if (tmp_backup) {
495 assert_se(setenv("TMP", tmp_backup, true) >= 0);
496 assert_se(streq(getenv("TMP"), tmp_backup));
497 }
498 }
499
500 static void test_dot_or_dot_dot(void) {
501 log_info("/* %s */", __func__);
502
503 assert_se(!dot_or_dot_dot(NULL));
504 assert_se(!dot_or_dot_dot(""));
505 assert_se(!dot_or_dot_dot("xxx"));
506 assert_se(dot_or_dot_dot("."));
507 assert_se(dot_or_dot_dot(".."));
508 assert_se(!dot_or_dot_dot(".foo"));
509 assert_se(!dot_or_dot_dot("..foo"));
510 }
511
512 static void test_access_fd(void) {
513 _cleanup_(rmdir_and_freep) char *p = NULL;
514 _cleanup_close_ int fd = -1;
515 const char *a;
516
517 log_info("/* %s */", __func__);
518
519 a = strjoina(arg_test_dir ?: "/tmp", "/access-fd.XXXXXX");
520 assert_se(mkdtemp_malloc(a, &p) >= 0);
521
522 fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
523 assert_se(fd >= 0);
524
525 assert_se(access_fd(fd, R_OK) >= 0);
526 assert_se(access_fd(fd, F_OK) >= 0);
527 assert_se(access_fd(fd, W_OK) >= 0);
528
529 assert_se(fchmod(fd, 0000) >= 0);
530
531 assert_se(access_fd(fd, F_OK) >= 0);
532
533 if (geteuid() == 0) {
534 assert_se(access_fd(fd, R_OK) >= 0);
535 assert_se(access_fd(fd, W_OK) >= 0);
536 } else {
537 assert_se(access_fd(fd, R_OK) == -EACCES);
538 assert_se(access_fd(fd, W_OK) == -EACCES);
539 }
540 }
541
542 static void test_touch_file(void) {
543 uid_t test_uid, test_gid;
544 _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
545 struct stat st;
546 const char *a;
547 usec_t test_mtime;
548 int r;
549
550 log_info("/* %s */", __func__);
551
552 test_uid = geteuid() == 0 ? 65534 : getuid();
553 test_gid = geteuid() == 0 ? 65534 : getgid();
554
555 test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK);
556
557 a = strjoina(arg_test_dir ?: "/dev/shm", "/touch-file-XXXXXX");
558 assert_se(mkdtemp_malloc(a, &p) >= 0);
559
560 a = strjoina(p, "/regular");
561 r = touch_file(a, false, test_mtime, test_uid, test_gid, 0640);
562 if (r < 0) {
563 assert_se(IN_SET(r, -EINVAL, -ENOSYS, -ENOTTY, -EPERM));
564 log_tests_skipped_errno(errno, "touch_file() not possible");
565 return;
566 }
567
568 assert_se(lstat(a, &st) >= 0);
569 assert_se(st.st_uid == test_uid);
570 assert_se(st.st_gid == test_gid);
571 assert_se(S_ISREG(st.st_mode));
572 assert_se((st.st_mode & 0777) == 0640);
573 assert_se(timespec_load(&st.st_mtim) == test_mtime);
574
575 a = strjoina(p, "/dir");
576 assert_se(mkdir(a, 0775) >= 0);
577 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
578 assert_se(lstat(a, &st) >= 0);
579 assert_se(st.st_uid == test_uid);
580 assert_se(st.st_gid == test_gid);
581 assert_se(S_ISDIR(st.st_mode));
582 assert_se((st.st_mode & 0777) == 0640);
583 assert_se(timespec_load(&st.st_mtim) == test_mtime);
584
585 a = strjoina(p, "/fifo");
586 assert_se(mkfifo(a, 0775) >= 0);
587 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
588 assert_se(lstat(a, &st) >= 0);
589 assert_se(st.st_uid == test_uid);
590 assert_se(st.st_gid == test_gid);
591 assert_se(S_ISFIFO(st.st_mode));
592 assert_se((st.st_mode & 0777) == 0640);
593 assert_se(timespec_load(&st.st_mtim) == test_mtime);
594
595 a = strjoina(p, "/sock");
596 assert_se(mknod(a, 0775 | S_IFSOCK, 0) >= 0);
597 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
598 assert_se(lstat(a, &st) >= 0);
599 assert_se(st.st_uid == test_uid);
600 assert_se(st.st_gid == test_gid);
601 assert_se(S_ISSOCK(st.st_mode));
602 assert_se((st.st_mode & 0777) == 0640);
603 assert_se(timespec_load(&st.st_mtim) == test_mtime);
604
605 if (geteuid() == 0) {
606 a = strjoina(p, "/cdev");
607 r = mknod(a, 0775 | S_IFCHR, makedev(0, 0));
608 if (r < 0 && errno == EPERM && detect_container() > 0) {
609 log_notice("Running in unprivileged container? Skipping remaining tests in %s", __func__);
610 return;
611 }
612 assert_se(r >= 0);
613 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
614 assert_se(lstat(a, &st) >= 0);
615 assert_se(st.st_uid == test_uid);
616 assert_se(st.st_gid == test_gid);
617 assert_se(S_ISCHR(st.st_mode));
618 assert_se((st.st_mode & 0777) == 0640);
619 assert_se(timespec_load(&st.st_mtim) == test_mtime);
620
621 a = strjoina(p, "/bdev");
622 assert_se(mknod(a, 0775 | S_IFBLK, makedev(0, 0)) >= 0);
623 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
624 assert_se(lstat(a, &st) >= 0);
625 assert_se(st.st_uid == test_uid);
626 assert_se(st.st_gid == test_gid);
627 assert_se(S_ISBLK(st.st_mode));
628 assert_se((st.st_mode & 0777) == 0640);
629 assert_se(timespec_load(&st.st_mtim) == test_mtime);
630 }
631
632 a = strjoina(p, "/lnk");
633 assert_se(symlink("target", a) >= 0);
634 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
635 assert_se(lstat(a, &st) >= 0);
636 assert_se(st.st_uid == test_uid);
637 assert_se(st.st_gid == test_gid);
638 assert_se(S_ISLNK(st.st_mode));
639 assert_se((st.st_mode & 0777) == 0640);
640 assert_se(timespec_load(&st.st_mtim) == test_mtime);
641 }
642
643 static void test_unlinkat_deallocate(void) {
644 _cleanup_free_ char *p = NULL;
645 _cleanup_close_ int fd = -1;
646 struct stat st;
647
648 log_info("/* %s */", __func__);
649
650 assert_se(tempfn_random_child(arg_test_dir, "unlink-deallocation", &p) >= 0);
651
652 fd = open(p, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0600);
653 assert_se(fd >= 0);
654
655 assert_se(write(fd, "hallo\n", 6) == 6);
656
657 assert_se(fstat(fd, &st) >= 0);
658 assert_se(st.st_size == 6);
659 assert_se(st.st_blocks > 0);
660 assert_se(st.st_nlink == 1);
661
662 assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
663
664 assert_se(fstat(fd, &st) >= 0);
665 assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6
666 (it worked) or 0 (we had to resort to truncation) */
667 assert_se(st.st_blocks == 0);
668 assert_se(st.st_nlink == 0);
669 }
670
671 static void test_fsync_directory_of_file(void) {
672 _cleanup_close_ int fd = -1;
673
674 log_info("/* %s */", __func__);
675
676 fd = open_tmpfile_unlinkable(arg_test_dir, O_RDWR);
677 assert_se(fd >= 0);
678
679 assert_se(fsync_directory_of_file(fd) >= 0);
680 }
681
682 static void test_rename_noreplace(void) {
683 static const char* const table[] = {
684 "/reg",
685 "/dir",
686 "/fifo",
687 "/socket",
688 "/symlink",
689 NULL
690 };
691
692 _cleanup_(rm_rf_physical_and_freep) char *z = NULL;
693 const char *j = NULL;
694 char **a, **b;
695
696 log_info("/* %s */", __func__);
697
698 if (arg_test_dir)
699 j = strjoina(arg_test_dir, "/testXXXXXX");
700 assert_se(mkdtemp_malloc(j, &z) >= 0);
701
702 j = strjoina(z, table[0]);
703 assert_se(touch(j) >= 0);
704
705 j = strjoina(z, table[1]);
706 assert_se(mkdir(j, 0777) >= 0);
707
708 j = strjoina(z, table[2]);
709 (void) mkfifo(j, 0777);
710
711 j = strjoina(z, table[3]);
712 (void) mknod(j, S_IFSOCK | 0777, 0);
713
714 j = strjoina(z, table[4]);
715 (void) symlink("foobar", j);
716
717 STRV_FOREACH(a, (char**) table) {
718 _cleanup_free_ char *x = NULL, *y = NULL;
719
720 x = strjoin(z, *a);
721 assert_se(x);
722
723 if (access(x, F_OK) < 0) {
724 assert_se(errno == ENOENT);
725 continue;
726 }
727
728 STRV_FOREACH(b, (char**) table) {
729 _cleanup_free_ char *w = NULL;
730
731 w = strjoin(w, *b);
732 assert_se(w);
733
734 if (access(w, F_OK) < 0) {
735 assert_se(errno == ENOENT);
736 continue;
737 }
738
739 assert_se(rename_noreplace(AT_FDCWD, w, AT_FDCWD, y) == -EEXIST);
740 }
741
742 y = strjoin(z, "/somethingelse");
743 assert_se(y);
744
745 assert_se(rename_noreplace(AT_FDCWD, x, AT_FDCWD, y) >= 0);
746 assert_se(rename_noreplace(AT_FDCWD, y, AT_FDCWD, x) >= 0);
747 }
748 }
749
750 static void test_chmod_and_chown(void) {
751 _cleanup_(rm_rf_physical_and_freep) char *d = NULL;
752 _unused_ _cleanup_umask_ mode_t u = umask(0000);
753 struct stat st;
754 const char *p;
755
756 if (geteuid() != 0)
757 return;
758
759 log_info("/* %s */", __func__);
760
761 assert_se(mkdtemp_malloc(NULL, &d) >= 0);
762
763 p = strjoina(d, "/reg");
764 assert_se(mknod(p, S_IFREG | 0123, 0) >= 0);
765
766 assert_se(chmod_and_chown(p, S_IFREG | 0321, 1, 2) >= 0);
767 assert_se(chmod_and_chown(p, S_IFDIR | 0555, 3, 4) == -EINVAL);
768
769 assert_se(lstat(p, &st) >= 0);
770 assert_se(S_ISREG(st.st_mode));
771 assert_se((st.st_mode & 07777) == 0321);
772
773 p = strjoina(d, "/dir");
774 assert_se(mkdir(p, 0123) >= 0);
775
776 assert_se(chmod_and_chown(p, S_IFDIR | 0321, 1, 2) >= 0);
777 assert_se(chmod_and_chown(p, S_IFREG | 0555, 3, 4) == -EINVAL);
778
779 assert_se(lstat(p, &st) >= 0);
780 assert_se(S_ISDIR(st.st_mode));
781 assert_se((st.st_mode & 07777) == 0321);
782
783 p = strjoina(d, "/lnk");
784 assert_se(symlink("idontexist", p) >= 0);
785
786 assert_se(chmod_and_chown(p, S_IFLNK | 0321, 1, 2) >= 0);
787 assert_se(chmod_and_chown(p, S_IFREG | 0555, 3, 4) == -EINVAL);
788 assert_se(chmod_and_chown(p, S_IFDIR | 0555, 3, 4) == -EINVAL);
789
790 assert_se(lstat(p, &st) >= 0);
791 assert_se(S_ISLNK(st.st_mode));
792 }
793
794 int main(int argc, char *argv[]) {
795 test_setup_logging(LOG_INFO);
796
797 arg_test_dir = argv[1];
798
799 test_unlink_noerrno();
800 test_get_files_in_directory();
801 test_readlink_and_make_absolute();
802 test_var_tmp();
803 test_chase_symlinks();
804 test_dot_or_dot_dot();
805 test_access_fd();
806 test_touch_file();
807 test_unlinkat_deallocate();
808 test_fsync_directory_of_file();
809 test_rename_noreplace();
810 test_chmod_and_chown();
811
812 return 0;
813 }