summaryrefslogtreecommitdiffstats
path: root/libblkid/canonicalize.c
diff options
context:
space:
mode:
Diffstat (limited to 'libblkid/canonicalize.c')
-rw-r--r--libblkid/canonicalize.c247
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