]>
git.ipfire.org Git - thirdparty/util-linux.git/blob - libmount/src/utils.c
2 * Copyright (C) 2008-2009 Karel Zak <kzak@redhat.com>
4 * This file may be redistributed under the terms of the
5 * GNU Lesser General Public License.
11 * @short_description: misc utils.
19 #include "pathnames.h"
22 #include "canonicalize.h"
26 int endswith(const char *s
, const char *sx
)
40 return !strcmp(s
+ off
, sx
);
43 int startswith(const char *s
, const char *sx
)
54 return !strncmp(s
, sx
, off
);
58 * Return 1 if the file does not accessible of empty
60 int is_file_empty(const char *name
)
65 return (stat(name
, &st
) != 0 || st
.st_size
== 0);
68 int mnt_parse_offset(const char *str
, size_t len
, uintmax_t *res
)
76 p
= strndup(str
, len
);
80 if (strtosize(p
, res
))
86 /* used as a callback by bsearch in mnt_fstype_is_pseudofs() */
87 static int fstype_cmp(const void *v1
, const void *v2
)
89 const char *s1
= *(const char **)v1
;
90 const char *s2
= *(const char **)v2
;
92 return strcmp(s1
, s2
);
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
)
99 char *p
= path
? strrchr(path
, '/') : NULL
;
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:
111 * mnt_chdir_to_parent("/mnt/test", &buf) ==> chdir("/mnt"), buf="test"
113 int mnt_chdir_to_parent(const char *target
, char **filename
)
115 char *buf
, *parent
, *last
= NULL
;
119 if (!target
|| *target
!= '/')
122 DBG(UTILS
, mnt_debug("moving to %s parent", target
));
124 buf
= strdup(target
);
128 if (*(buf
+ 1) != '\0') {
129 last
= stripoff_last_component(buf
);
134 parent
= buf
&& *buf
? buf
: "/";
136 if (chdir(parent
) == -1) {
137 DBG(UTILS
, mnt_debug("failed to chdir to %s: %m", parent
));
141 if (!getcwd(cwd
, sizeof(cwd
))) {
142 DBG(UTILS
, mnt_debug("failed to obtain current directory: %m"));
146 if (strcmp(cwd
, parent
) != 0) {
147 DBG(UTILS
, mnt_debug(
148 "unexpected chdir (expected=%s, cwd=%s)", parent
, cwd
));
153 "current directory moved to %s [last_component='%s']",
160 memcpy(*filename
, ".", 2);
162 memcpy(*filename
, last
, strlen(last
) + 1);
171 * Check if @path is on read-only filesystem independently on file permissions.
173 int mnt_is_readonly(const char *path
)
175 if (access(path
, W_OK
) == 0)
184 * access(2) returns EACCES on read-only FS:
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).
190 * - for read-write filesystem with read-only VFS node (aka -o remount,ro,bind)
193 struct timespec times
[2];
195 times
[0].tv_nsec
= UTIME_NOW
; /* atime */
196 times
[1].tv_nsec
= UTIME_OMIT
; /* mtime */
198 if (utimensat(AT_FDCWD
, path
, times
, 0) == -1)
199 return errno
== EROFS
;
209 * Encode @str to be compatible with fstab/mtab
211 * Returns: new allocated string or NULL in case of error.
213 char *mnt_mangle(const char *str
)
222 * Decode @str from fstab/mtab
224 * Returns: new allocated string or NULL in case of error.
226 char *mnt_unmangle(const char *str
)
228 return unmangle(str
, NULL
);
232 * mnt_fstype_is_pseudofs:
233 * @type: filesystem name
235 * Returns: 1 for filesystems like proc, sysfs, ... or 0.
237 int mnt_fstype_is_pseudofs(const char *type
)
239 /* This array must remain sorted when adding new fstypes */
240 static const char *pseudofs
[] = {
253 "fuse.gvfs-fuse-daemon",
274 return !(bsearch(&type
, pseudofs
, ARRAY_SIZE(pseudofs
),
275 sizeof(char*), fstype_cmp
) == NULL
);
279 * mnt_fstype_is_netfs:
280 * @type: filesystem name
282 * Returns: 1 for filesystems like cifs, nfs, ... or 0.
284 int mnt_fstype_is_netfs(const char *type
)
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)
300 * @type: filesystem type
301 * @pattern: filesystem name or comma delimited list of names
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".
308 * "bar" : "nofoo,bar" -> False (global "no" prefix)
310 * "bar" : "foo,bar" -> True
312 * "bar" : "foo,nobar" -> False
314 * Returns: 1 if type is matching, else 0. This function also returns
315 * 0 if @pattern is NULL and @type is non-NULL.
317 int mnt_match_fstype(const char *type
, const char *pattern
)
319 return match_fstype(type
, pattern
);
323 /* Returns 1 if needle found or noneedle not found in haystack
324 * Otherwise returns 0
326 static int check_option(const char *haystack
, size_t len
,
327 const char *needle
, size_t needle_len
)
332 if (needle_len
>= 1 && *needle
== '+') {
335 } else if (needle_len
>= 2 && !strncmp(needle
, "no", 2)) {
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
);
346 if (plen
== needle_len
) {
347 if (!strncmp(p
, needle
, plen
))
348 return !no
; /* foo or nofoo was found */
353 return no
; /* foo or nofoo was not found */
358 * @optstr: options string
359 * @pattern: comma delimited list of options
361 * The "no" could used for individual items in the @options list. The "no"
362 * prefix does not have a global meaning.
364 * Unlike fs type matching, nonetdev,user and nonetdev,nouser have
365 * DIFFERENT meanings; each option is matched explicitly as specified.
367 * The "no" prefix interpretation could be disable by "+" prefix, for example
368 * "+noauto" matches if @optstr literally contains "noauto" string.
370 * "xxx,yyy,zzz" : "nozzz" -> False
372 * "xxx,yyy,zzz" : "xxx,noeee" -> True
374 * "bar,zzz" : "nofoo" -> True
376 * "nofoo,bar" : "+nofoo" -> True
378 * "bar,zzz" : "+nofoo" -> False
381 * Returns: 1 if pattern is matching, else 0. This function also returns 0
382 * if @pattern is NULL and @optstr is non-NULL.
384 int mnt_match_options(const char *optstr
, const char *pattern
)
387 size_t len
, optstr_len
= 0;
389 if (!pattern
&& !optstr
)
394 len
= strlen(pattern
);
396 optstr_len
= strlen(optstr
);
398 for (p
= pattern
; p
< pattern
+ len
; p
++) {
399 char *sep
= strchr(p
, ',');
400 size_t plen
= sep
? (size_t) (sep
- p
) :
404 continue; /* if two ',' appear in a row */
406 if (!check_option(optstr
, optstr_len
, p
, plen
))
407 return 0; /* any match failure means failure */
412 /* no match failures in list means success */
416 void mnt_free_filesystems(char **filesystems
)
422 for (p
= filesystems
; *p
; p
++)
427 static int add_filesystem(char ***filesystems
, char *name
)
436 for (n
= 0, p
= *filesystems
; *p
; p
++, n
++) {
437 if (strcmp(*p
, name
) == 0)
444 if (n
== 0 || !((n
+ 1) % MYCHUNK
)) {
445 size_t items
= ((n
+ 1 + MYCHUNK
) / MYCHUNK
) * MYCHUNK
;
446 char **x
= realloc(*filesystems
, items
* sizeof(char *));
455 (*filesystems
)[n
] = name
;
456 (*filesystems
)[n
+ 1] = NULL
;
459 mnt_free_filesystems(*filesystems
);
463 static int get_filesystems(const char *filename
, char ***filesystems
, const char *pattern
)
469 f
= fopen(filename
, "r" UL_CLOEXECSTR
);
473 DBG(UTILS
, mnt_debug("reading filesystems list from: %s", filename
));
475 while (fgets(line
, sizeof(line
), f
)) {
476 char name
[sizeof(line
)];
478 if (*line
== '#' || strncmp(line
, "nodev", 5) == 0)
480 if (sscanf(line
, " %128[^\n ]\n", name
) != 1)
482 if (strcmp(name
, "*") == 0) {
484 break; /* end of the /etc/filesystems */
486 if (pattern
&& !mnt_match_fstype(name
, pattern
))
488 rc
= add_filesystem(filesystems
, name
);
498 * Always check @filesystems pointer!
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.
508 int mnt_get_filesystems(char ***filesystems
, const char *pattern
)
517 rc
= get_filesystems(_PATH_FILESYSTEMS
, filesystems
, pattern
);
521 rc
= get_filesystems(_PATH_PROC_FILESYSTEMS
, filesystems
, pattern
);
522 if (rc
== 1 && *filesystems
)
523 rc
= 0; /* not found /proc/filesystems */
528 static size_t get_pw_record_size(void)
530 #ifdef _SC_GETPW_R_SIZE_MAX
531 long sz
= sysconf(_SC_GETPW_R_SIZE_MAX
);
539 * Returns allocated string with username or NULL.
541 char *mnt_get_username(const uid_t uid
)
545 size_t sz
= get_pw_record_size();
546 char *buf
, *username
= NULL
;
552 if (!getpwuid_r(uid
, &pwd
, buf
, sz
, &res
) && res
)
553 username
= strdup(pwd
.pw_name
);
559 int mnt_get_uid(const char *username
, uid_t
*uid
)
564 size_t sz
= get_pw_record_size();
567 if (!username
|| !uid
)
574 if (!getpwnam_r(username
, &pwd
, buf
, sz
, &pw
) && pw
) {
578 DBG(UTILS
, mnt_debug(
579 "cannot convert '%s' username to UID", username
));
580 rc
= errno
? -errno
: -EINVAL
;
587 int mnt_get_gid(const char *groupname
, gid_t
*gid
)
592 size_t sz
= get_pw_record_size();
595 if (!groupname
|| !gid
)
602 if (!getgrnam_r(groupname
, &grp
, buf
, sz
, &gr
) && gr
) {
606 DBG(UTILS
, mnt_debug(
607 "cannot convert '%s' groupname to GID", groupname
));
608 rc
= errno
? -errno
: -EINVAL
;
615 int mnt_in_group(gid_t gid
)
623 n
= getgroups(0, NULL
);
627 grps
= malloc(n
* sizeof(*grps
));
631 if (getgroups(n
, grps
) == n
) {
632 for (i
= 0; i
< n
; i
++) {
633 if (grps
[i
] == gid
) {
644 static int try_write(const char *filename
)
651 fd
= open(filename
, O_RDWR
|O_CREAT
|O_CLOEXEC
,
652 S_IWUSR
|S_IRUSR
|S_IRGRP
|S_IROTH
);
661 * mnt_has_regular_mtab:
662 * @mtab: returns path to mtab
663 * @writable: returns 1 if the file is writable
665 * If the file does not exist and @writable argument is not NULL then it will
666 * try to create the file
668 * Returns: 1 if /etc/mtab is a regular file, and 0 in case of error (check
669 * errno for more details).
671 int mnt_has_regular_mtab(const char **mtab
, int *writable
)
675 const char *filename
= mtab
&& *mtab
? *mtab
: mnt_get_mtab_path();
682 DBG(UTILS
, mnt_debug("mtab: %s", filename
));
684 rc
= lstat(filename
, &st
);
688 if (S_ISREG(st
.st_mode
)) {
690 *writable
= !try_write(filename
);
696 /* try to create the file */
698 *writable
= !try_write(filename
);
704 DBG(UTILS
, mnt_debug("%s: irregular/non-writable", filename
));
709 * Don't export this to libmount API -- utab is private library stuff.
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.
714 * Returns: 1 if utab is a regular file, and 0 in case of
715 * error (check errno for more details).
717 int mnt_has_regular_utab(const char **utab
, int *writable
)
721 const char *filename
= utab
&& *utab
? *utab
: mnt_get_utab_path();
728 DBG(UTILS
, mnt_debug("utab: %s", filename
));
730 rc
= lstat(filename
, &st
);
734 if (S_ISREG(st
.st_mode
)) {
736 *writable
= !try_write(filename
);
739 goto done
; /* it's not regular file */
743 char *dirname
= strdup(filename
);
748 stripoff_last_component(dirname
); /* remove filename */
750 rc
= mkdir(dirname
, S_IWUSR
|
751 S_IRUSR
|S_IRGRP
|S_IROTH
|
752 S_IXUSR
|S_IXGRP
|S_IXOTH
);
754 if (rc
&& errno
!= EEXIST
)
755 goto done
; /* probably EACCES */
757 *writable
= !try_write(filename
);
762 DBG(UTILS
, mnt_debug("%s: irregular/non-writable file", filename
));
767 * mnt_get_swaps_path:
769 * Returns: path to /proc/swaps or $LIBMOUNT_SWAPS.
771 const char *mnt_get_swaps_path(void)
773 const char *p
= safe_getenv("LIBMOUNT_SWAPS");
774 return p
? : _PATH_PROC_SWAPS
;
778 * mnt_get_fstab_path:
780 * Returns: path to /etc/fstab or $LIBMOUNT_FSTAB.
782 const char *mnt_get_fstab_path(void)
784 const char *p
= safe_getenv("LIBMOUNT_FSTAB");
785 return p
? : _PATH_MNTTAB
;
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().
794 * Returns: path to /etc/mtab or $LIBMOUNT_MTAB.
796 const char *mnt_get_mtab_path(void)
798 const char *p
= safe_getenv("LIBMOUNT_MTAB");
799 return p
? : _PATH_MOUNTED
;
803 * Don't export this to libmount API -- utab is private library stuff.
805 * Returns: path to /run/mount/utab (or /dev/.mount/utab) or $LIBMOUNT_UTAB.
807 const char *mnt_get_utab_path(void)
810 const char *p
= safe_getenv("LIBMOUNT_UTAB");
815 if (stat(MNT_RUNTIME_TOPDIR
, &st
) == 0)
816 return MNT_PATH_UTAB
;
818 return MNT_PATH_UTAB_OLD
;
822 /* returns file descriptor or -errno, @name returns uniques filename
824 int mnt_open_uniq_filename(const char *filename
, char **name
)
835 rc
= asprintf(&n
, "%s.XXXXXX", filename
);
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).
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
);
852 return fd
< 0 ? -errno
: fd
;
856 * mnt_get_mountpoint:
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.
862 * Returns: allocated string with target of the mounted device or NULL on error
864 char *mnt_get_mountpoint(const char *path
)
875 if (*mnt
== '/' && *(mnt
+ 1) == '\0')
883 char *p
= stripoff_last_component(mnt
);
887 if (stat(*mnt
? mnt
: "/", &st
))
896 } while (mnt
&& *(mnt
+ 1) != '\0');
900 DBG(UTILS
, mnt_debug("%s mountpoint is %s", path
, mnt
));
907 char *mnt_get_fs_root(const char *path
, const char *mnt
)
909 char *m
= (char *) mnt
, *res
;
914 m
= mnt_get_mountpoint(path
);
919 p
= sz
> 1 ? path
+ sz
: path
;
924 res
= *p
? strdup(p
) : strdup("/");
925 DBG(UTILS
, mnt_debug("%s fs-root is %s", path
, res
));
930 * Search for @name kernel command parametr.
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
936 * For example cmdline: "aaa bbb=BBB ccc"
938 * @name is "aaa" --returns--> "aaa" (pointer to @name)
939 * @name is "bbb=" --returns--> "BBB" (allocated)
940 * @name is "foo" --returns--> NULL
942 char *mnt_get_kernel_cmdline_option(const char *name
)
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
;
955 path
= getenv("LIBMOUNT_KERNEL_CMDLINE");
957 path
= _PATH_PROC_CMDLINE
;
959 f
= fopen(path
, "r" UL_CLOEXECSTR
);
963 p
= fgets(buf
, sizeof(buf
), f
);
966 if (!p
|| !*p
|| *p
== '\n')
970 *(buf
+ len
- 1) = '\0'; /* remove last '\n' */
973 if (len
&& *(name
+ len
- 1) == '=')
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 */
986 while (*p
&& !isblank(*p
)) /* jump to the end of the argument */
992 res
= (char *) name
; /* option without '=' */
999 int mkdir_p(const char *path
, mode_t mode
)
1004 if (!path
|| !*path
)
1007 dir
= p
= strdup(path
);
1015 char *e
= strchr(p
, '/');
1019 rc
= mkdir(dir
, mode
);
1020 if (rc
&& errno
!= EEXIST
)
1030 DBG(UTILS
, mnt_debug("%s mkdir %s", path
, rc
? "FAILED" : "SUCCESS"));
1037 int test_match_fstype(struct libmnt_test
*ts
, int argc
, char *argv
[])
1039 char *type
= argv
[1];
1040 char *pattern
= argv
[2];
1042 printf("%s\n", mnt_match_fstype(type
, pattern
) ? "MATCH" : "NOT-MATCH");
1046 int test_match_options(struct libmnt_test
*ts
, int argc
, char *argv
[])
1048 char *optstr
= argv
[1];
1049 char *pattern
= argv
[2];
1051 printf("%s\n", mnt_match_options(optstr
, pattern
) ? "MATCH" : "NOT-MATCH");
1055 int test_startswith(struct libmnt_test
*ts
, int argc
, char *argv
[])
1057 char *optstr
= argv
[1];
1058 char *pattern
= argv
[2];
1060 printf("%s\n", startswith(optstr
, pattern
) ? "YES" : "NOT");
1064 int test_endswith(struct libmnt_test
*ts
, int argc
, char *argv
[])
1066 char *optstr
= argv
[1];
1067 char *pattern
= argv
[2];
1069 printf("%s\n", endswith(optstr
, pattern
) ? "YES" : "NOT");
1073 int test_mountpoint(struct libmnt_test
*ts
, int argc
, char *argv
[])
1075 char *path
= canonicalize_path(argv
[1]),
1076 *mnt
= path
? mnt_get_mountpoint(path
) : NULL
;
1078 printf("%s: %s\n", argv
[1], mnt
? : "unknown");
1084 int test_fsroot(struct libmnt_test
*ts
, int argc
, char *argv
[])
1086 char *path
= canonicalize_path(argv
[1]),
1087 *mnt
= path
? mnt_get_fs_root(path
, NULL
) : NULL
;
1089 printf("%s: %s\n", argv
[1], mnt
? : "unknown");
1095 int test_filesystems(struct libmnt_test
*ts
, int argc
, char *argv
[])
1097 char **filesystems
= NULL
;
1100 rc
= mnt_get_filesystems(&filesystems
, argc
? argv
[1] : NULL
);
1103 for (p
= filesystems
; *p
; p
++)
1105 mnt_free_filesystems(filesystems
);
1110 int test_chdir(struct libmnt_test
*ts
, int argc
, char *argv
[])
1113 char *path
= canonicalize_path(argv
[1]),
1119 rc
= mnt_chdir_to_parent(path
, &last
);
1121 printf("path='%s', abs='%s', last='%s'\n",
1122 argv
[1], path
, last
);
1129 int test_kernel_cmdline(struct libmnt_test
*ts
, int argc
, char *argv
[])
1131 char *name
= argv
[1];
1134 res
= mnt_get_kernel_cmdline_option(name
);
1136 printf("'%s' not found\n", name
);
1137 else if (res
== name
)
1138 printf("'%s' found\n", name
);
1140 printf("'%s' found, argument: '%s'\n", name
, res
);
1147 int test_mkdir(struct libmnt_test
*ts
, int argc
, char *argv
[])
1151 rc
= mkdir_p(argv
[1], S_IRWXU
|
1155 printf("mkdir %s failed\n", argv
[1]);
1160 int main(int argc
, char *argv
[])
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>" },
1177 return mnt_run_test(tss
, argc
, argv
);
1180 #endif /* TEST_PROGRAM */