| // SPDX-License-Identifier: GPL-2.0-or-later |
| |
| /* |
| * Use pidfds, nsfds, listmount() and statmount() mimic the |
| * contents of /proc/self/mountinfo. |
| */ |
| #define _GNU_SOURCE |
| #define __SANE_USERSPACE_TYPES__ |
| #include <stdio.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <alloca.h> |
| #include <getopt.h> |
| #include <stdlib.h> |
| #include <stdbool.h> |
| #include <errno.h> |
| |
| #include "samples-vfs.h" |
| |
| /* max mounts per listmount call */ |
| #define MAXMOUNTS 1024 |
| |
| /* size of struct statmount (including trailing string buffer) */ |
| #define STATMOUNT_BUFSIZE 4096 |
| |
| static bool ext_format; |
| |
| #ifndef __NR_pidfd_open |
| #define __NR_pidfd_open -1 |
| #endif |
| |
| /* |
| * There are no bindings in glibc for listmount() and statmount() (yet), |
| * make our own here. |
| */ |
| static int statmount(__u64 mnt_id, __u64 mnt_ns_id, __u64 mask, |
| struct statmount *buf, size_t bufsize, |
| unsigned int flags) |
| { |
| struct mnt_id_req req = { |
| .size = MNT_ID_REQ_SIZE_VER0, |
| .mnt_id = mnt_id, |
| .param = mask, |
| }; |
| |
| if (mnt_ns_id) { |
| req.size = MNT_ID_REQ_SIZE_VER1; |
| req.mnt_ns_id = mnt_ns_id; |
| } |
| |
| return syscall(__NR_statmount, &req, buf, bufsize, flags); |
| } |
| |
| static ssize_t listmount(__u64 mnt_id, __u64 mnt_ns_id, __u64 last_mnt_id, |
| __u64 list[], size_t num, unsigned int flags) |
| { |
| struct mnt_id_req req = { |
| .size = MNT_ID_REQ_SIZE_VER0, |
| .mnt_id = mnt_id, |
| .param = last_mnt_id, |
| }; |
| |
| if (mnt_ns_id) { |
| req.size = MNT_ID_REQ_SIZE_VER1; |
| req.mnt_ns_id = mnt_ns_id; |
| } |
| |
| return syscall(__NR_listmount, &req, list, num, flags); |
| } |
| |
| static void show_mnt_attrs(__u64 flags) |
| { |
| printf("%s", flags & MOUNT_ATTR_RDONLY ? "ro" : "rw"); |
| |
| if (flags & MOUNT_ATTR_NOSUID) |
| printf(",nosuid"); |
| if (flags & MOUNT_ATTR_NODEV) |
| printf(",nodev"); |
| if (flags & MOUNT_ATTR_NOEXEC) |
| printf(",noexec"); |
| |
| switch (flags & MOUNT_ATTR__ATIME) { |
| case MOUNT_ATTR_RELATIME: |
| printf(",relatime"); |
| break; |
| case MOUNT_ATTR_NOATIME: |
| printf(",noatime"); |
| break; |
| case MOUNT_ATTR_STRICTATIME: |
| /* print nothing */ |
| break; |
| } |
| |
| if (flags & MOUNT_ATTR_NODIRATIME) |
| printf(",nodiratime"); |
| if (flags & MOUNT_ATTR_NOSYMFOLLOW) |
| printf(",nosymfollow"); |
| if (flags & MOUNT_ATTR_IDMAP) |
| printf(",idmapped"); |
| } |
| |
| static void show_propagation(struct statmount *sm) |
| { |
| if (sm->mnt_propagation & MS_SHARED) |
| printf(" shared:%llu", sm->mnt_peer_group); |
| if (sm->mnt_propagation & MS_SLAVE) { |
| printf(" master:%llu", sm->mnt_master); |
| if (sm->propagate_from && sm->propagate_from != sm->mnt_master) |
| printf(" propagate_from:%llu", sm->propagate_from); |
| } |
| if (sm->mnt_propagation & MS_UNBINDABLE) |
| printf(" unbindable"); |
| } |
| |
| static void show_sb_flags(__u64 flags) |
| { |
| printf("%s", flags & MS_RDONLY ? "ro" : "rw"); |
| if (flags & MS_SYNCHRONOUS) |
| printf(",sync"); |
| if (flags & MS_DIRSYNC) |
| printf(",dirsync"); |
| if (flags & MS_MANDLOCK) |
| printf(",mand"); |
| if (flags & MS_LAZYTIME) |
| printf(",lazytime"); |
| } |
| |
| static int dump_mountinfo(__u64 mnt_id, __u64 mnt_ns_id) |
| { |
| int ret; |
| struct statmount *buf = alloca(STATMOUNT_BUFSIZE); |
| const __u64 mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC | |
| STATMOUNT_PROPAGATE_FROM | STATMOUNT_FS_TYPE | |
| STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT | |
| STATMOUNT_MNT_OPTS | STATMOUNT_FS_SUBTYPE | |
| STATMOUNT_SB_SOURCE; |
| |
| ret = statmount(mnt_id, mnt_ns_id, mask, buf, STATMOUNT_BUFSIZE, 0); |
| if (ret < 0) { |
| perror("statmount"); |
| return 1; |
| } |
| |
| if (ext_format) |
| printf("0x%llx 0x%llx 0x%llx ", mnt_ns_id, mnt_id, buf->mnt_parent_id); |
| |
| printf("%u %u %u:%u %s %s ", buf->mnt_id_old, buf->mnt_parent_id_old, |
| buf->sb_dev_major, buf->sb_dev_minor, |
| &buf->str[buf->mnt_root], |
| &buf->str[buf->mnt_point]); |
| show_mnt_attrs(buf->mnt_attr); |
| show_propagation(buf); |
| |
| printf(" - %s", &buf->str[buf->fs_type]); |
| if (buf->mask & STATMOUNT_FS_SUBTYPE) |
| printf(".%s", &buf->str[buf->fs_subtype]); |
| if (buf->mask & STATMOUNT_SB_SOURCE) |
| printf(" %s ", &buf->str[buf->sb_source]); |
| else |
| printf(" :none "); |
| |
| show_sb_flags(buf->sb_flags); |
| if (buf->mask & STATMOUNT_MNT_OPTS) |
| printf(",%s", &buf->str[buf->mnt_opts]); |
| printf("\n"); |
| return 0; |
| } |
| |
| static int dump_mounts(__u64 mnt_ns_id) |
| { |
| __u64 mntid[MAXMOUNTS]; |
| __u64 last_mnt_id = 0; |
| ssize_t count; |
| int i; |
| |
| /* |
| * Get a list of all mntids in mnt_ns_id. If it returns MAXMOUNTS |
| * mounts, then go again until we get everything. |
| */ |
| do { |
| count = listmount(LSMT_ROOT, mnt_ns_id, last_mnt_id, mntid, MAXMOUNTS, 0); |
| if (count < 0 || count > MAXMOUNTS) { |
| errno = count < 0 ? errno : count; |
| perror("listmount"); |
| return 1; |
| } |
| |
| /* Walk the returned mntids and print info about each */ |
| for (i = 0; i < count; ++i) { |
| int ret = dump_mountinfo(mntid[i], mnt_ns_id); |
| |
| if (ret != 0) |
| return ret; |
| } |
| /* Set up last_mnt_id to pick up where we left off */ |
| last_mnt_id = mntid[count - 1]; |
| } while (count == MAXMOUNTS); |
| return 0; |
| } |
| |
| static void usage(const char * const prog) |
| { |
| printf("Usage:\n"); |
| printf("%s [-e] [-p pid] [-r] [-h]\n", prog); |
| printf(" -e: extended format\n"); |
| printf(" -h: print usage message\n"); |
| printf(" -p: get mount namespace from given pid\n"); |
| printf(" -r: recursively print all mounts in all child namespaces\n"); |
| } |
| |
| int main(int argc, char * const *argv) |
| { |
| struct mnt_ns_info mni = { .size = MNT_NS_INFO_SIZE_VER0 }; |
| int pidfd, mntns, ret, opt; |
| pid_t pid = getpid(); |
| bool recursive = false; |
| |
| while ((opt = getopt(argc, argv, "ehp:r")) != -1) { |
| switch (opt) { |
| case 'e': |
| ext_format = true; |
| break; |
| case 'h': |
| usage(argv[0]); |
| return 0; |
| case 'p': |
| pid = atoi(optarg); |
| break; |
| case 'r': |
| recursive = true; |
| break; |
| } |
| } |
| |
| /* Get a pidfd for pid */ |
| pidfd = syscall(__NR_pidfd_open, pid, 0); |
| if (pidfd < 0) { |
| perror("pidfd_open"); |
| return 1; |
| } |
| |
| /* Get the mnt namespace for pidfd */ |
| mntns = ioctl(pidfd, PIDFD_GET_MNT_NAMESPACE, NULL); |
| if (mntns < 0) { |
| perror("PIDFD_GET_MNT_NAMESPACE"); |
| return 1; |
| } |
| close(pidfd); |
| |
| /* get info about mntns. In particular, the mnt_ns_id */ |
| ret = ioctl(mntns, NS_MNT_GET_INFO, &mni); |
| if (ret < 0) { |
| perror("NS_MNT_GET_INFO"); |
| return 1; |
| } |
| |
| do { |
| int ret; |
| |
| ret = dump_mounts(mni.mnt_ns_id); |
| if (ret) |
| return ret; |
| |
| if (!recursive) |
| break; |
| |
| /* get the next mntns (and overwrite the old mount ns info) */ |
| ret = ioctl(mntns, NS_MNT_GET_NEXT, &mni); |
| close(mntns); |
| mntns = ret; |
| } while (mntns >= 0); |
| |
| return 0; |
| } |