#include #include #include #include #include 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= 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; }