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 } 275