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