xref: /linux/samples/vfs/mountinfo.c (revision 100ceb4817a2ac650e29f107cf97161ce3e2289a)
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