1 /* relpath - print the relative path
2 Copyright (C) 2012-2023 Free Software Foundation, Inc.
3
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 /* Written by Pádraig Brady. */
18
19 #include <config.h>
20
21 #include "system.h"
22 #include "relpath.h"
23
24
25 /* Return the length of the longest common prefix
26 of canonical PATH1 and PATH2, ensuring only full path components
27 are matched. Return 0 on no match. */
28 ATTRIBUTE_PURE
29 static int
path_common_prefix(char const * path1,char const * path2)30 path_common_prefix (char const *path1, char const *path2)
31 {
32 int i = 0;
33 int ret = 0;
34
35 /* We already know path1[0] and path2[0] are '/'. Special case
36 '//', which is only present in a canonical name on platforms
37 where it is distinct. */
38 if ((path1[1] == '/') != (path2[1] == '/'))
39 return 0;
40
41 while (*path1 && *path2)
42 {
43 if (*path1 != *path2)
44 break;
45 if (*path1 == '/')
46 ret = i + 1;
47 path1++;
48 path2++;
49 i++;
50 }
51
52 if ((!*path1 && !*path2)
53 || (!*path1 && *path2 == '/')
54 || (!*path2 && *path1 == '/'))
55 ret = i;
56
57 return ret;
58 }
59
60 /* Either output STR to stdout or
61 if *PBUF is not null then append STR to *PBUF
62 and update *PBUF to point to the end of the buffer
63 and adjust *PLEN to reflect the remaining space.
64 Return TRUE on failure. */
65 static bool
buffer_or_output(char const * str,char ** pbuf,size_t * plen)66 buffer_or_output (char const *str, char **pbuf, size_t *plen)
67 {
68 if (*pbuf)
69 {
70 size_t slen = strlen (str);
71 if (slen >= *plen)
72 return true;
73 memcpy (*pbuf, str, slen + 1);
74 *pbuf += slen;
75 *plen -= slen;
76 }
77 else
78 {
79 fputs (str, stdout);
80 }
81
82 return false;
83 }
84
85 /* Output the relative representation if possible.
86 If BUF is non-null, write to that buffer rather than to stdout. */
87 bool
relpath(char const * can_fname,char const * can_reldir,char * buf,size_t len)88 relpath (char const *can_fname, char const *can_reldir, char *buf, size_t len)
89 {
90 bool buf_err = false;
91
92 /* Skip the prefix common to --relative-to and path. */
93 int common_index = path_common_prefix (can_reldir, can_fname);
94 if (!common_index)
95 return false;
96
97 char const *relto_suffix = can_reldir + common_index;
98 char const *fname_suffix = can_fname + common_index;
99
100 /* Skip over extraneous '/'. */
101 if (*relto_suffix == '/')
102 relto_suffix++;
103 if (*fname_suffix == '/')
104 fname_suffix++;
105
106 /* Replace remaining components of --relative-to with '..', to get
107 to a common directory. Then output the remainder of fname. */
108 if (*relto_suffix)
109 {
110 buf_err |= buffer_or_output ("..", &buf, &len);
111 for (; *relto_suffix; ++relto_suffix)
112 {
113 if (*relto_suffix == '/')
114 buf_err |= buffer_or_output ("/..", &buf, &len);
115 }
116
117 if (*fname_suffix)
118 {
119 buf_err |= buffer_or_output ("/", &buf, &len);
120 buf_err |= buffer_or_output (fname_suffix, &buf, &len);
121 }
122 }
123 else
124 {
125 buf_err |= buffer_or_output (*fname_suffix ? fname_suffix : ".",
126 &buf, &len);
127 }
128
129 if (buf_err)
130 error (0, ENAMETOOLONG, "%s", _("generating relative path"));
131
132 return !buf_err;
133 }
134