1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
/* relpath - print the relative path
Copyright (C) 2012-2020 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. */
static int _GL_ATTRIBUTE_PURE
path_common_prefix (const char *path1, const char *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 (const char* 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 (const char *can_fname, const char *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;
const char *relto_suffix = can_reldir + common_index;
const char *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;
}
|