diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 4ca36faf09..16f97893c9 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -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); + /* 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 (parents) - mkdir_parents(path, 0755); + (void) 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; + /* 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 (mode != MODE_INVALID) { - r = fchmod(fd, mode); - 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; } - if (uid != UID_INVALID || gid != GID_INVALID) { - r = fchown(fd, uid, gid); - if (r < 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) { diff --git a/src/test/test-fs-util.c b/src/test/test-fs-util.c index 86d963c4c7..2ad9e71856 100644 --- a/src/test/test-fs-util.c +++ b/src/test/test-fs-util.c @@ -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; }