test: avoid false failure with setgid directories
[coreutils.git] / src / relpath.c
blob04bfe24a9d807d296189f3daadcee086bea680bc
1 /* relpath - print the relative path
2 Copyright (C) 2012-2024 Free Software Foundation, Inc.
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.
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.
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/>. */
17 /* Written by Pádraig Brady. */
19 #include <config.h>
21 #include "system.h"
22 #include "relpath.h"
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
30 path_common_prefix (char const *path1, char const *path2)
32 int i = 0;
33 int ret = 0;
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;
41 while (*path1 && *path2)
43 if (*path1 != *path2)
44 break;
45 if (*path1 == '/')
46 ret = i + 1;
47 path1++;
48 path2++;
49 i++;
52 if ((!*path1 && !*path2)
53 || (!*path1 && *path2 == '/')
54 || (!*path2 && *path1 == '/'))
55 ret = i;
57 return ret;
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
66 buffer_or_output (char const *str, char **pbuf, size_t *plen)
68 if (*pbuf)
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;
77 else
79 fputs (str, stdout);
82 return false;
85 /* Output the relative representation if possible.
86 If BUF is non-null, write to that buffer rather than to stdout. */
87 bool
88 relpath (char const *can_fname, char const *can_reldir, char *buf, size_t len)
90 bool buf_err = false;
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;
97 char const *relto_suffix = can_reldir + common_index;
98 char const *fname_suffix = can_fname + common_index;
100 /* Skip over extraneous '/'. */
101 if (*relto_suffix == '/')
102 relto_suffix++;
103 if (*fname_suffix == '/')
104 fname_suffix++;
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)
110 buf_err |= buffer_or_output ("..", &buf, &len);
111 for (; *relto_suffix; ++relto_suffix)
113 if (*relto_suffix == '/')
114 buf_err |= buffer_or_output ("/..", &buf, &len);
117 if (*fname_suffix)
119 buf_err |= buffer_or_output ("/", &buf, &len);
120 buf_err |= buffer_or_output (fname_suffix, &buf, &len);
123 else
125 buf_err |= buffer_or_output (*fname_suffix ? fname_suffix : ".",
126 &buf, &len);
129 if (buf_err)
130 error (0, ENAMETOOLONG, "%s", _("generating relative path"));
132 return !buf_err;