diff options
Diffstat (limited to 'src/relpath.c')
-rw-r--r-- | src/relpath.c | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/src/relpath.c b/src/relpath.c new file mode 100644 index 0000000..bec34f7 --- /dev/null +++ b/src/relpath.c @@ -0,0 +1,134 @@ +/* relpath - print the relative path + Copyright (C) 2012-2022 Free Software Foundation, Inc. + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. */ + +/* Written by Pádraig Brady. */ + +#include <config.h> + +#include "error.h" +#include "system.h" +#include "relpath.h" + + +/* Return the length of the longest common prefix + of canonical PATH1 and PATH2, ensuring only full path components + are matched. Return 0 on no match. */ +ATTRIBUTE_PURE +static int +path_common_prefix (char const *path1, char const *path2) +{ + int i = 0; + int ret = 0; + + /* We already know path1[0] and path2[0] are '/'. Special case + '//', which is only present in a canonical name on platforms + where it is distinct. */ + if ((path1[1] == '/') != (path2[1] == '/')) + return 0; + + while (*path1 && *path2) + { + if (*path1 != *path2) + break; + if (*path1 == '/') + ret = i + 1; + path1++; + path2++; + i++; + } + + if ((!*path1 && !*path2) + || (!*path1 && *path2 == '/') + || (!*path2 && *path1 == '/')) + ret = i; + + return ret; +} + +/* Either output STR to stdout or + if *PBUF is not NULL then append STR to *PBUF + and update *PBUF to point to the end of the buffer + and adjust *PLEN to reflect the remaining space. + Return TRUE on failure. */ +static bool +buffer_or_output (char const *str, char **pbuf, size_t *plen) +{ + if (*pbuf) + { + size_t slen = strlen (str); + if (slen >= *plen) + return true; + memcpy (*pbuf, str, slen + 1); + *pbuf += slen; + *plen -= slen; + } + else + { + fputs (str, stdout); + } + + return false; +} + +/* Output the relative representation if possible. + If BUF is non-NULL, write to that buffer rather than to stdout. */ +bool +relpath (char const *can_fname, char const *can_reldir, char *buf, size_t len) +{ + bool buf_err = false; + + /* Skip the prefix common to --relative-to and path. */ + int common_index = path_common_prefix (can_reldir, can_fname); + if (!common_index) + return false; + + char const *relto_suffix = can_reldir + common_index; + char const *fname_suffix = can_fname + common_index; + + /* Skip over extraneous '/'. */ + if (*relto_suffix == '/') + relto_suffix++; + if (*fname_suffix == '/') + fname_suffix++; + + /* Replace remaining components of --relative-to with '..', to get + to a common directory. Then output the remainder of fname. */ + if (*relto_suffix) + { + buf_err |= buffer_or_output ("..", &buf, &len); + for (; *relto_suffix; ++relto_suffix) + { + if (*relto_suffix == '/') + buf_err |= buffer_or_output ("/..", &buf, &len); + } + + if (*fname_suffix) + { + buf_err |= buffer_or_output ("/", &buf, &len); + buf_err |= buffer_or_output (fname_suffix, &buf, &len); + } + } + else + { + buf_err |= buffer_or_output (*fname_suffix ? fname_suffix : ".", + &buf, &len); + } + + if (buf_err) + error (0, ENAMETOOLONG, "%s", _("generating relative path")); + + return !buf_err; +} |