1570af302Sopenharmony_ci#include <stdlib.h>
2570af302Sopenharmony_ci#include <limits.h>
3570af302Sopenharmony_ci#include <errno.h>
4570af302Sopenharmony_ci#include <unistd.h>
5570af302Sopenharmony_ci#include <string.h>
6570af302Sopenharmony_ci
7570af302Sopenharmony_cistatic size_t slash_len(const char *s)
8570af302Sopenharmony_ci{
9570af302Sopenharmony_ci	const char *s0 = s;
10570af302Sopenharmony_ci	while (*s == '/') s++;
11570af302Sopenharmony_ci	return s-s0;
12570af302Sopenharmony_ci}
13570af302Sopenharmony_ci
14570af302Sopenharmony_cichar *realpath(const char *restrict filename, char *restrict resolved)
15570af302Sopenharmony_ci{
16570af302Sopenharmony_ci	char stack[PATH_MAX+1];
17570af302Sopenharmony_ci	char output[PATH_MAX];
18570af302Sopenharmony_ci	size_t p, q, l, l0, cnt=0, nup=0;
19570af302Sopenharmony_ci	int check_dir=0;
20570af302Sopenharmony_ci
21570af302Sopenharmony_ci	if (!filename) {
22570af302Sopenharmony_ci		errno = EINVAL;
23570af302Sopenharmony_ci		return 0;
24570af302Sopenharmony_ci	}
25570af302Sopenharmony_ci	l = strnlen(filename, sizeof stack);
26570af302Sopenharmony_ci	if (!l) {
27570af302Sopenharmony_ci		errno = ENOENT;
28570af302Sopenharmony_ci		return 0;
29570af302Sopenharmony_ci	}
30570af302Sopenharmony_ci	if (l >= PATH_MAX) goto toolong;
31570af302Sopenharmony_ci	p = sizeof stack - l - 1;
32570af302Sopenharmony_ci	q = 0;
33570af302Sopenharmony_ci	memcpy(stack+p, filename, l+1);
34570af302Sopenharmony_ci
35570af302Sopenharmony_ci	/* Main loop. Each iteration pops the next part from stack of
36570af302Sopenharmony_ci	 * remaining path components and consumes any slashes that follow.
37570af302Sopenharmony_ci	 * If not a link, it's moved to output; if a link, contents are
38570af302Sopenharmony_ci	 * pushed to the stack. */
39570af302Sopenharmony_cirestart:
40570af302Sopenharmony_ci	for (; ; p+=slash_len(stack+p)) {
41570af302Sopenharmony_ci		/* If stack starts with /, the whole component is / or //
42570af302Sopenharmony_ci		 * and the output state must be reset. */
43570af302Sopenharmony_ci		if (stack[p] == '/') {
44570af302Sopenharmony_ci			check_dir=0;
45570af302Sopenharmony_ci			nup=0;
46570af302Sopenharmony_ci			q=0;
47570af302Sopenharmony_ci			output[q++] = '/';
48570af302Sopenharmony_ci			p++;
49570af302Sopenharmony_ci			/* Initial // is special. */
50570af302Sopenharmony_ci			if (stack[p] == '/' && stack[p+1] != '/')
51570af302Sopenharmony_ci				output[q++] = '/';
52570af302Sopenharmony_ci			continue;
53570af302Sopenharmony_ci		}
54570af302Sopenharmony_ci
55570af302Sopenharmony_ci		char *z = __strchrnul(stack+p, '/');
56570af302Sopenharmony_ci		l0 = l = z-(stack+p);
57570af302Sopenharmony_ci
58570af302Sopenharmony_ci		if (!l && !check_dir) break;
59570af302Sopenharmony_ci
60570af302Sopenharmony_ci		/* Skip any . component but preserve check_dir status. */
61570af302Sopenharmony_ci		if (l==1 && stack[p]=='.') {
62570af302Sopenharmony_ci			p += l;
63570af302Sopenharmony_ci			continue;
64570af302Sopenharmony_ci		}
65570af302Sopenharmony_ci
66570af302Sopenharmony_ci		/* Copy next component onto output at least temporarily, to
67570af302Sopenharmony_ci		 * call readlink, but wait to advance output position until
68570af302Sopenharmony_ci		 * determining it's not a link. */
69570af302Sopenharmony_ci		if (q && output[q-1] != '/') {
70570af302Sopenharmony_ci			if (!p) goto toolong;
71570af302Sopenharmony_ci			stack[--p] = '/';
72570af302Sopenharmony_ci			l++;
73570af302Sopenharmony_ci		}
74570af302Sopenharmony_ci		if (q+l >= PATH_MAX) goto toolong;
75570af302Sopenharmony_ci		memcpy(output+q, stack+p, l);
76570af302Sopenharmony_ci		output[q+l] = 0;
77570af302Sopenharmony_ci		p += l;
78570af302Sopenharmony_ci
79570af302Sopenharmony_ci		int up = 0;
80570af302Sopenharmony_ci		if (l0==2 && stack[p-2]=='.' && stack[p-1]=='.') {
81570af302Sopenharmony_ci			up = 1;
82570af302Sopenharmony_ci			/* Any non-.. path components we could cancel start
83570af302Sopenharmony_ci			 * after nup repetitions of the 3-byte string "../";
84570af302Sopenharmony_ci			 * if there are none, accumulate .. components to
85570af302Sopenharmony_ci			 * later apply to cwd, if needed. */
86570af302Sopenharmony_ci			if (q <= 3*nup) {
87570af302Sopenharmony_ci				nup++;
88570af302Sopenharmony_ci				q += l;
89570af302Sopenharmony_ci				continue;
90570af302Sopenharmony_ci			}
91570af302Sopenharmony_ci			/* When previous components are already known to be
92570af302Sopenharmony_ci			 * directories, processing .. can skip readlink. */
93570af302Sopenharmony_ci			if (!check_dir) goto skip_readlink;
94570af302Sopenharmony_ci		}
95570af302Sopenharmony_ci		ssize_t k = readlink(output, stack, p);
96570af302Sopenharmony_ci		if (k==p) goto toolong;
97570af302Sopenharmony_ci		if (!k) {
98570af302Sopenharmony_ci			errno = ENOENT;
99570af302Sopenharmony_ci			return 0;
100570af302Sopenharmony_ci		}
101570af302Sopenharmony_ci		if (k<0) {
102570af302Sopenharmony_ci			if (errno != EINVAL) return 0;
103570af302Sopenharmony_ciskip_readlink:
104570af302Sopenharmony_ci			check_dir = 0;
105570af302Sopenharmony_ci			if (up) {
106570af302Sopenharmony_ci				while(q && output[q-1]!='/') q--;
107570af302Sopenharmony_ci				if (q>1 && (q>2 || output[0]!='/')) q--;
108570af302Sopenharmony_ci				continue;
109570af302Sopenharmony_ci			}
110570af302Sopenharmony_ci			if (l0) q += l;
111570af302Sopenharmony_ci			check_dir = stack[p];
112570af302Sopenharmony_ci			continue;
113570af302Sopenharmony_ci		}
114570af302Sopenharmony_ci		if (++cnt == SYMLOOP_MAX) {
115570af302Sopenharmony_ci			errno = ELOOP;
116570af302Sopenharmony_ci			return 0;
117570af302Sopenharmony_ci		}
118570af302Sopenharmony_ci
119570af302Sopenharmony_ci		/* If link contents end in /, strip any slashes already on
120570af302Sopenharmony_ci		 * stack to avoid /->// or //->/// or spurious toolong. */
121570af302Sopenharmony_ci		if (stack[k-1]=='/') while (stack[p]=='/') p++;
122570af302Sopenharmony_ci		p -= k;
123570af302Sopenharmony_ci		memmove(stack+p, stack, k);
124570af302Sopenharmony_ci
125570af302Sopenharmony_ci		/* Skip the stack advancement in case we have a new
126570af302Sopenharmony_ci		 * absolute base path. */
127570af302Sopenharmony_ci		goto restart;
128570af302Sopenharmony_ci	}
129570af302Sopenharmony_ci
130570af302Sopenharmony_ci 	output[q] = 0;
131570af302Sopenharmony_ci
132570af302Sopenharmony_ci	if (output[0] != '/') {
133570af302Sopenharmony_ci		if (!getcwd(stack, sizeof stack)) return 0;
134570af302Sopenharmony_ci		l = strlen(stack);
135570af302Sopenharmony_ci		/* Cancel any initial .. components. */
136570af302Sopenharmony_ci		p = 0;
137570af302Sopenharmony_ci		while (nup--) {
138570af302Sopenharmony_ci			while(l>1 && stack[l-1]!='/') l--;
139570af302Sopenharmony_ci			if (l>1) l--;
140570af302Sopenharmony_ci			p += 2;
141570af302Sopenharmony_ci			if (p<q) p++;
142570af302Sopenharmony_ci		}
143570af302Sopenharmony_ci		if (q-p && stack[l-1]!='/') stack[l++] = '/';
144570af302Sopenharmony_ci		if (l + (q-p) + 1 >= PATH_MAX) goto toolong;
145570af302Sopenharmony_ci		memmove(output + l, output + p, q - p + 1);
146570af302Sopenharmony_ci		memcpy(output, stack, l);
147570af302Sopenharmony_ci		q = l + q-p;
148570af302Sopenharmony_ci	}
149570af302Sopenharmony_ci
150570af302Sopenharmony_ci	if (resolved) return memcpy(resolved, output, q+1);
151570af302Sopenharmony_ci	else return strdup(output);
152570af302Sopenharmony_ci
153570af302Sopenharmony_citoolong:
154570af302Sopenharmony_ci	errno = ENAMETOOLONG;
155570af302Sopenharmony_ci	return 0;
156570af302Sopenharmony_ci}
157