File Index
Files and directories
Sign in
FILE

statmount_test.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/filesystems/statmount/statmount_test.c C
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2
  3#define _GNU_SOURCE
  4
  5#include <assert.h>
  6#include <stddef.h>
  7#include <sched.h>
  8#include <fcntl.h>
  9#include <sys/param.h>
 10#include <sys/mount.h>
 11#include <sys/stat.h>
 12#include <sys/statfs.h>
 13#include <linux/stat.h>
 14
 15#include "statmount.h"
 16#include "kselftest.h"
 17
 18static const char *const known_fs[] = {
 19	"9p", "adfs", "affs", "afs", "aio", "anon_inodefs", "apparmorfs",
 20	"autofs", "bcachefs", "bdev", "befs", "bfs", "binder", "binfmt_misc",
 21	"bpf", "btrfs", "btrfs_test_fs", "ceph", "cgroup", "cgroup2", "cifs",
 22	"coda", "configfs", "cpuset", "cramfs", "cxl", "dax", "debugfs",
 23	"devpts", "devtmpfs", "dmabuf", "drm", "ecryptfs", "efivarfs", "efs",
 24	"erofs", "exfat", "ext2", "ext3", "ext4", "f2fs", "functionfs",
 25	"fuse", "fuseblk", "fusectl", "gadgetfs", "gfs2", "gfs2meta", "hfs",
 26	"hfsplus", "hostfs", "hpfs", "hugetlbfs", "ibmasmfs", "iomem",
 27	"ipathfs", "iso9660", "jffs2", "jfs", "minix", "mqueue", "msdos",
 28	"nfs", "nfs4", "nfsd", "nilfs2", "nsfs", "ntfs", "ntfs3", "ocfs2",
 29	"ocfs2_dlmfs", "omfs", "openpromfs", "overlay", "pipefs", "proc",
 30	"pstore", "pvfs2", "qnx4", "qnx6", "ramfs", "resctrl", "romfs",
 31	"rootfs", "rpc_pipefs", "s390_hypfs", "secretmem", "securityfs",
 32	"selinuxfs", "smackfs", "smb3", "sockfs", "spufs", "squashfs", "sysfs",
 33	"sysv", "tmpfs", "tracefs", "ubifs", "udf", "ufs", "v7", "vboxsf",
 34	"vfat", "virtiofs", "vxfs", "xenfs", "xfs", "zonefs", NULL };
 35
 36static struct statmount *statmount_alloc(uint64_t mnt_id, uint64_t mask, unsigned int flags)
 37{
 38	size_t bufsize = 1 << 15;
 39	struct statmount *buf = NULL, *tmp = alloca(bufsize);
 40	int tofree = 0;
 41	int ret;
 42
 43	for (;;) {
 44		ret = statmount(mnt_id, 0, mask, tmp, bufsize, flags);
 45		if (ret != -1)
 46			break;
 47		if (tofree)
 48			free(tmp);
 49		if (errno != EOVERFLOW)
 50			return NULL;
 51		bufsize <<= 1;
 52		tofree = 1;
 53		tmp = malloc(bufsize);
 54		if (!tmp)
 55			return NULL;
 56	}
 57	buf = malloc(tmp->size);
 58	if (buf)
 59		memcpy(buf, tmp, tmp->size);
 60	if (tofree)
 61		free(tmp);
 62
 63	return buf;
 64}
 65
 66static void write_file(const char *path, const char *val)
 67{
 68	int fd = open(path, O_WRONLY);
 69	size_t len = strlen(val);
 70	int ret;
 71
 72	if (fd == -1)
 73		ksft_exit_fail_msg("opening %s for write: %s\n", path, strerror(errno));
 74
 75	ret = write(fd, val, len);
 76	if (ret == -1)
 77		ksft_exit_fail_msg("writing to %s: %s\n", path, strerror(errno));
 78	if (ret != len)
 79		ksft_exit_fail_msg("short write to %s\n", path);
 80
 81	ret = close(fd);
 82	if (ret == -1)
 83		ksft_exit_fail_msg("closing %s\n", path);
 84}
 85
 86static uint64_t get_mnt_id(const char *name, const char *path, uint64_t mask)
 87{
 88	struct statx sx;
 89	int ret;
 90
 91	ret = statx(AT_FDCWD, path, 0, mask, &sx);
 92	if (ret == -1)
 93		ksft_exit_fail_msg("retrieving %s mount ID for %s: %s\n",
 94				   mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",
 95				   name, strerror(errno));
 96	if (!(sx.stx_mask & mask))
 97		ksft_exit_fail_msg("no %s mount ID available for %s\n",
 98				   mask & STATX_MNT_ID_UNIQUE ? "unique" : "old",
 99				   name);
100
101	return sx.stx_mnt_id;
102}
103
104
105static char root_mntpoint[] = "/tmp/statmount_test_root.XXXXXX";
106static int orig_root;
107static uint64_t root_id, parent_id;
108static uint32_t old_root_id, old_parent_id;
109static FILE *f_mountinfo;
110
111static void cleanup_namespace(void)
112{
113	int ret;
114
115	ret = fchdir(orig_root);
116	if (ret == -1)
117		ksft_perror("fchdir to original root");
118
119	ret = chroot(".");
120	if (ret == -1)
121		ksft_perror("chroot to original root");
122
123	umount2(root_mntpoint, MNT_DETACH);
124	rmdir(root_mntpoint);
125}
126
127static void setup_namespace(void)
128{
129	int ret;
130	char buf[32];
131	uid_t uid = getuid();
132	gid_t gid = getgid();
133
134	ret = unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWPID);
135	if (ret == -1)
136		ksft_exit_fail_msg("unsharing mountns and userns: %s\n",
137				   strerror(errno));
138
139	sprintf(buf, "0 %d 1", uid);
140	write_file("/proc/self/uid_map", buf);
141	write_file("/proc/self/setgroups", "deny");
142	sprintf(buf, "0 %d 1", gid);
143	write_file("/proc/self/gid_map", buf);
144
145	f_mountinfo = fopen("/proc/self/mountinfo", "re");
146	if (!f_mountinfo)
147		ksft_exit_fail_msg("failed to open mountinfo: %s\n",
148				   strerror(errno));
149
150	ret = mount("", "/", NULL, MS_REC|MS_PRIVATE, NULL);
151	if (ret == -1)
152		ksft_exit_fail_msg("making mount tree private: %s\n",
153				   strerror(errno));
154
155	if (!mkdtemp(root_mntpoint))
156		ksft_exit_fail_msg("creating temporary directory %s: %s\n",
157				   root_mntpoint, strerror(errno));
158
159	old_parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID);
160	parent_id = get_mnt_id("parent", root_mntpoint, STATX_MNT_ID_UNIQUE);
161
162	orig_root = open("/", O_PATH);
163	if (orig_root == -1)
164		ksft_exit_fail_msg("opening root directory: %s",
165				   strerror(errno));
166
167	atexit(cleanup_namespace);
168
169	ret = mount(root_mntpoint, root_mntpoint, NULL, MS_BIND, NULL);
170	if (ret == -1)
171		ksft_exit_fail_msg("mounting temp root %s: %s\n",
172				   root_mntpoint, strerror(errno));
173
174	ret = chroot(root_mntpoint);
175	if (ret == -1)
176		ksft_exit_fail_msg("chroot to temp root %s: %s\n",
177				   root_mntpoint, strerror(errno));
178
179	ret = chdir("/");
180	if (ret == -1)
181		ksft_exit_fail_msg("chdir to root: %s\n", strerror(errno));
182
183	old_root_id = get_mnt_id("root", "/", STATX_MNT_ID);
184	root_id = get_mnt_id("root", "/", STATX_MNT_ID_UNIQUE);
185}
186
187static int setup_mount_tree(int log2_num)
188{
189	int ret, i;
190
191	ret = mount("", "/", NULL, MS_REC|MS_SHARED, NULL);
192	if (ret == -1) {
193		ksft_test_result_fail("making mount tree shared: %s\n",
194				   strerror(errno));
195		return -1;
196	}
197
198	for (i = 0; i < log2_num; i++) {
199		ret = mount("/", "/", NULL, MS_BIND, NULL);
200		if (ret == -1) {
201			ksft_test_result_fail("mounting submount %s: %s\n",
202					      root_mntpoint, strerror(errno));
203			return -1;
204		}
205	}
206	return 0;
207}
208
209static void test_listmount_empty_root(void)
210{
211	ssize_t res;
212	const unsigned int size = 32;
213	uint64_t list[size];
214
215	res = listmount(LSMT_ROOT, 0, 0, list, size, 0);
216	if (res == -1) {
217		ksft_test_result_fail("listmount: %s\n", strerror(errno));
218		return;
219	}
220	if (res != 1) {
221		ksft_test_result_fail("listmount result is %zi != 1\n", res);
222		return;
223	}
224
225	if (list[0] != root_id) {
226		ksft_test_result_fail("listmount ID doesn't match 0x%llx != 0x%llx\n",
227				      (unsigned long long) list[0],
228				      (unsigned long long) root_id);
229		return;
230	}
231
232	ksft_test_result_pass("listmount empty root\n");
233}
234
235static void test_statmount_zero_mask(void)
236{
237	struct statmount sm;
238	int ret;
239
240	ret = statmount(root_id, 0, 0, &sm, sizeof(sm), 0);
241	if (ret == -1) {
242		ksft_test_result_fail("statmount zero mask: %s\n",
243				      strerror(errno));
244		return;
245	}
246	if (sm.size != sizeof(sm)) {
247		ksft_test_result_fail("unexpected size: %u != %u\n",
248				      sm.size, (uint32_t) sizeof(sm));
249		return;
250	}
251	if (sm.mask != 0) {
252		ksft_test_result_fail("unexpected mask: 0x%llx != 0x0\n",
253				      (unsigned long long) sm.mask);
254		return;
255	}
256
257	ksft_test_result_pass("statmount zero mask\n");
258}
259
260static void test_statmount_mnt_basic(void)
261{
262	struct statmount sm;
263	int ret;
264	uint64_t mask = STATMOUNT_MNT_BASIC;
265
266	ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0);
267	if (ret == -1) {
268		ksft_test_result_fail("statmount mnt basic: %s\n",
269				      strerror(errno));
270		return;
271	}
272	if (sm.size != sizeof(sm)) {
273		ksft_test_result_fail("unexpected size: %u != %u\n",
274				      sm.size, (uint32_t) sizeof(sm));
275		return;
276	}
277	if (sm.mask != mask) {
278		ksft_test_result_skip("statmount mnt basic unavailable\n");
279		return;
280	}
281
282	if (sm.mnt_id != root_id) {
283		ksft_test_result_fail("unexpected root ID: 0x%llx != 0x%llx\n",
284				      (unsigned long long) sm.mnt_id,
285				      (unsigned long long) root_id);
286		return;
287	}
288
289	if (sm.mnt_id_old != old_root_id) {
290		ksft_test_result_fail("unexpected old root ID: %u != %u\n",
291				      sm.mnt_id_old, old_root_id);
292		return;
293	}
294
295	if (sm.mnt_parent_id != parent_id) {
296		ksft_test_result_fail("unexpected parent ID: 0x%llx != 0x%llx\n",
297				      (unsigned long long) sm.mnt_parent_id,
298				      (unsigned long long) parent_id);
299		return;
300	}
301
302	if (sm.mnt_parent_id_old != old_parent_id) {
303		ksft_test_result_fail("unexpected old parent ID: %u != %u\n",
304				      sm.mnt_parent_id_old, old_parent_id);
305		return;
306	}
307
308	if (sm.mnt_propagation != MS_PRIVATE) {
309		ksft_test_result_fail("unexpected propagation: 0x%llx\n",
310				      (unsigned long long) sm.mnt_propagation);
311		return;
312	}
313
314	ksft_test_result_pass("statmount mnt basic\n");
315}
316
317
318static void test_statmount_sb_basic(void)
319{
320	struct statmount sm;
321	int ret;
322	uint64_t mask = STATMOUNT_SB_BASIC;
323	struct statx sx;
324	struct statfs sf;
325
326	ret = statmount(root_id, 0, mask, &sm, sizeof(sm), 0);
327	if (ret == -1) {
328		ksft_test_result_fail("statmount sb basic: %s\n",
329				      strerror(errno));
330		return;
331	}
332	if (sm.size != sizeof(sm)) {
333		ksft_test_result_fail("unexpected size: %u != %u\n",
334				      sm.size, (uint32_t) sizeof(sm));
335		return;
336	}
337	if (sm.mask != mask) {
338		ksft_test_result_skip("statmount sb basic unavailable\n");
339		return;
340	}
341
342	ret = statx(AT_FDCWD, "/", 0, 0, &sx);
343	if (ret == -1) {
344		ksft_test_result_fail("stat root failed: %s\n",
345				      strerror(errno));
346		return;
347	}
348
349	if (sm.sb_dev_major != sx.stx_dev_major ||
350	    sm.sb_dev_minor != sx.stx_dev_minor) {
351		ksft_test_result_fail("unexpected sb dev %u:%u != %u:%u\n",
352				      sm.sb_dev_major, sm.sb_dev_minor,
353				      sx.stx_dev_major, sx.stx_dev_minor);
354		return;
355	}
356
357	ret = statfs("/", &sf);
358	if (ret == -1) {
359		ksft_test_result_fail("statfs root failed: %s\n",
360				      strerror(errno));
361		return;
362	}
363
364	if (sm.sb_magic != sf.f_type) {
365		ksft_test_result_fail("unexpected sb magic: 0x%llx != 0x%lx\n",
366				      (unsigned long long) sm.sb_magic,
367				      sf.f_type);
368		return;
369	}
370
371	ksft_test_result_pass("statmount sb basic\n");
372}
373
374static void test_statmount_mnt_point(void)
375{
376	struct statmount *sm;
377
378	sm = statmount_alloc(root_id, STATMOUNT_MNT_POINT, 0);
379	if (!sm) {
380		ksft_test_result_fail("statmount mount point: %s\n",
381				      strerror(errno));
382		return;
383	}
384
385	if (!(sm->mask & STATMOUNT_MNT_POINT)) {
386		ksft_test_result_fail("missing STATMOUNT_MNT_POINT in mask\n");
387		return;
388	}
389	if (strcmp(sm->str + sm->mnt_point, "/") != 0) {
390		ksft_test_result_fail("unexpected mount point: '%s' != '/'\n",
391				      sm->str + sm->mnt_point);
392		goto out;
393	}
394	ksft_test_result_pass("statmount mount point\n");
395out:
396	free(sm);
397}
398
399static void test_statmount_mnt_root(void)
400{
401	struct statmount *sm;
402	const char *mnt_root, *last_dir, *last_root;
403
404	last_dir = strrchr(root_mntpoint, '/');
405	assert(last_dir);
406	last_dir++;
407
408	sm = statmount_alloc(root_id, STATMOUNT_MNT_ROOT, 0);
409	if (!sm) {
410		ksft_test_result_fail("statmount mount root: %s\n",
411				      strerror(errno));
412		return;
413	}
414	if (!(sm->mask & STATMOUNT_MNT_ROOT)) {
415		ksft_test_result_fail("missing STATMOUNT_MNT_ROOT in mask\n");
416		return;
417	}
418	mnt_root = sm->str + sm->mnt_root;
419	last_root = strrchr(mnt_root, '/');
420	if (last_root)
421		last_root++;
422	else
423		last_root = mnt_root;
424
425	if (strcmp(last_dir, last_root) != 0) {
426		ksft_test_result_fail("unexpected mount root last component: '%s' != '%s'\n",
427				      last_root, last_dir);
428		goto out;
429	}
430	ksft_test_result_pass("statmount mount root\n");
431out:
432	free(sm);
433}
434
435static void test_statmount_fs_type(void)
436{
437	struct statmount *sm;
438	const char *fs_type;
439	const char *const *s;
440
441	sm = statmount_alloc(root_id, STATMOUNT_FS_TYPE, 0);
442	if (!sm) {
443		ksft_test_result_fail("statmount fs type: %s\n",
444				      strerror(errno));
445		return;
446	}
447	if (!(sm->mask & STATMOUNT_FS_TYPE)) {
448		ksft_test_result_fail("missing STATMOUNT_FS_TYPE in mask\n");
449		return;
450	}
451	fs_type = sm->str + sm->fs_type;
452	for (s = known_fs; s != NULL; s++) {
453		if (strcmp(fs_type, *s) == 0)
454			break;
455	}
456	if (!s)
457		ksft_print_msg("unknown filesystem type: %s\n", fs_type);
458
459	ksft_test_result_pass("statmount fs type\n");
460	free(sm);
461}
462
463static void test_statmount_mnt_opts(void)
464{
465	struct statmount *sm;
466	const char *statmount_opts;
467	char *line = NULL;
468	size_t len = 0;
469
470	sm = statmount_alloc(root_id, STATMOUNT_MNT_BASIC | STATMOUNT_MNT_OPTS,
471			     0);
472	if (!sm) {
473		ksft_test_result_fail("statmount mnt opts: %s\n",
474				      strerror(errno));
475		return;
476	}
477
478	if (!(sm->mask & STATMOUNT_MNT_BASIC)) {
479		ksft_test_result_fail("missing STATMOUNT_MNT_BASIC in mask\n");
480		return;
481	}
482
483	while (getline(&line, &len, f_mountinfo) != -1) {
484		int i;
485		char *p, *p2;
486		unsigned int old_mnt_id;
487
488		old_mnt_id = atoi(line);
489		if (old_mnt_id != sm->mnt_id_old)
490			continue;
491
492		for (p = line, i = 0; p && i < 5; i++)
493			p = strchr(p + 1, ' ');
494		if (!p)
495			continue;
496
497		p2 = strchr(p + 1, ' ');
498		if (!p2)
499			continue;
500		*p2 = '\0';
501		p = strchr(p2 + 1, '-');
502		if (!p)
503			continue;
504		for (p++, i = 0; p && i < 2; i++)
505			p = strchr(p + 1, ' ');
506		if (!p)
507			continue;
508		p++;
509
510		/* skip generic superblock options */
511		if (strncmp(p, "ro", 2) == 0)
512			p += 2;
513		else if (strncmp(p, "rw", 2) == 0)
514			p += 2;
515		if (*p == ',')
516			p++;
517		if (strncmp(p, "sync", 4) == 0)
518			p += 4;
519		if (*p == ',')
520			p++;
521		if (strncmp(p, "dirsync", 7) == 0)
522			p += 7;
523		if (*p == ',')
524			p++;
525		if (strncmp(p, "lazytime", 8) == 0)
526			p += 8;
527		if (*p == ',')
528			p++;
529		p2 = strrchr(p, '\n');
530		if (p2)
531			*p2 = '\0';
532
533		if (sm->mask & STATMOUNT_MNT_OPTS)
534			statmount_opts = sm->str + sm->mnt_opts;
535		else
536			statmount_opts = "";
537		if (strcmp(statmount_opts, p) != 0)
538			ksft_test_result_fail(
539				"unexpected mount options: '%s' != '%s'\n",
540				statmount_opts, p);
541		else
542			ksft_test_result_pass("statmount mount options\n");
543		free(sm);
544		free(line);
545		return;
546	}
547
548	ksft_test_result_fail("didnt't find mount entry\n");
549	free(sm);
550	free(line);
551}
552
553static void test_statmount_string(uint64_t mask, size_t off, const char *name)
554{
555	struct statmount *sm;
556	size_t len, shortsize, exactsize;
557	uint32_t start, i;
558	int ret;
559
560	sm = statmount_alloc(root_id, mask, 0);
561	if (!sm) {
562		ksft_test_result_fail("statmount %s: %s\n", name,
563				      strerror(errno));
564		goto out;
565	}
566	if (sm->size < sizeof(*sm)) {
567		ksft_test_result_fail("unexpected size: %u < %u\n",
568				      sm->size, (uint32_t) sizeof(*sm));
569		goto out;
570	}
571	if (sm->mask != mask) {
572		ksft_test_result_skip("statmount %s unavailable\n", name);
573		goto out;
574	}
575	len = sm->size - sizeof(*sm);
576	start = ((uint32_t *) sm)[off];
577
578	for (i = start;; i++) {
579		if (i >= len) {
580			ksft_test_result_fail("string out of bounds\n");
581			goto out;
582		}
583		if (!sm->str[i])
584			break;
585	}
586	exactsize = sm->size;
587	shortsize = sizeof(*sm) + i;
588
589	ret = statmount(root_id, 0, mask, sm, exactsize, 0);
590	if (ret == -1) {
591		ksft_test_result_fail("statmount exact size: %s\n",
592				      strerror(errno));
593		goto out;
594	}
595	errno = 0;
596	ret = statmount(root_id, 0, mask, sm, shortsize, 0);
597	if (ret != -1 || errno != EOVERFLOW) {
598		ksft_test_result_fail("should have failed with EOVERFLOW: %s\n",
599				      strerror(errno));
600		goto out;
601	}
602
603	ksft_test_result_pass("statmount string %s\n", name);
604out:
605	free(sm);
606}
607
608static void test_listmount_tree(void)
609{
610	ssize_t res;
611	const unsigned int log2_num = 4;
612	const unsigned int step = 3;
613	const unsigned int size = (1 << log2_num) + step + 1;
614	size_t num, expect = 1 << log2_num;
615	uint64_t list[size];
616	uint64_t list2[size];
617	size_t i;
618
619
620	res = setup_mount_tree(log2_num);
621	if (res == -1)
622		return;
623
624	num = res = listmount(LSMT_ROOT, 0, 0, list, size, 0);
625	if (res == -1) {
626		ksft_test_result_fail("listmount: %s\n", strerror(errno));
627		return;
628	}
629	if (num != expect) {
630		ksft_test_result_fail("listmount result is %zi != %zi\n",
631				      res, expect);
632		return;
633	}
634
635	for (i = 0; i < size - step;) {
636		res = listmount(LSMT_ROOT, 0, i ? list2[i - 1] : 0, list2 + i, step, 0);
637		if (res == -1)
638			ksft_test_result_fail("short listmount: %s\n",
639					      strerror(errno));
640		i += res;
641		if (res < step)
642			break;
643	}
644	if (i != num) {
645		ksft_test_result_fail("different number of entries: %zu != %zu\n",
646				      i, num);
647		return;
648	}
649	for (i = 0; i < num; i++) {
650		if (list2[i] != list[i]) {
651			ksft_test_result_fail("different value for entry %zu: 0x%llx != 0x%llx\n",
652					      i,
653					      (unsigned long long) list2[i],
654					      (unsigned long long) list[i]);
655		}
656	}
657
658	ksft_test_result_pass("listmount tree\n");
659}
660
661#define str_off(memb) (offsetof(struct statmount, memb) / sizeof(uint32_t))
662
663int main(void)
664{
665	int ret;
666	uint64_t all_mask = STATMOUNT_SB_BASIC | STATMOUNT_MNT_BASIC |
667		STATMOUNT_PROPAGATE_FROM | STATMOUNT_MNT_ROOT |
668		STATMOUNT_MNT_POINT | STATMOUNT_FS_TYPE | STATMOUNT_MNT_NS_ID;
669
670	ksft_print_header();
671
672	ret = statmount(0, 0, 0, NULL, 0, 0);
673	assert(ret == -1);
674	if (errno == ENOSYS)
675		ksft_exit_skip("statmount() syscall not supported\n");
676
677	setup_namespace();
678
679	ksft_set_plan(15);
680	test_listmount_empty_root();
681	test_statmount_zero_mask();
682	test_statmount_mnt_basic();
683	test_statmount_sb_basic();
684	test_statmount_mnt_root();
685	test_statmount_mnt_point();
686	test_statmount_fs_type();
687	test_statmount_mnt_opts();
688	test_statmount_string(STATMOUNT_MNT_ROOT, str_off(mnt_root), "mount root");
689	test_statmount_string(STATMOUNT_MNT_POINT, str_off(mnt_point), "mount point");
690	test_statmount_string(STATMOUNT_FS_TYPE, str_off(fs_type), "fs type");
691	test_statmount_string(all_mask, str_off(mnt_root), "mount root & all");
692	test_statmount_string(all_mask, str_off(mnt_point), "mount point & all");
693	test_statmount_string(all_mask, str_off(fs_type), "fs type & all");
694
695	test_listmount_tree();
696
697
698	if (ksft_get_fail_cnt() + ksft_get_error_cnt() > 0)
699		ksft_exit_fail();
700	else
701		ksft_exit_pass();
702}
~/ Read-only file preview.