1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2010 Lennart Poettering
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.
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.
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/>.
23 #include "alloc-util.h"
25 //#include "fd-util.h"
28 #include "id128-util.h"
31 #include "path-util.h"
33 #include "stdio-util.h"
34 #include "string-util.h"
36 #include "user-util.h"
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;
45 assert_se(mkdtemp(temp));
47 top = strjoina(temp, "/top");
48 assert_se(mkdir(top, 0700) >= 0);
50 p = strjoina(top, "/dot");
51 assert_se(symlink(".", p) >= 0);
53 p = strjoina(top, "/dotdot");
54 assert_se(symlink("..", p) >= 0);
56 p = strjoina(top, "/dotdota");
57 assert_se(symlink("../a", p) >= 0);
59 p = strjoina(temp, "/a");
60 assert_se(symlink("b", p) >= 0);
62 p = strjoina(temp, "/b");
63 assert_se(symlink("/usr", p) >= 0);
65 p = strjoina(temp, "/start");
66 assert_se(symlink("top/dot/dotdota", p) >= 0);
68 /* Paths that use symlinks underneath the "root" */
70 r = chase_symlinks(p, NULL, 0, &result);
72 assert_se(path_equal(result, "/usr"));
73 result = mfree(result);
75 pslash = strjoina(p, "/");
76 r = chase_symlinks(pslash, NULL, 0, &result);
78 assert_se(path_equal(result, "/usr/"));
79 result = mfree(result);
81 r = chase_symlinks(p, temp, 0, &result);
82 assert_se(r == -ENOENT);
84 r = chase_symlinks(pslash, temp, 0, &result);
85 assert_se(r == -ENOENT);
87 q = strjoina(temp, "/usr");
89 r = chase_symlinks(p, temp, CHASE_NONEXISTENT, &result);
91 assert_se(path_equal(result, q));
92 result = mfree(result);
94 qslash = strjoina(q, "/");
96 r = chase_symlinks(pslash, temp, CHASE_NONEXISTENT, &result);
98 assert_se(path_equal(result, qslash));
99 result = mfree(result);
101 assert_se(mkdir(q, 0700) >= 0);
103 r = chase_symlinks(p, temp, 0, &result);
105 assert_se(path_equal(result, q));
106 result = mfree(result);
108 r = chase_symlinks(pslash, temp, 0, &result);
110 assert_se(path_equal(result, qslash));
111 result = mfree(result);
113 p = strjoina(temp, "/slash");
114 assert_se(symlink("/", p) >= 0);
116 r = chase_symlinks(p, NULL, 0, &result);
118 assert_se(path_equal(result, "/"));
119 result = mfree(result);
121 r = chase_symlinks(p, temp, 0, &result);
123 assert_se(path_equal(result, temp));
124 result = mfree(result);
126 /* Paths that would "escape" outside of the "root" */
128 p = strjoina(temp, "/6dots");
129 assert_se(symlink("../../..", p) >= 0);
131 r = chase_symlinks(p, temp, 0, &result);
132 assert_se(r > 0 && path_equal(result, temp));
133 result = mfree(result);
135 p = strjoina(temp, "/6dotsusr");
136 assert_se(symlink("../../../usr", p) >= 0);
138 r = chase_symlinks(p, temp, 0, &result);
139 assert_se(r > 0 && path_equal(result, q));
140 result = mfree(result);
142 p = strjoina(temp, "/top/8dotsusr");
143 assert_se(symlink("../../../../usr", p) >= 0);
145 r = chase_symlinks(p, temp, 0, &result);
146 assert_se(r > 0 && path_equal(result, q));
147 result = mfree(result);
149 /* Paths that contain repeated slashes */
151 p = strjoina(temp, "/slashslash");
152 assert_se(symlink("///usr///", p) >= 0);
154 r = chase_symlinks(p, NULL, 0, &result);
156 assert_se(path_equal(result, "/usr"));
157 result = mfree(result);
159 r = chase_symlinks(p, temp, 0, &result);
161 assert_se(path_equal(result, q));
162 result = mfree(result);
166 r = chase_symlinks("/etc/./.././", NULL, 0, &result);
168 assert_se(path_equal(result, "/"));
169 result = mfree(result);
171 r = chase_symlinks("/etc/./.././", "/etc", 0, &result);
172 assert_se(r > 0 && path_equal(result, "/etc"));
173 result = mfree(result);
175 r = chase_symlinks("/../.././//../../etc", NULL, 0, &result);
177 assert_se(streq(result, "/etc"));
178 result = mfree(result);
180 r = chase_symlinks("/../.././//../../test-chase.fsldajfl", NULL, CHASE_NONEXISTENT, &result);
182 assert_se(streq(result, "/test-chase.fsldajfl"));
183 result = mfree(result);
185 r = chase_symlinks("/../.././//../../etc", "/", CHASE_PREFIX_ROOT, &result);
187 assert_se(streq(result, "/etc"));
188 result = mfree(result);
190 r = chase_symlinks("/../.././//../../test-chase.fsldajfl", "/", CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &result);
192 assert_se(streq(result, "/test-chase.fsldajfl"));
193 result = mfree(result);
195 r = chase_symlinks("/etc/machine-id/foo", NULL, 0, &result);
196 assert_se(r == -ENOTDIR);
197 result = mfree(result);
199 /* Path that loops back to self */
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);
206 /* Path which doesn't exist */
208 p = strjoina(temp, "/idontexist");
209 r = chase_symlinks(p, NULL, 0, &result);
210 assert_se(r == -ENOENT);
212 r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
214 assert_se(path_equal(result, p));
215 result = mfree(result);
217 p = strjoina(temp, "/idontexist/meneither");
218 r = chase_symlinks(p, NULL, 0, &result);
219 assert_se(r == -ENOENT);
221 r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
223 assert_se(path_equal(result, p));
224 result = mfree(result);
226 /* Path which doesn't exist, but contains weird stuff */
228 p = strjoina(temp, "/idontexist/..");
229 r = chase_symlinks(p, NULL, 0, &result);
230 assert_se(r == -ENOENT);
232 r = chase_symlinks(p, NULL, CHASE_NONEXISTENT, &result);
233 assert_se(r == -ENOENT);
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);
242 if (geteuid() == 0) {
243 p = strjoina(temp, "/priv1");
244 assert_se(mkdir(p, 0755) >= 0);
246 q = strjoina(p, "/priv2");
247 assert_se(mkdir(q, 0755) >= 0);
249 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
251 assert_se(chown(q, UID_NOBODY, GID_NOBODY) >= 0);
252 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
254 assert_se(chown(p, UID_NOBODY, GID_NOBODY) >= 0);
255 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
257 assert_se(chown(q, 0, 0) >= 0);
258 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) == -EPERM);
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);
264 assert_se(chown(p, 0, 0) >= 0);
265 assert_se(chase_symlinks(q, NULL, CHASE_SAFE, NULL) >= 0);
268 p = strjoina(temp, "/machine-id-test");
269 assert_se(symlink("/usr/../etc/./machine-id", p) >= 0);
271 pfd = chase_symlinks(p, NULL, CHASE_OPEN, NULL);
272 if (pfd != -ENOENT) {
273 _cleanup_close_ int fd = -1;
278 fd = fd_reopen(pfd, O_RDONLY|O_CLOEXEC);
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));
287 assert_se(rm_rf(temp, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
290 static void test_unlink_noerrno(void) {
291 char name[] = "/tmp/test-close_nointr.XXXXXX";
294 fd = mkostemp_safe(name);
296 assert_se(close_nointr(fd) >= 0);
301 assert_se(unlink_noerrno(name) >= 0);
302 assert_se(errno == -42);
303 assert_se(unlink_noerrno(name) < 0);
304 assert_se(errno == -42);
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";
314 _cleanup_free_ char *pwd = NULL;
316 assert_se(mkdir_safe(tempdir, 0755, getuid(), getgid(), MKDIR_WARN_MODE) >= 0);
317 assert_se(touch(name) >= 0);
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));
323 assert_se(unlink(name_alias) >= 0);
325 assert_se(safe_getcwd(&pwd) >= 0);
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));
332 assert_se(unlink(name_alias) >= 0);
334 assert_se(chdir(pwd) >= 0);
336 assert_se(rm_rf(tempdir, REMOVE_ROOT|REMOVE_PHYSICAL) >= 0);
339 static void test_get_files_in_directory(void) {
340 _cleanup_strv_free_ char **l = NULL, **t = NULL;
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);
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;
352 t = getenv("TMPDIR");
354 tmpdir_backup = strdup(t);
355 assert_se(tmpdir_backup);
360 temp_backup = strdup(t);
361 assert_se(temp_backup);
366 tmp_backup = strdup(t);
367 assert_se(tmp_backup);
370 assert_se(unsetenv("TMPDIR") >= 0);
371 assert_se(unsetenv("TEMP") >= 0);
372 assert_se(unsetenv("TMP") >= 0);
374 assert_se(var_tmp_dir(&tmp_dir) >= 0);
375 assert_se(streq(tmp_dir, "/var/tmp"));
377 assert_se(setenv("TMPDIR", "/tmp", true) >= 0);
378 assert_se(streq(getenv("TMPDIR"), "/tmp"));
380 assert_se(var_tmp_dir(&tmp_dir) >= 0);
381 assert_se(streq(tmp_dir, "/tmp"));
383 assert_se(setenv("TMPDIR", "/88_does_not_exist_88", true) >= 0);
384 assert_se(streq(getenv("TMPDIR"), "/88_does_not_exist_88"));
386 assert_se(var_tmp_dir(&tmp_dir) >= 0);
387 assert_se(streq(tmp_dir, "/var/tmp"));
390 assert_se(setenv("TMPDIR", tmpdir_backup, true) >= 0);
391 assert_se(streq(getenv("TMPDIR"), tmpdir_backup));
395 assert_se(setenv("TEMP", temp_backup, true) >= 0);
396 assert_se(streq(getenv("TEMP"), temp_backup));
400 assert_se(setenv("TMP", tmp_backup, true) >= 0);
401 assert_se(streq(getenv("TMP"), tmp_backup));
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"));
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;
421 assert_se(mkdtemp_malloc("/tmp/access-fd.XXXXXX", &p) >= 0);
423 fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
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);
430 assert_se(fchmod(fd, 0000) >= 0);
432 assert_se(access_fd(fd, F_OK) >= 0);
434 if (geteuid() == 0) {
435 assert_se(access_fd(fd, R_OK) >= 0);
436 assert_se(access_fd(fd, W_OK) >= 0);
438 assert_se(access_fd(fd, R_OK) == -EACCES);
439 assert_se(access_fd(fd, W_OK) == -EACCES);
443 static void test_touch_file(void) {
444 uid_t test_uid, test_gid;
445 _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
450 test_uid = geteuid() == 0 ? 65534 : getuid();
451 test_gid = geteuid() == 0 ? 65534 : getgid();
453 test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK);
455 assert_se(mkdtemp_malloc("/dev/shm/touch-file-XXXXXX", &p) >= 0);
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);
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);
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);
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);
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);
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);
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);
529 static void test_unlinkat_deallocate(void) {
530 _cleanup_free_ char *p = NULL;
531 _cleanup_close_ int fd = -1;
534 assert_se(tempfn_random_child(NULL, "unlink-deallocation", &p) >= 0);
536 fd = open(p, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL, 0600);
539 assert_se(write(fd, "hallo\n", 6) == 6);
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);
546 assert_se(unlinkat_deallocate(AT_FDCWD, p, 0) >= 0);
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);
555 static void test_fsync_directory_of_file(void) {
556 _cleanup_close_ int fd = -1;
558 fd = open_tmpfile_unlinkable(NULL, O_RDWR);
561 assert_se(fsync_directory_of_file(fd) >= 0);
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
571 test_chase_symlinks();
572 test_dot_or_dot_dot();
573 #if 0 /// Uses functions that elogind does not need
576 test_unlinkat_deallocate();
578 test_fsync_directory_of_file();