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