chiark / gitweb /
fs-util: add new unlinkat_deallocate() helper
[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                 char procfs[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(pfd) + 1];
274                 _cleanup_close_ int fd = -1;
275                 sd_id128_t a, b;
276
277                 assert_se(pfd >= 0);
278
279                 xsprintf(procfs, "/proc/self/fd/%i", pfd);
280
281                 fd = open(procfs, O_RDONLY|O_CLOEXEC);
282                 assert_se(fd >= 0);
283
284                 safe_close(pfd);
285
286                 assert_se(id128_read_fd(fd, ID128_PLAIN, &a) >= 0);
287                 assert_se(sd_id128_get_machine(&b) >= 0);
288                 assert_se(sd_id128_equal(a, b));
289         }
290
291         assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
292 }
293
294 static void test_unlink_noerrno(void) {
295         char name[] = "/tmp/test-close_nointr.XXXXXX";
296         int fd;
297
298         fd = mkostemp_safe(name);
299         assert_se(fd >= 0);
300         assert_se(close_nointr(fd) >= 0);
301
302         {
303                 PROTECT_ERRNO;
304                 errno = -42;
305                 assert_se(unlink_noerrno(name) >= 0);
306                 assert_se(errno == -42);
307                 assert_se(unlink_noerrno(name) < 0);
308                 assert_se(errno == -42);
309         }
310 }
311
312 static void test_readlink_and_make_absolute(void) {
313         char tempdir[] = "/tmp/test-readlink_and_make_absolute";
314         char name[] = "/tmp/test-readlink_and_make_absolute/original";
315         char name2[] = "test-readlink_and_make_absolute/original";
316         char name_alias[] = "/tmp/test-readlink_and_make_absolute-alias";
317         char *r = NULL;
318         _cleanup_free_ char *pwd = NULL;
319
320         assert_se(mkdir_safe(tempdir, 0755, getuid(), getgid(), false) >= 0);
321         assert_se(touch(name) >= 0);
322
323         assert_se(symlink(name, name_alias) >= 0);
324         assert_se(readlink_and_make_absolute(name_alias, &r) >= 0);
325         assert_se(streq(r, name));
326         free(r);
327         assert_se(unlink(name_alias) >= 0);
328
329         assert_se(safe_getcwd(&pwd) >= 0);
330
331         assert_se(chdir(tempdir) >= 0);
332         assert_se(symlink(name2, name_alias) >= 0);
333         assert_se(readlink_and_make_absolute(name_alias, &r) >= 0);
334         assert_se(streq(r, name));
335         free(r);
336         assert_se(unlink(name_alias) >= 0);
337
338         assert_se(chdir(pwd) >= 0);
339
340         assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
341 }
342
343 static void test_get_files_in_directory(void) {
344         _cleanup_strv_free_ char **l = NULL, **t = NULL;
345
346         assert_se(get_files_in_directory("/tmp", &l) >= 0);
347         assert_se(get_files_in_directory(".", &t) >= 0);
348         assert_se(get_files_in_directory(".", NULL) >= 0);
349 }
350
351 #if 0 /// UNNEEDED by elogind
352 static void test_var_tmp(void) {
353         _cleanup_free_ char *tmpdir_backup = NULL, *temp_backup = NULL, *tmp_backup = NULL;
354         const char *tmp_dir = NULL, *t;
355
356         t = getenv("TMPDIR");
357         if (t) {
358                 tmpdir_backup = strdup(t);
359                 assert_se(tmpdir_backup);
360         }
361
362         t = getenv("TEMP");
363         if (t) {
364                 temp_backup = strdup(t);
365                 assert_se(temp_backup);
366         }
367
368         t = getenv("TMP");
369         if (t) {
370                 tmp_backup = strdup(t);
371                 assert_se(tmp_backup);
372         }
373
374         assert_se(unsetenv("TMPDIR") >= 0);
375         assert_se(unsetenv("TEMP") >= 0);
376         assert_se(unsetenv("TMP") >= 0);
377
378         assert_se(var_tmp_dir(&tmp_dir) >= 0);
379         assert_se(streq(tmp_dir, "/var/tmp"));
380
381         assert_se(setenv("TMPDIR", "/tmp", true) >= 0);
382         assert_se(streq(getenv("TMPDIR"), "/tmp"));
383
384         assert_se(var_tmp_dir(&tmp_dir) >= 0);
385         assert_se(streq(tmp_dir, "/tmp"));
386
387         assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0);
388         assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88"));
389
390         assert_se(var_tmp_dir(&tmp_dir) >= 0);
391         assert_se(streq(tmp_dir, "/var/tmp"));
392
393         if (tmpdir_backup)  {
394                 assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0);
395                 assert_se(streq(getenv("TMPDIR"), tmpdir_backup));
396         }
397
398         if (temp_backup)  {
399                 assert_se(setenv("TEMP", temp_backup, true) >= 0);
400                 assert_se(streq(getenv("TEMP"), temp_backup));
401         }
402
403         if (tmp_backup)  {
404                 assert_se(setenv("TMP", tmp_backup, true) >= 0);
405                 assert_se(streq(getenv("TMP"), tmp_backup));
406         }
407 }
408 #endif // 0
409
410 static void test_dot_or_dot_dot(void) {
411         assert_se(!dot_or_dot_dot(NULL));
412         assert_se(!dot_or_dot_dot(""));
413         assert_se(!dot_or_dot_dot("xxx"));
414         assert_se(dot_or_dot_dot("."));
415         assert_se(dot_or_dot_dot(".."));
416         assert_se(!dot_or_dot_dot(".foo"));
417         assert_se(!dot_or_dot_dot("..foo"));
418 }
419
420 #if 0 /// Uses functions that elogind does not need
421 static void test_access_fd(void) {
422         _cleanup_(rmdir_and_freep) char *p = NULL;
423         _cleanup_close_ int fd = -1;
424
425         assert_se(mkdtemp_malloc("/tmp/access-fd.XXXXXX", &p) >= 0);
426
427         fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
428         assert_se(fd >= 0);
429
430         assert_se(access_fd(fd, R_OK) >= 0);
431         assert_se(access_fd(fd, F_OK) >= 0);
432         assert_se(access_fd(fd, W_OK) >= 0);
433
434         assert_se(fchmod(fd, 0000) >= 0);
435
436         assert_se(access_fd(fd, F_OK) >= 0);
437
438         if (geteuid() == 0) {
439                 assert_se(access_fd(fd, R_OK) >= 0);
440                 assert_se(access_fd(fd, W_OK) >= 0);
441         } else {
442                 assert_se(access_fd(fd, R_OK) == -EACCES);
443                 assert_se(access_fd(fd, W_OK) == -EACCES);
444         }
445 }
446 #endif // 0
447
448 static void test_touch_file(void) {
449         uid_t test_uid, test_gid;
450         _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
451         struct stat st;
452         const char *a;
453         usec_t test_mtime;
454
455         test_uid = geteuid() == 0 ? 65534 : getuid();
456         test_gid = geteuid() == 0 ? 65534 : getgid();
457
458         test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK);
459
460         assert_se(mkdtemp_malloc("/dev/shm/touch-file-XXXXXX", &p) >= 0);
461
462         a = strjoina(p, "/regular");
463         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
464         assert_se(lstat(a, &st) >= 0);
465         assert_se(st.st_uid == test_uid);
466         assert_se(st.st_gid == test_gid);
467         assert_se(S_ISREG(st.st_mode));
468         assert_se((st.st_mode & 0777) == 0640);
469         assert_se(timespec_load(&st.st_mtim) == test_mtime);
470
471         a = strjoina(p, "/dir");
472         assert_se(mkdir(a, 0775) >= 0);
473         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
474         assert_se(lstat(a, &st) >= 0);
475         assert_se(st.st_uid == test_uid);
476         assert_se(st.st_gid == test_gid);
477         assert_se(S_ISDIR(st.st_mode));
478         assert_se((st.st_mode & 0777) == 0640);
479         assert_se(timespec_load(&st.st_mtim) == test_mtime);
480
481         a = strjoina(p, "/fifo");
482         assert_se(mkfifo(a, 0775) >= 0);
483         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
484         assert_se(lstat(a, &st) >= 0);
485         assert_se(st.st_uid == test_uid);
486         assert_se(st.st_gid == test_gid);
487         assert_se(S_ISFIFO(st.st_mode));
488         assert_se((st.st_mode & 0777) == 0640);
489         assert_se(timespec_load(&st.st_mtim) == test_mtime);
490
491         a = strjoina(p, "/sock");
492         assert_se(mknod(a, 0775 | S_IFSOCK, 0) >= 0);
493         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
494         assert_se(lstat(a, &st) >= 0);
495         assert_se(st.st_uid == test_uid);
496         assert_se(st.st_gid == test_gid);
497         assert_se(S_ISSOCK(st.st_mode));
498         assert_se((st.st_mode & 0777) == 0640);
499         assert_se(timespec_load(&st.st_mtim) == test_mtime);
500
501         if (geteuid() == 0) {
502                 a = strjoina(p, "/cdev");
503                 assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 0);
504                 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
505                 assert_se(lstat(a, &st) >= 0);
506                 assert_se(st.st_uid == test_uid);
507                 assert_se(st.st_gid == test_gid);
508                 assert_se(S_ISCHR(st.st_mode));
509                 assert_se((st.st_mode & 0777) == 0640);
510                 assert_se(timespec_load(&st.st_mtim) == test_mtime);
511
512                 a = strjoina(p, "/bdev");
513                 assert_se(mknod(a, 0775 | S_IFBLK, makedev(0, 0)) >= 0);
514                 assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
515                 assert_se(lstat(a, &st) >= 0);
516                 assert_se(st.st_uid == test_uid);
517                 assert_se(st.st_gid == test_gid);
518                 assert_se(S_ISBLK(st.st_mode));
519                 assert_se((st.st_mode & 0777) == 0640);
520                 assert_se(timespec_load(&st.st_mtim) == test_mtime);
521         }
522
523         a = strjoina(p, "/lnk");
524         assert_se(symlink("target", a) >= 0);
525         assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
526         assert_se(lstat(a, &st) >= 0);
527         assert_se(st.st_uid == test_uid);
528         assert_se(st.st_gid == test_gid);
529         assert_se(S_ISLNK(st.st_mode));
530         assert_se((st.st_mode & 0777) == 0640);
531         assert_se(timespec_load(&st.st_mtim) == test_mtime);
532 }
533
534 static void test_unlinkat_deallocate(void) {
535         _cleanup_free_ char *p = NULL;
536         _cleanup_close_ int fd = -1;
537         struct stat st;
538
539         assert_se(tempfn_random_child(NULL, "unlink-deallocation", &p) >= 0);
540
541         fd = open(p, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0600);
542         assert_se(fd >= 0);
543
544         assert_se(write(fd, "hallo\n", 6) == 6);
545
546         assert_se(fstat(fd, &st) >= 0);
547         assert_se(st.st_size == 6);
548         assert_se(st.st_blocks > 0);
549         assert_se(st.st_nlink == 1);
550
551         assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
552
553         assert_se(fstat(fd, &st) >= 0);
554         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) */
555         assert_se(st.st_blocks == 0);
556         assert_se(st.st_nlink == 0);
557 }
558
559 int main(int argc, char *argv[]) {
560         test_unlink_noerrno();
561         test_get_files_in_directory();
562         test_readlink_and_make_absolute();
563 #if 0 /// UNNEEDED by elogind
564         test_var_tmp();
565 #endif // 0
566         test_chase_symlinks();
567         test_dot_or_dot_dot();
568 #if 0 /// Uses functions that elogind does not need
569         test_access_fd();
570 #endif // 0
571         test_touch_file();
572         test_unlinkat_deallocate();
573
574         return 0;
575 }