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 <unistd.h>
12 #include <alloca.h>
13 #include <getopt.h>
14 #include <stdlib.h>
15 #include <stdbool.h>
16 #include <errno.h>
17
18 #include "samples-vfs.h"
19
20 /* max mounts per listmount call */
21 #define MAXMOUNTS 1024
22
23 /* size of struct statmount (including trailing string buffer) */
24 #define STATMOUNT_BUFSIZE 4096
25
26 static bool ext_format;
27
28 #ifndef __NR_pidfd_open
29 #define __NR_pidfd_open -1
30 #endif
31
32 /*
33 * There are no bindings in glibc for listmount() and statmount() (yet),
34 * make our own here.
35 */
statmount(__u64 mnt_id,__u64 mnt_ns_id,__u64 mask,struct statmount * buf,size_t bufsize,unsigned int flags)36 static int statmount(__u64 mnt_id, __u64 mnt_ns_id, __u64 mask,
37 struct statmount *buf, size_t bufsize,
38 unsigned int flags)
39 {
40 struct mnt_id_req req = {
41 .size = MNT_ID_REQ_SIZE_VER0,
42 .mnt_id = mnt_id,
43 .param = mask,
44 };
45
46 if (mnt_ns_id) {
47 req.size = MNT_ID_REQ_SIZE_VER1;
48 req.mnt_ns_id = mnt_ns_id;
49 }
50
51 return syscall(__NR_statmount, &req, buf, bufsize, flags);
52 }
53
listmount(__u64 mnt_id,__u64 mnt_ns_id,__u64 last_mnt_id,__u64 list[],size_t num,unsigned int flags)54 static ssize_t listmount(__u64 mnt_id, __u64 mnt_ns_id, __u64 last_mnt_id,
55 __u64 list[], size_t num, 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
show_mnt_attrs(__u64 flags)71 static void show_mnt_attrs(__u64 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
show_propagation(struct statmount * sm)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
show_sb_flags(__u64 flags)115 static void show_sb_flags(__u64 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
dump_mountinfo(__u64 mnt_id,__u64 mnt_ns_id)128 static int dump_mountinfo(__u64 mnt_id, __u64 mnt_ns_id)
129 {
130 int ret;
131 struct statmount *buf = alloca(STATMOUNT_BUFSIZE);
132 const __u64 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%llx 0x%llx 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
dump_mounts(__u64 mnt_ns_id)169 static int dump_mounts(__u64 mnt_ns_id)
170 {
171 __u64 mntid[MAXMOUNTS];
172 __u64 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
usage(const char * const prog)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
main(int argc,char * const * argv)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(__NR_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