1 /* realpath - print the resolved path
2 Copyright (C) 2011-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. */
22 #include <sys/types.h>
25 #include "canonicalize.h"
28 /* The official name of this program (e.g., no 'g' prefix). */
29 #define PROGRAM_NAME "realpath"
31 #define AUTHORS proper_name_lite ("Padraig Brady", "P\303\241draig Brady")
35 RELATIVE_TO_OPTION
= CHAR_MAX
+ 1,
39 static bool verbose
= true;
42 static char const *can_relative_to
;
43 static char const *can_relative_base
;
45 static struct option
const longopts
[] =
47 {"canonicalize-existing", no_argument
, nullptr, 'e'},
48 {"canonicalize-missing", no_argument
, nullptr, 'm'},
49 {"relative-to", required_argument
, nullptr, RELATIVE_TO_OPTION
},
50 {"relative-base", required_argument
, nullptr, RELATIVE_BASE_OPTION
},
51 {"quiet", no_argument
, nullptr, 'q'},
52 {"strip", no_argument
, nullptr, 's'},
53 {"no-symlinks", no_argument
, nullptr, 's'},
54 {"zero", no_argument
, nullptr, 'z'},
55 {"logical", no_argument
, nullptr, 'L'},
56 {"physical", no_argument
, nullptr, 'P'},
57 {GETOPT_HELP_OPTION_DECL
},
58 {GETOPT_VERSION_OPTION_DECL
},
59 {nullptr, 0, nullptr, 0}
65 if (status
!= EXIT_SUCCESS
)
69 printf (_("Usage: %s [OPTION]... FILE...\n"), program_name
);
71 Print the resolved absolute file name;\n\
72 all but the last component must exist\n\
76 -e, --canonicalize-existing all components of the path must exist\n\
77 -m, --canonicalize-missing no path components need exist or be a directory\
79 -L, --logical resolve '..' components before symlinks\n\
80 -P, --physical resolve symlinks as encountered (default)\n\
81 -q, --quiet suppress most error messages\n\
82 --relative-to=DIR print the resolved path relative to DIR\n\
83 --relative-base=DIR print absolute paths unless paths below DIR\n\
84 -s, --strip, --no-symlinks don't expand symlinks\n\
85 -z, --zero end each output line with NUL, not newline\n\
87 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
88 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
89 emit_ancillary_info (PROGRAM_NAME
);
94 /* A wrapper around canonicalize_filename_mode(),
95 to call it twice when in LOGICAL mode. */
97 realpath_canon (char const *fname
, int can_mode
)
99 char *can_fname
= canonicalize_filename_mode (fname
, can_mode
);
100 if (logical
&& can_fname
) /* canonicalize again to resolve symlinks. */
102 can_mode
&= ~CAN_NOLINKS
;
103 char *can_fname2
= canonicalize_filename_mode (can_fname
, can_mode
);
110 /* Test whether canonical prefix is parent or match of path. */
113 path_prefix (char const *prefix
, char const *path
)
115 /* We already know prefix[0] and path[0] are '/'. */
119 /* '/' is the prefix of everything except '//' (since we know '//'
120 is only present after canonicalization if it is distinct). */
124 /* Likewise, '//' is a prefix of any double-slash path. */
125 if (*prefix
== '/' && !prefix
[1])
128 /* Any other prefix has a non-slash portion. */
129 while (*prefix
&& *path
)
131 if (*prefix
!= *path
)
136 return (!*prefix
&& (*path
== '/' || !*path
));
140 isdir (char const *path
)
143 if (stat (path
, &sb
) != 0)
144 error (EXIT_FAILURE
, errno
, _("cannot stat %s"), quoteaf (path
));
145 return S_ISDIR (sb
.st_mode
);
149 process_path (char const *fname
, int can_mode
)
151 char *can_fname
= realpath_canon (fname
, can_mode
);
155 error (0, errno
, "%s", quotef (fname
));
160 || (can_relative_base
&& !path_prefix (can_relative_base
, can_fname
))
161 || (can_relative_to
&& !relpath (can_fname
, can_relative_to
, nullptr, 0)))
162 fputs (can_fname
, stdout
);
164 putchar (use_nuls
? '\0' : '\n');
172 main (int argc
, char **argv
)
175 int can_mode
= CAN_ALL_BUT_LAST
;
176 char const *relative_to
= nullptr;
177 char const *relative_base
= nullptr;
179 initialize_main (&argc
, &argv
);
180 set_program_name (argv
[0]);
181 setlocale (LC_ALL
, "");
182 bindtextdomain (PACKAGE
, LOCALEDIR
);
183 textdomain (PACKAGE
);
185 atexit (close_stdout
);
189 int c
= getopt_long (argc
, argv
, "eLmPqsz", longopts
, nullptr);
195 can_mode
&= ~CAN_MODE_MASK
;
196 can_mode
|= CAN_EXISTING
;
199 can_mode
&= ~CAN_MODE_MASK
;
200 can_mode
|= CAN_MISSING
;
203 can_mode
|= CAN_NOLINKS
;
207 can_mode
|= CAN_NOLINKS
;
211 can_mode
&= ~CAN_NOLINKS
;
220 case RELATIVE_TO_OPTION
:
221 relative_to
= optarg
;
223 case RELATIVE_BASE_OPTION
:
224 relative_base
= optarg
;
226 case_GETOPT_HELP_CHAR
;
227 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
229 usage (EXIT_FAILURE
);
235 error (0, 0, _("missing operand"));
236 usage (EXIT_FAILURE
);
239 if (relative_base
&& !relative_to
)
240 relative_to
= relative_base
;
242 bool need_dir
= (can_mode
& CAN_MODE_MASK
) == CAN_EXISTING
;
245 can_relative_to
= realpath_canon (relative_to
, can_mode
);
246 if (!can_relative_to
)
247 error (EXIT_FAILURE
, errno
, "%s", quotef (relative_to
));
248 if (need_dir
&& !isdir (can_relative_to
))
249 error (EXIT_FAILURE
, ENOTDIR
, "%s", quotef (relative_to
));
251 if (relative_base
== relative_to
)
252 can_relative_base
= can_relative_to
;
253 else if (relative_base
)
255 char *base
= realpath_canon (relative_base
, can_mode
);
257 error (EXIT_FAILURE
, errno
, "%s", quotef (relative_base
));
258 if (need_dir
&& !isdir (base
))
259 error (EXIT_FAILURE
, ENOTDIR
, "%s", quotef (relative_base
));
260 /* --relative-to is a no-op if it does not have --relative-base
262 if (path_prefix (base
, can_relative_to
))
263 can_relative_base
= base
;
267 can_relative_base
= can_relative_to
;
268 can_relative_to
= nullptr;
272 for (; optind
< argc
; ++optind
)
273 ok
&= process_path (argv
[optind
], can_mode
);
275 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;