diff options
Diffstat (limited to 'libblkid/canonicalize.c')
-rw-r--r-- | libblkid/canonicalize.c | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/libblkid/canonicalize.c b/libblkid/canonicalize.c new file mode 100644 index 000000000..1e8aff4f2 --- /dev/null +++ b/libblkid/canonicalize.c @@ -0,0 +1,247 @@ +/* + * canonicalize.c -- canonicalize pathname by removing symlinks + * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Library Public License for more details. + * + */ + +/* + * This routine is part of libc. We include it nevertheless, + * since the libc version has some security flaws. + * + * TODO: use canonicalize_file_name() when exist in glibc + */ +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <errno.h> +#include <stdlib.h> + +#include "canonicalize.h" + +#ifndef MAXSYMLINKS +# define MAXSYMLINKS 256 +#endif + +static char * +myrealpath(const char *path, char *resolved_path, int maxreslth) { + int readlinks = 0; + char *npath; + char link_path[PATH_MAX+1]; + int n; + char *buf = NULL; + + npath = resolved_path; + + /* If it's a relative pathname use getcwd for starters. */ + if (*path != '/') { + if (!getcwd(npath, maxreslth-2)) + return NULL; + npath += strlen(npath); + if (npath[-1] != '/') + *npath++ = '/'; + } else { + *npath++ = '/'; + path++; + } + + /* Expand each slash-separated pathname component. */ + while (*path != '\0') { + /* Ignore stray "/" */ + if (*path == '/') { + path++; + continue; + } + if (*path == '.' && (path[1] == '\0' || path[1] == '/')) { + /* Ignore "." */ + path++; + continue; + } + if (*path == '.' && path[1] == '.' && + (path[2] == '\0' || path[2] == '/')) { + /* Backup for ".." */ + path += 2; + while (npath > resolved_path+1 && + (--npath)[-1] != '/') + ; + continue; + } + /* Safely copy the next pathname component. */ + while (*path != '\0' && *path != '/') { + if (npath-resolved_path > maxreslth-2) { + errno = ENAMETOOLONG; + goto err; + } + *npath++ = *path++; + } + + /* Protect against infinite loops. */ + if (readlinks++ > MAXSYMLINKS) { + errno = ELOOP; + goto err; + } + + /* See if last pathname component is a symlink. */ + *npath = '\0'; + n = readlink(resolved_path, link_path, PATH_MAX); + if (n < 0) { + /* EINVAL means the file exists but isn't a symlink. */ + if (errno != EINVAL) + goto err; + } else { + int m; + char *newbuf; + + /* Note: readlink doesn't add the null byte. */ + link_path[n] = '\0'; + if (*link_path == '/') + /* Start over for an absolute symlink. */ + npath = resolved_path; + else + /* Otherwise back up over this component. */ + while (*(--npath) != '/') + ; + + /* Insert symlink contents into path. */ + m = strlen(path); + newbuf = malloc(m + n + 1); + if (!newbuf) + goto err; + memcpy(newbuf, link_path, n); + memcpy(newbuf + n, path, m + 1); + free(buf); + path = buf = newbuf; + } + *npath++ = '/'; + } + /* Delete trailing slash but don't whomp a lone slash. */ + if (npath != resolved_path+1 && npath[-1] == '/') + npath--; + /* Make sure it's null terminated. */ + *npath = '\0'; + + free(buf); + return resolved_path; + + err: + free(buf); + return NULL; +} + +/* + * Converts private "dm-N" names to "/dev/mapper/<name>" + * + * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs + * provides the real DM device names in /sys/block/<ptname>/dm/name + */ +char * +canonicalize_dm_name(const char *ptname) +{ + FILE *f; + size_t sz; + char path[256], name[256], *res = NULL; + + snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname); + if (!(f = fopen(path, "r"))) + return NULL; + + /* read "<name>\n" from sysfs */ + if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) { + name[sz - 1] = '\0'; + snprintf(path, sizeof(path), "/dev/mapper/%s", name); + res = strdup(path); + } + fclose(f); + return res; +} + +char * +canonicalize_path(const char *path) +{ + char canonical[PATH_MAX+2]; + char *p; + + if (path == NULL) + return NULL; + + if (!myrealpath(path, canonical, PATH_MAX+1)) + return strdup(path); + + + p = strrchr(canonical, '/'); + if (p && strncmp(p, "/dm-", 4) == 0 && isdigit(*(p + 4))) { + p = canonicalize_dm_name(p+1); + if (p) + return p; + } + + return strdup(canonical); +} + +char * +canonicalize_path_restricted(const char *path) +{ + char canonical[PATH_MAX+2]; + char *p = NULL; + int errsv; + uid_t euid; + gid_t egid; + + if (path == NULL) + return NULL; + + euid = geteuid(); + egid = getegid(); + + /* drop permissions */ + if (setegid(getgid()) < 0 || seteuid(getuid()) < 0) + return NULL; + + errsv = errno = 0; + + if (myrealpath(path, canonical, PATH_MAX+1)) { + p = strrchr(canonical, '/'); + if (p && strncmp(p, "/dm-", 4) == 0 && isdigit(*(p + 4))) + p = canonicalize_dm_name(p+1); + else + p = NULL; + if (!p) + p = strdup(canonical); + } else + errsv = errno; + + /* restore */ + if (setegid(egid) < 0 || seteuid(euid) < 0) { + free(p); + return NULL; + } + + errno = errsv; + return p; +} + + +#ifdef TEST_PROGRAM_CANONICALIZE +int main(int argc, char **argv) +{ + if (argc < 2) { + fprintf(stderr, "usage: %s <device>\n", argv[0]); + exit(EXIT_FAILURE); + } + + fprintf(stdout, "orig: %s\n", argv[1]); + fprintf(stdout, "real: %s\n", canonicalize_path(argv[1])); + + exit(EXIT_SUCCESS); +} +#endif |