1 #include <stdlib.h>
2 #include <limits.h>
3 #include <errno.h>
4 #include <unistd.h>
5 #include <string.h>
6
slash_len(const char * s)7 static size_t slash_len(const char *s)
8 {
9 const char *s0 = s;
10 while (*s == '/') s++;
11 return s-s0;
12 }
13
realpath(const char * restrict filename,char * restrict resolved)14 char *realpath(const char *restrict filename, char *restrict resolved)
15 {
16 #ifdef ENABLE_HWASAN
17 /* In realpath, the stack is read in 8-byte chunks. hwasan
18 * detects out-of-bounds reads. Therefore, the stack is
19 * changed to be an 8-byte multiple.*/
20 char stack[PATH_MAX+8];
21 #else
22 char stack[PATH_MAX+1];
23 #endif
24 char output[PATH_MAX];
25 size_t p, q, l, l0, cnt=0, nup=0;
26 int check_dir=0;
27
28 if (!filename) {
29 errno = EINVAL;
30 return 0;
31 }
32 l = strnlen(filename, sizeof stack);
33 if (!l) {
34 errno = ENOENT;
35 return 0;
36 }
37 if (l >= PATH_MAX) goto toolong;
38 p = sizeof stack - l - 1;
39 q = 0;
40 memcpy(stack+p, filename, l+1);
41
42 /* Main loop. Each iteration pops the next part from stack of
43 * remaining path components and consumes any slashes that follow.
44 * If not a link, it's moved to output; if a link, contents are
45 * pushed to the stack. */
46 restart:
47 for (; ; p+=slash_len(stack+p)) {
48 /* If stack starts with /, the whole component is / or //
49 * and the output state must be reset. */
50 if (stack[p] == '/') {
51 check_dir=0;
52 nup=0;
53 q=0;
54 output[q++] = '/';
55 p++;
56 /* Initial // is special. */
57 if (stack[p] == '/' && stack[p+1] != '/')
58 output[q++] = '/';
59 continue;
60 }
61
62 char *z = __strchrnul(stack+p, '/');
63 l0 = l = z-(stack+p);
64
65 if (!l && !check_dir) break;
66
67 /* Skip any . component but preserve check_dir status. */
68 if (l==1 && stack[p]=='.') {
69 p += l;
70 continue;
71 }
72
73 /* Copy next component onto output at least temporarily, to
74 * call readlink, but wait to advance output position until
75 * determining it's not a link. */
76 if (q && output[q-1] != '/') {
77 if (!p) goto toolong;
78 stack[--p] = '/';
79 l++;
80 }
81 if (q+l >= PATH_MAX) goto toolong;
82 memcpy(output+q, stack+p, l);
83 output[q+l] = 0;
84 p += l;
85
86 int up = 0;
87 if (l0==2 && stack[p-2]=='.' && stack[p-1]=='.') {
88 up = 1;
89 /* Any non-.. path components we could cancel start
90 * after nup repetitions of the 3-byte string "../";
91 * if there are none, accumulate .. components to
92 * later apply to cwd, if needed. */
93 if (q <= 3*nup) {
94 nup++;
95 q += l;
96 continue;
97 }
98 /* When previous components are already known to be
99 * directories, processing .. can skip readlink. */
100 if (!check_dir) goto skip_readlink;
101 }
102 ssize_t k = readlink(output, stack, p);
103 if (k==p) goto toolong;
104 if (!k) {
105 errno = ENOENT;
106 return 0;
107 }
108 if (k<0) {
109 if (errno != EINVAL) return 0;
110 skip_readlink:
111 check_dir = 0;
112 if (up) {
113 while(q && output[q-1]!='/') q--;
114 if (q>1 && (q>2 || output[0]!='/')) q--;
115 continue;
116 }
117 if (l0) q += l;
118 check_dir = stack[p];
119 continue;
120 }
121 if (++cnt == SYMLOOP_MAX) {
122 errno = ELOOP;
123 return 0;
124 }
125
126 /* If link contents end in /, strip any slashes already on
127 * stack to avoid /->// or //->/// or spurious toolong. */
128 if (stack[k-1]=='/') while (stack[p]=='/') p++;
129 p -= k;
130 memmove(stack+p, stack, k);
131
132 /* Skip the stack advancement in case we have a new
133 * absolute base path. */
134 goto restart;
135 }
136
137 output[q] = 0;
138
139 if (output[0] != '/') {
140 if (!getcwd(stack, sizeof stack)) return 0;
141 l = strlen(stack);
142 /* Cancel any initial .. components. */
143 p = 0;
144 while (nup--) {
145 while(l>1 && stack[l-1]!='/') l--;
146 if (l>1) l--;
147 p += 2;
148 if (p<q) p++;
149 }
150 if (q-p && stack[l-1]!='/') stack[l++] = '/';
151 if (l + (q-p) + 1 >= PATH_MAX) goto toolong;
152 memmove(output + l, output + p, q - p + 1);
153 memcpy(output, stack, l);
154 q = l + q-p;
155 }
156
157 if (resolved) return memcpy(resolved, output, q+1);
158 else return strdup(output);
159
160 toolong:
161 errno = ENAMETOOLONG;
162 return 0;
163 }
164