#include <errno.h>
#include <stddef.h>
-#include <stdio.h>
#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
#include <linux/falloc.h>
#include <linux/magic.h>
-#include <time.h>
#include <unistd.h>
#include "alloc-util.h"
+#include "blockdev-util.h"
#include "dirent-util.h"
#include "fd-util.h"
+#include "fileio.h"
#include "fs-util.h"
#include "locale-util.h"
#include "log.h"
#include "macro.h"
-#include "missing.h"
+#include "missing_fcntl.h"
+#include "missing_fs.h"
+#include "missing_syscall.h"
#include "mkdir.h"
#include "parse-util.h"
#include "path-util.h"
return do_chown || do_chmod;
}
+int chmod_and_chown_unsafe(const char *path, mode_t mode, uid_t uid, gid_t gid) {
+ bool do_chown, do_chmod;
+ struct stat st;
+
+ assert(path);
+
+ /* Change ownership and access mode of the specified path, see description of fchmod_and_chown().
+ * Should only be used on trusted paths. */
+
+ if (lstat(path, &st) < 0)
+ return -errno;
+
+ do_chown =
+ (uid != UID_INVALID && st.st_uid != uid) ||
+ (gid != GID_INVALID && st.st_gid != gid);
+
+ do_chmod =
+ !S_ISLNK(st.st_mode) && /* chmod is not defined on symlinks */
+ ((mode != MODE_INVALID && ((st.st_mode ^ mode) & 07777) != 0) ||
+ do_chown); /* If we change ownership, make sure we reset the mode afterwards, since chown()
+ * modifies the access mode too */
+
+ if (mode == MODE_INVALID)
+ mode = st.st_mode; /* If we only shall do a chown(), save original mode, since chown() might break it. */
+ else if ((mode & S_IFMT) != 0 && ((mode ^ st.st_mode) & S_IFMT) != 0)
+ return -EINVAL; /* insist on the right file type if it was specified */
+
+ if (do_chown && do_chmod) {
+ mode_t minimal = st.st_mode & mode; /* the subset of the old and the new mask */
+
+ if (((minimal ^ st.st_mode) & 07777) != 0)
+ if (chmod(path, minimal & 07777) < 0)
+ return -errno;
+ }
+
+ if (do_chown)
+ if (lchown(path, uid, gid) < 0)
+ return -errno;
+
+ if (do_chmod)
+ if (chmod(path, mode & 07777) < 0)
+ return -errno;
+
+ return do_chown || do_chmod;
+}
+
int fchmod_umask(int fd, mode_t m) {
mode_t u;
int r;
* fchownat() does. */
xsprintf(procfs_path, "/proc/self/fd/%i", fd);
- if (chmod(procfs_path, m) < 0)
- return -errno;
+ if (chmod(procfs_path, m) < 0) {
+ if (errno != ENOENT)
+ return -errno;
+
+ if (proc_mounted() == 0)
+ return -ENOSYS; /* if we have no /proc/, the concept is not implementable */
+
+ return -ENOENT;
+ }
return 0;
}
int inotify_add_watch_fd(int fd, int what, uint32_t mask) {
char path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int) + 1];
- int r;
+ int wd;
/* This is like inotify_add_watch(), except that the file to watch is not referenced by a path, but by an fd */
xsprintf(path, "/proc/self/fd/%i", what);
- r = inotify_add_watch(fd, path, mask);
- if (r < 0)
+ wd = inotify_add_watch(fd, path, mask);
+ if (wd < 0)
return -errno;
- return r;
+ return wd;
+}
+
+int inotify_add_watch_and_warn(int fd, const char *pathname, uint32_t mask) {
+ int wd;
+
+ wd = inotify_add_watch(fd, pathname, mask);
+ if (wd < 0) {
+ if (errno == ENOSPC)
+ return log_error_errno(errno, "Failed to add a watch for %s: inotify watch limit reached", pathname);
+
+ return log_error_errno(errno, "Failed to add a watch for %s: %m", pathname);
+ }
+
+ return wd;
}
static bool unsafe_transition(const struct stat *a, const struct stat *b) {
n1, path);
}
-int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret) {
+int chase_symlinks(const char *path, const char *original_root, unsigned flags, char **ret_path, int *ret_fd) {
_cleanup_free_ char *buffer = NULL, *done = NULL, *root = NULL;
_cleanup_close_ int fd = -1;
unsigned max_follow = CHASE_SYMLINKS_MAX; /* how many symlinks to follow before giving up and returning ELOOP */
assert(path);
/* Either the file may be missing, or we return an fd to the final object, but both make no sense */
- if (FLAGS_SET(flags, CHASE_NONEXISTENT | CHASE_OPEN))
+ if ((flags & CHASE_NONEXISTENT) && ret_fd)
return -EINVAL;
- if (FLAGS_SET(flags, CHASE_STEP | CHASE_OPEN))
+ if ((flags & CHASE_STEP) && ret_fd)
return -EINVAL;
if (isempty(path))
* process. On each iteration, we move one component from "todo" to "done", processing it's special meaning
* each time. The "todo" path always starts with at least one slash, the "done" path always ends in no
* slash. We always keep an O_PATH fd to the component we are currently processing, thus keeping lookup races
- * at a minimum.
+ * to a minimum.
*
* Suggested usage: whenever you want to canonicalize a path, use this function. Pass the absolute path you got
* as-is: fully qualified and relative to your host's root. Optionally, specify the root parameter to tell this
* function what to do when encountering a symlink with an absolute path as directory: prefix it by the
* specified path.
*
- * There are three ways to invoke this function:
+ * There are five ways to invoke this function:
*
- * 1. Without CHASE_STEP or CHASE_OPEN: in this case the path is resolved and the normalized path is returned
- * in `ret`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set 0 is returned if the file
- * doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set >= 0 is returned if the destination was
- * found, -ENOENT if it doesn't.
+ * 1. Without CHASE_STEP or ret_fd: in this case the path is resolved and the normalized path is
+ * returned in `ret_path`. The return value is < 0 on error. If CHASE_NONEXISTENT is also set, 0
+ * is returned if the file doesn't exist, > 0 otherwise. If CHASE_NONEXISTENT is not set, >= 0 is
+ * returned if the destination was found, -ENOENT if it wasn't.
*
- * 2. With CHASE_OPEN: in this case the destination is opened after chasing it as O_PATH and this file
+ * 2. With ret_fd: in this case the destination is opened after chasing it as O_PATH and this file
* descriptor is returned as return value. This is useful to open files relative to some root
* directory. Note that the returned O_PATH file descriptors must be converted into a regular one (using
- * fd_reopen() or such) before it can be used for reading/writing. CHASE_OPEN may not be combined with
+ * fd_reopen() or such) before it can be used for reading/writing. ret_fd may not be combined with
* CHASE_NONEXISTENT.
*
* 3. With CHASE_STEP: in this case only a single step of the normalization is executed, i.e. only the first
*
* 4. With CHASE_SAFE: in this case the path must not contain unsafe transitions, i.e. transitions from
* unprivileged to privileged files or directories. In such cases the return value is -ENOLINK. If
- * CHASE_WARN is also set a warning describing the unsafe transition is emitted.
- *
- * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, the path normalization is
- * aborted and -EREMOTE is returned. If CHASE_WARN is also set a warning showing the path of the mount point
- * is emitted.
+ * CHASE_WARN is also set, a warning describing the unsafe transition is emitted.
*
- * */
+ * 5. With CHASE_NO_AUTOFS: in this case if an autofs mount point is encountered, path normalization
+ * is aborted and -EREMOTE is returned. If CHASE_WARN is also set, a warning showing the path of
+ * the mount point is emitted.
+ */
/* A root directory of "/" or "" is identical to none */
if (empty_or_root(original_root))
original_root = NULL;
- if (!original_root && !ret && (flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_OPEN|CHASE_STEP)) == CHASE_OPEN) {
- /* Shortcut the CHASE_OPEN case if the caller isn't interested in the actual path and has no root set
+ if (!original_root && !ret_path && !(flags & (CHASE_NONEXISTENT|CHASE_NO_AUTOFS|CHASE_SAFE|CHASE_STEP)) && ret_fd) {
+ /* Shortcut the ret_fd case if the caller isn't interested in the actual path and has no root set
* and doesn't care about any of the other special features we provide either. */
r = open(path, O_PATH|O_CLOEXEC|((flags & CHASE_NOFOLLOW) ? O_NOFOLLOW : 0));
if (r < 0)
return -errno;
- return r;
+ *ret_fd = r;
+ return 0;
}
if (original_root) {
if (r < 0)
return r;
- if (flags & CHASE_PREFIX_ROOT) {
+ /* Simplify the root directory, so that it has no duplicate slashes and nothing at the
+ * end. While we won't resolve the root path we still simplify it. Note that dropping the
+ * trailing slash should not change behaviour, since when opening it we specify O_DIRECTORY
+ * anyway. Moreover at the end of this function after processing everything we'll always turn
+ * the empty string back to "/". */
+ delete_trailing_chars(root, "/");
+ path_simplify(root, true);
+ if (flags & CHASE_PREFIX_ROOT) {
/* We don't support relative paths in combination with a root directory */
if (!path_is_absolute(path))
return -EINVAL;
if (r < 0)
return r;
- fd = open("/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+ fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
return -errno;
}
+ if (root) {
+ _cleanup_free_ char *absolute = NULL;
+ const char *e;
+
+ /* If we are operating on a root directory, let's take the root directory as it is. */
+
+ e = path_startswith(buffer, root);
+ if (!e)
+ return log_full_errno(flags & CHASE_WARN ? LOG_WARNING : LOG_DEBUG,
+ SYNTHETIC_ERRNO(ECHRNG),
+ "Specified path '%s' is outside of specified root directory '%s', refusing to resolve.",
+ path, root);
+
+ done = strdup(root);
+ if (!done)
+ return -ENOMEM;
+
+ /* Make sure "todo" starts with a slash */
+ absolute = strjoin("/", e);
+ if (!absolute)
+ return -ENOMEM;
+
+ free_and_replace(buffer, absolute);
+ }
+
todo = buffer;
for (;;) {
_cleanup_free_ char *first = NULL;
/* Determine length of first component in the path */
n = strspn(todo, "/"); /* The slashes */
+
+ if (n > 1) {
+ /* If we are looking at more than a single slash then skip all but one, so that when
+ * we are done with everything we have a normalized path with only single slashes
+ * separating the path components. */
+ todo += n - 1;
+ n = 1;
+ }
+
m = n + strcspn(todo + n, "/"); /* The entire length of the component */
/* Extract the first component. */
if (fstat(child, &st) < 0)
return -errno;
if ((flags & CHASE_SAFE) &&
- (empty_or_root(root) || (size_t)(todo - buffer) > strlen(root)) &&
unsafe_transition(&previous_stat, &st))
return log_unsafe_transition(fd, child, path, flags);
if (S_ISLNK(st.st_mode) && !((flags & CHASE_NOFOLLOW) && isempty(todo))) {
char *joined;
-
_cleanup_free_ char *destination = NULL;
/* This is a symlink, in this case read the destination. But let's make sure we don't follow
* directory as base. */
safe_close(fd);
- fd = open(root ?: "/", O_CLOEXEC|O_NOFOLLOW|O_PATH);
+ fd = open(root ?: "/", O_CLOEXEC|O_DIRECTORY|O_PATH);
if (fd < 0)
return -errno;
return -ENOMEM;
}
- if (ret)
- *ret = TAKE_PTR(done);
+ if (ret_path)
+ *ret_path = TAKE_PTR(done);
- if (flags & CHASE_OPEN) {
- /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a proper fd by
- * opening /proc/self/fd/xyz. */
+ if (ret_fd) {
+ /* Return the O_PATH fd we currently are looking to the caller. It can translate it to a
+ * proper fd by opening /proc/self/fd/xyz. */
assert(fd >= 0);
- return TAKE_FD(fd);
+ *ret_fd = TAKE_FD(fd);
}
if (flags & CHASE_STEP)
return exists;
chased_one:
- if (ret) {
+ if (ret_path) {
char *c;
c = strjoin(strempty(done), todo);
if (!c)
return -ENOMEM;
- *ret = c;
+ *ret_path = c;
}
return 0;
return r;
}
- path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
- if (path_fd < 0)
- return path_fd;
+ r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
r = fd_reopen(path_fd, open_flags);
if (r < 0)
_cleanup_close_ int path_fd = -1;
_cleanup_free_ char *p = NULL;
DIR *d;
+ int r;
if (!ret_dir)
return -EINVAL;
return 0;
}
- path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
- if (path_fd < 0)
- return path_fd;
+ r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
xsprintf(procfs_path, "/proc/self/fd/%i", path_fd);
d = opendir(procfs_path);
const char *root,
unsigned chase_flags,
char **ret_path,
- struct stat *ret_stat) {
+ struct stat *ret_stat,
+ int *ret_fd) {
_cleanup_close_ int path_fd = -1;
_cleanup_free_ char *p = NULL;
+ int r;
assert(path);
assert(ret_stat);
return 1;
}
- path_fd = chase_symlinks(path, root, chase_flags|CHASE_OPEN, ret_path ? &p : NULL);
- if (path_fd < 0)
- return path_fd;
+ r = chase_symlinks(path, root, chase_flags, ret_path ? &p : NULL, &path_fd);
+ if (r < 0)
+ return r;
+ assert(path_fd >= 0);
if (fstat(path_fd, ret_stat) < 0)
return -errno;
if (ret_path)
*ret_path = TAKE_PTR(p);
-
- if (chase_flags & CHASE_OPEN)
- return TAKE_FD(path_fd);
+ if (ret_fd)
+ *ret_fd = TAKE_FD(path_fd);
return 1;
}
return 0;
}
+int fsync_full(int fd) {
+ int r, q;
+
+ /* Sync both the file and the directory */
+
+ r = fsync(fd) < 0 ? -errno : 0;
+ q = fsync_directory_of_file(fd);
+
+ return r < 0 ? r : q;
+}
+
int fsync_path_at(int at_fd, const char *path) {
_cleanup_close_ int opened_fd = -1;
int fd;
return fd;
}
+
+int path_is_encrypted(const char *path) {
+ _cleanup_free_ char *uuids = NULL;
+ char p[SYS_BLOCK_PATH_MAX("/dm/uuid")];
+ dev_t devt;
+ int r;
+
+ r = get_block_device(path, &devt);
+ if (r < 0)
+ return r;
+ if (r == 0) /* doesn't have a block device */
+ return false;
+
+ xsprintf_sys_block_path(p, "/dm/uuid", devt);
+ r = read_one_line_file(p, &uuids);
+ if (r == -ENOENT)
+ return false;
+ if (r < 0)
+ return r;
+
+ /* The DM device's uuid attribute is prefixed with "CRYPT-" if this is a dm-crypt device. */
+ return !!startswith(uuids, "CRYPT-");
+}