(root)/
util-linux-2.39/
lib/
canonicalize.c
       1  /*
       2   * canonicalize.c -- canonicalize pathname by removing symlinks
       3   *
       4   * This file may be distributed under the terms of the
       5   * GNU Lesser General Public License.
       6   *
       7   * Copyright (C) 2009-2013 Karel Zak <kzak@redhat.com>
       8   */
       9  #include <stdio.h>
      10  #include <string.h>
      11  #include <ctype.h>
      12  #include <unistd.h>
      13  #include <errno.h>
      14  #include <stdlib.h>
      15  #include <sys/types.h>
      16  #include <sys/stat.h>
      17  #include <sys/wait.h>
      18  
      19  #include "canonicalize.h"
      20  #include "pathnames.h"
      21  #include "all-io.h"
      22  #include "strutils.h"
      23  
      24  /*
      25   * Converts private "dm-N" names to "/dev/mapper/<name>"
      26   *
      27   * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs
      28   * provides the real DM device names in /sys/block/<ptname>/dm/name
      29   */
      30  char *__canonicalize_dm_name(const char *prefix, const char *ptname)
      31  {
      32  	FILE	*f;
      33  	size_t	sz;
      34  	char	path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL;
      35  
      36  	if (!ptname || !*ptname)
      37  		return NULL;
      38  
      39  	if (!prefix)
      40  		prefix = "";
      41  
      42  	snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname);
      43  	if (!(f = fopen(path, "r" UL_CLOEXECSTR)))
      44  		return NULL;
      45  
      46  	/* read "<name>\n" from sysfs */
      47  	if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) {
      48  		name[sz - 1] = '\0';
      49  		snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name);
      50  
      51  		if ((prefix && *prefix) || access(path, F_OK) == 0)
      52  			res = strdup(path);
      53  	}
      54  	fclose(f);
      55  	return res;
      56  }
      57  
      58  char *canonicalize_dm_name(const char *ptname)
      59  {
      60  	return __canonicalize_dm_name(NULL, ptname);
      61  }
      62  
      63  static int is_dm_devname(char *canonical, char **name)
      64  {
      65  	struct stat sb;
      66  	char *p = strrchr(canonical, '/');
      67  
      68  	*name = NULL;
      69  
      70  	if (!p
      71  	    || strncmp(p, "/dm-", 4) != 0
      72  	    || !isdigit(*(p + 4))
      73  	    || stat(canonical, &sb) != 0
      74  	    || !S_ISBLK(sb.st_mode))
      75  		return 0;
      76  
      77  	*name = p + 1;
      78  	return 1;
      79  }
      80  
      81  /*
      82   * This function does not canonicalize the path! It just prepends CWD before a
      83   * relative path. If the path is no relative than returns NULL. The path does
      84   * not have to exist.
      85   */
      86  char *absolute_path(const char *path)
      87  {
      88  	char cwd[PATH_MAX], *res, *p;
      89  	size_t psz, csz;
      90  
      91  	if (!is_relative_path(path)) {
      92  		errno = EINVAL;
      93  		return NULL;
      94  	}
      95  	if (!getcwd(cwd, sizeof(cwd)))
      96  		return NULL;
      97  
      98  	/* simple clean up */
      99  	if (startswith(path, "./"))
     100  		path += 2;
     101  	else if (strcmp(path, ".") == 0)
     102  		path = NULL;
     103  
     104  	if (!path || !*path)
     105  		return strdup(cwd);
     106  
     107  	csz = strlen(cwd);
     108  	psz = strlen(path);
     109  
     110  	p = res = malloc(csz + 1 + psz + 1);
     111  	if (!res)
     112  		return NULL;
     113  
     114  	p = mempcpy(p, cwd, csz);
     115  	*p++ = '/';
     116  	memcpy(p, path, psz + 1);
     117  
     118  	return res;
     119  }
     120  
     121  char *canonicalize_path(const char *path)
     122  {
     123  	char *canonical, *dmname;
     124  
     125  	if (!path || !*path)
     126  		return NULL;
     127  
     128  	canonical = realpath(path, NULL);
     129  	if (!canonical)
     130  		return strdup(path);
     131  
     132  	if (is_dm_devname(canonical, &dmname)) {
     133  		char *dm = canonicalize_dm_name(dmname);
     134  		if (dm) {
     135  			free(canonical);
     136  			return dm;
     137  		}
     138  	}
     139  
     140  	return canonical;
     141  }
     142  
     143  char *canonicalize_path_restricted(const char *path)
     144  {
     145  	char *canonical = NULL;
     146  	int errsv = 0;
     147  	int pipes[2];
     148  	ssize_t len;
     149  	pid_t pid;
     150  
     151  	if (!path || !*path)
     152  		return NULL;
     153  
     154  	if (pipe(pipes) != 0)
     155  		return NULL;
     156  
     157  	/*
     158  	 * To accurately assume identity of getuid() we must use setuid()
     159  	 * but if we do that, we lose ability to reassume euid of 0, so
     160  	 * we fork to do the check to keep euid intact.
     161  	 */
     162  	pid = fork();
     163  	switch (pid) {
     164  	case -1:
     165  		close(pipes[0]);
     166  		close(pipes[1]);
     167  		return NULL;			/* fork error */
     168  	case 0:
     169  		close(pipes[0]);		/* close unused end */
     170  		pipes[0] = -1;
     171  		errno = 0;
     172  
     173  		if (drop_permissions() != 0)
     174  			canonical = NULL;	/* failed */
     175  		else {
     176  			char *dmname = NULL;
     177  
     178  			canonical = realpath(path, NULL);
     179  			if (canonical && is_dm_devname(canonical, &dmname)) {
     180  				char *dm = canonicalize_dm_name(dmname);
     181  				if (dm) {
     182  					free(canonical);
     183  					canonical = dm;
     184  				}
     185  			}
     186  		}
     187  
     188  		len = canonical ? (ssize_t) strlen(canonical) :
     189  		          errno ? -errno : -EINVAL;
     190  
     191  		/* send length or errno */
     192  		write_all(pipes[1], (char *) &len, sizeof(len));
     193  		if (canonical)
     194  			write_all(pipes[1], canonical, len);
     195  		exit(0);
     196  	default:
     197  		break;
     198  	}
     199  
     200  	close(pipes[1]);		/* close unused end */
     201  	pipes[1] = -1;
     202  
     203  	/* read size or -errno */
     204  	if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len))
     205  		goto done;
     206  	if (len < 0) {
     207  		errsv = -len;
     208  		goto done;
     209  	}
     210  
     211  	canonical = malloc(len + 1);
     212  	if (!canonical) {
     213  		errsv = ENOMEM;
     214  		goto done;
     215  	}
     216  	/* read path */
     217  	if (read_all(pipes[0], canonical, len) != len) {
     218  		errsv = errno;
     219  		goto done;
     220  	}
     221  	canonical[len] = '\0';
     222  done:
     223  	if (errsv) {
     224  		free(canonical);
     225  		canonical = NULL;
     226  	}
     227  	close(pipes[0]);
     228  
     229  	/* We make a best effort to reap child */
     230  	ignore_result( waitpid(pid, NULL, 0) );
     231  
     232  	errno = errsv;
     233  	return canonical;
     234  }
     235  
     236  
     237  #ifdef TEST_PROGRAM_CANONICALIZE
     238  int main(int argc, char **argv)
     239  {
     240  	if (argc < 2) {
     241  		fprintf(stderr, "usage: %s <device>\n", argv[0]);
     242  		exit(EXIT_FAILURE);
     243  	}
     244  
     245  	fprintf(stdout, "orig: %s\n", argv[1]);
     246  	fprintf(stdout, "real: %s\n", canonicalize_path(argv[1]));
     247  	exit(EXIT_SUCCESS);
     248  }
     249  #endif