|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | + |
| 3 | +/* |
| 4 | + * Use pidfds, nsfds, listmount() and statmount() mimic the |
| 5 | + * contents of /proc/self/mountinfo. |
| 6 | + */ |
| 7 | +#define _GNU_SOURCE |
| 8 | +#define __SANE_USERSPACE_TYPES__ |
| 9 | +#include <stdio.h> |
| 10 | +#include <stdint.h> |
| 11 | +#include <sys/ioctl.h> |
| 12 | +#include <sys/syscall.h> |
| 13 | +#include <linux/pidfd.h> |
| 14 | +#include <linux/mount.h> |
| 15 | +#include <linux/nsfs.h> |
| 16 | +#include <unistd.h> |
| 17 | +#include <alloca.h> |
| 18 | +#include <getopt.h> |
| 19 | +#include <stdlib.h> |
| 20 | +#include <stdbool.h> |
| 21 | +#include <errno.h> |
| 22 | + |
| 23 | +/* max mounts per listmount call */ |
| 24 | +#define MAXMOUNTS 1024 |
| 25 | + |
| 26 | +/* size of struct statmount (including trailing string buffer) */ |
| 27 | +#define STATMOUNT_BUFSIZE 4096 |
| 28 | + |
| 29 | +static bool ext_format; |
| 30 | + |
| 31 | +/* |
| 32 | + * There are no bindings in glibc for listmount() and statmount() (yet), |
| 33 | + * make our own here. |
| 34 | + */ |
| 35 | +static int statmount(uint64_t mnt_id, uint64_t mnt_ns_id, uint64_t mask, |
| 36 | + struct statmount *buf, size_t bufsize, |
| 37 | + unsigned int flags) |
| 38 | +{ |
| 39 | + struct mnt_id_req req = { |
| 40 | + .size = MNT_ID_REQ_SIZE_VER0, |
| 41 | + .mnt_id = mnt_id, |
| 42 | + .param = mask, |
| 43 | + }; |
| 44 | + |
| 45 | + if (mnt_ns_id) { |
| 46 | + req.size = MNT_ID_REQ_SIZE_VER1; |
| 47 | + req.mnt_ns_id = mnt_ns_id; |
| 48 | + } |
| 49 | + |
| 50 | + return syscall(__NR_statmount, &req, buf, bufsize, flags); |
| 51 | +} |
| 52 | + |
| 53 | +static ssize_t listmount(uint64_t mnt_id, uint64_t mnt_ns_id, |
| 54 | + uint64_t last_mnt_id, uint64_t list[], size_t num, |
| 55 | + unsigned int flags) |
| 56 | +{ |
| 57 | + struct mnt_id_req req = { |
| 58 | + .size = MNT_ID_REQ_SIZE_VER0, |
| 59 | + .mnt_id = mnt_id, |
| 60 | + .param = last_mnt_id, |
| 61 | + }; |
| 62 | + |
| 63 | + if (mnt_ns_id) { |
| 64 | + req.size = MNT_ID_REQ_SIZE_VER1; |
| 65 | + req.mnt_ns_id = mnt_ns_id; |
| 66 | + } |
| 67 | + |
| 68 | + return syscall(__NR_listmount, &req, list, num, flags); |
| 69 | +} |
| 70 | + |
| 71 | +static void show_mnt_attrs(uint64_t flags) |
| 72 | +{ |
| 73 | + printf("%s", flags & MOUNT_ATTR_RDONLY ? "ro" : "rw"); |
| 74 | + |
| 75 | + if (flags & MOUNT_ATTR_NOSUID) |
| 76 | + printf(",nosuid"); |
| 77 | + if (flags & MOUNT_ATTR_NODEV) |
| 78 | + printf(",nodev"); |
| 79 | + if (flags & MOUNT_ATTR_NOEXEC) |
| 80 | + printf(",noexec"); |
| 81 | + |
| 82 | + switch (flags & MOUNT_ATTR__ATIME) { |
| 83 | + case MOUNT_ATTR_RELATIME: |
| 84 | + printf(",relatime"); |
| 85 | + break; |
| 86 | + case MOUNT_ATTR_NOATIME: |
| 87 | + printf(",noatime"); |
| 88 | + break; |
| 89 | + case MOUNT_ATTR_STRICTATIME: |
| 90 | + /* print nothing */ |
| 91 | + break; |
| 92 | + } |
| 93 | + |
| 94 | + if (flags & MOUNT_ATTR_NODIRATIME) |
| 95 | + printf(",nodiratime"); |
| 96 | + if (flags & MOUNT_ATTR_NOSYMFOLLOW) |
| 97 | + printf(",nosymfollow"); |
| 98 | + if (flags & MOUNT_ATTR_IDMAP) |
| 99 | + printf(",idmapped"); |
| 100 | +} |
| 101 | + |
| 102 | +static void show_propagation(struct statmount *sm) |
| 103 | +{ |
| 104 | + if (sm->mnt_propagation & MS_SHARED) |
| 105 | + printf(" shared:%llu", sm->mnt_peer_group); |
| 106 | + if (sm->mnt_propagation & MS_SLAVE) { |
| 107 | + printf(" master:%llu", sm->mnt_master); |
| 108 | + if (sm->propagate_from && sm->propagate_from != sm->mnt_master) |
| 109 | + printf(" propagate_from:%llu", sm->propagate_from); |
| 110 | + } |
| 111 | + if (sm->mnt_propagation & MS_UNBINDABLE) |
| 112 | + printf(" unbindable"); |
| 113 | +} |
| 114 | + |
| 115 | +static void show_sb_flags(uint64_t flags) |
| 116 | +{ |
| 117 | + printf("%s", flags & MS_RDONLY ? "ro" : "rw"); |
| 118 | + if (flags & MS_SYNCHRONOUS) |
| 119 | + printf(",sync"); |
| 120 | + if (flags & MS_DIRSYNC) |
| 121 | + printf(",dirsync"); |
| 122 | + if (flags & MS_MANDLOCK) |
| 123 | + printf(",mand"); |
| 124 | + if (flags & MS_LAZYTIME) |
| 125 | + printf(",lazytime"); |
| 126 | +} |
| 127 | + |
| 128 | +static int dump_mountinfo(uint64_t mnt_id, uint64_t mnt_ns_id) |
| 129 | +{ |
| 130 | + int ret; |
| 131 | + struct statmount *buf = alloca(STATMOUNT_BUFSIZE); |
| 132 | + const uint64_t mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC | |
| 133 | + STATMOUNT_PROPAGATE_FROM | STATMOUNT_FS_TYPE | |
| 134 | + STATMOUNT_MNT_ROOT | STATMOUNT_MNT_POINT | |
| 135 | + STATMOUNT_MNT_OPTS | STATMOUNT_FS_SUBTYPE | |
| 136 | + STATMOUNT_SB_SOURCE; |
| 137 | + |
| 138 | + ret = statmount(mnt_id, mnt_ns_id, mask, buf, STATMOUNT_BUFSIZE, 0); |
| 139 | + if (ret < 0) { |
| 140 | + perror("statmount"); |
| 141 | + return 1; |
| 142 | + } |
| 143 | + |
| 144 | + if (ext_format) |
| 145 | + printf("0x%lx 0x%lx 0x%llx ", mnt_ns_id, mnt_id, buf->mnt_parent_id); |
| 146 | + |
| 147 | + printf("%u %u %u:%u %s %s ", buf->mnt_id_old, buf->mnt_parent_id_old, |
| 148 | + buf->sb_dev_major, buf->sb_dev_minor, |
| 149 | + &buf->str[buf->mnt_root], |
| 150 | + &buf->str[buf->mnt_point]); |
| 151 | + show_mnt_attrs(buf->mnt_attr); |
| 152 | + show_propagation(buf); |
| 153 | + |
| 154 | + printf(" - %s", &buf->str[buf->fs_type]); |
| 155 | + if (buf->mask & STATMOUNT_FS_SUBTYPE) |
| 156 | + printf(".%s", &buf->str[buf->fs_subtype]); |
| 157 | + if (buf->mask & STATMOUNT_SB_SOURCE) |
| 158 | + printf(" %s ", &buf->str[buf->sb_source]); |
| 159 | + else |
| 160 | + printf(" :none "); |
| 161 | + |
| 162 | + show_sb_flags(buf->sb_flags); |
| 163 | + if (buf->mask & STATMOUNT_MNT_OPTS) |
| 164 | + printf(",%s", &buf->str[buf->mnt_opts]); |
| 165 | + printf("\n"); |
| 166 | + return 0; |
| 167 | +} |
| 168 | + |
| 169 | +static int dump_mounts(uint64_t mnt_ns_id) |
| 170 | +{ |
| 171 | + uint64_t mntid[MAXMOUNTS]; |
| 172 | + uint64_t last_mnt_id = 0; |
| 173 | + ssize_t count; |
| 174 | + int i; |
| 175 | + |
| 176 | + /* |
| 177 | + * Get a list of all mntids in mnt_ns_id. If it returns MAXMOUNTS |
| 178 | + * mounts, then go again until we get everything. |
| 179 | + */ |
| 180 | + do { |
| 181 | + count = listmount(LSMT_ROOT, mnt_ns_id, last_mnt_id, mntid, MAXMOUNTS, 0); |
| 182 | + if (count < 0 || count > MAXMOUNTS) { |
| 183 | + errno = count < 0 ? errno : count; |
| 184 | + perror("listmount"); |
| 185 | + return 1; |
| 186 | + } |
| 187 | + |
| 188 | + /* Walk the returned mntids and print info about each */ |
| 189 | + for (i = 0; i < count; ++i) { |
| 190 | + int ret = dump_mountinfo(mntid[i], mnt_ns_id); |
| 191 | + |
| 192 | + if (ret != 0) |
| 193 | + return ret; |
| 194 | + } |
| 195 | + /* Set up last_mnt_id to pick up where we left off */ |
| 196 | + last_mnt_id = mntid[count - 1]; |
| 197 | + } while (count == MAXMOUNTS); |
| 198 | + return 0; |
| 199 | +} |
| 200 | + |
| 201 | +static void usage(const char * const prog) |
| 202 | +{ |
| 203 | + printf("Usage:\n"); |
| 204 | + printf("%s [-e] [-p pid] [-r] [-h]\n", prog); |
| 205 | + printf(" -e: extended format\n"); |
| 206 | + printf(" -h: print usage message\n"); |
| 207 | + printf(" -p: get mount namespace from given pid\n"); |
| 208 | + printf(" -r: recursively print all mounts in all child namespaces\n"); |
| 209 | +} |
| 210 | + |
| 211 | +int main(int argc, char * const *argv) |
| 212 | +{ |
| 213 | + struct mnt_ns_info mni = { .size = MNT_NS_INFO_SIZE_VER0 }; |
| 214 | + int pidfd, mntns, ret, opt; |
| 215 | + pid_t pid = getpid(); |
| 216 | + bool recursive = false; |
| 217 | + |
| 218 | + while ((opt = getopt(argc, argv, "ehp:r")) != -1) { |
| 219 | + switch (opt) { |
| 220 | + case 'e': |
| 221 | + ext_format = true; |
| 222 | + break; |
| 223 | + case 'h': |
| 224 | + usage(argv[0]); |
| 225 | + return 0; |
| 226 | + case 'p': |
| 227 | + pid = atoi(optarg); |
| 228 | + break; |
| 229 | + case 'r': |
| 230 | + recursive = true; |
| 231 | + break; |
| 232 | + } |
| 233 | + } |
| 234 | + |
| 235 | + /* Get a pidfd for pid */ |
| 236 | + pidfd = syscall(SYS_pidfd_open, pid, 0); |
| 237 | + if (pidfd < 0) { |
| 238 | + perror("pidfd_open"); |
| 239 | + return 1; |
| 240 | + } |
| 241 | + |
| 242 | + /* Get the mnt namespace for pidfd */ |
| 243 | + mntns = ioctl(pidfd, PIDFD_GET_MNT_NAMESPACE, NULL); |
| 244 | + if (mntns < 0) { |
| 245 | + perror("PIDFD_GET_MNT_NAMESPACE"); |
| 246 | + return 1; |
| 247 | + } |
| 248 | + close(pidfd); |
| 249 | + |
| 250 | + /* get info about mntns. In particular, the mnt_ns_id */ |
| 251 | + ret = ioctl(mntns, NS_MNT_GET_INFO, &mni); |
| 252 | + if (ret < 0) { |
| 253 | + perror("NS_MNT_GET_INFO"); |
| 254 | + return 1; |
| 255 | + } |
| 256 | + |
| 257 | + do { |
| 258 | + int ret; |
| 259 | + |
| 260 | + ret = dump_mounts(mni.mnt_ns_id); |
| 261 | + if (ret) |
| 262 | + return ret; |
| 263 | + |
| 264 | + if (!recursive) |
| 265 | + break; |
| 266 | + |
| 267 | + /* get the next mntns (and overwrite the old mount ns info) */ |
| 268 | + ret = ioctl(mntns, NS_MNT_GET_NEXT, &mni); |
| 269 | + close(mntns); |
| 270 | + mntns = ret; |
| 271 | + } while (mntns >= 0); |
| 272 | + |
| 273 | + return 0; |
| 274 | +} |
0 commit comments