return safe_atoi(p, ret_mnt_id);
}
+static bool filename_possibly_with_slash_suffix(const char *s) {
+ const char *slash, *copied;
+
+ /* Checks whether the specified string is either file name, or a filename with a suffix of
+ * slashes. But nothing else.
+ *
+ * this is OK: foo, bar, foo/, bar/, foo//, bar///
+ * this is not OK: "", "/", "/foo", "foo/bar", ".", ".." … */
+
+ slash = strchr(s, '/');
+ if (!slash)
+ return filename_is_valid(s);
+
+ if (slash - s > FILENAME_MAX) /* We want to allocate on the stack below, hence do a size check first */
+ return false;
+
+ if (slash[strspn(slash, "/")] != 0) /* Check that the suffix consist only of one or more slashes */
+ return false;
+
+ copied = strndupa(s, slash - s);
+ return filename_is_valid(copied);
+}
+
int fd_is_mount_point(int fd, const char *filename, int flags) {
_cleanup_free_ struct file_handle *h = NULL, *h_parent = NULL;
int mount_id = -1, mount_id_parent = -1;
assert(filename);
assert((flags & ~(AT_SYMLINK_FOLLOW|AT_EMPTY_PATH)) == 0);
+ /* Insist that the specified filename is actually a filename, and not a path, i.e. some inode further
+ * up or down the tree then immediately below the specified directory fd. */
+ if (!filename_possibly_with_slash_suffix(filename))
+ return -EINVAL;
+
/* First we will try statx()' STATX_ATTR_MOUNT_ROOT attribute, which is our ideal API, available
* since kernel 5.8.
*
assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
}
+static void test_fd_is_mount_point(void) {
+ _cleanup_close_ int fd = -1;
+
+ log_info("/* %s */", __func__);
+
+ fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY);
+ assert_se(fd >= 0);
+
+ /* Not allowed, since "/" is a path, not a plain filename */
+ assert_se(fd_is_mount_point(fd, "/", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, ".", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "./", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "..", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "../", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "/proc", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "/proc/", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "proc/sys", 0) == -EINVAL);
+ assert_se(fd_is_mount_point(fd, "proc/sys/", 0) == -EINVAL);
+
+ /* This one definitely is a mount point */
+ assert_se(fd_is_mount_point(fd, "proc", 0) > 0);
+ assert_se(fd_is_mount_point(fd, "proc/", 0) > 0);
+
+ /* /root's entire raison d'etre is to be on the root file system (i.e. not in /home/ which might be
+ * split off), so that the user can always log in, so it cannot be a mount point unless the system is
+ * borked. Let's allow for it to be missing though. */
+ assert_se(IN_SET(fd_is_mount_point(fd, "root", 0), -ENOENT, 0));
+ assert_se(IN_SET(fd_is_mount_point(fd, "root/", 0), -ENOENT, 0));
+}
+
int main(int argc, char *argv[]) {
test_setup_logging(LOG_DEBUG);
test_mnt_id();
test_path_is_mount_point();
+ test_fd_is_mount_point();
return 0;
}
#include "exec-util.h"
#include "fd-util.h"
#include "macro.h"
-#include "mountpoint-util.h"
#include "path-util.h"
#include "process-util.h"
#include "rm-rf.h"
}
static void test_path(void) {
- _cleanup_close_ int fd = -1;
-
log_info("/* %s */", __func__);
test_path_compare("/goo", "/goo", 0);
assert_se(streq(basename("/aa///file..."), "file..."));
assert_se(streq(basename("file.../"), ""));
- fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY|O_NOCTTY);
- assert_se(fd >= 0);
- assert_se(fd_is_mount_point(fd, "/", 0) > 0);
-
test_path_simplify("aaa/bbb////ccc", "aaa/bbb/ccc", "aaa/bbb/ccc");
test_path_simplify("//aaa/.////ccc", "/aaa/./ccc", "/aaa/ccc");
test_path_simplify("///", "/", "/");