/* */
This source file includes following definitions.
- normalize
- normalize_pathname
- abs2rel
- rel2abs
1 /*
2 * Copyright (c) 1997, 1999, 2008 Tama Communications Corporation
3 *
4 * This file is part of GNU GLOBAL.
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 #include <errno.h>
23 #ifdef STDC_HEADERS
24 #include <stdlib.h>
25 #endif
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #else
29 #include <strings.h>
30 #endif
31
32 #include "abs2rel.h"
33 #include "die.h"
34 #include "gparam.h"
35 #include "locatestring.h"
36 #include "strlimcpy.h"
37 #include "path.h"
38
39 /** @file
40 @verbatim
41
42 NAME
43 abs2rel - make a relative path name from an absolute path
44
45 SYNOPSIS
46 char *
47 abs2rel(const char *path, const char *base, char *result, size_t size)
48
49 DESCRIPTION
50 The abs2rel() function makes a relative path name from an absolute path
51 name path based on a directory base and copies the resulting path name
52 into the memory referenced by result. The result argument must refer to
53 a buffer capable of storing at least size characters.
54
55 The resulting path name may include symbolic links. The abs2rel() func-
56 tion doesn't check whether or not any path exists.
57
58 RETURN VALUES
59 The abs2rel() function returns relative path name on success. If an er-
60 ror occurs, it returns NULL.
61
62 ERRORS
63 The abs2rel() function may fail and set the external variable errno to
64 indicate the error.
65
66 [EINVAL] The base directory isn't an absolute path name or the
67 size argument is zero.
68
69 [ERANGE] The size argument is greater than zero but smaller
70 than the length of the pathname plus 1.
71
72 EXAMPLE
73 char result[MAXPATHLEN];
74 char *path = abs2rel("/usr/src/sys", "/usr/local/lib", result, MAX-
75 PATHLEN);
76
77 yields:
78
79 path == "../../src/sys"
80
81 Similarly,
82
83 path1 = abs2rel("/usr/src/sys", "/usr", result, MAXPATHLEN);
84 path2 = abs2rel("/usr/src/sys", "/usr/src/sys", result, MAXPATHLEN);
85
86 yields:
87
88 path1 == "src/sys"
89 path2 == "."
90
91
92 BUGS
93 If the base directory includes symbolic links, the abs2rel() function
94 produces the wrong path. For example, if '/sys' is a symbolic link to
95 '/usr/src/sys',
96
97 char *path = abs2rel("/usr/local/lib", "/sys", result, MAXPATHLEN);
98
99 yields:
100
101 path == "../usr/local/lib" -- It's wrong!!
102
103 You should convert the base directory into a real path in advance.
104
105 path = abs2rel("/sys/kern", realpath("/sys", resolvedname), result,
106 MAXPATHLEN);
107
108 yields:
109
110 path == "../../../sys/kern" -- It's correct but ...
111
112 That is correct, but a little redundant. If you wish get the simple an-
113 swer 'kern', do the following.
114
115 path = abs2rel(realpath("/sys/kern", r1), realpath("/sys", r2),
116 result, MAXPATHLEN);
117
118 The realpath() function assures correct result, but don't forget that
119 realpath() requires that all but the last component of the path exist.
120
121 -------------------------------------------------------------------------------
122 NAME
123 rel2abs - make an absolute path name from a relative path
124
125 SYNOPSIS
126 char *
127 rel2abs(const char *path, const char *base, char *result, size_t size)
128
129 DESCRIPTION
130 The rel2abs() function makes an absolute path name from a relative path
131 name path based on a directory base and copies the resulting path name
132 into the memory referenced by result. The result argument must refer to
133 a buffer capable of storing at least size character
134
135 The resulting path name may include symbolic links. abs2rel() doesn't
136 check whether or not any path exists.
137
138 RETURN VALUES
139 The rel2abs() function returns absolute path name on success. If an er-
140 ror occurs, it returns NULL.
141
142 ERRORS
143 The rel2abs() function may fail and set the external variable errno to
144 indicate the error.
145
146 [EINVAL] The base directory isn't an absolute path name or the
147 size argument is zero.
148
149 [ERANGE] The size argument is greater than zero but smaller
150 than the length of the pathname plus 1
151
152 EXAMPLE
153 char result[MAXPATHLEN];
154 char *path = rel2abs("../../src/sys", "/usr/local/lib", result, MAX-
155 PATHLEN);
156
157 yields:
158
159 path == "/usr/src/sys"
160
161 Similarly,
162
163 path1 = rel2abs("src/sys", "/usr", result, MAXPATHLEN);
164 path2 = rel2abs(".", "/usr/src/sys", result, MAXPATHLEN);
165
166 yields:
167
168 path1 == "/usr/src/sys"
169 path2 == "/usr/src/sys"
170
171 @endverbatim
172 */
173 /**
174 * @details
175 * normalize: normalize path name
176 *
177 * @param[in] path path name
178 * @param[in] root root of project (@STRONG{must be end with a '/'})
179 * @param[in] cwd current directory
180 * @param[out] result normalized path name
181 * @param[in] size size of the @a result
182 * @return ==NULL: error <br>
183 * !=NULL: @a result
184 *
185 * @note Calls die() if the result path name is too long (#MAXPATHLEN).
186 */
187 char *
188 normalize(const char *path, const char *root, const char *cwd, char *result, const int size)
189 {
190 char *p, abs[MAXPATHLEN];
191
192 if (normalize_pathname(path, result, size) == NULL)
193 goto toolong;
194 if (isabspath(path)) {
195 if (strlen(result) > MAXPATHLEN)
196 goto toolong;
197 strcpy(abs, result);
198 } else {
199 if (rel2abs(result, cwd, abs, sizeof(abs)) == NULL)
200 goto toolong;
201 }
202 /*
203 * Remove the root part of path and insert './'.
204 * rootdir /a/b/
205 * path /a/b/c/d.c -> c/d.c -> ./c/d.c
206 */
207 p = locatestring(abs, root, MATCH_AT_FIRST);
208 if (p == NULL) {
209 p = locatestring(root, abs, MATCH_AT_FIRST);
210 /*
211 * abs == /usr/src should be considered to be equal to root == /usr/src/.
212 */
213 if (p && !strcmp(p, "/"))
214 result[0] = '\0';
215 else
216 return NULL;
217 }
218 strlimcpy(result, "./", size);
219 strlimcpy(result + 2, p, size - 2);
220 return result;
221 toolong:
222 die("path name is too long.");
223 }
224 /**
225 * @details
226 * normalize_pathname: normalize relative path name.
227 *
228 * @param[in] path relative path name
229 * @param[out] result result buffer
230 * @param[in] size size of @a result buffer
231 * @return != NULL: normalized path name <br>
232 * == NULL: error (ERANGE)
233 *
234 * @par Examples:
235 * @code
236 * path result
237 * ---------------------------
238 * /a /a
239 * ./a/./b/c a/b/c
240 * a////b///c a/b/c
241 * ../a/b/c ../a/b/c
242 * a/d/../b/c a/b/c
243 * a/../b/../c/../d d
244 * a/../../d ../d
245 * /a/../../d /d
246 * @endcode
247 */
248 char *
249 normalize_pathname(const char *path, char *result, const int size)
250 {
251 const char *savep, *p = path;
252 char *final, *q = result;
253 char *endp = result + size - 1;
254
255 /* accept the first '/' */
256 if (isabspath(p)) {
257 *q++ = *p++;
258 #if defined(_WIN32) || defined(__DJGPP__)
259 if (*p == ':') {
260 *q++ = *p++;
261 *q++ = *p++;
262 }
263 #endif
264 final = q;
265 }
266 do {
267 savep = p;
268 while (!strncmp(p, "./", 2)) /* skip "./" at the head of the path */
269 p += 2;
270 while (!strncmp(p, "../", 3)) { /* accept the first "../" */
271 if (q + 3 > endp)
272 goto erange;
273 strcpy(q, "../");
274 p += 3;
275 q += 3;
276 }
277 } while (savep != p);
278
279 final = q;
280 while (*p) {
281 if (*p == '/') {
282 p++;
283 do {
284 savep = p;
285 /* skip consecutive '/' */
286 while (*p == '/')
287 p++;
288 /* skip consecutive './' */
289 while (!strncmp(p, "./", 2))
290 p += 2;
291 /* resolve '../'(parent directory) */
292 while (!strncmp(p, "../", 3)) {
293 p += 3;
294 if (q > final) {
295 while (q > final && *--q != '/')
296 ;
297 } else if (!(*result == '/' && result + 1 == q)) {
298 if (q + 3 > endp)
299 goto erange;
300 strcpy(q, "../");
301 q += 3;
302 final = q;
303 }
304 }
305 } while (savep != p);
306 if (q > endp)
307 goto erange;
308 if (q > final) {
309 *q++ = '/';
310 }
311 } else {
312 if (q > endp)
313 goto erange;
314 *q++ = *p++;
315 }
316 }
317 *q = '\0';
318 return result;
319 erange:
320 errno = ERANGE;
321 return NULL;
322 }
323 /**
324 * @details
325 * abs2rel: convert an absolute path name into relative.
326 *
327 * @param[in] path absolute path
328 * @param[in] base base directory (@STRONG{must be absolute path})
329 * @param[out] result result buffer
330 * @param[in] size size of @a result buffer
331 * @return != NULL: relative path <br>
332 * == NULL: error (ERANGE or EINVAL)
333 */
334 char *
335 abs2rel(const char *path, const char *base, char *result, const int size)
336 {
337 const char *pp, *bp, *branch;
338 /*
339 * endp points the last position which is safe in the result buffer.
340 */
341 const char *endp = result + size - 1;
342 char *rp;
343
344 if (!isabspath(path)) {
345 if (strlen(path) >= size)
346 goto erange;
347 strcpy(result, path);
348 goto finish;
349 } else if (!isabspath(base) || !size) {
350 errno = EINVAL;
351 return (NULL);
352 } else if (size == 1)
353 goto erange;
354 /*
355 * seek to branched point.
356 */
357 branch = path;
358 for (pp = path, bp = base; *pp && *bp && *pp == *bp; pp++, bp++)
359 if (*pp == '/')
360 branch = pp;
361 if ((*pp == 0 || (*pp == '/' && *(pp + 1) == 0)) &&
362 (*bp == 0 || (*bp == '/' && *(bp + 1) == 0))) {
363 rp = result;
364 *rp++ = '.';
365 if (*pp == '/' || *(pp - 1) == '/')
366 *rp++ = '/';
367 if (rp > endp)
368 goto erange;
369 *rp = 0;
370 goto finish;
371 }
372 if ((*pp == 0 && *bp == '/') || (*pp == '/' && *bp == 0))
373 branch = pp;
374 /*
375 * up to root.
376 */
377 rp = result;
378 for (bp = base + (branch - path); *bp; bp++)
379 if (*bp == '/' && *(bp + 1) != 0) {
380 if (rp + 3 > endp)
381 goto erange;
382 *rp++ = '.';
383 *rp++ = '.';
384 *rp++ = '/';
385 }
386 if (rp > endp)
387 goto erange;
388 *rp = 0;
389 /*
390 * down to leaf.
391 */
392 if (*branch) {
393 if (rp + strlen(branch + 1) > endp)
394 goto erange;
395 strcpy(rp, branch + 1);
396 } else
397 *--rp = 0;
398 finish:
399 return result;
400 erange:
401 errno = ERANGE;
402 return (NULL);
403 }
404 /**
405 * @details
406 * rel2abs: convert an relative path name into absolute.
407 *
408 * @param[in] path relative path
409 * @param[in] base base directory (@STRONG{must be absolute path})
410 * @param[out] result result buffer
411 * @param[in] size size of @a result buffer
412 * @return != NULL: absolute path <br>
413 * == NULL: error (ERANGE or EINVAL)
414 */
415 char *
416 rel2abs(const char *path, const char *base, char *result, const int size)
417 {
418 const char *pp, *bp;
419 /*
420 * endp points the last position which is safe in the result buffer.
421 */
422 const char *endp = result + size - 1;
423 char *rp;
424 int length;
425
426 if (isabspath(path)) {
427 if (strlen(path) >= size)
428 goto erange;
429 strcpy(result, path);
430 goto finish;
431 } else if (!isabspath(base) || !size) {
432 errno = EINVAL;
433 return (NULL);
434 } else if (size == 1)
435 goto erange;
436
437 length = strlen(base);
438
439 if (!strcmp(path, ".") || !strcmp(path, "./")) {
440 if (length >= size)
441 goto erange;
442 strcpy(result, base);
443 /*
444 * rp points the last char.
445 */
446 rp = result + length - 1;
447 /*
448 * remove the last '/'.
449 */
450 if (*rp == '/') {
451 if (length > 1)
452 *rp = 0;
453 } else
454 rp++;
455 /* rp point NULL char */
456 if (*++path == '/') {
457 /*
458 * Append '/' to the tail of path name.
459 */
460 *rp++ = '/';
461 if (rp > endp)
462 goto erange;
463 *rp = 0;
464 }
465 goto finish;
466 }
467 bp = base + length;
468 if (*(bp - 1) == '/')
469 --bp;
470 /*
471 * up to root.
472 */
473 for (pp = path; *pp && *pp == '.'; ) {
474 if (!strncmp(pp, "../", 3)) {
475 pp += 3;
476 while (bp > base && *--bp != '/')
477 ;
478 } else if (!strncmp(pp, "./", 2)) {
479 pp += 2;
480 } else if (!strncmp(pp, "..\0", 3)) {
481 pp += 2;
482 while (bp > base && *--bp != '/')
483 ;
484 } else
485 break;
486 }
487 /*
488 * down to leaf.
489 */
490 length = bp - base;
491 if (length >= size)
492 goto erange;
493 strncpy(result, base, length);
494 rp = result + length;
495 if (*pp || *(pp - 1) == '/' || length == 0)
496 *rp++ = '/';
497 if (rp + strlen(pp) > endp)
498 goto erange;
499 strcpy(rp, pp);
500 finish:
501 return result;
502 erange:
503 errno = ERANGE;
504 return (NULL);
505 }
/* */