]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fs-util: rework touch_file() so that it can touch socket file nodes
authorLennart Poettering <lennart@poettering.net>
Wed, 27 Dec 2017 15:20:28 +0000 (16:20 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 5 Jan 2018 12:55:08 +0000 (13:55 +0100)
Let's rework touch_file() so that it works correctly on sockets, fifos,
and device nodes: let's open an O_PATH file descriptor first and operate
based on that, if we can. This is usually the better option as it this
means we can open AF_UNIX nodes in the file system, and update their
timestamps and ownership correctly. It also means we can correctly touch
symlinks and block/character devices without triggering their drivers.

Moreover, by operating on an O_PATH fd we can make sure that we
operate on the same inode the whole time, and it can't be swapped out in
the middle.

While we are at it, rework the call so that we try to adjust as much as
we can before returning on error. This is a good idea as we call the
function quite often without checking its result, and hence it's best to
leave the files around in the most "correct" fashion possible.

src/basic/fs-util.c
src/test/test-fs-util.c

index 4ca36faf094d885ef72c3e326438d5e7fd0e41b6..16f97893c951cd1f5024e56033a30111348453c5 100644 (file)
@@ -315,43 +315,60 @@ int fd_warn_permissions(const char *path, int fd) {
 }
 
 int touch_file(const char *path, bool parents, usec_t stamp, uid_t uid, gid_t gid, mode_t mode) {
-        _cleanup_close_ int fd;
-        int r;
+        char fdpath[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+        _cleanup_close_ int fd = -1;
+        int r, ret = 0;
 
         assert(path);
 
-        if (parents)
-                mkdir_parents(path, 0755);
-
-        fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY,
-                  IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
-        if (fd < 0)
-                return -errno;
+        /* Note that touch_file() does not follow symlinks: if invoked on an existing symlink, then it is the symlink
+         * itself which is updated, not its target
+         *
+         * Returns the first error we encounter, but tries to apply as much as possible. */
 
-        if (mode != MODE_INVALID) {
-                r = fchmod(fd, mode);
-                if (r < 0)
+        if (parents)
+                (void) mkdir_parents(path, 0755);
+
+        /* Initially, we try to open the node with O_PATH, so that we get a reference to the node. This is useful in
+         * case the path refers to an existing device or socket node, as we can open it successfully in all cases, and
+         * won't trigger any driver magic or so. */
+        fd = open(path, O_PATH|O_CLOEXEC|O_NOFOLLOW);
+        if (fd < 0) {
+                if (errno != ENOENT)
                         return -errno;
-        }
 
-        if (uid != UID_INVALID || gid != GID_INVALID) {
-                r = fchown(fd, uid, gid);
-                if (r < 0)
+                /* if the node doesn't exist yet, we create it, but with O_EXCL, so that we only create a regular file
+                 * here, and nothing else */
+                fd = open(path, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, IN_SET(mode, 0, MODE_INVALID) ? 0644 : mode);
+                if (fd < 0)
                         return -errno;
         }
 
+        /* Let's make a path from the fd, and operate on that. With this logic, we can adjust the access mode,
+         * ownership and time of the file node in all cases, even if the fd refers to an O_PATH object — which is
+         * something fchown(), fchmod(), futimensat() don't allow. */
+        xsprintf(fdpath, "/proc/self/fd/%i", fd);
+
+        if (mode != MODE_INVALID)
+                if (chmod(fdpath, mode) < 0)
+                        ret = -errno;
+
+        if (uid_is_valid(uid) || gid_is_valid(gid))
+                if (chown(fdpath, uid, gid) < 0 && ret >= 0)
+                        ret = -errno;
+
         if (stamp != USEC_INFINITY) {
                 struct timespec ts[2];
 
                 timespec_store(&ts[0], stamp);
                 ts[1] = ts[0];
-                r = futimens(fd, ts);
+                r = utimensat(AT_FDCWD, fdpath, ts, 0);
         } else
-                r = futimens(fd, NULL);
-        if (r < 0)
+                r = utimensat(AT_FDCWD, fdpath, NULL, 0);
+        if (r < 0 && ret >= 0)
                 return -errno;
 
-        return 0;
+        return ret;
 }
 
 int touch(const char *path) {
index 86d963c4c788124d3c0dc5f664719dde7bdeffca..2ad9e71856329f226d63ea057c4db8554be58d09 100644 (file)
@@ -388,6 +388,92 @@ static void test_access_fd(void) {
         }
 }
 
+static void test_touch_file(void) {
+        uid_t test_uid, test_gid;
+        _cleanup_(rm_rf_physical_and_freep) char *p = NULL;
+        struct stat st;
+        const char *a;
+        usec_t test_mtime;
+
+        test_uid = geteuid() == 0 ? 65534 : getuid();
+        test_gid = geteuid() == 0 ? 65534 : getgid();
+
+        test_mtime = usec_sub_unsigned(now(CLOCK_REALTIME), USEC_PER_WEEK);
+
+        assert_se(mkdtemp_malloc("/dev/shm/touch-file-XXXXXX", &p) >= 0);
+
+        a = strjoina(p, "/regular");
+        assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+        assert_se(lstat(a, &st) >= 0);
+        assert_se(st.st_uid == test_uid);
+        assert_se(st.st_gid == test_gid);
+        assert_se(S_ISREG(st.st_mode));
+        assert_se((st.st_mode & 0777) == 0640);
+        assert_se(timespec_load(&st.st_mtim) == test_mtime);
+
+        a = strjoina(p, "/dir");
+        assert_se(mkdir(a, 0775) >= 0);
+        assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+        assert_se(lstat(a, &st) >= 0);
+        assert_se(st.st_uid == test_uid);
+        assert_se(st.st_gid == test_gid);
+        assert_se(S_ISDIR(st.st_mode));
+        assert_se((st.st_mode & 0777) == 0640);
+        assert_se(timespec_load(&st.st_mtim) == test_mtime);
+
+        a = strjoina(p, "/fifo");
+        assert_se(mkfifo(a, 0775) >= 0);
+        assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+        assert_se(lstat(a, &st) >= 0);
+        assert_se(st.st_uid == test_uid);
+        assert_se(st.st_gid == test_gid);
+        assert_se(S_ISFIFO(st.st_mode));
+        assert_se((st.st_mode & 0777) == 0640);
+        assert_se(timespec_load(&st.st_mtim) == test_mtime);
+
+        a = strjoina(p, "/sock");
+        assert_se(mknod(a, 0775 | S_IFSOCK, 0) >= 0);
+        assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+        assert_se(lstat(a, &st) >= 0);
+        assert_se(st.st_uid == test_uid);
+        assert_se(st.st_gid == test_gid);
+        assert_se(S_ISSOCK(st.st_mode));
+        assert_se((st.st_mode & 0777) == 0640);
+        assert_se(timespec_load(&st.st_mtim) == test_mtime);
+
+        if (geteuid() == 0) {
+                a = strjoina(p, "/cdev");
+                assert_se(mknod(a, 0775 | S_IFCHR, makedev(0, 0)) >= 0);
+                assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+                assert_se(lstat(a, &st) >= 0);
+                assert_se(st.st_uid == test_uid);
+                assert_se(st.st_gid == test_gid);
+                assert_se(S_ISCHR(st.st_mode));
+                assert_se((st.st_mode & 0777) == 0640);
+                assert_se(timespec_load(&st.st_mtim) == test_mtime);
+
+                a = strjoina(p, "/bdev");
+                assert_se(mknod(a, 0775 | S_IFBLK, makedev(0, 0)) >= 0);
+                assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+                assert_se(lstat(a, &st) >= 0);
+                assert_se(st.st_uid == test_uid);
+                assert_se(st.st_gid == test_gid);
+                assert_se(S_ISBLK(st.st_mode));
+                assert_se((st.st_mode & 0777) == 0640);
+                assert_se(timespec_load(&st.st_mtim) == test_mtime);
+        }
+
+        a = strjoina(p, "/lnk");
+        assert_se(symlink("target", a) >= 0);
+        assert_se(touch_file(a, false, test_mtime, test_uid, test_gid, 0640) >= 0);
+        assert_se(lstat(a, &st) >= 0);
+        assert_se(st.st_uid == test_uid);
+        assert_se(st.st_gid == test_gid);
+        assert_se(S_ISLNK(st.st_mode));
+        assert_se((st.st_mode & 0777) == 0640);
+        assert_se(timespec_load(&st.st_mtim) == test_mtime);
+}
+
 int main(int argc, char *argv[]) {
         test_unlink_noerrno();
         test_get_files_in_directory();
@@ -396,6 +482,7 @@ int main(int argc, char *argv[]) {
         test_chase_symlinks();
         test_dot_or_dot_dot();
         test_access_fd();
+        test_touch_file();
 
         return 0;
 }