maint: update all copyright year number ranges
[platform/upstream/coreutils.git] / src / relpath.c
1 /* relpath - print the relative path
2    Copyright (C) 2012-2013 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 <http://www.gnu.org/licenses/>.  */
16
17 /* Written by Pádraig Brady.  */
18
19 #include <config.h>
20
21 #include "error.h"
22 #include "system.h"
23 #include "relpath.h"
24
25
26 /* Return the length of the longest common prefix
27    of canonical PATH1 and PATH2, ensuring only full path components
28    are matched.  Return 0 on no match.  */
29 static int _GL_ATTRIBUTE_PURE
30 path_common_prefix (const char *path1, const char *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
66 buffer_or_output (const char* 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
88 relpath (const char *can_fname, const char *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   const char *relto_suffix = can_reldir + common_index;
98   const char *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 }