chiark / gitweb /
tree-wide: drop license boilerplate
[elogind.git] / src / test / test-fs-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   This file is part of systemd.
4
5   Copyright 2010 Lennart Poettering
6 ***/
7
8 #include <unistd.h>
9
10 #include "alloc-util.h"
11 #include "fd-util.h"
12 //#include "fd-util.h"
13 #include "fileio.h"
14 #include "fs-util.h"
15 #include "id128-util.h"
16 #include "macro.h"
17 #include "mkdir.h"
18 #include "path-util.h"
19 #include "rm-rf.h"
20 #include "stdio-util.h"
21 #include "string-util.h"
22 #include "strv.h"
23 #include "user-util.h"
24 #include "util.h"
25
26 static void test_chase_symlinks(void) {
27         _cleanup_free_ char *result = NULL;
28         char temp[] = "/tmp/test-chase.XXXXXX";
29         const char *top, *p, *pslash, *q, *qslash;
30         int r, pfd;
31
32         assert_se(mkdtemp(temp));
33
34         top = strjoina(temp, "/top");
35         assert_se(mkdir(top, 0700) >= 0);
36
37         p = strjoina(top, "/dot");
38         assert_se(symlink(".", p) >= 0);
39
40         p = strjoina(top, "/dotdot");
41         assert_se(symlink("..", p) >= 0);
42
43         p = strjoina(top, "/dotdota");
44         assert_se(symlink("../a", p) >= 0);
45
46         p = strjoina(temp, "/a");
47         assert_se(symlink("b", p) >= 0);
48
49         p = strjoina(temp, "/b");
50         assert_se(symlink("/usr", p) >= 0);
51
52         p = strjoina(temp, "/start");
53         assert_se(symlink("top/dot/dotdota", p) >= 0);
54
55         /* Paths that use symlinks underneath the "root" */
56
57         r = chase_symlinks(p, NULL, 0, &result);
58         assert_se(r > 0);
59         assert_se(path_equal(result, "/usr"));
60         result = mfree(result);
61
62         pslash = strjoina(p, "/");
63         r = chase_symlinks(pslash, NULL, 0, &result);
64         assert_se(r > 0);
65         assert_se(path_equal(result, "/usr/"));
66         result = mfree(result);
67
68         r = chase_symlinks(p, temp, 0, &result);
69         assert_se(r == -ENOENT);
70
71         r = chase_symlinks(pslash, temp, 0, &result);
72         assert_se(r == -ENOENT);
73
74         q = strjoina(temp, "/usr");
75
76         r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result);
77         assert_se(r == 0);
78         assert_se(path_equal(result, q));
79         result = mfree(result);
80
81         qslash = strjoina(q, "/");
82
83         r = chase_symlinks(pslash, temp, CHASE_NONEXISTENT, &result);
84         assert_se(r == 0);
85         assert_se(path_equal(result, qslash));
86         result = mfree(result);
87
88         assert_se(mkdir(q, 0700) >= 0);
89
90         r = chase_symlinks(p, temp, 0, &result);
91         assert_se(r > 0);
92         assert_se(path_equal(result, q));
93         result = mfree(result);
94
95         r = chase_symlinks(pslash, temp, 0, &result);
96         assert_se(r > 0);
97         assert_se(path_equal(result, qslash));
98         result = mfree(result);
99
100         p = strjoina(temp, "/slash");
101         assert_se(symlink("/", p) >= 0);
102
103         r = chase_symlinks(p, NULL, 0, &result);
104         assert_se(r > 0);
105         assert_se(path_equal(result, "/"));
106         result = mfree(result);
107
108         r = chase_symlinks(p, temp, 0, &result);
109         assert_se(r > 0);
110         assert_se(path_equal(result, temp));
111         result = mfree(result);
112
113         /* Paths that would "escape" outside of the "root" */
114
115         p = strjoina(temp, "/6dots");
116         assert_se(symlink("../../..", p) >= 0);
117
118         r = chase_symlinks(p, temp, 0, &result);
119         assert_se(r > 0 && path_equal(result, temp));
120         result = mfree(result);
121
122         p = strjoina(temp, "/6dotsusr");
123         assert_se(symlink("../../../usr", p) >= 0);
124
125         r = chase_symlinks(p, temp, 0, &result);
126         assert_se(r > 0 && path_equal(result, q));
127         result = mfree(result);
128
129         p = strjoina(temp, "/top/8dotsusr");
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         /* Paths that contain repeated slashes */
137
138         p = strjoina(temp, "/slashslash");
139         assert_se(symlink("///usr///", p) >= 0);
140
141         r = chase_symlinks(p, NULL, 0, &result);
142         assert_se(r > 0);
143         assert_se(path_equal(result, "/usr"));
144         result = mfree(result);
145
146         r = chase_symlinks(p, temp, 0, &result);
147         assert_se(r > 0);
148         assert_se(path_equal(result, q));
149         result = mfree(result);
150
151         /* Paths using . */
152
153         r = chase_symlinks("/etc/./.././", NULL, 0, &result);
154         assert_se(r > 0);
155         assert_se(path_equal(result, "/"));
156         result = mfree(result);
157
158         r = chase_symlinks("/etc/./.././", "/etc", 0, &result);
159         assert_se(r > 0 && path_equal(result, "/etc"));
160         result = mfree(result);
161
162         r = chase_symlinks("/../.././//../../etc", NULL, 0, &result);
163         assert_se(r > 0);
164         assert_se(streq(result, "/etc"));
165         result = mfree(result);
166
167         r = chase_symlinks("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result);
168         assert_se(r == 0);
169         assert_se(streq(result, "/test-chase.fsldajfl"));
170         result = mfree(result);
171
172         r = chase_symlinks("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result);
173         assert_se(r > 0);
174         assert_se(streq(result, "/etc"));
175         result = mfree(result);
176
177         r = chase_symlinks("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result);
178         assert_se(r == 0);
179         assert_se(streq(result, "/test-chase.fsldajfl"));
180         result = mfree(result);
181
182         r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result);
183         assert_se(r == -ENOTDIR);
184         result = mfree(result);
185
186         /* Path that loops back to self */
187
188         p = strjoina(temp, "/recursive-symlink");
189         assert_se(symlink("recursive-symlink", p) >= 0);
190         r = chase_symlinks(p, NULL, 0, &result);
191         assert_se(r == -ELOOP);
192
193         /* Path which doesn't exist */
194
195         p = strjoina(temp, "/idontexist");
196         r = chase_symlinks(p, NULL, 0, &result);
197         assert_se(r == -ENOENT);
198
199         r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
200         assert_se(r == 0);
201         assert_se(path_equal(result, p));
202         result = mfree(result);
203
204         p = strjoina(temp, "/idontexist/meneither");
205         r = chase_symlinks(p, NULL, 0, &result);
206         assert_se(r == -ENOENT);
207
208         r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
209         assert_se(r == 0);
210         assert_se(path_equal(result, p));
211         result = mfree(result);
212
213         /* Path which doesn't exist, but contains weird stuff */
214
215         p = strjoina(temp, "/idontexist/..");
216         r = chase_symlinks(p, NULL, 0, &result);
217         assert_se(r == -ENOENT);
218
219         r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
220         assert_se(r == -ENOENT);
221
222         p = strjoina(temp, "/target");
223         q = strjoina(temp, "/top");
224         assert_se(symlink(q, p) >= 0);
225         p = strjoina(temp, "/target/idontexist");
226         r = chase_symlinks(p, NULL, 0, &result);
227         assert_se(r == -ENOENT);
228
229         if (geteuid() == 0) {
230                 p = strjoina(temp, "/priv1");
231                 assert_se(mkdir(p, 0755) >= 0);
232
233                 q = strjoina(p, "/priv2");
234                 assert_se(mkdir(q, 0755) >= 0);
235
236                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
237
238                 assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
239                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
240
241                 assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
242                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
243
244                 assert_se(chown(q, 0, 0) >= 0);
245                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -EPERM);
246
247                 assert_se(rmdir(q) >= 0);
248                 assert_se(symlink("/etc/passwd", q) >= 0);
249                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -EPERM);
250
251                 assert_se(chown(p, 0, 0) >= 0);
252                 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
253         }
254
255         p = strjoina(temp, "/machine-id-test");
256         assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
257
258         pfd = chase_symlinks(p, NULL, CHASE_OPEN, NULL);
259         if (pfd != -ENOENT) {
260                 _cleanup_close_ int fd = -1;
261                 sd_id128_t a, b;
262
263                 assert_se(pfd >= 0);
264
265                 fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
266                 assert_se(fd >= 0);
267                 safe_close(pfd);
268
269                 assert_se(id128_read_fd(fd, ID128_PLAIN, &a) >= 0);
270                 assert_se(sd_id128_get_machine(&b) >= 0);
271                 assert_se(sd_id128_equal(a, b));
272         }
273
274         assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
275 }
276
277 static void test_unlink_noerrno(void) {
278         char name[] = "/tmp/test-close_nointr.XXXXXX";
279         int fd;
280
281         fd = mkostemp_safe(name);
282         assert_se(fd >= 0);
283         assert_se(close_nointr(fd) >= 0);
284
285         {
286                 PROTECT_ERRNO;
287                 errno = -42;
288                 assert_se(unlink_noerrno(name) >= 0);
289                 assert_se(errno == -42);
290                 assert_se(unlink_noerrno(name) < 0);
291                 assert_se(errno == -42);
292         }
293 }
294
295 static void test_readlink_and_make_absolute(void) {
296         char tempdir[] = "/tmp/test-readlink_and_make_absolute";
297         char name[] = "/tmp/test-readlink_and_make_absolute/original";
298         char name2[] = "test-readlink_and_make_absolute/original";
299         char name_alias[] = "/tmp/test-readlink_and_make_absolute-alias";
300         char *r = NULL;
301         _cleanup_free_ char *pwd = NULL;
302
303         assert_se(mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0);
304         assert_se(touch(name) >= 0);
305
306         assert_se(symlink(name, name_alias) >= 0);
307         assert_se(readlink_and_make_absolute(name_alias, &r) >= 0);
308         assert_se(streq(r, name));
309         free(r);
310         assert_se(unlink(name_alias) >= 0);
311
312         assert_se(safe_getcwd(&pwd) >= 0);
313
314         assert_se(chdir(tempdir) >= 0);
315         assert_se(symlink(name2, name_alias) >= 0);
316         assert_se(readlink_and_make_absolute(name_alias, &r) >= 0);
317         assert_se(streq(r, name));
318         free(r);
319         assert_se(unlink(name_alias) >= 0);
320
321         assert_se(chdir(pwd) >= 0);
322
323         assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
324 }
325
326 static void test_get_files_in_directory(void) {
327         _cleanup_strv_free_ char **l = NULL, **t = NULL;
328
329         assert_se(get_files_in_directory("/tmp", &l) >= 0);
330         assert_se(get_files_in_directory(".", &t) >= 0);
331         assert_se(get_files_in_directory(".", NULL) >= 0);
332 }
333
334 #if 0 /// UNNEEDED by elogind
335 static void test_var_tmp(void) {
336         _cleanup_free_ char *tmpdir_backup = NULL, *temp_backup = NULL, *tmp_backup = NULL;
337         const char *tmp_dir = NULL, *t;
338
339         t = getenv("TMPDIR");
340         if (t) {
341                 tmpdir_backup = strdup(t);
342                 assert_se(tmpdir_backup);
343         }
344
345         t = getenv("TEMP");
346         if (t) {
347                 temp_backup = strdup(t);
348                 assert_se(temp_backup);
349         }
350
351         t = getenv("TMP");
352         if (t) {
353                 tmp_backup = strdup(t);
354                 assert_se(tmp_backup);
355         }
356
357         assert_se(unsetenv("TMPDIR") >= 0);
358         assert_se(unsetenv("TEMP") >= 0);
359         assert_se(unsetenv("TMP") >= 0);
360
361         assert_se(var_tmp_dir(&tmp_dir) >= 0);
362         assert_se(streq(tmp_dir, "/var/tmp"));
363
364         assert_se(setenv("TMPDIR", "/tmp", true) >= 0);
365         assert_se(streq(getenv("TMPDIR"), "/tmp"));
366
367         assert_se(var_tmp_dir(&tmp_dir) >= 0);
368         assert_se(streq(tmp_dir, "/tmp"));
369
370         assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0);
371         assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88"));
372
373         assert_se(var_tmp_dir(&tmp_dir) >= 0);
374         assert_se(streq(tmp_dir, "/var/tmp"));
375
376         if (tmpdir_backup)  {
377                 assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0);
378                 assert_se(streq(getenv("TMPDIR"), tmpdir_backup));
379         }
380
381         if (temp_backup)  {
382                 assert_se(setenv("TEMP", temp_backup, true) >= 0);
383                 assert_se(streq(getenv("TEMP"), temp_backup));
384         }
385
386         if (tmp_backup)  {
387                 assert_se(setenv("TMP", tmp_backup, true) >= 0);
388                 assert_se(streq(getenv("TMP"), tmp_backup));
389         }
390 }
391 #endif // 0
392
393 static void test_dot_or_dot_dot(void) {
394         assert_se(!dot_or_dot_dot(NULL));
395         assert_se(!dot_or_dot_dot(""));
396         assert_se(!dot_or_dot_dot("xxx"));
397         assert_se(dot_or_dot_dot("."));
398         assert_se(dot_or_dot_dot(".."));
399         assert_se(!dot_or_dot_dot(".foo"));
400         assert_se(!dot_or_dot_dot("..foo"));
401 }
402
403 #if 0 /// Uses functions that elogind does not need
404 static void test_access_fd(void) {
405         _cleanup_(rmdir_and_freep) char *p = NULL;
406         _cleanup_close_ int fd = -1;
407
408         assert_se(mkdtemp_malloc("/tmp/access-fd.XXXXXX", &p) >= 0);
409
410         fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
411         assert_se(fd >= 0);
412
413         assert_se(access_fd(fd, R_OK) >= 0);
414         assert_se(access_fd(fd, F_OK) >= 0);
415         assert_se(access_fd(fd, W_OK) >= 0);
416
417         assert_se(fchmod(fd, 0000) >= 0);
418
419         assert_se(access_fd(fd, F_OK) >= 0);
420
421         if (geteuid() == 0) {
422                 assert_se(access_fd(fd, R_OK) >= 0);
423                 assert_se(access_fd(fd, W_OK) >= 0);
424         } else {
425                 assert_se(access_fd(fd, R_OK) == -EACCES);
426                 assert_se(access_fd(fd, W_OK) == -EACCES);
427         }
428 }
429
430 static void test_touch_file(void) {
431         uid_t test_uid, test_gid;
432         _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
433         struct stat st;
434         const char *a;
435         usec_t test_mtime;
436
437         test_uid = geteuid() == 0 ? 65534 : getuid();
438         test_gid = geteuid() == 0 ? 65534 : getgid();
439
440         test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK);
441
442         assert_se(mkdtemp_malloc("/dev/shm/touch-file-XXXXXX", &p) >= 0);
443
444         a = strjoina(p, "/regular");
445         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
446         assert_se(lstat(a, &st) >= 0);
447         assert_se(st.st_uid == test_uid);
448         assert_se(st.st_gid == test_gid);
449         assert_se(S_ISREG(st.st_mode));
450         assert_se((st.st_mode & 0777) == 0640);
451         assert_se(timespec_load(&st.st_mtim) == test_mtime);
452
453         a = strjoina(p, "/dir");
454         assert_se(mkdir(a, 0775) >= 0);
455         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
456         assert_se(lstat(a, &st) >= 0);
457         assert_se(st.st_uid == test_uid);
458         assert_se(st.st_gid == test_gid);
459         assert_se(S_ISDIR(st.st_mode));
460         assert_se((st.st_mode & 0777) == 0640);
461         assert_se(timespec_load(&st.st_mtim) == test_mtime);
462
463         a = strjoina(p, "/fifo");
464         assert_se(mkfifo(a, 0775) >= 0);
465         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
466         assert_se(lstat(a, &st) >= 0);
467         assert_se(st.st_uid == test_uid);
468         assert_se(st.st_gid == test_gid);
469         assert_se(S_ISFIFO(st.st_mode));
470         assert_se((st.st_mode & 0777) == 0640);
471         assert_se(timespec_load(&st.st_mtim) == test_mtime);
472
473         a = strjoina(p, "/sock");
474         assert_se(mknod(a, 0775 | S_IFSOCK, 0) >= 0);
475         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
476         assert_se(lstat(a, &st) >= 0);
477         assert_se(st.st_uid == test_uid);
478         assert_se(st.st_gid == test_gid);
479         assert_se(S_ISSOCK(st.st_mode));
480         assert_se((st.st_mode & 0777) == 0640);
481         assert_se(timespec_load(&st.st_mtim) == test_mtime);
482
483         if (geteuid() == 0) {
484                 a = strjoina(p, "/cdev");
485                 assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 0);
486                 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
487                 assert_se(lstat(a, &st) >= 0);
488                 assert_se(st.st_uid == test_uid);
489                 assert_se(st.st_gid == test_gid);
490                 assert_se(S_ISCHR(st.st_mode));
491                 assert_se((st.st_mode & 0777) == 0640);
492                 assert_se(timespec_load(&st.st_mtim) == test_mtime);
493
494                 a = strjoina(p, "/bdev");
495                 assert_se(mknod(a, 0775 | S_IFBLK, makedev(0, 0)) >= 0);
496                 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
497                 assert_se(lstat(a, &st) >= 0);
498                 assert_se(st.st_uid == test_uid);
499                 assert_se(st.st_gid == test_gid);
500                 assert_se(S_ISBLK(st.st_mode));
501                 assert_se((st.st_mode & 0777) == 0640);
502                 assert_se(timespec_load(&st.st_mtim) == test_mtime);
503         }
504
505         a = strjoina(p, "/lnk");
506         assert_se(symlink("target", a) >= 0);
507         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
508         assert_se(lstat(a, &st) >= 0);
509         assert_se(st.st_uid == test_uid);
510         assert_se(st.st_gid == test_gid);
511         assert_se(S_ISLNK(st.st_mode));
512         assert_se((st.st_mode & 0777) == 0640);
513         assert_se(timespec_load(&st.st_mtim) == test_mtime);
514 }
515
516 static void test_unlinkat_deallocate(void) {
517         _cleanup_free_ char *p = NULL;
518         _cleanup_close_ int fd = -1;
519         struct stat st;
520
521         assert_se(tempfn_random_child(NULL, "unlink-deallocation", &p) >= 0);
522
523         fd = open(p, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0600);
524         assert_se(fd >= 0);
525
526         assert_se(write(fd, "hallo\n", 6) == 6);
527
528         assert_se(fstat(fd, &st) >= 0);
529         assert_se(st.st_size == 6);
530         assert_se(st.st_blocks > 0);
531         assert_se(st.st_nlink == 1);
532
533         assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
534
535         assert_se(fstat(fd, &st) >= 0);
536         assert_se(IN_SET(st.st_size, 0, 6)); /* depending on whether hole punching worked the size will be 6 (it worked) or 0 (we had to resort to truncation) */
537         assert_se(st.st_blocks == 0);
538         assert_se(st.st_nlink == 0);
539 }
540 #endif // 0
541
542 static void test_fsync_directory_of_file(void) {
543         _cleanup_close_ int fd = -1;
544
545         fd = open_tmpfile_unlinkable(NULL, O_RDWR);
546         assert_se(fd >= 0);
547
548         assert_se(fsync_directory_of_file(fd) >= 0);
549 }
550
551 int main(int argc, char *argv[]) {
552         test_unlink_noerrno();
553         test_get_files_in_directory();
554         test_readlink_and_make_absolute();
555 #if 0 /// UNNEEDED by elogind
556         test_var_tmp();
557 #endif // 0
558         test_chase_symlinks();
559         test_dot_or_dot_dot();
560 #if 0 /// Uses functions that elogind does not need
561         test_access_fd();
562         test_touch_file();
563         test_unlinkat_deallocate();
564 #endif // 0
565         test_fsync_directory_of_file();
566
567         return 0;
568 }