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