/* 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 . */
/* Written by Pádraig Brady. */
#include
#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;
}