root/libutil/abs2rel.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. normalize
  2. normalize_pathname
  3. abs2rel
  4. 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 }

/* [previous][next][first][last][top][bottom][index][help] */