/src/util-linux/lib/canonicalize.c
Line | Count | Source (jump to first uncovered line) |
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 | 0 | { |
32 | 0 | FILE *f; |
33 | 0 | size_t sz; |
34 | 0 | char path[256], name[sizeof(path) - sizeof(_PATH_DEV_MAPPER)], *res = NULL; |
35 | |
|
36 | 0 | if (!ptname || !*ptname) |
37 | 0 | return NULL; |
38 | | |
39 | 0 | if (!prefix) |
40 | 0 | prefix = ""; |
41 | |
|
42 | 0 | snprintf(path, sizeof(path), "%s/sys/block/%s/dm/name", prefix, ptname); |
43 | 0 | if (!(f = fopen(path, "r" UL_CLOEXECSTR))) |
44 | 0 | return NULL; |
45 | | |
46 | | /* read "<name>\n" from sysfs */ |
47 | 0 | if (fgets(name, sizeof(name), f) && (sz = strlen(name)) > 1) { |
48 | 0 | name[sz - 1] = '\0'; |
49 | 0 | snprintf(path, sizeof(path), _PATH_DEV_MAPPER "/%s", name); |
50 | |
|
51 | 0 | if ((prefix && *prefix) || access(path, F_OK) == 0) |
52 | 0 | res = strdup(path); |
53 | 0 | } |
54 | 0 | fclose(f); |
55 | 0 | return res; |
56 | 0 | } |
57 | | |
58 | | char *canonicalize_dm_name(const char *ptname) |
59 | 0 | { |
60 | 0 | return __canonicalize_dm_name(NULL, ptname); |
61 | 0 | } |
62 | | |
63 | | static int is_dm_devname(char *canonical, char **name) |
64 | 0 | { |
65 | 0 | struct stat sb; |
66 | 0 | char *p = strrchr(canonical, '/'); |
67 | |
|
68 | 0 | *name = NULL; |
69 | |
|
70 | 0 | if (!p |
71 | 0 | || strncmp(p, "/dm-", 4) != 0 |
72 | 0 | || !isdigit(*(p + 4)) |
73 | 0 | || stat(canonical, &sb) != 0 |
74 | 0 | || !S_ISBLK(sb.st_mode)) |
75 | 0 | return 0; |
76 | | |
77 | 0 | *name = p + 1; |
78 | 0 | return 1; |
79 | 0 | } |
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 | 0 | { |
88 | 0 | char cwd[PATH_MAX], *res, *p; |
89 | 0 | size_t psz, csz; |
90 | |
|
91 | 0 | if (!is_relative_path(path)) { |
92 | 0 | errno = EINVAL; |
93 | 0 | return NULL; |
94 | 0 | } |
95 | 0 | if (!getcwd(cwd, sizeof(cwd))) |
96 | 0 | return NULL; |
97 | | |
98 | | /* simple clean up */ |
99 | 0 | if (startswith(path, "./")) |
100 | 0 | path += 2; |
101 | 0 | else if (strcmp(path, ".") == 0) |
102 | 0 | path = NULL; |
103 | |
|
104 | 0 | if (!path || !*path) |
105 | 0 | return strdup(cwd); |
106 | | |
107 | 0 | csz = strlen(cwd); |
108 | 0 | psz = strlen(path); |
109 | |
|
110 | 0 | p = res = malloc(csz + 1 + psz + 1); |
111 | 0 | if (!res) |
112 | 0 | return NULL; |
113 | | |
114 | 0 | p = mempcpy(p, cwd, csz); |
115 | 0 | *p++ = '/'; |
116 | 0 | memcpy(p, path, psz + 1); |
117 | |
|
118 | 0 | return res; |
119 | 0 | } |
120 | | |
121 | | char *canonicalize_path(const char *path) |
122 | 0 | { |
123 | 0 | char *canonical, *dmname; |
124 | |
|
125 | 0 | if (!path || !*path) |
126 | 0 | return NULL; |
127 | | |
128 | 0 | canonical = realpath(path, NULL); |
129 | 0 | if (!canonical) |
130 | 0 | return strdup(path); |
131 | | |
132 | 0 | if (is_dm_devname(canonical, &dmname)) { |
133 | 0 | char *dm = canonicalize_dm_name(dmname); |
134 | 0 | if (dm) { |
135 | 0 | free(canonical); |
136 | 0 | return dm; |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | 0 | return canonical; |
141 | 0 | } |
142 | | |
143 | | char *canonicalize_path_restricted(const char *path) |
144 | 0 | { |
145 | 0 | char *canonical = NULL; |
146 | 0 | int errsv = 0; |
147 | 0 | int pipes[2]; |
148 | 0 | ssize_t len; |
149 | 0 | pid_t pid; |
150 | |
|
151 | 0 | if (!path || !*path) |
152 | 0 | return NULL; |
153 | | |
154 | 0 | if (pipe(pipes) != 0) |
155 | 0 | 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 | 0 | pid = fork(); |
163 | 0 | switch (pid) { |
164 | 0 | case -1: |
165 | 0 | close(pipes[0]); |
166 | 0 | close(pipes[1]); |
167 | 0 | return NULL; /* fork error */ |
168 | 0 | case 0: |
169 | 0 | close(pipes[0]); /* close unused end */ |
170 | 0 | pipes[0] = -1; |
171 | 0 | errno = 0; |
172 | |
|
173 | 0 | if (drop_permissions() != 0) |
174 | 0 | canonical = NULL; /* failed */ |
175 | 0 | else { |
176 | 0 | char *dmname = NULL; |
177 | |
|
178 | 0 | canonical = realpath(path, NULL); |
179 | 0 | if (canonical && is_dm_devname(canonical, &dmname)) { |
180 | 0 | char *dm = canonicalize_dm_name(dmname); |
181 | 0 | if (dm) { |
182 | 0 | free(canonical); |
183 | 0 | canonical = dm; |
184 | 0 | } |
185 | 0 | } |
186 | 0 | } |
187 | |
|
188 | 0 | len = canonical ? (ssize_t) strlen(canonical) : |
189 | 0 | errno ? -errno : -EINVAL; |
190 | | |
191 | | /* send length or errno */ |
192 | 0 | write_all(pipes[1], (char *) &len, sizeof(len)); |
193 | 0 | if (canonical) |
194 | 0 | write_all(pipes[1], canonical, len); |
195 | 0 | _exit(0); |
196 | 0 | default: |
197 | 0 | break; |
198 | 0 | } |
199 | | |
200 | 0 | close(pipes[1]); /* close unused end */ |
201 | 0 | pipes[1] = -1; |
202 | | |
203 | | /* read size or -errno */ |
204 | 0 | if (read_all(pipes[0], (char *) &len, sizeof(len)) != sizeof(len)) |
205 | 0 | goto done; |
206 | 0 | if (len < 0) { |
207 | 0 | errsv = -len; |
208 | 0 | goto done; |
209 | 0 | } |
210 | | |
211 | 0 | canonical = malloc(len + 1); |
212 | 0 | if (!canonical) { |
213 | 0 | errsv = ENOMEM; |
214 | 0 | goto done; |
215 | 0 | } |
216 | | /* read path */ |
217 | 0 | if (read_all(pipes[0], canonical, len) != len) { |
218 | 0 | errsv = errno; |
219 | 0 | goto done; |
220 | 0 | } |
221 | 0 | canonical[len] = '\0'; |
222 | 0 | done: |
223 | 0 | if (errsv) { |
224 | 0 | free(canonical); |
225 | 0 | canonical = NULL; |
226 | 0 | } |
227 | 0 | close(pipes[0]); |
228 | | |
229 | | /* We make a best effort to reap child */ |
230 | 0 | ignore_result( waitpid(pid, NULL, 0) ); |
231 | |
|
232 | 0 | errno = errsv; |
233 | 0 | return canonical; |
234 | 0 | } |
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 |