blob: db8b74dc8dbbcce1b77885bcc446d603db22f284 [file] [log] [blame]
#include <stdlib.h>
#include <limits.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
static size_t slash_len(const char *s)
{
const char *s0 = s;
while (*s == '/') s++;
return s-s0;
}
char *realpath(const char *restrict filename, char *restrict resolved)
{
char stack[PATH_MAX+1];
char output[PATH_MAX];
size_t p, q, l, l0, cnt=0, nup=0;
int check_dir=0;
if (!filename) {
errno = EINVAL;
return 0;
}
l = strnlen(filename, sizeof stack);
if (!l) {
errno = ENOENT;
return 0;
}
if (l >= PATH_MAX) goto toolong;
p = sizeof stack - l - 1;
q = 0;
memcpy(stack+p, filename, l+1);
/* Main loop. Each iteration pops the next part from stack of
* remaining path components and consumes any slashes that follow.
* If not a link, it's moved to output; if a link, contents are
* pushed to the stack. */
restart:
for (; ; p+=slash_len(stack+p)) {
/* If stack starts with /, the whole component is / or //
* and the output state must be reset. */
if (stack[p] == '/') {
check_dir=0;
nup=0;
q=0;
output[q++] = '/';
p++;
/* Initial // is special. */
if (stack[p] == '/' && stack[p+1] != '/')
output[q++] = '/';
continue;
}
char *z = __strchrnul(stack+p, '/');
l0 = l = z-(stack+p);
if (!l && !check_dir) break;
/* Skip any . component but preserve check_dir status. */
if (l==1 && stack[p]=='.') {
p += l;
continue;
}
/* Copy next component onto output at least temporarily, to
* call readlink, but wait to advance output position until
* determining it's not a link. */
if (q && output[q-1] != '/') {
if (!p) goto toolong;
stack[--p] = '/';
l++;
}
if (q+l >= PATH_MAX) goto toolong;
memcpy(output+q, stack+p, l);
output[q+l] = 0;
p += l;
int up = 0;
if (l0==2 && stack[p-2]=='.' && stack[p-1]=='.') {
up = 1;
/* Any non-.. path components we could cancel start
* after nup repetitions of the 3-byte string "../";
* if there are none, accumulate .. components to
* later apply to cwd, if needed. */
if (q <= 3*nup) {
nup++;
q += l;
continue;
}
/* When previous components are already known to be
* directories, processing .. can skip readlink. */
if (!check_dir) goto skip_readlink;
}
ssize_t k = readlink(output, stack, p);
if (k==p) goto toolong;
if (!k) {
errno = ENOENT;
return 0;
}
if (k<0) {
if (errno != EINVAL) return 0;
skip_readlink:
check_dir = 0;
if (up) {
while(q && output[q-1]!='/') q--;
if (q>1 && (q>2 || output[0]!='/')) q--;
continue;
}
if (l0) q += l;
check_dir = stack[p];
continue;
}
if (++cnt == SYMLOOP_MAX) {
errno = ELOOP;
return 0;
}
/* If link contents end in /, strip any slashes already on
* stack to avoid /->// or //->/// or spurious toolong. */
if (stack[k-1]=='/') while (stack[p]=='/') p++;
p -= k;
memmove(stack+p, stack, k);
/* Skip the stack advancement in case we have a new
* absolute base path. */
goto restart;
}
output[q] = 0;
if (output[0] != '/') {
if (!getcwd(stack, sizeof stack)) return 0;
l = strlen(stack);
/* Cancel any initial .. components. */
p = 0;
while (nup--) {
while(l>1 && stack[l-1]!='/') l--;
if (l>1) l--;
p += 2;
if (p<q) p++;
}
if (q-p && stack[l-1]!='/') stack[l++] = '/';
if (l + (q-p) + 1 >= PATH_MAX) goto toolong;
memmove(output + l, output + p, q - p + 1);
memcpy(output, stack, l);
q = l + q-p;
}
if (resolved) return memcpy(resolved, output, q+1);
else return strdup(output);
toolong:
errno = ENAMETOOLONG;
return 0;
}