]>
Commit | Line | Data |
---|---|---|
6dbe3af9 | 1 | /* |
74a9c6f7 | 2 | * canonicalize.c -- canonicalize pathname by removing symlinks |
6dbe3af9 KZ |
3 | * Copyright (C) 1993 Rick Sladkey <jrs@world.std.com> |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU Library Public License as published by | |
7 | * the Free Software Foundation; either version 2, or (at your option) | |
8 | * any later version. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
13 | * GNU Library Public License for more details. | |
74a9c6f7 | 14 | * |
6dbe3af9 KZ |
15 | */ |
16 | ||
17 | /* | |
fd6b7a7f KZ |
18 | * This routine is part of libc. We include it nevertheless, |
19 | * since the libc version has some security flaws. | |
74a9c6f7 KZ |
20 | * |
21 | * TODO: use canonicalize_file_name() when exist in glibc | |
6dbe3af9 | 22 | */ |
a992137b KZ |
23 | #include <stdio.h> |
24 | #include <string.h> | |
25 | #include <ctype.h> | |
6dbe3af9 | 26 | #include <unistd.h> |
6dbe3af9 | 27 | #include <errno.h> |
74a9c6f7 KZ |
28 | #include <stdlib.h> |
29 | ||
30 | #include "canonicalize.h" | |
6dbe3af9 | 31 | |
4717ea4a KZ |
32 | #ifndef MAXSYMLINKS |
33 | # define MAXSYMLINKS 256 | |
34 | #endif | |
6dbe3af9 | 35 | |
74a9c6f7 | 36 | static char * |
eb63b9b8 | 37 | myrealpath(const char *path, char *resolved_path, int maxreslth) { |
6dbe3af9 | 38 | int readlinks = 0; |
364cda48 KZ |
39 | char *npath; |
40 | char link_path[PATH_MAX+1]; | |
41 | int n; | |
d26aa358 | 42 | char *buf = NULL; |
6dbe3af9 | 43 | |
eb63b9b8 | 44 | npath = resolved_path; |
fd6b7a7f | 45 | |
eb63b9b8 | 46 | /* If it's a relative pathname use getcwd for starters. */ |
6dbe3af9 | 47 | if (*path != '/') { |
22853e4a KZ |
48 | if (!getcwd(npath, maxreslth-2)) |
49 | return NULL; | |
eb63b9b8 KZ |
50 | npath += strlen(npath); |
51 | if (npath[-1] != '/') | |
52 | *npath++ = '/'; | |
53 | } else { | |
54 | *npath++ = '/'; | |
6dbe3af9 KZ |
55 | path++; |
56 | } | |
eb63b9b8 | 57 | |
6dbe3af9 KZ |
58 | /* Expand each slash-separated pathname component. */ |
59 | while (*path != '\0') { | |
eb63b9b8 | 60 | /* Ignore stray "/" */ |
6dbe3af9 KZ |
61 | if (*path == '/') { |
62 | path++; | |
63 | continue; | |
64 | } | |
eb63b9b8 KZ |
65 | if (*path == '.' && (path[1] == '\0' || path[1] == '/')) { |
66 | /* Ignore "." */ | |
67 | path++; | |
68 | continue; | |
69 | } | |
70 | if (*path == '.' && path[1] == '.' && | |
71 | (path[2] == '\0' || path[2] == '/')) { | |
72 | /* Backup for ".." */ | |
73 | path += 2; | |
74 | while (npath > resolved_path+1 && | |
75 | (--npath)[-1] != '/') | |
76 | ; | |
77 | continue; | |
6dbe3af9 KZ |
78 | } |
79 | /* Safely copy the next pathname component. */ | |
80 | while (*path != '\0' && *path != '/') { | |
eb63b9b8 | 81 | if (npath-resolved_path > maxreslth-2) { |
6dbe3af9 | 82 | errno = ENAMETOOLONG; |
d26aa358 | 83 | goto err; |
6dbe3af9 | 84 | } |
eb63b9b8 | 85 | *npath++ = *path++; |
6dbe3af9 | 86 | } |
eb63b9b8 | 87 | |
6dbe3af9 | 88 | /* Protect against infinite loops. */ |
4717ea4a | 89 | if (readlinks++ > MAXSYMLINKS) { |
6dbe3af9 | 90 | errno = ELOOP; |
d26aa358 | 91 | goto err; |
6dbe3af9 | 92 | } |
eb63b9b8 | 93 | |
364cda48 | 94 | /* See if last pathname component is a symlink. */ |
eb63b9b8 KZ |
95 | *npath = '\0'; |
96 | n = readlink(resolved_path, link_path, PATH_MAX); | |
6dbe3af9 KZ |
97 | if (n < 0) { |
98 | /* EINVAL means the file exists but isn't a symlink. */ | |
99 | if (errno != EINVAL) | |
d26aa358 | 100 | goto err; |
eb63b9b8 | 101 | } else { |
d26aa358 | 102 | int m; |
a9d6150d | 103 | char *newbuf; |
d26aa358 | 104 | |
6dbe3af9 KZ |
105 | /* Note: readlink doesn't add the null byte. */ |
106 | link_path[n] = '\0'; | |
107 | if (*link_path == '/') | |
108 | /* Start over for an absolute symlink. */ | |
eb63b9b8 | 109 | npath = resolved_path; |
6dbe3af9 KZ |
110 | else |
111 | /* Otherwise back up over this component. */ | |
eb63b9b8 | 112 | while (*(--npath) != '/') |
6dbe3af9 | 113 | ; |
22853e4a | 114 | |
6dbe3af9 | 115 | /* Insert symlink contents into path. */ |
22853e4a | 116 | m = strlen(path); |
74a9c6f7 KZ |
117 | newbuf = malloc(m + n + 1); |
118 | if (!newbuf) | |
119 | goto err; | |
a9d6150d NB |
120 | memcpy(newbuf, link_path, n); |
121 | memcpy(newbuf + n, path, m + 1); | |
71c445db | 122 | free(buf); |
a9d6150d | 123 | path = buf = newbuf; |
6dbe3af9 | 124 | } |
eb63b9b8 | 125 | *npath++ = '/'; |
6dbe3af9 KZ |
126 | } |
127 | /* Delete trailing slash but don't whomp a lone slash. */ | |
eb63b9b8 KZ |
128 | if (npath != resolved_path+1 && npath[-1] == '/') |
129 | npath--; | |
6dbe3af9 | 130 | /* Make sure it's null terminated. */ |
eb63b9b8 | 131 | *npath = '\0'; |
d26aa358 | 132 | |
71c445db | 133 | free(buf); |
6dbe3af9 | 134 | return resolved_path; |
d26aa358 KZ |
135 | |
136 | err: | |
71c445db | 137 | free(buf); |
d26aa358 | 138 | return NULL; |
6dbe3af9 | 139 | } |
74a9c6f7 | 140 | |
a992137b KZ |
141 | /* |
142 | * Converts private "dm-N" names to "/dev/mapper/<name>" | |
143 | * | |
144 | * Since 2.6.29 (patch 784aae735d9b0bba3f8b9faef4c8b30df3bf0128) kernel sysfs | |
145 | * provides the real DM device names in /sys/block/<ptname>/dm/name | |
146 | */ | |
74a9c6f7 | 147 | char * |
a992137b KZ |
148 | canonicalize_dm_name(const char *ptname) |
149 | { | |
150 | FILE *f; | |
151 | size_t sz; | |
152 | char path[256], name[256], *res = NULL; | |
153 | ||
154 | snprintf(path, sizeof(path), "/sys/block/%s/dm/name", ptname); | |
155 | if (!(f = fopen(path, "r"))) | |
156 | return NULL; | |
157 | ||
158 | /* read "<name>\n" from sysfs */ | |
159 | if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) { | |
160 | name[sz - 1] = '\0'; | |
161 | snprintf(path, sizeof(path), "/dev/mapper/%s", name); | |
162 | res = strdup(path); | |
163 | } | |
164 | fclose(f); | |
165 | return res; | |
166 | } | |
167 | ||
168 | char * | |
169 | canonicalize_path(const char *path) | |
170 | { | |
74a9c6f7 | 171 | char canonical[PATH_MAX+2]; |
a992137b | 172 | char *p; |
74a9c6f7 KZ |
173 | |
174 | if (path == NULL) | |
175 | return NULL; | |
176 | ||
a992137b KZ |
177 | if (!myrealpath(path, canonical, PATH_MAX+1)) |
178 | return strdup(path); | |
179 | ||
180 | ||
181 | p = strrchr(canonical, '/'); | |
182 | if (p && strncmp(p, "/dm-", 4) == 0 && isdigit(*(p + 4))) { | |
183 | p = canonicalize_dm_name(p+1); | |
184 | if (p) | |
185 | return p; | |
186 | } | |
74a9c6f7 | 187 | |
a992137b | 188 | return strdup(canonical); |
74a9c6f7 KZ |
189 | } |
190 | ||
191 | ||
10ee5932 | 192 | #ifdef TEST_PROGRAM_CANONICALIZE |
6cfa971e KZ |
193 | int main(int argc, char **argv) |
194 | { | |
195 | if (argc < 2) { | |
196 | fprintf(stderr, "usage: %s <device>\n", argv[0]); | |
197 | exit(EXIT_FAILURE); | |
198 | } | |
199 | ||
200 | fprintf(stdout, "orig: %s\n", argv[1]); | |
201 | fprintf(stdout, "real: %s\n", canonicalize_path(argv[1])); | |
202 | ||
203 | exit(EXIT_SUCCESS); | |
204 | } | |
205 | #endif |