1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
2 /*
3 * This file is part of libmount from util-linux project.
4 *
5 * Copyright (C) 2008-2018 Karel Zak <kzak@redhat.com>
6 *
7 * libmount is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 2.1 of the License, or
10 * (at your option) any later version.
11 */
12
13 /**
14 * SECTION: utils
15 * @title: Utils
16 * @short_description: misc utils.
17 */
18 #include <ctype.h>
19 #include <fcntl.h>
20 #include <pwd.h>
21 #include <grp.h>
22 #include <blkid.h>
23
24 #include "strutils.h"
25 #include "pathnames.h"
26 #include "mountP.h"
27 #include "mangle.h"
28 #include "canonicalize.h"
29 #include "env.h"
30 #include "match.h"
31 #include "fileutils.h"
32 #include "statfs_magic.h"
33 #include "sysfs.h"
34 #include "namespace.h"
35
36 /*
37 * Return 1 if the file is not accessible or empty
38 */
39 int is_file_empty(const char *name)
40 {
41 struct stat st;
42 assert(name);
43
44 return (stat(name, &st) != 0 || st.st_size == 0);
45 }
46
47 int mnt_valid_tagname(const char *tagname)
48 {
49 if (tagname && *tagname && (
50 strcmp("ID", tagname) == 0 ||
51 strcmp("UUID", tagname) == 0 ||
52 strcmp("LABEL", tagname) == 0 ||
53 strcmp("PARTUUID", tagname) == 0 ||
54 strcmp("PARTLABEL", tagname) == 0))
55 return 1;
56
57 return 0;
58 }
59
60 /**
61 * mnt_tag_is_valid:
62 * @tag: NAME=value string
63 *
64 * Returns: 1 if the @tag is parsable and tag NAME= is supported by libmount, or 0.
65 */
66 int mnt_tag_is_valid(const char *tag)
67 {
68 char *t = NULL;
69 int rc = tag && blkid_parse_tag_string(tag, &t, NULL) == 0
70 && mnt_valid_tagname(t);
71
72 free(t);
73 return rc;
74 }
75
76 int mnt_parse_offset(const char *str, size_t len, uintmax_t *res)
77 {
78 char *p;
79 int rc = 0;
80
81 if (!str || !*str)
82 return -EINVAL;
83
84 p = strndup(str, len);
85 if (!p)
86 return -errno;
87
88 if (strtosize(p, res))
89 rc = -EINVAL;
90 free(p);
91 return rc;
92 }
93
94 /* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
95 static int fstype_cmp(const void *v1, const void *v2)
96 {
97 const char *s1 = *(char * const *)v1;
98 const char *s2 = *(char * const *)v2;
99
100 return strcmp(s1, s2);
101 }
102
103 /* This very simplified stat() alternative uses cached VFS data and does not
104 * directly ask the filesystem for details. It requires a kernel that supports
105 * statx(). It's usable only for file type, rdev and ino!
106 */
107 static int safe_stat(const char *target, struct stat *st, int nofollow)
108 {
109 assert(target);
110 assert(st);
111
112 memset(st, 0, sizeof(struct stat));
113
114 #ifdef AT_STATX_DONT_SYNC
115 {
116 int rc;
117 struct statx stx = { 0 };
118
119 rc = statx(AT_FDCWD, target,
120 /* flags */
121 AT_STATX_DONT_SYNC
122 | AT_NO_AUTOMOUNT
123 | (nofollow ? AT_SYMLINK_NOFOLLOW : 0),
124 /* mask */
125 STATX_TYPE
126 | STATX_MODE
127 | STATX_INO,
128 &stx);
129 if (rc == 0) {
130 st->st_ino = stx.stx_ino;
131 st->st_dev = makedev(stx.stx_dev_major, stx.stx_dev_minor);
132 st->st_rdev = makedev(stx.stx_rdev_major, stx.stx_rdev_minor);
133 st->st_mode = stx.stx_mode;
134 }
135
136 if (rc == 0 || errno != EOPNOTSUPP)
137 return rc;
138 }
139 #endif
140
141 #ifdef AT_NO_AUTOMOUNT
142 return fstatat(AT_FDCWD, target, st,
143 AT_NO_AUTOMOUNT | (nofollow ? AT_SYMLINK_NOFOLLOW : 0));
144 #endif
145 return nofollow ? lstat(target, st) : stat(target, st);
146 }
147
148 int mnt_safe_stat(const char *target, struct stat *st)
149 {
150 return safe_stat(target, st, 0);
151 }
152
153 int mnt_safe_lstat(const char *target, struct stat *st)
154 {
155 return safe_stat(target, st, 1);
156 }
157
158 /* Don't use access() or stat() here, we need a way how to check the path
159 * without trigger an automount or hangs on NFS, etc. */
160 int mnt_is_path(const char *target)
161 {
162 struct stat st;
163
164 return safe_stat(target, &st, 0) == 0;
165 }
166
167 /*
168 * Note that the @target has to be an absolute path (so at least "/"). The
169 * @filename returns an allocated buffer with the last path component, for example:
170 *
171 * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test"
172 */
173 int mnt_chdir_to_parent(const char *target, char **filename)
174 {
175 char *buf, *parent, *last = NULL;
176 char cwd[PATH_MAX];
177 int rc = -EINVAL;
178
179 if (!target || *target != '/')
180 return -EINVAL;
181
182 DBG(UTILS, ul_debug("moving to %s parent", target));
183
184 buf = strdup(target);
185 if (!buf)
186 return -ENOMEM;
187
188 if (*(buf + 1) != '\0') {
189 last = stripoff_last_component(buf);
190 if (!last)
191 goto err;
192 }
193
194 parent = buf && *buf ? buf : "/";
195
196 if (chdir(parent) == -1) {
197 DBG(UTILS, ul_debug("failed to chdir to %s: %m", parent));
198 rc = -errno;
199 goto err;
200 }
201 if (!getcwd(cwd, sizeof(cwd))) {
202 DBG(UTILS, ul_debug("failed to obtain current directory: %m"));
203 rc = -errno;
204 goto err;
205 }
206 if (strcmp(cwd, parent) != 0) {
207 DBG(UTILS, ul_debug(
208 "unexpected chdir (expected=%s, cwd=%s)", parent, cwd));
209 goto err;
210 }
211
212 DBG(CXT, ul_debug(
213 "current directory moved to %s [last_component='%s']",
214 parent, last));
215
216 if (filename) {
217 *filename = buf;
218
219 if (!last || !*last)
220 memcpy(*filename, ".", 2);
221 else
222 memmove(*filename, last, strlen(last) + 1);
223 } else
224 free(buf);
225 return 0;
226 err:
227 free(buf);
228 return rc;
229 }
230
231 /*
232 * Check if @path is on a read-only filesystem independently of file permissions.
233 */
234 int mnt_is_readonly(const char *path)
235 {
236 if (access(path, W_OK) == 0)
237 return 0;
238 if (errno == EROFS)
239 return 1;
240 if (errno != EACCES)
241 return 0;
242
243 #ifdef HAVE_UTIMENSAT
244 /*
245 * access(2) returns EACCES on read-only FS:
246 *
247 * - for set-uid application if one component of the path is not
248 * accessible for the current rUID. (Note that euidaccess(2) does not
249 * check for EROFS at all).
250 *
251 * - for a read-write filesystem with a read-only VFS node (aka -o remount,ro,bind)
252 */
253 {
254 struct timespec times[2];
255
256 DBG(UTILS, ul_debug(" doing utimensat() based write test"));
257
258 times[0].tv_nsec = UTIME_NOW; /* atime */
259 times[1].tv_nsec = UTIME_OMIT; /* mtime */
260
261 if (utimensat(AT_FDCWD, path, times, 0) == -1)
262 return errno == EROFS;
263 }
264 #endif
265 return 0;
266 }
267
268 /**
269 * mnt_mangle:
270 * @str: string
271 *
272 * Encode @str to be compatible with fstab/mtab
273 *
274 * Returns: newly allocated string or NULL in case of error.
275 */
276 char *mnt_mangle(const char *str)
277 {
278 return mangle(str);
279 }
280
281 /**
282 * mnt_unmangle:
283 * @str: string
284 *
285 * Decode @str from fstab/mtab
286 *
287 * Returns: newly allocated string or NULL in case of error.
288 */
289 char *mnt_unmangle(const char *str)
290 {
291 return unmangle(str, NULL);
292 }
293
294 /**
295 * mnt_fstype_is_pseudofs:
296 * @type: filesystem name
297 *
298 * Returns: 1 for filesystems like proc, sysfs, ... or 0.
299 */
300 int mnt_fstype_is_pseudofs(const char *type)
301 {
302 /* This array must remain sorted when adding new fstypes */
303 static const char *pseudofs[] = {
304 "anon_inodefs",
305 "apparmorfs",
306 "autofs",
307 "bdev",
308 "binder",
309 "binfmt_misc",
310 "bpf",
311 "cgroup",
312 "cgroup2",
313 "configfs",
314 "cpuset",
315 "debugfs",
316 "devfs",
317 "devpts",
318 "devtmpfs",
319 "dlmfs",
320 "dmabuf",
321 "drm",
322 "efivarfs",
323 "fuse", /* Fallback name of fuse used by many poorly written drivers. */
324 "fuse.archivemount", /* Not a true pseudofs (has source), but source is not reported. */
325 "fuse.avfsd", /* Not a true pseudofs (has source), but source is not reported. */
326 "fuse.dumpfs", /* In fact, it is a netfs, but source is not reported. */
327 "fuse.encfs", /* Not a true pseudofs (has source), but source is not reported. */
328 "fuse.gvfs-fuse-daemon", /* Old name, not used by gvfs any more. */
329 "fuse.gvfsd-fuse",
330 "fuse.lxcfs",
331 "fuse.rofiles-fuse",
332 "fuse.vmware-vmblock",
333 "fuse.xwmfs",
334 "fusectl",
335 "hugetlbfs",
336 "ipathfs",
337 "mqueue",
338 "nfsd",
339 "none",
340 "nsfs",
341 "overlay",
342 "pipefs",
343 "proc",
344 "pstore",
345 "ramfs",
346 "resctrl",
347 "rootfs",
348 "rpc_pipefs",
349 "securityfs",
350 "selinuxfs",
351 "smackfs",
352 "sockfs",
353 "spufs",
354 "sysfs",
355 "tmpfs",
356 "tracefs",
357 "vboxsf",
358 "virtiofs"
359 };
360
361 assert(type);
362
363 return !(bsearch(&type, pseudofs, ARRAY_SIZE(pseudofs),
364 sizeof(char*), fstype_cmp) == NULL);
365 }
366
367 /**
368 * mnt_fstype_is_netfs:
369 * @type: filesystem name
370 *
371 * Returns: 1 for filesystems like cifs, nfs, ... or 0.
372 */
373 int mnt_fstype_is_netfs(const char *type)
374 {
375 if (strcmp(type, "cifs") == 0 ||
376 strcmp(type, "smb3") == 0 ||
377 strcmp(type, "smbfs") == 0 ||
378 strncmp(type,"nfs", 3) == 0 ||
379 strcmp(type, "afs") == 0 ||
380 strcmp(type, "ncpfs") == 0 ||
381 strcmp(type, "glusterfs") == 0 ||
382 strcmp(type, "fuse.curlftpfs") == 0 ||
383 strcmp(type, "fuse.sshfs") == 0 ||
384 strncmp(type,"9p", 2) == 0)
385 return 1;
386 return 0;
387 }
388
389 const char *mnt_statfs_get_fstype(struct statfs *vfs)
390 {
391 assert(vfs);
392
393 switch (vfs->f_type) {
394 case STATFS_ADFS_MAGIC: return "adfs";
395 case STATFS_AFFS_MAGIC: return "affs";
396 case STATFS_AFS_MAGIC: return "afs";
397 case STATFS_AUTOFS_MAGIC: return "autofs";
398 case STATFS_BDEVFS_MAGIC: return "bdev";
399 case STATFS_BEFS_MAGIC: return "befs";
400 case STATFS_BFS_MAGIC: return "befs";
401 case STATFS_BINFMTFS_MAGIC: return "binfmt_misc";
402 case STATFS_BTRFS_MAGIC: return "btrfs";
403 case STATFS_CEPH_MAGIC: return "ceph";
404 case STATFS_CGROUP_MAGIC: return "cgroup";
405 case STATFS_CIFS_MAGIC: return "cifs";
406 case STATFS_CODA_MAGIC: return "coda";
407 case STATFS_CONFIGFS_MAGIC: return "configfs";
408 case STATFS_CRAMFS_MAGIC: return "cramfs";
409 case STATFS_DEBUGFS_MAGIC: return "debugfs";
410 case STATFS_DEVPTS_MAGIC: return "devpts";
411 case STATFS_ECRYPTFS_MAGIC: return "ecryptfs";
412 case STATFS_EFIVARFS_MAGIC: return "efivarfs";
413 case STATFS_EFS_MAGIC: return "efs";
414 case STATFS_EXOFS_MAGIC: return "exofs";
415 case STATFS_EXT4_MAGIC: return "ext4"; /* all extN use the same magic */
416 case STATFS_F2FS_MAGIC: return "f2fs";
417 case STATFS_FUSE_MAGIC: return "fuse";
418 case STATFS_FUTEXFS_MAGIC: return "futexfs";
419 case STATFS_GFS2_MAGIC: return "gfs2";
420 case STATFS_HFSPLUS_MAGIC: return "hfsplus";
421 case STATFS_HOSTFS_MAGIC: return "hostfs";
422 case STATFS_HPFS_MAGIC: return "hpfs";
423 case STATFS_HPPFS_MAGIC: return "hppfs";
424 case STATFS_HUGETLBFS_MAGIC: return "hugetlbfs";
425 case STATFS_ISOFS_MAGIC: return "iso9660";
426 case STATFS_JFFS2_MAGIC: return "jffs2";
427 case STATFS_JFS_MAGIC: return "jfs";
428 case STATFS_LOGFS_MAGIC: return "logfs";
429 case STATFS_MINIX2_MAGIC:
430 case STATFS_MINIX2_MAGIC2:
431 case STATFS_MINIX3_MAGIC:
432 case STATFS_MINIX_MAGIC:
433 case STATFS_MINIX_MAGIC2: return "minix";
434 case STATFS_MQUEUE_MAGIC: return "mqueue";
435 case STATFS_MSDOS_MAGIC: return "vfat";
436 case STATFS_NCP_MAGIC: return "ncp";
437 case STATFS_NFS_MAGIC: return "nfs";
438 case STATFS_NILFS_MAGIC: return "nilfs2";
439 case STATFS_NTFS_MAGIC: return "ntfs";
440 case STATFS_OCFS2_MAGIC: return "ocfs2";
441 case STATFS_OMFS_MAGIC: return "omfs";
442 case STATFS_OPENPROMFS_MAGIC: return "openpromfs";
443 case STATFS_PIPEFS_MAGIC: return "pipefs";
444 case STATFS_PROC_MAGIC: return "proc";
445 case STATFS_PSTOREFS_MAGIC: return "pstore";
446 case STATFS_QNX4_MAGIC: return "qnx4";
447 case STATFS_QNX6_MAGIC: return "qnx6";
448 case STATFS_RAMFS_MAGIC: return "ramfs";
449 case STATFS_REISERFS_MAGIC: return "reiser4";
450 case STATFS_ROMFS_MAGIC: return "romfs";
451 case STATFS_SECURITYFS_MAGIC: return "securityfs";
452 case STATFS_SELINUXFS_MAGIC: return "selinuxfs";
453 case STATFS_SMACKFS_MAGIC: return "smackfs";
454 case STATFS_SMB_MAGIC: return "smb";
455 case STATFS_SOCKFS_MAGIC: return "sockfs";
456 case STATFS_SQUASHFS_MAGIC: return "squashfs";
457 case STATFS_SYSFS_MAGIC: return "sysfs";
458 case STATFS_TMPFS_MAGIC: return "tmpfs";
459 case STATFS_UBIFS_MAGIC: return "ubifs";
460 case STATFS_UDF_MAGIC: return "udf";
461 case STATFS_UFS2_MAGIC:
462 case STATFS_UFS_MAGIC: return "ufs";
463 case STATFS_V9FS_MAGIC: return "9p";
464 case STATFS_VXFS_MAGIC: return "vxfs";
465 case STATFS_XENFS_MAGIC: return "xenfs";
466 case STATFS_XFS_MAGIC: return "xfs";
467 default:
468 break;
469 }
470
471 return NULL;
472 }
473
474 /**
475 * mnt_match_fstype:
476 * @type: filesystem type
477 * @pattern: filesystem name or comma delimited list of names
478 *
479 * The @pattern list of filesystems can be prefixed with a global
480 * "no" prefix to invert matching of the whole list. The "no" could
481 * also be used for individual items in the @pattern list. So,
482 * "nofoo,bar" has the same meaning as "nofoo,nobar".
483 *
484 * "bar" : "nofoo,bar" -> False (global "no" prefix)
485 *
486 * "bar" : "foo,bar" -> True
487 *
488 * "bar" : "foo,nobar" -> False
489 *
490 * Returns: 1 if type is matching, else 0. This function also returns
491 * 0 if @pattern is NULL and @type is non-NULL.
492 */
493 int mnt_match_fstype(const char *type, const char *pattern)
494 {
495 return match_fstype(type, pattern);
496 }
497
498 void mnt_free_filesystems(char **filesystems)
499 {
500 char **p;
501
502 if (!filesystems)
503 return;
504 for (p = filesystems; *p; p++)
505 free(*p);
506 free(filesystems);
507 }
508
509 static int add_filesystem(char ***filesystems, char *name)
510 {
511 int n = 0;
512
513 assert(filesystems);
514 assert(name);
515
516 if (*filesystems) {
517 char **p;
518 for (n = 0, p = *filesystems; *p; p++, n++) {
519 if (strcmp(*p, name) == 0)
520 return 0;
521 }
522 }
523
524 #define MYCHUNK 16
525
526 if (n == 0 || !((n + 1) % MYCHUNK)) {
527 size_t items = ((n + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK;
528 char **x = realloc(*filesystems, items * sizeof(char *));
529
530 if (!x)
531 goto err;
532 *filesystems = x;
533 }
534 name = strdup(name);
535 (*filesystems)[n] = name;
536 (*filesystems)[n + 1] = NULL;
537 if (!name)
538 goto err;
539 return 0;
540 err:
541 mnt_free_filesystems(*filesystems);
542 return -ENOMEM;
543 }
544
545 static int get_filesystems(const char *filename, char ***filesystems, const char *pattern)
546 {
547 int rc = 0;
548 FILE *f;
549 char line[129];
550
551 f = fopen(filename, "r" UL_CLOEXECSTR);
552 if (!f)
553 return 1;
554
555 DBG(UTILS, ul_debug("reading filesystems list from: %s", filename));
556
557 while (fgets(line, sizeof(line), f)) {
558 char name[sizeof(line)];
559
560 if (*line == '#' || strncmp(line, "nodev", 5) == 0)
561 continue;
562 if (sscanf(line, " %128[^\n ]\n", name) != 1)
563 continue;
564 if (strcmp(name, "*") == 0) {
565 rc = 1;
566 break; /* end of the /etc/filesystems */
567 }
568 if (pattern && !mnt_match_fstype(name, pattern))
569 continue;
570 rc = add_filesystem(filesystems, name);
571 if (rc)
572 break;
573 }
574
575 fclose(f);
576 return rc;
577 }
578
579 /*
580 * Always check the @filesystems pointer!
581 *
582 * man mount:
583 *
584 * ...mount will try to read the file /etc/filesystems, or, if that does not
585 * exist, /proc/filesystems. All of the filesystem types listed there will
586 * be tried, except for those that are labeled "nodev" (e.g., devpts,
587 * proc and nfs). If /etc/filesystems ends in a line with a single * only,
588 * mount will read /proc/filesystems afterwards.
589 */
590 int mnt_get_filesystems(char ***filesystems, const char *pattern)
591 {
592 int rc;
593
594 if (!filesystems)
595 return -EINVAL;
596
597 *filesystems = NULL;
598
599 rc = get_filesystems(_PATH_FILESYSTEMS, filesystems, pattern);
600 if (rc != 1)
601 return rc;
602
603 rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern);
604 if (rc == 1 && *filesystems)
605 rc = 0; /* /proc/filesystems not found */
606
607 return rc;
608 }
609
610 /*
611 * Returns an allocated string with username or NULL.
612 */
613 char *mnt_get_username(const uid_t uid)
614 {
615 struct passwd pwd;
616 struct passwd *res;
617 char *buf, *username = NULL;
618
619 buf = malloc(UL_GETPW_BUFSIZ);
620 if (!buf)
621 return NULL;
622
623 if (!getpwuid_r(uid, &pwd, buf, UL_GETPW_BUFSIZ, &res) && res)
624 username = strdup(pwd.pw_name);
625
626 free(buf);
627 return username;
628 }
629
630 int mnt_get_uid(const char *username, uid_t *uid)
631 {
632 int rc = -1;
633 struct passwd pwd;
634 struct passwd *pw;
635 char *buf;
636
637 if (!username || !uid)
638 return -EINVAL;
639
640 buf = malloc(UL_GETPW_BUFSIZ);
641 if (!buf)
642 return -ENOMEM;
643
644 if (!getpwnam_r(username, &pwd, buf, UL_GETPW_BUFSIZ, &pw) && pw) {
645 *uid = pw->pw_uid;
646 rc = 0;
647 } else {
648 DBG(UTILS, ul_debug(
649 "cannot convert '%s' username to UID", username));
650 if (errno == 0)
651 errno = EINVAL;
652 rc = -errno;;
653 }
654
655 free(buf);
656 return rc;
657 }
658
659 int mnt_get_gid(const char *groupname, gid_t *gid)
660 {
661 int rc = -1;
662 struct group grp;
663 struct group *gr;
664 char *buf;
665
666 if (!groupname || !gid)
667 return -EINVAL;
668
669 buf = malloc(UL_GETPW_BUFSIZ);
670 if (!buf)
671 return -ENOMEM;
672
673 if (!getgrnam_r(groupname, &grp, buf, UL_GETPW_BUFSIZ, &gr) && gr) {
674 *gid = gr->gr_gid;
675 rc = 0;
676 } else {
677 DBG(UTILS, ul_debug(
678 "cannot convert '%s' groupname to GID", groupname));
679 if (errno == 0)
680 errno = EINVAL;
681 rc = -errno;;
682 }
683
684 free(buf);
685 return rc;
686 }
687
688 static int parse_uid_numeric(const char *value, uid_t *uid)
689 {
690 uint64_t num;
691 int rc;
692
693 assert(value);
694 assert(uid);
695
696 rc = ul_strtou64(value, &num, 10);
697 if (rc != 0)
698 goto fail;
699
700 if (num > ULONG_MAX || (uid_t) num != num) {
701 rc = -(errno = ERANGE);
702 goto fail;
703 }
704 *uid = (uid_t) num;
705
706 return 0;
707 fail:
708 DBG(UTILS, ul_debug("failed to convert '%s' to number [rc=%d, errno=%d]", value, rc, errno));
709 return rc;
710 }
711
712 /* Parse user_len-sized user; returns <0 on error, or 0 on success */
713 int mnt_parse_uid(const char *user, size_t user_len, uid_t *uid)
714 {
715 char *user_tofree = NULL;
716 int rc;
717
718 assert(user);
719 assert(user_len);
720 assert(uid);
721
722 if (user[user_len] != '\0') {
723 user = user_tofree = strndup(user, user_len);
724 if (!user)
725 return -ENOMEM;
726 }
727
728 rc = mnt_get_uid(user, uid);
729 if (rc != 0 && isdigit(*user))
730 rc = parse_uid_numeric(user, uid);
731
732 free(user_tofree);
733 return rc;
734 }
735
736 static int parse_gid_numeric(const char *value, gid_t *gid)
737 {
738 uint64_t num;
739 int rc;
740
741 assert(value);
742 assert(gid);
743
744 rc = ul_strtou64(value, &num, 10);
745 if (rc != 0)
746 goto fail;
747
748 if (num > ULONG_MAX || (gid_t) num != num) {
749 rc = -(errno = ERANGE);
750 goto fail;
751 }
752 *gid = (gid_t) num;
753
754 return 0;
755 fail:
756 DBG(UTILS, ul_debug("failed to convert '%s' to number [rc=%d, errno=%d]", value, rc, errno));
757 return rc;
758 }
759
760 /* POSIX-parse group_len-sized group; -1 and errno set, or 0 on success */
761 int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid)
762 {
763 char *group_tofree = NULL;
764 int rc;
765
766 assert(group);
767 assert(group_len);
768 assert(gid);
769
770 if (group[group_len] != '\0') {
771 group = group_tofree = strndup(group, group_len);
772 if (!group)
773 return -ENOMEM;
774 }
775
776 rc = mnt_get_gid(group, gid);
777 if (rc != 0 && isdigit(*group))
778 rc = parse_gid_numeric(group, gid);
779
780 free(group_tofree);
781 return rc;
782 }
783
784 int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *uid)
785 {
786 char buf[sizeof(stringify_value(UINT32_MAX))];
787 uint32_t num;
788 int rc;
789
790 assert(mode);
791 assert(mode_len);
792 assert(uid);
793
794 if (mode_len > sizeof(buf) - 1) {
795 rc = -(errno = ERANGE);
796 goto fail;
797 }
798 mem2strcpy(buf, mode, mode_len, sizeof(buf));
799
800 rc = ul_strtou32(buf, &num, 8);
801 if (rc != 0)
802 goto fail;
803 if (num > 07777) {
804 rc = -(errno = ERANGE);
805 goto fail;
806 }
807 *uid = (mode_t) num;
808
809 return 0;
810 fail:
811 DBG(UTILS, ul_debug("failed to convert '%.*s' to mode [rc=%d, errno=%d]",
812 (int) mode_len, mode, rc, errno));
813 return rc;
814 }
815
816 int mnt_in_group(gid_t gid)
817 {
818 int rc = 0, n, i;
819 gid_t *grps = NULL;
820
821 if (getgid() == gid)
822 return 1;
823
824 n = getgroups(0, NULL);
825 if (n <= 0)
826 goto done;
827
828 grps = malloc(n * sizeof(*grps));
829 if (!grps)
830 goto done;
831
832 if (getgroups(n, grps) == n) {
833 for (i = 0; i < n; i++) {
834 if (grps[i] == gid) {
835 rc = 1;
836 break;
837 }
838 }
839 }
840 done:
841 free(grps);
842 return rc;
843 }
844
845 static int try_write(const char *filename, const char *directory)
846 {
847 int rc = 0;
848
849 if (!filename)
850 return -EINVAL;
851
852 DBG(UTILS, ul_debug("try write %s dir: %s", filename, directory));
853
854 #ifdef HAVE_EACCESS
855 /* Try eaccess() first, because open() is overkill, may be monitored by
856 * audit and we don't want to fill logs by our checks...
857 */
858 if (eaccess(filename, R_OK|W_OK) == 0) {
859 DBG(UTILS, ul_debug(" access OK"));
860 return 0;
861 }
862
863 if (errno != ENOENT) {
864 DBG(UTILS, ul_debug(" access FAILED"));
865 return -errno;
866 }
867
868 if (directory) {
869 /* file does not exist; try if directory is writable */
870 if (eaccess(directory, R_OK|W_OK) != 0)
871 rc = -errno;
872
873 DBG(UTILS, ul_debug(" access %s [%s]", rc ? "FAILED" : "OK", directory));
874 return rc;
875 }
876 #endif
877
878 DBG(UTILS, ul_debug(" doing open-write test"));
879
880 int fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC,
881 S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
882 if (fd < 0)
883 rc = -errno;
884 else
885 close(fd);
886
887 return rc;
888 }
889
890 /**
891 * mnt_has_regular_mtab:
892 * @mtab: returns NULL
893 * @writable: returns 0
894 *
895 * Returns: 0
896 *
897 * Deprecated: libmount does not use /etc/mtab at all since v2.39.
898 */
899 int mnt_has_regular_mtab(const char **mtab, int *writable)
900 {
901 if (writable)
902 *writable = 0;
903 if (mtab)
904 *mtab = NULL;
905 return 0;
906 }
907
908 /*
909 * Don't export this to libmount API -- utab is private library stuff.
910 *
911 * If the file does not exist and @writable argument is not NULL, then it will
912 * try to create the directory (e.g. /run/mount) and the file.
913 *
914 * Returns: 1 if utab is a regular file, and 0 in case of
915 * error (check errno for more details).
916 */
917 int mnt_has_regular_utab(const char **utab, int *writable)
918 {
919 struct stat st;
920 int rc;
921 const char *filename = utab && *utab ? *utab : mnt_get_utab_path();
922
923 if (writable)
924 *writable = 0;
925 if (utab && !*utab)
926 *utab = filename;
927
928 DBG(UTILS, ul_debug("utab: %s", filename));
929
930 rc = lstat(filename, &st);
931
932 if (rc == 0) {
933 /* file exists */
934 if (S_ISREG(st.st_mode)) {
935 if (writable)
936 *writable = !try_write(filename, NULL);
937 return 1;
938 }
939 goto done; /* it's not a regular file */
940 }
941
942 if (writable) {
943 char *dirname = strdup(filename);
944
945 if (!dirname)
946 goto done;
947
948 stripoff_last_component(dirname); /* remove filename */
949
950 rc = mkdir(dirname, S_IWUSR|
951 S_IRUSR|S_IRGRP|S_IROTH|
952 S_IXUSR|S_IXGRP|S_IXOTH);
953 if (rc && errno != EEXIST) {
954 free(dirname);
955 goto done; /* probably EACCES */
956 }
957
958 *writable = !try_write(filename, dirname);
959 free(dirname);
960 if (*writable)
961 return 1;
962 }
963 done:
964 DBG(UTILS, ul_debug("%s: irregular/non-writable file", filename));
965 return 0;
966 }
967
968 /**
969 * mnt_get_swaps_path:
970 *
971 * Returns: path to /proc/swaps or $LIBMOUNT_SWAPS.
972 */
973 const char *mnt_get_swaps_path(void)
974 {
975 const char *p = safe_getenv("LIBMOUNT_SWAPS");
976 return p ? : _PATH_PROC_SWAPS;
977 }
978
979 /**
980 * mnt_get_fstab_path:
981 *
982 * Returns: path to /etc/fstab or $LIBMOUNT_FSTAB.
983 */
984 const char *mnt_get_fstab_path(void)
985 {
986 const char *p = safe_getenv("LIBMOUNT_FSTAB");
987 return p ? : _PATH_MNTTAB;
988 }
989
990 /**
991 * mnt_get_mtab_path:
992 *
993 * This function returns the *default* location of the mtab file.
994 *
995 *
996 * Returns: path to /etc/mtab or $LIBMOUNT_MTAB.
997 *
998 * Deprecated: libmount uses /proc/self/mountinfo only.
999 *
1000 */
1001 const char *mnt_get_mtab_path(void)
1002 {
1003 const char *p = safe_getenv("LIBMOUNT_MTAB");
1004 return p ? : _PATH_MOUNTED;
1005 }
1006
1007 /*
1008 * Don't export this to libmount API -- utab is private library stuff.
1009 *
1010 * Returns: path to /run/mount/utab or $LIBMOUNT_UTAB.
1011 */
1012 const char *mnt_get_utab_path(void)
1013 {
1014 const char *p = safe_getenv("LIBMOUNT_UTAB");
1015 return p ? : MNT_PATH_UTAB;
1016 }
1017
1018
1019 /* returns file descriptor or -errno, @name returns a unique filename
1020 */
1021 int mnt_open_uniq_filename(const char *filename, char **name)
1022 {
1023 int rc, fd;
1024 char *n;
1025 mode_t oldmode;
1026
1027 if (!filename)
1028 return -EINVAL;
1029 if (name)
1030 *name = NULL;
1031
1032 rc = asprintf(&n, "%s.XXXXXX", filename);
1033 if (rc <= 0)
1034 return -errno;
1035
1036 /* This is for very old glibc and for compatibility with Posix, which says
1037 * nothing about mkstemp() mode. All sane glibc use secure mode (0600).
1038 */
1039 oldmode = umask(S_IRGRP|S_IWGRP|S_IXGRP|
1040 S_IROTH|S_IWOTH|S_IXOTH);
1041
1042 fd = mkstemp_cloexec(n);
1043 if (fd < 0)
1044 fd = -errno;
1045 umask(oldmode);
1046
1047 if (fd >= 0 && name)
1048 *name = n;
1049 else
1050 free(n);
1051
1052 return fd;
1053 }
1054
1055 /**
1056 * mnt_get_mountpoint:
1057 * @path: pathname
1058 *
1059 * This function finds the mountpoint that a given path resides in. @path
1060 * should be canonicalized. The returned pointer should be freed by the caller.
1061 *
1062 * WARNING: the function compares st_dev of the @path elements. This traditional
1063 * way may be insufficient on filesystems like Linux "overlay". See also
1064 * mnt_table_find_target().
1065 *
1066 * Returns: allocated string with the target of the mounted device or NULL on error
1067 */
1068 char *mnt_get_mountpoint(const char *path)
1069 {
1070 char *mnt;
1071 struct stat st;
1072 dev_t dir, base;
1073
1074 if (!path)
1075 return NULL;
1076
1077 mnt = strdup(path);
1078 if (!mnt)
1079 return NULL;
1080 if (*mnt == '/' && *(mnt + 1) == '\0')
1081 goto done;
1082
1083 if (mnt_safe_stat(mnt, &st))
1084 goto err;
1085 base = st.st_dev;
1086
1087 do {
1088 char *p = stripoff_last_component(mnt);
1089
1090 if (!p)
1091 break;
1092 if (mnt_safe_stat(*mnt ? mnt : "/", &st))
1093 goto err;
1094 dir = st.st_dev;
1095 if (dir != base) {
1096 if (p > mnt)
1097 *(p - 1) = '/';
1098 goto done;
1099 }
1100 base = dir;
1101 } while (mnt && *(mnt + 1) != '\0');
1102
1103 memcpy(mnt, "/", 2);
1104 done:
1105 DBG(UTILS, ul_debug("%s mountpoint is %s", path, mnt));
1106 return mnt;
1107 err:
1108 free(mnt);
1109 return NULL;
1110 }
1111
1112 /*
1113 * Search for @name kernel command parameter.
1114 *
1115 * Returns newly allocated string with a parameter argument if the @name is
1116 * specified as "name=" or returns pointer to @name or returns NULL if not
1117 * found. If it is specified more than once, we grab the last copy.
1118 *
1119 * For example cmdline: "aaa bbb=BBB ccc"
1120 *
1121 * @name is "aaa" --returns--> "aaa" (pointer to @name)
1122 * @name is "bbb=" --returns--> "BBB" (allocated)
1123 * @name is "foo" --returns--> NULL
1124 *
1125 * Note: It is not really feasible to parse the command line exactly the same
1126 * as the kernel does since we don't know which options are valid. We can use
1127 * the -- marker though and not walk past that.
1128 */
1129 char *mnt_get_kernel_cmdline_option(const char *name)
1130 {
1131 FILE *f;
1132 size_t len;
1133 int val = 0;
1134 char *p, *res = NULL, *mem = NULL;
1135 char buf[BUFSIZ]; /* see kernel include/asm-generic/setup.h: COMMAND_LINE_SIZE */
1136 const char *path = _PATH_PROC_CMDLINE;
1137
1138 if (!name || !name[0])
1139 return NULL;
1140
1141 #ifdef TEST_PROGRAM
1142 path = getenv("LIBMOUNT_KERNEL_CMDLINE");
1143 if (!path)
1144 path = _PATH_PROC_CMDLINE;
1145 #endif
1146 f = fopen(path, "r" UL_CLOEXECSTR);
1147 if (!f)
1148 return NULL;
1149
1150 p = fgets(buf, sizeof(buf), f);
1151 fclose(f);
1152
1153 if (!p || !*p || *p == '\n')
1154 return NULL;
1155
1156 p = strstr(p, " -- ");
1157 if (p) {
1158 /* no more kernel args after this */
1159 *p = '\0';
1160 } else {
1161 len = strlen(buf);
1162 buf[len - 1] = '\0'; /* remove last '\n' */
1163 }
1164
1165 len = strlen(name);
1166 if (name[len - 1] == '=')
1167 val = 1;
1168
1169 for (p = buf; p && *p; p++) {
1170 if (!(p = strstr(p, name)))
1171 break; /* not found the option */
1172 if (p != buf && !isblank(*(p - 1)))
1173 continue; /* no space before the option */
1174 if (!val && *(p + len) != '\0' && !isblank(*(p + len)))
1175 continue; /* no space after the option */
1176 if (val) {
1177 char *v = p + len;
1178 int end;
1179
1180 while (*p && !isblank(*p)) /* jump to the end of the argument */
1181 p++;
1182 end = (*p == '\0');
1183 *p = '\0';
1184 free(mem);
1185 res = mem = strdup(v);
1186 if (end)
1187 break;
1188 } else
1189 res = (char *) name; /* option without '=' */
1190 /* don't break -- keep scanning for more options */
1191 }
1192
1193 return res;
1194 }
1195
1196 /**
1197 * mnt_guess_system_root:
1198 * @devno: device number or zero
1199 * @cache: paths cache or NULL
1200 * @path: returns allocated path
1201 *
1202 * Converts @devno to the real device name if devno major number is greater
1203 * than zero, otherwise use root= kernel cmdline option to get device name.
1204 *
1205 * The function uses /sys to convert devno to device name.
1206 *
1207 * Returns: 0 = success, 1 = not found, <0 = error
1208 *
1209 * Since: 2.34
1210 */
1211 int mnt_guess_system_root(dev_t devno, struct libmnt_cache *cache, char **path)
1212 {
1213 char buf[PATH_MAX];
1214 char *dev = NULL, *spec = NULL;
1215 unsigned int x, y;
1216 int allocated = 0;
1217
1218 assert(path);
1219
1220 DBG(UTILS, ul_debug("guessing system root [devno %u:%u]", major(devno), minor(devno)));
1221
1222 /* The pseudo-fs, net-fs or btrfs devno is useless, otherwise it
1223 * usually matches with the source device, let's try to use it.
1224 */
1225 if (major(devno) > 0) {
1226 dev = sysfs_devno_to_devpath(devno, buf, sizeof(buf));
1227 if (dev) {
1228 DBG(UTILS, ul_debug(" devno converted to %s", dev));
1229 goto done;
1230 }
1231 }
1232
1233 /* Let's try to use root= kernel command line option
1234 */
1235 spec = mnt_get_kernel_cmdline_option("root=");
1236 if (!spec)
1237 goto done;
1238
1239 /* maj:min notation */
1240 if (sscanf(spec, "%u:%u", &x, &y) == 2) {
1241 dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
1242 if (dev) {
1243 DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev));
1244 goto done;
1245 }
1246
1247 /* hexhex notation */
1248 } else if (isxdigit_string(spec)) {
1249 char *end = NULL;
1250 uint32_t n;
1251
1252 errno = 0;
1253 n = strtoul(spec, &end, 16);
1254
1255 if (errno || spec == end || (end && *end))
1256 DBG(UTILS, ul_debug(" failed to parse root='%s'", spec));
1257 else {
1258 /* kernel new_decode_dev() */
1259 x = (n & 0xfff00) >> 8;
1260 y = (n & 0xff) | ((n >> 12) & 0xfff00);
1261 dev = sysfs_devno_to_devpath(makedev(x, y), buf, sizeof(buf));
1262 if (dev) {
1263 DBG(UTILS, ul_debug(" root=%s converted to %s", spec, dev));
1264 goto done;
1265 }
1266 }
1267
1268 /* devname or PARTUUID= etc. */
1269 } else {
1270 DBG(UTILS, ul_debug(" converting root='%s'", spec));
1271
1272 dev = mnt_resolve_spec(spec, cache);
1273 if (dev && !cache)
1274 allocated = 1;
1275 }
1276 done:
1277 free(spec);
1278 if (dev) {
1279 *path = allocated ? dev : strdup(dev);
1280 if (!*path)
1281 return -ENOMEM;
1282 return 0;
1283 }
1284
1285 return 1;
1286 }
1287
1288 #ifdef TEST_PROGRAM
1289 static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
1290 {
1291 char *type = argv[1];
1292 char *pattern = argv[2];
1293
1294 printf("%s\n", mnt_match_fstype(type, pattern) ? "MATCH" : "NOT-MATCH");
1295 return 0;
1296 }
1297
1298 static int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
1299 {
1300 char *optstr = argv[1];
1301 char *pattern = argv[2];
1302
1303 printf("%s\n", mnt_match_options(optstr, pattern) ? "MATCH" : "NOT-MATCH");
1304 return 0;
1305 }
1306
1307 static int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
1308 {
1309 char *optstr = argv[1];
1310 char *pattern = argv[2];
1311
1312 printf("%s\n", startswith(optstr, pattern) ? "YES" : "NOT");
1313 return 0;
1314 }
1315
1316 static int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
1317 {
1318 char *optstr = argv[1];
1319 char *pattern = argv[2];
1320
1321 printf("%s\n", endswith(optstr, pattern) ? "YES" : "NOT");
1322 return 0;
1323 }
1324
1325 static int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
1326 {
1327 char *path = canonicalize_path(argv[1]),
1328 *mnt = path ? mnt_get_mountpoint(path) : NULL;
1329
1330 printf("%s: %s\n", argv[1], mnt ? : "unknown");
1331 free(mnt);
1332 free(path);
1333 return 0;
1334 }
1335
1336 static int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
1337 {
1338 char **filesystems = NULL;
1339 int rc;
1340
1341 rc = mnt_get_filesystems(&filesystems, argc ? argv[1] : NULL);
1342 if (!rc) {
1343 char **p;
1344 for (p = filesystems; *p; p++)
1345 printf("%s\n", *p);
1346 mnt_free_filesystems(filesystems);
1347 }
1348 return rc;
1349 }
1350
1351 static int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
1352 {
1353 int rc;
1354 char *path = canonicalize_path(argv[1]),
1355 *last = NULL;
1356
1357 if (!path)
1358 return -errno;
1359
1360 rc = mnt_chdir_to_parent(path, &last);
1361 if (!rc) {
1362 printf("path='%s', abs='%s', last='%s'\n",
1363 argv[1], path, last);
1364 }
1365 free(path);
1366 free(last);
1367 return rc;
1368 }
1369
1370 static int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
1371 {
1372 char *name = argv[1];
1373 char *res;
1374
1375 res = mnt_get_kernel_cmdline_option(name);
1376 if (!res)
1377 printf("'%s' not found\n", name);
1378 else if (res == name)
1379 printf("'%s' found\n", name);
1380 else {
1381 printf("'%s' found, argument: '%s'\n", name, res);
1382 free(res);
1383 }
1384
1385 return 0;
1386 }
1387
1388
1389 static int test_guess_root(struct libmnt_test *ts, int argc, char *argv[])
1390 {
1391 int rc;
1392 char *real;
1393 dev_t devno = 0;
1394
1395 if (argc) {
1396 unsigned int x, y;
1397
1398 if (sscanf(argv[1], "%u:%u", &x, &y) != 2)
1399 return -EINVAL;
1400 devno = makedev(x, y);
1401 }
1402
1403 rc = mnt_guess_system_root(devno, NULL, &real);
1404 if (rc < 0)
1405 return rc;
1406 if (rc == 1)
1407 fputs("not found\n", stdout);
1408 else {
1409 printf("%s\n", real);
1410 free(real);
1411 }
1412 return 0;
1413 }
1414
1415 static int test_mkdir(struct libmnt_test *ts, int argc, char *argv[])
1416 {
1417 int rc;
1418
1419 rc = ul_mkdir_p(argv[1], S_IRWXU |
1420 S_IRGRP | S_IXGRP |
1421 S_IROTH | S_IXOTH);
1422 if (rc)
1423 printf("mkdir %s failed\n", argv[1]);
1424 return rc;
1425 }
1426
1427 static int test_statfs_type(struct libmnt_test *ts, int argc, char *argv[])
1428 {
1429 struct statfs vfs;
1430 int rc;
1431
1432 rc = statfs(argv[1], &vfs);
1433 if (rc)
1434 printf("%s: statfs failed: %m\n", argv[1]);
1435 else
1436 printf("%-30s: statfs type: %-12s [0x%lx]\n", argv[1],
1437 mnt_statfs_get_fstype(&vfs),
1438 (long) vfs.f_type);
1439 return rc;
1440 }
1441
1442 static int tests_parse_uid(struct libmnt_test *ts, int argc, char *argv[])
1443 {
1444 char *str = argv[1];
1445 uid_t uid = (uid_t) -1;
1446 int rc;
1447
1448 rc = mnt_parse_uid(str, strlen(str), &uid);
1449 if (rc != 0)
1450 printf("failed: rc=%d: %m\n", rc);
1451 else
1452 printf("'%s' --> %lu\n", str, (unsigned long) uid);
1453
1454 return rc;
1455 }
1456
1457 static int tests_parse_gid(struct libmnt_test *ts, int argc, char *argv[])
1458 {
1459 char *str = argv[1];
1460 gid_t gid = (gid_t) -1;
1461 int rc;
1462
1463 rc = mnt_parse_gid(str, strlen(str), &gid);
1464 if (rc != 0)
1465 printf("failed: rc=%d: %m\n", rc);
1466 else
1467 printf("'%s' --> %lu\n", str, (unsigned long) gid);
1468
1469 return rc;
1470 }
1471
1472 static int tests_parse_mode(struct libmnt_test *ts, int argc, char *argv[])
1473 {
1474 char *str = argv[1];
1475 mode_t mod = (mode_t) -1;
1476 int rc;
1477
1478 rc = mnt_parse_mode(str, strlen(str), &mod);
1479 if (rc != 0)
1480 printf("failed: rc=%d: %m\n", rc);
1481 else {
1482 char modstr[11];
1483
1484 xstrmode(mod, modstr);
1485 printf("'%s' --> %04o [%s]\n", str, (unsigned int) mod, modstr);
1486 }
1487 return rc;
1488 }
1489
1490 static int tests_stat(struct libmnt_test *ts, int argc, char *argv[])
1491 {
1492 char *path = argv[1];
1493 struct stat st;
1494 int rc;
1495
1496 if (strcmp(argv[0], "--lstat") == 0)
1497 rc = mnt_safe_lstat(path, &st);
1498 else
1499 rc = mnt_safe_stat(path, &st);
1500 if (rc)
1501 printf("%s: failed: rc=%d: %m\n", path, rc);
1502 else {
1503 printf("%s: \n", path);
1504 printf(" S_ISDIR: %s\n", S_ISDIR(st.st_mode) ? "y" : "n");
1505 printf(" S_ISREG: %s\n", S_ISREG(st.st_mode) ? "y" : "n");
1506 printf(" S_IFLNK: %s\n", S_ISLNK(st.st_mode) ? "y" : "n");
1507
1508 printf(" devno: %lu (%d:%d)\n", (unsigned long) st.st_dev,
1509 major(st.st_dev), minor(st.st_dev));
1510 printf(" ino: %lu\n", (unsigned long) st.st_ino);
1511
1512 }
1513 return rc;
1514 }
1515
1516 int main(int argc, char *argv[])
1517 {
1518 struct libmnt_test tss[] = {
1519 { "--match-fstype", test_match_fstype, "<type> <pattern> FS types matching" },
1520 { "--match-options", test_match_options, "<options> <pattern> options matching" },
1521 { "--filesystems", test_filesystems, "[<pattern>] list /{etc,proc}/filesystems" },
1522 { "--starts-with", test_startswith, "<string> <prefix>" },
1523 { "--ends-with", test_endswith, "<string> <prefix>" },
1524 { "--mountpoint", test_mountpoint, "<path>" },
1525 { "--cd-parent", test_chdir, "<path>" },
1526 { "--kernel-cmdline",test_kernel_cmdline, "<option> | <option>=" },
1527 { "--guess-root", test_guess_root, "[<maj:min>]" },
1528 { "--mkdir", test_mkdir, "<path>" },
1529 { "--statfs-type", test_statfs_type, "<path>" },
1530 { "--parse-uid", tests_parse_uid, "<username|uid>" },
1531 { "--parse-gid", tests_parse_gid, "<groupname|gid>" },
1532 { "--parse-mode", tests_parse_mode, "<number>" },
1533 { "--stat", tests_stat, "<path>" },
1534 { "--lstat", tests_stat, "<path>" },
1535 { NULL }
1536 };
1537
1538 return mnt_run_test(tss, argc, argv);
1539 }
1540
1541 #endif /* TEST_PROGRAM */