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