Blame SOURCES/0519-path-util-make-use-of-mnt_id-field-exported-in-proc-.patch

17b0f1
From f63b66b6347a8d8e5e6930a939d1997bfd8e2e7c Mon Sep 17 00:00:00 2001
17b0f1
From: Jan Synacek <jsynacek@redhat.com>
17b0f1
Date: Fri, 28 Jul 2017 15:31:50 +0200
17b0f1
Subject: [PATCH] path-util: make use of "mnt_id" field exported in
17b0f1
 /proc/self/fdinfo/<fd>
17b0f1
17b0f1
This commit is not a backport of a specific commit. It includes parts of
17b0f1
several upstream commits (3f72b427b44f39a1aec6806dad6f6b57103ae9ed,
17b0f1
5d409034017e9f9f8c4392157d95511fc2e05d87 and others).
17b0f1
17b0f1
The main goal was to bring path_is_mount_point() up to date, which meant
17b0f1
introducing fd_fdinfo_mnt_id() and fd_is_mount_point(). These were
17b0f1
needed mainly because we need to determine mount points based on
17b0f1
/proc/self/fdinfo/<fd> in containers. Also, there are more places in the
17b0f1
code where checks for mount points are performed, which would benefit from
17b0f1
this fix as well. Additionally, corresponding tests has been added.
17b0f1
17b0f1
Resolves: #1472439
17b0f1
---
17b0f1
 src/core/automount.c                        |   2 +-
17b0f1
 src/core/machine-id-setup.c                 |   2 +-
17b0f1
 src/core/mount-setup.c                      |   2 +-
17b0f1
 src/efi-boot-generator/efi-boot-generator.c |   2 +-
17b0f1
 src/gpt-auto-generator/gpt-auto-generator.c |   2 +-
17b0f1
 src/login/logind-user.c                     |   2 +-
17b0f1
 src/nspawn/nspawn.c                         |  10 +-
17b0f1
 src/shared/cgroup-util.c                    |   2 +-
17b0f1
 src/shared/condition.c                      |   2 +-
17b0f1
 src/shared/path-util.c                      | 209 +++++++++++++++-----
17b0f1
 src/shared/path-util.h                      |   3 +-
17b0f1
 src/test/test-path-util.c                   |  66 ++++++-
17b0f1
 12 files changed, 242 insertions(+), 62 deletions(-)
17b0f1
17b0f1
diff --git a/src/core/automount.c b/src/core/automount.c
17b0f1
index 4e066613d7..eedd9b8243 100644
17b0f1
--- a/src/core/automount.c
17b0f1
+++ b/src/core/automount.c
17b0f1
@@ -749,7 +749,7 @@ static int automount_start(Unit *u) {
17b0f1
         assert(a);
17b0f1
         assert(a->state == AUTOMOUNT_DEAD || a->state == AUTOMOUNT_FAILED);
17b0f1
 
17b0f1
-        if (path_is_mount_point(a->where, false)) {
17b0f1
+        if (path_is_mount_point(a->where, 0)) {
17b0f1
                 log_unit_error(u->id,
17b0f1
                                "Path %s is already a mount point, refusing start for %s",
17b0f1
                                a->where, u->id);
17b0f1
diff --git a/src/core/machine-id-setup.c b/src/core/machine-id-setup.c
17b0f1
index d00a53246f..1121d373fa 100644
17b0f1
--- a/src/core/machine-id-setup.c
17b0f1
+++ b/src/core/machine-id-setup.c
17b0f1
@@ -203,7 +203,7 @@ int machine_id_commit(const char *root) {
17b0f1
                 etc_machine_id = path_kill_slashes(x);
17b0f1
         }
17b0f1
 
17b0f1
-        r = path_is_mount_point(etc_machine_id, false);
17b0f1
+        r = path_is_mount_point(etc_machine_id, 0);
17b0f1
         if (r < 0)
17b0f1
                 return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", etc_machine_id);
17b0f1
         if (r == 0) {
17b0f1
diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c
17b0f1
index 521545e5ce..2b8fbab1a3 100644
17b0f1
--- a/src/core/mount-setup.c
17b0f1
+++ b/src/core/mount-setup.c
17b0f1
@@ -160,7 +160,7 @@ static int mount_one(const MountPoint *p, bool relabel) {
17b0f1
         if (relabel)
17b0f1
                 label_fix(p->where, true, true);
17b0f1
 
17b0f1
-        r = path_is_mount_point(p->where, true);
17b0f1
+        r = path_is_mount_point(p->where, AT_SYMLINK_FOLLOW);
17b0f1
         if (r < 0)
17b0f1
                 return r;
17b0f1
 
17b0f1
diff --git a/src/efi-boot-generator/efi-boot-generator.c b/src/efi-boot-generator/efi-boot-generator.c
17b0f1
index b3ff3a8b78..5492b19946 100644
17b0f1
--- a/src/efi-boot-generator/efi-boot-generator.c
17b0f1
+++ b/src/efi-boot-generator/efi-boot-generator.c
17b0f1
@@ -69,7 +69,7 @@ int main(int argc, char *argv[]) {
17b0f1
                 return EXIT_SUCCESS;
17b0f1
         }
17b0f1
 
17b0f1
-        if (path_is_mount_point("/boot", true) <= 0 &&
17b0f1
+        if (path_is_mount_point("/boot", AT_SYMLINK_FOLLOW) <= 0 &&
17b0f1
             dir_is_empty("/boot") <= 0) {
17b0f1
                 log_debug("/boot already populated, exiting.");
17b0f1
                 return EXIT_SUCCESS;
17b0f1
diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c
17b0f1
index 00a2141a58..d7b047118d 100644
17b0f1
--- a/src/gpt-auto-generator/gpt-auto-generator.c
17b0f1
+++ b/src/gpt-auto-generator/gpt-auto-generator.c
17b0f1
@@ -299,7 +299,7 @@ static int probe_and_add_mount(
17b0f1
         assert(where);
17b0f1
         assert(description);
17b0f1
 
17b0f1
-        if (path_is_mount_point(where, true) <= 0 &&
17b0f1
+        if (path_is_mount_point(where, AT_SYMLINK_FOLLOW) <= 0 &&
17b0f1
             dir_is_empty(where) <= 0) {
17b0f1
                 log_debug("%s already populated, ignoring.", where);
17b0f1
                 return 0;
17b0f1
diff --git a/src/login/logind-user.c b/src/login/logind-user.c
17b0f1
index 4298704cea..912c50ebde 100644
17b0f1
--- a/src/login/logind-user.c
17b0f1
+++ b/src/login/logind-user.c
17b0f1
@@ -320,7 +320,7 @@ static int user_mkdir_runtime_path(User *u) {
17b0f1
         } else
17b0f1
                 p = u->runtime_path;
17b0f1
 
17b0f1
-        if (path_is_mount_point(p, false) <= 0) {
17b0f1
+        if (path_is_mount_point(p, 0) <= 0) {
17b0f1
                 _cleanup_free_ char *t = NULL;
17b0f1
 
17b0f1
                 (void) mkdir(p, 0700);
17b0f1
diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c
17b0f1
index ea365b3f9b..a90a3a5d75 100644
17b0f1
--- a/src/nspawn/nspawn.c
17b0f1
+++ b/src/nspawn/nspawn.c
17b0f1
@@ -863,7 +863,7 @@ static int mount_all(const char *dest) {
17b0f1
                 if (!where)
17b0f1
                         return log_oom();
17b0f1
 
17b0f1
-                t = path_is_mount_point(where, true);
17b0f1
+                t = path_is_mount_point(where, AT_SYMLINK_FOLLOW);
17b0f1
                 if (t < 0) {
17b0f1
                         log_error_errno(t, "Failed to detect whether %s is a mount point: %m", where);
17b0f1
 
17b0f1
@@ -989,7 +989,7 @@ static int mount_cgroup_hierarchy(const char *dest, const char *controller, cons
17b0f1
 
17b0f1
         to = strjoina(dest, "/sys/fs/cgroup/", hierarchy);
17b0f1
 
17b0f1
-        r = path_is_mount_point(to, false);
17b0f1
+        r = path_is_mount_point(to, 0);
17b0f1
         if (r < 0)
17b0f1
                 return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to);
17b0f1
         if (r > 0)
17b0f1
@@ -1787,7 +1787,7 @@ static int setup_journal(const char *directory) {
17b0f1
         if (!p || !q)
17b0f1
                 return log_oom();
17b0f1
 
17b0f1
-        if (path_is_mount_point(p, false) > 0) {
17b0f1
+        if (path_is_mount_point(p, 0) > 0) {
17b0f1
                 if (arg_link_journal != LINK_AUTO) {
17b0f1
                         log_error("%s: already a mount point, refusing to use for journal", p);
17b0f1
                         return -EEXIST;
17b0f1
@@ -1796,7 +1796,7 @@ static int setup_journal(const char *directory) {
17b0f1
                 return 0;
17b0f1
         }
17b0f1
 
17b0f1
-        if (path_is_mount_point(q, false) > 0) {
17b0f1
+        if (path_is_mount_point(q, 0) > 0) {
17b0f1
                 if (arg_link_journal != LINK_AUTO) {
17b0f1
                         log_error("%s: already a mount point, refusing to use for journal", q);
17b0f1
                         return -EEXIST;
17b0f1
@@ -3665,7 +3665,7 @@ int main(int argc, char *argv[]) {
17b0f1
                          * the specified is not a mount point we
17b0f1
                          * create the new snapshot in the parent
17b0f1
                          * directory, just next to it. */
17b0f1
-                        r = path_is_mount_point(arg_directory, false);
17b0f1
+                        r = path_is_mount_point(arg_directory, 0);
17b0f1
                         if (r < 0) {
17b0f1
                                 log_error_errno(r, "Failed to determine whether directory %s is mount point: %m", arg_directory);
17b0f1
                                 goto finish;
17b0f1
diff --git a/src/shared/cgroup-util.c b/src/shared/cgroup-util.c
17b0f1
index c5d9e4bb58..cf085cb5ff 100644
17b0f1
--- a/src/shared/cgroup-util.c
17b0f1
+++ b/src/shared/cgroup-util.c
17b0f1
@@ -488,7 +488,7 @@ int cg_get_path(const char *controller, const char *path, const char *suffix, ch
17b0f1
         if (_unlikely_(!good)) {
17b0f1
                 int r;
17b0f1
 
17b0f1
-                r = path_is_mount_point("/sys/fs/cgroup", false);
17b0f1
+                r = path_is_mount_point("/sys/fs/cgroup", 0);
17b0f1
                 if (r <= 0)
17b0f1
                         return r < 0 ? r : -ENOENT;
17b0f1
 
17b0f1
diff --git a/src/shared/condition.c b/src/shared/condition.c
17b0f1
index 796cc520d7..0d2cd2bc3a 100644
17b0f1
--- a/src/shared/condition.c
17b0f1
+++ b/src/shared/condition.c
17b0f1
@@ -350,7 +350,7 @@ static int condition_test_path_is_mount_point(Condition *c) {
17b0f1
         assert(c->parameter);
17b0f1
         assert(c->type == CONDITION_PATH_IS_MOUNT_POINT);
17b0f1
 
17b0f1
-        return path_is_mount_point(c->parameter, true) > 0;
17b0f1
+        return path_is_mount_point(c->parameter, AT_SYMLINK_FOLLOW) > 0;
17b0f1
 }
17b0f1
 
17b0f1
 static int condition_test_path_is_read_write(Condition *c) {
17b0f1
diff --git a/src/shared/path-util.c b/src/shared/path-util.c
17b0f1
index 1181ffb9d4..0f252ec262 100644
17b0f1
--- a/src/shared/path-util.c
17b0f1
+++ b/src/shared/path-util.c
17b0f1
@@ -36,6 +36,7 @@
17b0f1
 #include "strv.h"
17b0f1
 #include "path-util.h"
17b0f1
 #include "missing.h"
17b0f1
+#include "fileio.h"
17b0f1
 
17b0f1
 bool path_is_absolute(const char *p) {
17b0f1
         return p[0] == '/';
17b0f1
@@ -473,87 +474,203 @@ char* path_join(const char *root, const char *path, const char *rest) {
17b0f1
                                NULL);
17b0f1
 }
17b0f1
 
17b0f1
-int path_is_mount_point(const char *t, bool allow_symlink) {
17b0f1
+static int fd_fdinfo_mnt_id(int fd, const char *filename, int flags, int *mnt_id) {
17b0f1
+        char path[strlen("/proc/self/fdinfo/") + DECIMAL_STR_MAX(int)];
17b0f1
+        _cleanup_free_ char *fdinfo = NULL;
17b0f1
+        _cleanup_close_ int subfd = -1;
17b0f1
+        char *p;
17b0f1
+        int r;
17b0f1
+
17b0f1
+        if ((flags & AT_EMPTY_PATH) && isempty(filename))
17b0f1
+                xsprintf(path, "/proc/self/fdinfo/%i", fd);
17b0f1
+        else {
17b0f1
+                subfd = openat(fd, filename, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH);
17b0f1
+                if (subfd < 0)
17b0f1
+                        return -errno;
17b0f1
+
17b0f1
+                xsprintf(path, "/proc/self/fdinfo/%i", subfd);
17b0f1
+        }
17b0f1
+
17b0f1
+        r = read_full_file(path, &fdinfo, NULL);
17b0f1
+        if (r == -ENOENT) /* The fdinfo directory is a relatively new addition */
17b0f1
+                return -EOPNOTSUPP;
17b0f1
+        if (r < 0)
17b0f1
+                return -errno;
17b0f1
+
17b0f1
+        p = startswith(fdinfo, "mnt_id:");
17b0f1
+        if (!p) {
17b0f1
+                p = strstr(fdinfo, "\nmnt_id:");
17b0f1
+                if (!p) /* The mnt_id field is a relatively new addition */
17b0f1
+                        return -EOPNOTSUPP;
17b0f1
+
17b0f1
+                p += 8;
17b0f1
+        }
17b0f1
 
17b0f1
-        union file_handle_union h = FILE_HANDLE_INIT;
17b0f1
+        p += strspn(p, WHITESPACE);
17b0f1
+        p[strcspn(p, WHITESPACE)] = 0;
17b0f1
+
17b0f1
+        return safe_atoi(p, mnt_id);
17b0f1
+}
17b0f1
+
17b0f1
+int fd_is_mount_point(int fd, const char *filename, int flags) {
17b0f1
+        union file_handle_union h = FILE_HANDLE_INIT, h_parent = FILE_HANDLE_INIT;
17b0f1
         int mount_id = -1, mount_id_parent = -1;
17b0f1
-        _cleanup_free_ char *parent = NULL;
17b0f1
+        bool nosupp = false, check_st_dev = true;
17b0f1
         struct stat a, b;
17b0f1
         int r;
17b0f1
-        bool nosupp = false;
17b0f1
 
17b0f1
-        /* We are not actually interested in the file handles, but
17b0f1
-         * name_to_handle_at() also passes us the mount ID, hence use
17b0f1
-         * it but throw the handle away */
17b0f1
+        assert(fd >= 0);
17b0f1
+        assert(filename);
17b0f1
 
17b0f1
-        if (path_equal(t, "/"))
17b0f1
-                return 1;
17b0f1
-
17b0f1
-        r = name_to_handle_at(AT_FDCWD, t, &h.handle, &mount_id, allow_symlink ? AT_SYMLINK_FOLLOW : 0);
17b0f1
+        /* First we will try the name_to_handle_at() syscall, which
17b0f1
+         * tells us the mount id and an opaque file "handle". It is
17b0f1
+         * not supported everywhere though (kernel compile-time
17b0f1
+         * option, not all file systems are hooked up). If it works
17b0f1
+         * the mount id is usually good enough to tell us whether
17b0f1
+         * something is a mount point.
17b0f1
+         *
17b0f1
+         * If that didn't work we will try to read the mount id from
17b0f1
+         * /proc/self/fdinfo/<fd>. This is almost as good as
17b0f1
+         * name_to_handle_at(), however, does not return the
17b0f1
+         * opaque file handle. The opaque file handle is pretty useful
17b0f1
+         * to detect the root directory, which we should always
17b0f1
+         * consider a mount point. Hence we use this only as
17b0f1
+         * fallback. Exporting the mnt_id in fdinfo is a pretty recent
17b0f1
+         * kernel addition.
17b0f1
+         *
17b0f1
+         * As last fallback we do traditional fstat() based st_dev
17b0f1
+         * comparisons. This is how things were traditionally done,
17b0f1
+         * but unionfs breaks breaks this since it exposes file
17b0f1
+         * systems with a variety of st_dev reported. Also, btrfs
17b0f1
+         * subvolumes have different st_dev, even though they aren't
17b0f1
+         * real mounts of their own. */
17b0f1
+
17b0f1
+        r = name_to_handle_at(fd, filename, &h.handle, &mount_id, flags);
17b0f1
         if (r < 0) {
17b0f1
                 if (errno == ENOSYS)
17b0f1
                         /* This kernel does not support name_to_handle_at()
17b0f1
-                         * fall back to the traditional stat() logic. */
17b0f1
-                        goto fallback;
17b0f1
+                         * fall back to simpler logic. */
17b0f1
+                        goto fallback_fdinfo;
17b0f1
                 else if (errno == EOPNOTSUPP)
17b0f1
                         /* This kernel or file system does not support
17b0f1
-                         * name_to_handle_at(), hence fallback to the
17b0f1
+                         * name_to_handle_at(), hence let's see if the
17b0f1
+                         * upper fs supports it (in which case it is a
17b0f1
+                         * mount point), otherwise fallback to the
17b0f1
                          * traditional stat() logic */
17b0f1
                         nosupp = true;
17b0f1
-                else if (errno == ENOENT)
17b0f1
-                        return 0;
17b0f1
                 else
17b0f1
                         return -errno;
17b0f1
         }
17b0f1
 
17b0f1
-        r = path_get_parent(t, &parent);
17b0f1
-        if (r < 0)
17b0f1
-                return r;
17b0f1
-
17b0f1
-        h.handle.handle_bytes = MAX_HANDLE_SZ;
17b0f1
-        r = name_to_handle_at(AT_FDCWD, parent, &h.handle, &mount_id_parent, AT_SYMLINK_FOLLOW);
17b0f1
-        if (r < 0)
17b0f1
-                if (errno == EOPNOTSUPP)
17b0f1
+        r = name_to_handle_at(fd, "", &h_parent.handle, &mount_id_parent, AT_EMPTY_PATH);
17b0f1
+        if (r < 0) {
17b0f1
+                if (errno == EOPNOTSUPP) {
17b0f1
                         if (nosupp)
17b0f1
                                 /* Neither parent nor child do name_to_handle_at()?
17b0f1
                                    We have no choice but to fall back. */
17b0f1
-                                goto fallback;
17b0f1
+                                goto fallback_fdinfo;
17b0f1
                         else
17b0f1
-                                /* The parent can't do name_to_handle_at() but
17b0f1
-                                 * the directory we are interested in can?
17b0f1
-                                 * Or the other way around?
17b0f1
+                                /* The parent can't do name_to_handle_at() but the
17b0f1
+                                 * directory we are interested in can?
17b0f1
                                  * If so, it must be a mount point. */
17b0f1
                                 return 1;
17b0f1
-                else
17b0f1
+                } else
17b0f1
                         return -errno;
17b0f1
-        else
17b0f1
-                return mount_id != mount_id_parent;
17b0f1
+        }
17b0f1
 
17b0f1
-fallback:
17b0f1
-        if (allow_symlink)
17b0f1
-                r = stat(t, &a);
17b0f1
-        else
17b0f1
-                r = lstat(t, &a);
17b0f1
+        /* The parent can do name_to_handle_at() but the
17b0f1
+         * directory we are interested in can't? If so, it
17b0f1
+         * must be a mount point. */
17b0f1
+        if (nosupp)
17b0f1
+                return 1;
17b0f1
 
17b0f1
-        if (r < 0) {
17b0f1
-                if (errno == ENOENT)
17b0f1
-                        return 0;
17b0f1
+        /* If the file handle for the directory we are
17b0f1
+         * interested in and its parent are identical, we
17b0f1
+         * assume this is the root directory, which is a mount
17b0f1
+         * point. */
17b0f1
 
17b0f1
-                return -errno;
17b0f1
-        }
17b0f1
+        if (h.handle.handle_bytes == h_parent.handle.handle_bytes &&
17b0f1
+            h.handle.handle_type == h_parent.handle.handle_type &&
17b0f1
+            memcmp(h.handle.f_handle, h_parent.handle.f_handle, h.handle.handle_bytes) == 0)
17b0f1
+                return 1;
17b0f1
 
17b0f1
-        free(parent);
17b0f1
-        parent = NULL;
17b0f1
+        return mount_id != mount_id_parent;
17b0f1
 
17b0f1
-        r = path_get_parent(t, &parent);
17b0f1
+fallback_fdinfo:
17b0f1
+        r = fd_fdinfo_mnt_id(fd, filename, flags, &mount_id);
17b0f1
+        if (r == -EOPNOTSUPP)
17b0f1
+                goto fallback_fstat;
17b0f1
         if (r < 0)
17b0f1
                 return r;
17b0f1
 
17b0f1
-        r = stat(parent, &b);
17b0f1
+        r = fd_fdinfo_mnt_id(fd, "", AT_EMPTY_PATH, &mount_id_parent);
17b0f1
         if (r < 0)
17b0f1
+                return r;
17b0f1
+
17b0f1
+        if (mount_id != mount_id_parent)
17b0f1
+                return 1;
17b0f1
+
17b0f1
+        /* Hmm, so, the mount ids are the same. This leaves one
17b0f1
+         * special case though for the root file system. For that,
17b0f1
+         * let's see if the parent directory has the same inode as we
17b0f1
+         * are interested in. Hence, let's also do fstat() checks now,
17b0f1
+         * too, but avoid the st_dev comparisons, since they aren't
17b0f1
+         * that useful on unionfs mounts. */
17b0f1
+        check_st_dev = false;
17b0f1
+
17b0f1
+fallback_fstat:
17b0f1
+        /* yay for fstatat() taking a different set of flags than the other
17b0f1
+         * _at() above */
17b0f1
+        if (flags & AT_SYMLINK_FOLLOW)
17b0f1
+                flags &= ~AT_SYMLINK_FOLLOW;
17b0f1
+        else
17b0f1
+                flags |= AT_SYMLINK_NOFOLLOW;
17b0f1
+        if (fstatat(fd, filename, &a, flags) < 0)
17b0f1
+                return -errno;
17b0f1
+
17b0f1
+        if (fstatat(fd, "", &b, AT_EMPTY_PATH) < 0)
17b0f1
+                return -errno;
17b0f1
+
17b0f1
+        /* A directory with same device and inode as its parent? Must
17b0f1
+         * be the root directory */
17b0f1
+        if (a.st_dev == b.st_dev &&
17b0f1
+            a.st_ino == b.st_ino)
17b0f1
+                return 1;
17b0f1
+
17b0f1
+        return check_st_dev && (a.st_dev != b.st_dev);
17b0f1
+}
17b0f1
+
17b0f1
+/* flags can be AT_SYMLINK_FOLLOW or 0 */
17b0f1
+int path_is_mount_point(const char *t, int flags) {
17b0f1
+        _cleanup_close_ int fd = -1;
17b0f1
+        _cleanup_free_ char *canonical = NULL, *parent = NULL;
17b0f1
+
17b0f1
+        assert(t);
17b0f1
+
17b0f1
+        if (path_equal(t, "/"))
17b0f1
+                return 1;
17b0f1
+
17b0f1
+        /* we need to resolve symlinks manually, we can't just rely on
17b0f1
+         * fd_is_mount_point() to do that for us; if we have a structure like
17b0f1
+         * /bin -> /usr/bin/ and /usr is a mount point, then the parent that we
17b0f1
+         * look at needs to be /usr, not /. */
17b0f1
+        if (flags & AT_SYMLINK_FOLLOW) {
17b0f1
+                canonical = canonicalize_file_name(t);
17b0f1
+                if (!canonical)
17b0f1
+                        return -errno;
17b0f1
+
17b0f1
+                t = canonical;
17b0f1
+        }
17b0f1
+
17b0f1
+        parent = dirname_malloc(t);
17b0f1
+        if (!parent)
17b0f1
+                return -ENOMEM;
17b0f1
+
17b0f1
+        fd = openat(AT_FDCWD, parent, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_PATH);
17b0f1
+        if (fd < 0)
17b0f1
                 return -errno;
17b0f1
 
17b0f1
-        return a.st_dev != b.st_dev;
17b0f1
+        return fd_is_mount_point(fd, basename(t), flags);
17b0f1
 }
17b0f1
 
17b0f1
 int path_is_read_only_fs(const char *path) {
17b0f1
diff --git a/src/shared/path-util.h b/src/shared/path-util.h
17b0f1
index 71bb740e98..e16484087f 100644
17b0f1
--- a/src/shared/path-util.h
17b0f1
+++ b/src/shared/path-util.h
17b0f1
@@ -53,7 +53,8 @@ char** path_strv_make_absolute_cwd(char **l);
17b0f1
 char** path_strv_resolve(char **l, const char *prefix);
17b0f1
 char** path_strv_resolve_uniq(char **l, const char *prefix);
17b0f1
 
17b0f1
-int path_is_mount_point(const char *path, bool allow_symlink);
17b0f1
+int fd_is_mount_point(int fd, const char *filename, int flags);
17b0f1
+int path_is_mount_point(const char *path, int flags);
17b0f1
 int path_is_read_only_fs(const char *path);
17b0f1
 int path_is_os_tree(const char *path);
17b0f1
 
17b0f1
diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c
17b0f1
index 6396fcb398..8870f178a1 100644
17b0f1
--- a/src/test/test-path-util.c
17b0f1
+++ b/src/test/test-path-util.c
17b0f1
@@ -21,6 +21,7 @@
17b0f1
 
17b0f1
 #include <stdio.h>
17b0f1
 #include <unistd.h>
17b0f1
+#include <sys/mount.h>
17b0f1
 
17b0f1
 #include "path-util.h"
17b0f1
 #include "util.h"
17b0f1
@@ -85,8 +86,8 @@ static void test_path(void) {
17b0f1
         test_parent("/aa///file...", "/aa///");
17b0f1
         test_parent("file.../", NULL);
17b0f1
 
17b0f1
-        assert_se(path_is_mount_point("/", true));
17b0f1
-        assert_se(path_is_mount_point("/", false));
17b0f1
+        assert_se(path_is_mount_point("/", AT_SYMLINK_FOLLOW));
17b0f1
+        assert_se(path_is_mount_point("/", 0));
17b0f1
 
17b0f1
         {
17b0f1
                 char p1[] = "aaa/bbb////ccc";
17b0f1
@@ -99,6 +100,66 @@ static void test_path(void) {
17b0f1
         }
17b0f1
 }
17b0f1
 
17b0f1
+static void test_path_is_mount_point(void) {
17b0f1
+        int fd, rt, rf, rlt, rlf;
17b0f1
+        char tmp_dir[] = "/tmp/test-path-is-mount-point-XXXXXX";
17b0f1
+        _cleanup_free_ char *file1 = NULL, *file2 = NULL, *link1 = NULL, *link2 = NULL;
17b0f1
+
17b0f1
+        assert_se(path_is_mount_point("/", AT_SYMLINK_FOLLOW) > 0);
17b0f1
+        assert_se(path_is_mount_point("/", 0) > 0);
17b0f1
+
17b0f1
+        assert_se(path_is_mount_point("/proc", AT_SYMLINK_FOLLOW) > 0);
17b0f1
+        assert_se(path_is_mount_point("/proc", 0) > 0);
17b0f1
+
17b0f1
+        assert_se(path_is_mount_point("/proc/1", AT_SYMLINK_FOLLOW) == 0);
17b0f1
+        assert_se(path_is_mount_point("/proc/1", 0) == 0);
17b0f1
+
17b0f1
+        assert_se(path_is_mount_point("/sys", AT_SYMLINK_FOLLOW) > 0);
17b0f1
+        assert_se(path_is_mount_point("/sys", 0) > 0);
17b0f1
+
17b0f1
+        /* file mountpoints */
17b0f1
+        assert_se(mkdtemp(tmp_dir) != NULL);
17b0f1
+        file1 = path_join(NULL, tmp_dir, "file1");
17b0f1
+        assert_se(file1);
17b0f1
+        file2 = path_join(NULL, tmp_dir, "file2");
17b0f1
+        assert_se(file2);
17b0f1
+        fd = open(file1, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664);
17b0f1
+        assert_se(fd > 0);
17b0f1
+        close(fd);
17b0f1
+        fd = open(file2, O_WRONLY|O_CREAT|O_EXCL|O_CLOEXEC, 0664);
17b0f1
+        assert_se(fd > 0);
17b0f1
+        close(fd);
17b0f1
+        link1 = path_join(NULL, tmp_dir, "link1");
17b0f1
+        assert_se(link1);
17b0f1
+        assert_se(symlink("file1", link1) == 0);
17b0f1
+        link2 = path_join(NULL, tmp_dir, "link2");
17b0f1
+        assert_se(link1);
17b0f1
+        assert_se(symlink("file2", link2) == 0);
17b0f1
+
17b0f1
+        assert_se(path_is_mount_point(file1, AT_SYMLINK_FOLLOW) == 0);
17b0f1
+        assert_se(path_is_mount_point(file1, 0) == 0);
17b0f1
+        assert_se(path_is_mount_point(link1, AT_SYMLINK_FOLLOW) == 0);
17b0f1
+        assert_se(path_is_mount_point(link1, 0) == 0);
17b0f1
+
17b0f1
+        /* this test will only work as root */
17b0f1
+        if (mount(file1, file2, NULL, MS_BIND, NULL) >= 0) {
17b0f1
+                rf = path_is_mount_point(file2, 0);
17b0f1
+                rt = path_is_mount_point(file2, AT_SYMLINK_FOLLOW);
17b0f1
+                rlf = path_is_mount_point(link2, 0);
17b0f1
+                rlt = path_is_mount_point(link2, AT_SYMLINK_FOLLOW);
17b0f1
+
17b0f1
+                assert_se(umount(file2) == 0);
17b0f1
+
17b0f1
+                assert_se(rf == 1);
17b0f1
+                assert_se(rt == 1);
17b0f1
+                assert_se(rlf == 0);
17b0f1
+                assert_se(rlt == 1);
17b0f1
+        } else
17b0f1
+                printf("Skipping bind mount file test: %m\n");
17b0f1
+
17b0f1
+        assert_se(rm_rf(tmp_dir, false, true, false) == 0);
17b0f1
+}
17b0f1
+
17b0f1
 static void test_find_binary(const char *self, bool local) {
17b0f1
         char *p;
17b0f1
 
17b0f1
@@ -288,6 +349,7 @@ int main(int argc, char **argv) {
17b0f1
         test_make_relative();
17b0f1
         test_strv_resolve();
17b0f1
         test_path_startswith();
17b0f1
+        test_path_is_mount_point();
17b0f1
 
17b0f1
         return 0;
17b0f1
 }