2
* readlink Implementation of the readlink program that has a
3
* proper canonicalize function.
5
* The canonicalized path is printed if the entire path
6
* exists, but _also_ if the last path element is missing
7
* and the path up till there is a directory.
9
* If you specify -f or --canonicalize twice (aka
10
* canonicalize-me-harder) then the "most canonical" path
11
* is printed. Even if a part of it doesn't exist, though
12
* the exit status will be non-zero in that case.
14
* realpath If argv[0] is (^|/)realpath$, this program will behave
15
* as the realpath utility (-s not implemented).
16
* Note that 'readlink -ff' and 'realpath' are equivalent.
18
* Author: Miquel van Smoorenburg.
20
* Version: @(#)readlink 1.02 21-Mar-2004 miquels@cistron.nl
22
* This file is part of the sysvinit suite,
23
* Copyright 2004 Miquel van Smoorenburg.
25
* License: This program is free software; you can redistribute it and/or
26
* modify it under the terms of the GNU General Public License
27
* as published by the Free Software Foundation; either version
28
* 2 of the License, or (at your option) any later version.
30
* The realpath_sz() function in this file may also be
31
* redistributed under the terms of the LGPL:
44
#include <sys/types.h>
46
#include <sys/param.h>
48
struct option readlink_options[] = {
49
{ "canonicalize", 0, NULL, 'f' },
50
{ "no-newline", 0, NULL, 'n' },
51
{ "quiet", 0, NULL, 'q' },
52
{ "silent", 0, NULL, 's' },
53
{ "help", 0, NULL, 'h' },
54
{ "verbose", 0, NULL, 'v' },
55
{ "version", 0, NULL, 'V' },
59
struct option realpath_options [] = {
60
{ "no-newline", 0, NULL, 'n' },
61
{ "help", 0, NULL, 'h' },
62
{ "verbose", 0, NULL, 'v' },
63
{ "version", 0, NULL, 'V' },
67
static int system_path_max(char *name)
73
if ((path_max = pathconf (name, _PC_PATH_MAX)) <= 0)
80
* Error. Try to build the path till here into resolved,
81
* return -1 if that works, -2 if failed (that means that
82
* the contents of "resolved" make no sense whatsoever).
84
static int path_err(char *resolved, unsigned int reslen, char *restpath)
86
if (strlen(resolved) + strlen(restpath) + 1 >= reslen) {
90
strcat(resolved, "/");
91
strcat(resolved, restpath);
96
* Ye olde realpath() functionne.
98
* It takes an extra size argument for the resolved argument.
100
* Exit status: 0 path resolved
101
* -1 path not fully resolved (part not accessible)
102
* but canonicalized as far as possible.
103
* -2 error, "resolved" is invalid.
105
* If exit status < 0 errno is set.
107
static int realpath_sz(char *path, char *resolved, unsigned int reslen)
114
int link_max = _POSIX_LINK_MAX;
119
* Some basic checks and allocations.
121
if (strlen(path) + 2 >= reslen) {
122
errno = ENAMETOOLONG;
125
if ((buf = alloca(reslen)) == NULL)
127
if ((buf2 = alloca(reslen)) == NULL)
131
* Start with the current working directory
132
* if the path is relative.
135
if (getcwd(resolved, reslen - 1) == NULL) {
137
errno = ENAMETOOLONG;
144
strcpy(resolved, "/");
147
st.st_mode = S_IFDIR;
152
* Skip multiple preceding slashes.
154
while (*path == '/' && path[1] == '/') path++;
157
* At this point, "resolved" contains the path up
158
* till here without a trailing slash, and "path"
159
* contains the rest of the path starting with a /.
161
* One exception: if path is empty, then resolved
162
* can contain a single trailing slash (happens if
163
* path was just "/" in the previous round)
165
st_mode = st.st_mode;
166
st.st_mode = S_IFREG;
167
if (exists > 0 && lstat(resolved, &st) < 0) {
168
if (errno != ENOENT && errno != ENOTDIR &&
170
return path_err(resolved, reslen, path);
172
if (errno != ENOENT) exists--;
176
* Remove trailing slash if this is a directory.
178
if ((s = strrchr(resolved, '/')) != NULL && s[1] == 0 &&
179
s > resolved && S_ISDIR(st.st_mode))
182
if (exists > 0 && S_ISLNK(st.st_mode)) {
184
* It's a link. Prepend the rest of the
185
* remaining path with the contents of
186
* the link, remove the last element
187
* from resolved, and loop.
189
if (++links > link_max) {
191
return path_err(resolved, reslen, path);
194
if ((n = readlink(resolved, buf, reslen - 1)) < 0)
195
return path_err(resolved, reslen, path);
201
p = strrchr(resolved, '/');
202
if (p == resolved) p++;
206
if (strlen(resolved) + strlen(buf) +
207
strlen(path) + 3 >= reslen) {
209
return path_err(resolved, reslen, path);
214
if (buf[0] != '/') strcat(buf2, "/");
228
* repeating /./ sequences can be skipped.
230
while (path[0] == '/' && path[1] == '.' && path[2] == '/') {
232
while (*path == '/' && path[1] == '/') path++;
236
* Is it a trailing / or /. or /./ ?
238
if (exists > 0 && (path[1] == 0 ||
239
(path[1] == '.' && (path[2] == 0 || path[2] == '/')))) {
241
if (S_ISDIR(st.st_mode)) {
246
if (*path == '.') path++;
251
* Yup, ".", but that doesn't work if the
252
* parent path isn't a directory.
261
if (exists > 0 && path[1] == '.' && path[2] == '.' &&
262
(path[3] == 0 || path[3] == '/')) {
264
if (S_ISDIR(st.st_mode)) {
266
* Back up one element.
268
p = strrchr(resolved, '/');
269
if (p == resolved) p++;
276
* Yup, "..", but that doesn't work if the
277
* parent path isn't a directory.
284
* Okay add this element to the resolved path.
286
for (p = path + 1; *p && *p != '/'; p++)
288
if (strlen(resolved) + (p - path) + 2 >= reslen) {
289
errno = ENAMETOOLONG;
294
if (resolved[0] == '/' && resolved[1] == 0) path++;
295
strcat(resolved, path);
300
return (exists > 0 || (exists == 0 && S_ISDIR(st_mode))) ? 0 : -1;
303
void usage(char *progname)
305
fprintf(stderr, "Usage: %s: [OPTION]... FILE\n", progname);
309
void version(char *progname)
311
printf("%s version 1.0 (sysvinit 2.85)\n", progname);
315
char *mystrerror(int e)
318
return "Not a symbolic link";
323
int cmd_readlink(int argc, char **argv, char *progname)
331
unsigned int path_max;
333
while ((c = getopt_long(argc, argv, "fnqsv", readlink_options, NULL)) != EOF) switch(c) {
355
if (optind != argc - 1)
360
* Rules on printing stdout, stderr and exit status:
362
* simple readlink: succes: print value on stdout, exit 0
363
* fail: print error on stderr, exit 1
365
* canonicalize: succes: print path on stdout, exit 0
366
* fail: print path on stdout if -ff
367
* print error on stderr if verbose
368
* exit 1 if path was still canonicalized
369
* exit 254 if not completely canon'ed
372
l = opt_canon ? -2 : -1;
374
path_max = system_path_max(path);
375
if ((buf = alloca(path_max)) != NULL) {
377
l = realpath_sz(path, buf, path_max);
379
l = readlink(path, buf, path_max - 1);
389
if (l >= 0 || opt_canon > 1)
390
printf("%s%s", (l == -2) ? path : buf, opt_nonl ? "" : "\n");
392
if (l < 0 && opt_verbose)
393
fprintf(stderr, "%s: %s: %s\n", progname,
394
(l == -2) ? path : buf, mystrerror(e));
396
if (l < 0) return (l == -2) ? 254 : 1;
400
int cmd_realpath(int argc, char **argv, char *progname)
409
while ((c = getopt_long(argc, argv, "nv", realpath_options, NULL)) != EOF) switch(c) {
424
if (optind != argc - 1)
428
path_max = system_path_max(path);
429
if ((buf = alloca(path_max)) != NULL)
430
l = realpath_sz(path, buf, path_max);
436
printf("%s%s", (l == -2) ? path : buf, opt_nonl ? "" : "\n");
438
if (l < 0 && opt_verbose)
439
fprintf(stderr, "%s: %s: %s\n", progname,
440
(l == -2) ? path : buf, mystrerror(e));
442
if (l < 0) return (l == -2) ? 254 : 1;
446
int main(int argc, char **argv)
450
if ((p = strrchr(argv[0], '/')) != NULL)
455
if (strcmp(p, "realpath") == 0)
456
return cmd_realpath(argc, argv, p);
458
return cmd_readlink(argc, argv, "readlink");