(root)/
util-linux-2.39/
libmount/
src/
utils.c
       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 */