]> git.ipfire.org Git - thirdparty/util-linux.git/blob - libmount/src/utils.c
libmount: fix mount.nfs segfault, rely on assert() rather than on nonnull
[thirdparty/util-linux.git] / libmount / src / utils.c
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
8 /**
9 * SECTION: utils
10 * @title: Utils
11 * @short_description: misc utils.
12 */
13 #include <ctype.h>
14 #include <fcntl.h>
15 #include <pwd.h>
16 #include <grp.h>
17
18 #include "strutils.h"
19 #include "pathnames.h"
20 #include "mountP.h"
21 #include "mangle.h"
22 #include "canonicalize.h"
23 #include "env.h"
24 #include "match.h"
25
26 int endswith(const char *s, const char *sx)
27 {
28 ssize_t off;
29
30 assert(s);
31 assert(sx);
32
33 off = strlen(s);
34 if (!off)
35 return 0;
36 off -= strlen(sx);
37 if (off < 0)
38 return 0;
39
40 return !strcmp(s + off, sx);
41 }
42
43 int startswith(const char *s, const char *sx)
44 {
45 size_t off;
46
47 assert(s);
48 assert(sx);
49
50 off = strlen(sx);
51 if (!off)
52 return 0;
53
54 return !strncmp(s, sx, off);
55 }
56
57 /*
58 * Return 1 if the file does not accessible of empty
59 */
60 int is_file_empty(const char *name)
61 {
62 struct stat st;
63 assert(name);
64
65 return (stat(name, &st) != 0 || st.st_size == 0);
66 }
67
68 int mnt_parse_offset(const char *str, size_t len, uintmax_t *res)
69 {
70 char *p;
71 int rc = 0;
72
73 if (!str || !*str)
74 return -EINVAL;
75
76 p = strndup(str, len);
77 if (!p)
78 return -errno;
79
80 if (strtosize(p, res))
81 rc = -EINVAL;
82 free(p);
83 return rc;
84 }
85
86 /* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
87 static int fstype_cmp(const void *v1, const void *v2)
88 {
89 const char *s1 = *(const char **)v1;
90 const char *s2 = *(const char **)v2;
91
92 return strcmp(s1, s2);
93 }
94
95 /* returns basename and keeps dirname in the @path, if @path is "/" (root)
96 * then returns empty string */
97 char *stripoff_last_component(char *path)
98 {
99 char *p = path ? strrchr(path, '/') : NULL;
100
101 if (!p)
102 return NULL;
103 *p = '\0';
104 return p + 1;
105 }
106
107 /*
108 * Note that the @target has to be absolute path (so at least "/"). The
109 * @filename returns allocated buffer with last path component, for example:
110 *
111 * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test"
112 */
113 int mnt_chdir_to_parent(const char *target, char **filename)
114 {
115 char *buf, *parent, *last = NULL;
116 char cwd[PATH_MAX];
117 int rc = -EINVAL;
118
119 if (!target || *target != '/')
120 return -EINVAL;
121
122 DBG(UTILS, mnt_debug("moving to %s parent", target));
123
124 buf = strdup(target);
125 if (!buf)
126 return -ENOMEM;
127
128 if (*(buf + 1) != '\0') {
129 last = stripoff_last_component(buf);
130 if (!last)
131 goto err;
132 }
133
134 parent = buf && *buf ? buf : "/";
135
136 if (chdir(parent) == -1) {
137 DBG(UTILS, mnt_debug("failed to chdir to %s: %m", parent));
138 rc = -errno;
139 goto err;
140 }
141 if (!getcwd(cwd, sizeof(cwd))) {
142 DBG(UTILS, mnt_debug("failed to obtain current directory: %m"));
143 rc = -errno;
144 goto err;
145 }
146 if (strcmp(cwd, parent) != 0) {
147 DBG(UTILS, mnt_debug(
148 "unexpected chdir (expected=%s, cwd=%s)", parent, cwd));
149 goto err;
150 }
151
152 DBG(CXT, mnt_debug(
153 "current directory moved to %s [last_component='%s']",
154 parent, last));
155
156 if (filename) {
157 *filename = buf;
158
159 if (!last || !*last)
160 memcpy(*filename, ".", 2);
161 else
162 memcpy(*filename, last, strlen(last) + 1);
163 }
164 return 0;
165 err:
166 free(buf);
167 return rc;
168 }
169
170 /*
171 * Check if @path is on read-only filesystem independently on file permissions.
172 */
173 int mnt_is_readonly(const char *path)
174 {
175 if (access(path, W_OK) == 0)
176 return 0;
177 if (errno == EROFS)
178 return 1;
179 if (errno != EACCES)
180 return 0;
181
182 #ifdef HAVE_FUTIMENS
183 /*
184 * access(2) returns EACCES on read-only FS:
185 *
186 * - for set-uid application if one component of the path is not
187 * accessible for the current rUID. (Note that euidaccess(2) does not
188 * check for EROFS at all).
189 *
190 * - for read-write filesystem with read-only VFS node (aka -o remount,ro,bind)
191 */
192 {
193 struct timespec times[2];
194
195 times[0].tv_nsec = UTIME_NOW; /* atime */
196 times[1].tv_nsec = UTIME_OMIT; /* mtime */
197
198 if (utimensat(AT_FDCWD, path, times, 0) == -1)
199 return errno == EROFS;
200 }
201 #endif
202 return 0;
203 }
204
205 /**
206 * mnt_mangle:
207 * @str: string
208 *
209 * Encode @str to be compatible with fstab/mtab
210 *
211 * Returns: new allocated string or NULL in case of error.
212 */
213 char *mnt_mangle(const char *str)
214 {
215 return mangle(str);
216 }
217
218 /**
219 * mnt_unmangle:
220 * @str: string
221 *
222 * Decode @str from fstab/mtab
223 *
224 * Returns: new allocated string or NULL in case of error.
225 */
226 char *mnt_unmangle(const char *str)
227 {
228 return unmangle(str, NULL);
229 }
230
231 /**
232 * mnt_fstype_is_pseudofs:
233 * @type: filesystem name
234 *
235 * Returns: 1 for filesystems like proc, sysfs, ... or 0.
236 */
237 int mnt_fstype_is_pseudofs(const char *type)
238 {
239 /* This array must remain sorted when adding new fstypes */
240 static const char *pseudofs[] = {
241 "anon_inodefs",
242 "autofs",
243 "bdev",
244 "binfmt_misc",
245 "cgroup",
246 "configfs",
247 "cpuset",
248 "debugfs",
249 "devfs",
250 "devpts",
251 "devtmpfs",
252 "dlmfs",
253 "fuse.gvfs-fuse-daemon",
254 "fusectl",
255 "hugetlbfs",
256 "mqueue",
257 "nfsd",
258 "none",
259 "pipefs",
260 "proc",
261 "pstore",
262 "ramfs",
263 "rootfs",
264 "rpc_pipefs",
265 "securityfs",
266 "sockfs",
267 "spufs",
268 "sysfs",
269 "tmpfs"
270 };
271
272 assert(type);
273
274 return !(bsearch(&type, pseudofs, ARRAY_SIZE(pseudofs),
275 sizeof(char*), fstype_cmp) == NULL);
276 }
277
278 /**
279 * mnt_fstype_is_netfs:
280 * @type: filesystem name
281 *
282 * Returns: 1 for filesystems like cifs, nfs, ... or 0.
283 */
284 int mnt_fstype_is_netfs(const char *type)
285 {
286 assert(type);
287
288 if (strcmp(type, "cifs") == 0 ||
289 strcmp(type, "smbfs") == 0 ||
290 strncmp(type,"nfs", 3) == 0 ||
291 strcmp(type, "afs") == 0 ||
292 strcmp(type, "ncpfs") == 0 ||
293 strncmp(type,"9p", 2) == 0)
294 return 1;
295 return 0;
296 }
297
298 /**
299 * mnt_match_fstype:
300 * @type: filesystem type
301 * @pattern: filesystem name or comma delimited list of names
302 *
303 * The @pattern list of filesystem can be prefixed with a global
304 * "no" prefix to invert matching of the whole list. The "no" could
305 * also be used for individual items in the @pattern list. So,
306 * "nofoo,bar" has the same meaning as "nofoo,nobar".
307 *
308 * "bar" : "nofoo,bar" -> False (global "no" prefix)
309 *
310 * "bar" : "foo,bar" -> True
311 *
312 * "bar" : "foo,nobar" -> False
313 *
314 * Returns: 1 if type is matching, else 0. This function also returns
315 * 0 if @pattern is NULL and @type is non-NULL.
316 */
317 int mnt_match_fstype(const char *type, const char *pattern)
318 {
319 return match_fstype(type, pattern);
320 }
321
322
323 /* Returns 1 if needle found or noneedle not found in haystack
324 * Otherwise returns 0
325 */
326 static int check_option(const char *haystack, size_t len,
327 const char *needle, size_t needle_len)
328 {
329 const char *p;
330 int no = 0;
331
332 if (needle_len >= 1 && *needle == '+') {
333 needle++;
334 needle_len--;
335 } else if (needle_len >= 2 && !strncmp(needle, "no", 2)) {
336 no = 1;
337 needle += 2;
338 needle_len -= 2;
339 }
340
341 for (p = haystack; p && p < haystack + len; p++) {
342 char *sep = strchr(p, ',');
343 size_t plen = sep ? (size_t) (sep - p) :
344 len - (p - haystack);
345
346 if (plen == needle_len) {
347 if (!strncmp(p, needle, plen))
348 return !no; /* foo or nofoo was found */
349 }
350 p += plen;
351 }
352
353 return no; /* foo or nofoo was not found */
354 }
355
356 /**
357 * mnt_match_options:
358 * @optstr: options string
359 * @pattern: comma delimited list of options
360 *
361 * The "no" could used for individual items in the @options list. The "no"
362 * prefix does not have a global meaning.
363 *
364 * Unlike fs type matching, nonetdev,user and nonetdev,nouser have
365 * DIFFERENT meanings; each option is matched explicitly as specified.
366 *
367 * The "no" prefix interpretation could be disable by "+" prefix, for example
368 * "+noauto" matches if @optstr literally contains "noauto" string.
369 *
370 * "xxx,yyy,zzz" : "nozzz" -> False
371 *
372 * "xxx,yyy,zzz" : "xxx,noeee" -> True
373 *
374 * "bar,zzz" : "nofoo" -> True
375 *
376 * "nofoo,bar" : "+nofoo" -> True
377 *
378 * "bar,zzz" : "+nofoo" -> False
379 *
380 *
381 * Returns: 1 if pattern is matching, else 0. This function also returns 0
382 * if @pattern is NULL and @optstr is non-NULL.
383 */
384 int mnt_match_options(const char *optstr, const char *pattern)
385 {
386 const char *p;
387 size_t len, optstr_len = 0;
388
389 if (!pattern && !optstr)
390 return 1;
391 if (!pattern)
392 return 0;
393
394 len = strlen(pattern);
395 if (optstr)
396 optstr_len = strlen(optstr);
397
398 for (p = pattern; p < pattern + len; p++) {
399 char *sep = strchr(p, ',');
400 size_t plen = sep ? (size_t) (sep - p) :
401 len - (p - pattern);
402
403 if (!plen)
404 continue; /* if two ',' appear in a row */
405
406 if (!check_option(optstr, optstr_len, p, plen))
407 return 0; /* any match failure means failure */
408
409 p += plen;
410 }
411
412 /* no match failures in list means success */
413 return 1;
414 }
415
416 void mnt_free_filesystems(char **filesystems)
417 {
418 char **p;
419
420 if (!filesystems)
421 return;
422 for (p = filesystems; *p; p++)
423 free(*p);
424 free(filesystems);
425 }
426
427 static int add_filesystem(char ***filesystems, char *name)
428 {
429 int n = 0;
430
431 assert(filesystems);
432 assert(name);
433
434 if (*filesystems) {
435 char **p;
436 for (n = 0, p = *filesystems; *p; p++, n++) {
437 if (strcmp(*p, name) == 0)
438 return 0;
439 }
440 }
441
442 #define MYCHUNK 16
443
444 if (n == 0 || !((n + 1) % MYCHUNK)) {
445 size_t items = ((n + 1 + MYCHUNK) / MYCHUNK) * MYCHUNK;
446 char **x = realloc(*filesystems, items * sizeof(char *));
447
448 if (!x)
449 goto err;
450 *filesystems = x;
451 }
452 name = strdup(name);
453 if (!name)
454 goto err;
455 (*filesystems)[n] = name;
456 (*filesystems)[n + 1] = NULL;
457 return 0;
458 err:
459 mnt_free_filesystems(*filesystems);
460 return -ENOMEM;
461 }
462
463 static int get_filesystems(const char *filename, char ***filesystems, const char *pattern)
464 {
465 int rc = 0;
466 FILE *f;
467 char line[128];
468
469 f = fopen(filename, "r" UL_CLOEXECSTR);
470 if (!f)
471 return 1;
472
473 DBG(UTILS, mnt_debug("reading filesystems list from: %s", filename));
474
475 while (fgets(line, sizeof(line), f)) {
476 char name[sizeof(line)];
477
478 if (*line == '#' || strncmp(line, "nodev", 5) == 0)
479 continue;
480 if (sscanf(line, " %128[^\n ]\n", name) != 1)
481 continue;
482 if (strcmp(name, "*") == 0) {
483 rc = 1;
484 break; /* end of the /etc/filesystems */
485 }
486 if (pattern && !mnt_match_fstype(name, pattern))
487 continue;
488 rc = add_filesystem(filesystems, name);
489 if (rc)
490 break;
491 }
492
493 fclose(f);
494 return rc;
495 }
496
497 /*
498 * Always check @filesystems pointer!
499 *
500 * man mount:
501 *
502 * ...mount will try to read the file /etc/filesystems, or, if that does not
503 * exist, /proc/filesystems. All of the filesystem types listed there will
504 * be tried, except for those that are labeled "nodev" (e.g., devpts,
505 * proc and nfs). If /etc/filesystems ends in a line with a single * only,
506 * mount will read /proc/filesystems afterwards.
507 */
508 int mnt_get_filesystems(char ***filesystems, const char *pattern)
509 {
510 int rc;
511
512 if (!filesystems)
513 return -EINVAL;
514
515 *filesystems = NULL;
516
517 rc = get_filesystems(_PATH_FILESYSTEMS, filesystems, pattern);
518 if (rc != 1)
519 return rc;
520
521 rc = get_filesystems(_PATH_PROC_FILESYSTEMS, filesystems, pattern);
522 if (rc == 1 && *filesystems)
523 rc = 0; /* not found /proc/filesystems */
524
525 return rc;
526 }
527
528 static size_t get_pw_record_size(void)
529 {
530 #ifdef _SC_GETPW_R_SIZE_MAX
531 long sz = sysconf(_SC_GETPW_R_SIZE_MAX);
532 if (sz > 0)
533 return sz;
534 #endif
535 return 16384;
536 }
537
538 /*
539 * Returns allocated string with username or NULL.
540 */
541 char *mnt_get_username(const uid_t uid)
542 {
543 struct passwd pwd;
544 struct passwd *res;
545 size_t sz = get_pw_record_size();
546 char *buf, *username = NULL;
547
548 buf = malloc(sz);
549 if (!buf)
550 return NULL;
551
552 if (!getpwuid_r(uid, &pwd, buf, sz, &res) && res)
553 username = strdup(pwd.pw_name);
554
555 free(buf);
556 return username;
557 }
558
559 int mnt_get_uid(const char *username, uid_t *uid)
560 {
561 int rc = -1;
562 struct passwd pwd;
563 struct passwd *pw;
564 size_t sz = get_pw_record_size();
565 char *buf;
566
567 if (!username || !uid)
568 return -EINVAL;
569
570 buf = malloc(sz);
571 if (!buf)
572 return -ENOMEM;
573
574 if (!getpwnam_r(username, &pwd, buf, sz, &pw) && pw) {
575 *uid= pw->pw_uid;
576 rc = 0;
577 } else {
578 DBG(UTILS, mnt_debug(
579 "cannot convert '%s' username to UID", username));
580 rc = errno ? -errno : -EINVAL;
581 }
582
583 free(buf);
584 return rc;
585 }
586
587 int mnt_get_gid(const char *groupname, gid_t *gid)
588 {
589 int rc = -1;
590 struct group grp;
591 struct group *gr;
592 size_t sz = get_pw_record_size();
593 char *buf;
594
595 if (!groupname || !gid)
596 return -EINVAL;
597
598 buf = malloc(sz);
599 if (!buf)
600 return -ENOMEM;
601
602 if (!getgrnam_r(groupname, &grp, buf, sz, &gr) && gr) {
603 *gid= gr->gr_gid;
604 rc = 0;
605 } else {
606 DBG(UTILS, mnt_debug(
607 "cannot convert '%s' groupname to GID", groupname));
608 rc = errno ? -errno : -EINVAL;
609 }
610
611 free(buf);
612 return rc;
613 }
614
615 int mnt_in_group(gid_t gid)
616 {
617 int rc = 0, n, i;
618 gid_t *grps = NULL;
619
620 if (getgid() == gid)
621 return 1;
622
623 n = getgroups(0, NULL);
624 if (n <= 0)
625 goto done;
626
627 grps = malloc(n * sizeof(*grps));
628 if (!grps)
629 goto done;
630
631 if (getgroups(n, grps) == n) {
632 for (i = 0; i < n; i++) {
633 if (grps[i] == gid) {
634 rc = 1;
635 break;
636 }
637 }
638 }
639 done:
640 free(grps);
641 return rc;
642 }
643
644 static int try_write(const char *filename)
645 {
646 int fd;
647
648 if (!filename)
649 return -EINVAL;
650
651 fd = open(filename, O_RDWR|O_CREAT|O_CLOEXEC,
652 S_IWUSR|S_IRUSR|S_IRGRP|S_IROTH);
653 if (fd >= 0) {
654 close(fd);
655 return 0;
656 }
657 return -errno;
658 }
659
660 /**
661 * mnt_has_regular_mtab:
662 * @mtab: returns path to mtab
663 * @writable: returns 1 if the file is writable
664 *
665 * If the file does not exist and @writable argument is not NULL then it will
666 * try to create the file
667 *
668 * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check
669 * errno for more details).
670 */
671 int mnt_has_regular_mtab(const char **mtab, int *writable)
672 {
673 struct stat st;
674 int rc;
675 const char *filename = mtab && *mtab ? *mtab : mnt_get_mtab_path();
676
677 if (writable)
678 *writable = 0;
679 if (mtab && !*mtab)
680 *mtab = filename;
681
682 DBG(UTILS, mnt_debug("mtab: %s", filename));
683
684 rc = lstat(filename, &st);
685
686 if (rc == 0) {
687 /* file exist */
688 if (S_ISREG(st.st_mode)) {
689 if (writable)
690 *writable = !try_write(filename);
691 return 1;
692 }
693 goto done;
694 }
695
696 /* try to create the file */
697 if (writable) {
698 *writable = !try_write(filename);
699 if (*writable)
700 return 1;
701 }
702
703 done:
704 DBG(UTILS, mnt_debug("%s: irregular/non-writable", filename));
705 return 0;
706 }
707
708 /*
709 * Don't export this to libmount API -- utab is private library stuff.
710 *
711 * If the file does not exist and @writable argument is not NULL then it will
712 * try to create the directory (e.g. /run/mount) and the file.
713 *
714 * Returns: 1 if utab is a regular file, and 0 in case of
715 * error (check errno for more details).
716 */
717 int mnt_has_regular_utab(const char **utab, int *writable)
718 {
719 struct stat st;
720 int rc;
721 const char *filename = utab && *utab ? *utab : mnt_get_utab_path();
722
723 if (writable)
724 *writable = 0;
725 if (utab && !*utab)
726 *utab = filename;
727
728 DBG(UTILS, mnt_debug("utab: %s", filename));
729
730 rc = lstat(filename, &st);
731
732 if (rc == 0) {
733 /* file exist */
734 if (S_ISREG(st.st_mode)) {
735 if (writable)
736 *writable = !try_write(filename);
737 return 1;
738 }
739 goto done; /* it's not regular file */
740 }
741
742 if (writable) {
743 char *dirname = strdup(filename);
744
745 if (!dirname)
746 goto done;
747
748 stripoff_last_component(dirname); /* remove filename */
749
750 rc = mkdir(dirname, S_IWUSR|
751 S_IRUSR|S_IRGRP|S_IROTH|
752 S_IXUSR|S_IXGRP|S_IXOTH);
753 free(dirname);
754 if (rc && errno != EEXIST)
755 goto done; /* probably EACCES */
756
757 *writable = !try_write(filename);
758 if (*writable)
759 return 1;
760 }
761 done:
762 DBG(UTILS, mnt_debug("%s: irregular/non-writable file", filename));
763 return 0;
764 }
765
766 /**
767 * mnt_get_swaps_path:
768 *
769 * Returns: path to /proc/swaps or $LIBMOUNT_SWAPS.
770 */
771 const char *mnt_get_swaps_path(void)
772 {
773 const char *p = safe_getenv("LIBMOUNT_SWAPS");
774 return p ? : _PATH_PROC_SWAPS;
775 }
776
777 /**
778 * mnt_get_fstab_path:
779 *
780 * Returns: path to /etc/fstab or $LIBMOUNT_FSTAB.
781 */
782 const char *mnt_get_fstab_path(void)
783 {
784 const char *p = safe_getenv("LIBMOUNT_FSTAB");
785 return p ? : _PATH_MNTTAB;
786 }
787
788 /**
789 * mnt_get_mtab_path:
790 *
791 * This function returns *default* location of the mtab file. The result does
792 * not have to be writable. See also mnt_has_regular_mtab().
793 *
794 * Returns: path to /etc/mtab or $LIBMOUNT_MTAB.
795 */
796 const char *mnt_get_mtab_path(void)
797 {
798 const char *p = safe_getenv("LIBMOUNT_MTAB");
799 return p ? : _PATH_MOUNTED;
800 }
801
802 /*
803 * Don't export this to libmount API -- utab is private library stuff.
804 *
805 * Returns: path to /run/mount/utab (or /dev/.mount/utab) or $LIBMOUNT_UTAB.
806 */
807 const char *mnt_get_utab_path(void)
808 {
809 struct stat st;
810 const char *p = safe_getenv("LIBMOUNT_UTAB");
811
812 if (p)
813 return p;
814
815 if (stat(MNT_RUNTIME_TOPDIR, &st) == 0)
816 return MNT_PATH_UTAB;
817
818 return MNT_PATH_UTAB_OLD;
819 }
820
821
822 /* returns file descriptor or -errno, @name returns uniques filename
823 */
824 int mnt_open_uniq_filename(const char *filename, char **name)
825 {
826 int rc, fd;
827 char *n;
828 mode_t oldmode;
829
830 if (!filename)
831 return -EINVAL;
832 if (name)
833 *name = NULL;
834
835 rc = asprintf(&n, "%s.XXXXXX", filename);
836 if (rc <= 0)
837 return -errno;
838
839 /* This is for very old glibc and for compatibility with Posix where is
840 * nothing about mkstemp() mode. All sane glibc use secure mode (0600).
841 */
842 oldmode = umask(S_IRGRP|S_IWGRP|S_IXGRP|
843 S_IROTH|S_IWOTH|S_IXOTH);
844 fd = mkostemp(n, O_RDWR|O_CREAT|O_EXCL|O_CLOEXEC);
845 umask(oldmode);
846
847 if (fd >= 0 && name)
848 *name = n;
849 else
850 free(n);
851
852 return fd < 0 ? -errno : fd;
853 }
854
855 /**
856 * mnt_get_mountpoint:
857 * @path: pathname
858 *
859 * This function finds the mountpoint that a given path resides in. @path
860 * should be canonicalized. The returned pointer should be freed by the caller.
861 *
862 * Returns: allocated string with target of the mounted device or NULL on error
863 */
864 char *mnt_get_mountpoint(const char *path)
865 {
866 char *mnt;
867 struct stat st;
868 dev_t dir, base;
869
870 assert(path);
871
872 mnt = strdup(path);
873 if (!mnt)
874 return NULL;
875 if (*mnt == '/' && *(mnt + 1) == '\0')
876 goto done;
877
878 if (stat(mnt, &st))
879 goto err;
880 base = st.st_dev;
881
882 do {
883 char *p = stripoff_last_component(mnt);
884
885 if (!p)
886 break;
887 if (stat(*mnt ? mnt : "/", &st))
888 goto err;
889 dir = st.st_dev;
890 if (dir != base) {
891 if (p > mnt)
892 *(p - 1) = '/';
893 goto done;
894 }
895 base = dir;
896 } while (mnt && *(mnt + 1) != '\0');
897
898 memcpy(mnt, "/", 2);
899 done:
900 DBG(UTILS, mnt_debug("%s mountpoint is %s", path, mnt));
901 return mnt;
902 err:
903 free(mnt);
904 return NULL;
905 }
906
907 char *mnt_get_fs_root(const char *path, const char *mnt)
908 {
909 char *m = (char *) mnt, *res;
910 const char *p;
911 size_t sz;
912
913 if (!m)
914 m = mnt_get_mountpoint(path);
915 if (!m)
916 return NULL;
917
918 sz = strlen(m);
919 p = sz > 1 ? path + sz : path;
920
921 if (m != mnt)
922 free(m);
923
924 res = *p ? strdup(p) : strdup("/");
925 DBG(UTILS, mnt_debug("%s fs-root is %s", path, res));
926 return res;
927 }
928
929 /*
930 * Search for @name kernel command parametr.
931 *
932 * Returns newly allocated string with parameter argument if the @name is
933 * specified as "name=" or returns pointer to @name or returns NULL if not
934 * found.
935 *
936 * For example cmdline: "aaa bbb=BBB ccc"
937 *
938 * @name is "aaa" --returns--> "aaa" (pointer to @name)
939 * @name is "bbb=" --returns--> "BBB" (allocated)
940 * @name is "foo" --returns--> NULL
941 */
942 char *mnt_get_kernel_cmdline_option(const char *name)
943 {
944 FILE *f;
945 size_t len;
946 int val = 0;
947 char *p, *res = NULL;
948 char buf[BUFSIZ]; /* see kernel include/asm-generic/setup.h: COMMAND_LINE_SIZE */
949 const char *path = _PATH_PROC_CMDLINE;
950
951 if (!name)
952 return NULL;
953
954 #ifdef TEST_PROGRAM
955 path = getenv("LIBMOUNT_KERNEL_CMDLINE");
956 if (!path)
957 path = _PATH_PROC_CMDLINE;
958 #endif
959 f = fopen(path, "r" UL_CLOEXECSTR);
960 if (!f)
961 return NULL;
962
963 p = fgets(buf, sizeof(buf), f);
964 fclose(f);
965
966 if (!p || !*p || *p == '\n')
967 return NULL;
968
969 len = strlen(buf);
970 *(buf + len - 1) = '\0'; /* remove last '\n' */
971
972 len = strlen(name);
973 if (len && *(name + len - 1) == '=')
974 val = 1;
975
976 for ( ; p && *p; p++) {
977 if (!(p = strstr(p, name)))
978 break; /* not found the option */
979 if (p != buf && !isblank(*(p - 1)))
980 continue; /* no space before the option */
981 if (!val && *(p + len) != '\0' && !isblank(*(p + len)))
982 continue; /* no space behind the option */
983 if (val) {
984 char *v = p + len;
985
986 while (*p && !isblank(*p)) /* jump to the end of the argument */
987 p++;
988 *p = '\0';
989 res = strdup(v);
990 break;
991 } else
992 res = (char *) name; /* option without '=' */
993 break;
994 }
995
996 return res;
997 }
998
999 int mkdir_p(const char *path, mode_t mode)
1000 {
1001 char *p, *dir;
1002 int rc = 0;
1003
1004 if (!path || !*path)
1005 return -EINVAL;
1006
1007 dir = p = strdup(path);
1008 if (!dir)
1009 return -ENOMEM;
1010
1011 if (*p == '/')
1012 p++;
1013
1014 while (p && *p) {
1015 char *e = strchr(p, '/');
1016 if (e)
1017 *e = '\0';
1018 if (*p) {
1019 rc = mkdir(dir, mode);
1020 if (rc && errno != EEXIST)
1021 break;
1022 rc = 0;
1023 }
1024 if (!e)
1025 break;
1026 *e = '/';
1027 p = e + 1;
1028 }
1029
1030 DBG(UTILS, mnt_debug("%s mkdir %s", path, rc ? "FAILED" : "SUCCESS"));
1031
1032 free(dir);
1033 return rc;
1034 }
1035
1036 #ifdef TEST_PROGRAM
1037 int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
1038 {
1039 char *type = argv[1];
1040 char *pattern = argv[2];
1041
1042 printf("%s\n", mnt_match_fstype(type, pattern) ? "MATCH" : "NOT-MATCH");
1043 return 0;
1044 }
1045
1046 int test_match_options(struct libmnt_test *ts, int argc, char *argv[])
1047 {
1048 char *optstr = argv[1];
1049 char *pattern = argv[2];
1050
1051 printf("%s\n", mnt_match_options(optstr, pattern) ? "MATCH" : "NOT-MATCH");
1052 return 0;
1053 }
1054
1055 int test_startswith(struct libmnt_test *ts, int argc, char *argv[])
1056 {
1057 char *optstr = argv[1];
1058 char *pattern = argv[2];
1059
1060 printf("%s\n", startswith(optstr, pattern) ? "YES" : "NOT");
1061 return 0;
1062 }
1063
1064 int test_endswith(struct libmnt_test *ts, int argc, char *argv[])
1065 {
1066 char *optstr = argv[1];
1067 char *pattern = argv[2];
1068
1069 printf("%s\n", endswith(optstr, pattern) ? "YES" : "NOT");
1070 return 0;
1071 }
1072
1073 int test_mountpoint(struct libmnt_test *ts, int argc, char *argv[])
1074 {
1075 char *path = canonicalize_path(argv[1]),
1076 *mnt = path ? mnt_get_mountpoint(path) : NULL;
1077
1078 printf("%s: %s\n", argv[1], mnt ? : "unknown");
1079 free(mnt);
1080 free(path);
1081 return 0;
1082 }
1083
1084 int test_fsroot(struct libmnt_test *ts, int argc, char *argv[])
1085 {
1086 char *path = canonicalize_path(argv[1]),
1087 *mnt = path ? mnt_get_fs_root(path, NULL) : NULL;
1088
1089 printf("%s: %s\n", argv[1], mnt ? : "unknown");
1090 free(mnt);
1091 free(path);
1092 return 0;
1093 }
1094
1095 int test_filesystems(struct libmnt_test *ts, int argc, char *argv[])
1096 {
1097 char **filesystems = NULL;
1098 int rc;
1099
1100 rc = mnt_get_filesystems(&filesystems, argc ? argv[1] : NULL);
1101 if (!rc) {
1102 char **p;
1103 for (p = filesystems; *p; p++)
1104 printf("%s\n", *p);
1105 mnt_free_filesystems(filesystems);
1106 }
1107 return rc;
1108 }
1109
1110 int test_chdir(struct libmnt_test *ts, int argc, char *argv[])
1111 {
1112 int rc;
1113 char *path = canonicalize_path(argv[1]),
1114 *last = NULL;
1115
1116 if (!path)
1117 return -errno;
1118
1119 rc = mnt_chdir_to_parent(path, &last);
1120 if (!rc) {
1121 printf("path='%s', abs='%s', last='%s'\n",
1122 argv[1], path, last);
1123 }
1124 free(path);
1125 free(last);
1126 return rc;
1127 }
1128
1129 int test_kernel_cmdline(struct libmnt_test *ts, int argc, char *argv[])
1130 {
1131 char *name = argv[1];
1132 char *res;
1133
1134 res = mnt_get_kernel_cmdline_option(name);
1135 if (!res)
1136 printf("'%s' not found\n", name);
1137 else if (res == name)
1138 printf("'%s' found\n", name);
1139 else {
1140 printf("'%s' found, argument: '%s'\n", name, res);
1141 free(res);
1142 }
1143
1144 return 0;
1145 }
1146
1147 int test_mkdir(struct libmnt_test *ts, int argc, char *argv[])
1148 {
1149 int rc;
1150
1151 rc = mkdir_p(argv[1], S_IRWXU |
1152 S_IRGRP | S_IXGRP |
1153 S_IROTH | S_IXOTH);
1154 if (rc)
1155 printf("mkdir %s failed\n", argv[1]);
1156 return rc;
1157 }
1158
1159
1160 int main(int argc, char *argv[])
1161 {
1162 struct libmnt_test tss[] = {
1163 { "--match-fstype", test_match_fstype, "<type> <pattern> FS types matching" },
1164 { "--match-options", test_match_options, "<options> <pattern> options matching" },
1165 { "--filesystems", test_filesystems, "[<pattern>] list /{etc,proc}/filesystems" },
1166 { "--starts-with", test_startswith, "<string> <prefix>" },
1167 { "--ends-with", test_endswith, "<string> <prefix>" },
1168 { "--mountpoint", test_mountpoint, "<path>" },
1169 { "--fs-root", test_fsroot, "<path>" },
1170 { "--cd-parent", test_chdir, "<path>" },
1171 { "--kernel-cmdline",test_kernel_cmdline, "<option> | <option>=" },
1172 { "--mkdir", test_mkdir, "<path>" },
1173
1174 { NULL }
1175 };
1176
1177 return mnt_run_test(tss, argc, argv);
1178 }
1179
1180 #endif /* TEST_PROGRAM */