offline_realpath.c (3354B)
- // Based on the realpath(3) function from musl
- // Copyright © 2005-2020 Rich Felker, et al.
- // SPDX-License-Identifier: MIT
- #define _POSIX_C_SOURCE 200809L
- #include "fs.h"
- #include <errno.h>
- #include <limits.h>
- #include <stdlib.h>
- #include <string.h>
- #include <unistd.h>
- static size_t
- slash_len(const char *s)
- {
- const char *s0 = s;
- while(*s == '/')
- s++;
- return s - s0;
- }
- static char *
- __strchrnul(const char *s, int c)
- {
- c = (unsigned char)c;
- if(!c) return (char *)s + strlen(s);
- for(; *s && *(unsigned char *)s != c; s++)
- ;
- return (char *)s;
- }
- // realpath(3) but without checking for symlinks
- char *
- offline_realpath(const char *restrict filename, char *restrict resolved)
- {
- char stack[PATH_MAX + 1];
- char output[PATH_MAX];
- size_t p, q, l, l0, 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. */
- 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;
- }
- }
- 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];
- }
- 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;
- }