elf.h: add ELFCOMPRESS_ZSTD
[musl] / src / misc / realpath.c
index 5756817..db8b74d 100644 (file)
 #include <stdlib.h>
-#include <stdio.h>
 #include <limits.h>
-#include <sys/stat.h>
-#include <fcntl.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)
 {
-       int fd;
-       ssize_t r;
-       struct stat st1, st2;
-       char buf[15+3*sizeof(int)];
-       int alloc = 0;
+       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;
+               }
 
-       fd = open(filename, O_RDONLY|O_NONBLOCK);
-       if (fd < 0) return 0;
-       snprintf(buf, sizeof buf, "/proc/self/fd/%d", fd);
+               char *z = __strchrnul(stack+p, '/');
+               l0 = l = z-(stack+p);
 
-       if (!resolved) {
-               alloc = 1;
-               resolved = malloc(PATH_MAX);
-               if (!resolved) return 0;
+               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;
        }
 
-       r = readlink(buf, resolved, PATH_MAX-1);
-       if (r < 0) goto err;
-       resolved[r] = 0;
+       output[q] = 0;
 
-       fstat(fd, &st1);
-       r = stat(resolved, &st2);
-       if (r<0 || st1.st_dev != st2.st_dev || st1.st_ino != st2.st_ino) {
-               if (!r) errno = ELOOP;
-               goto err;
+       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;
        }
 
-       close(fd);
-       return resolved;
-err:
-       if (alloc) free(resolved);
-       close(fd);
+       if (resolved) return memcpy(resolved, output, q+1);
+       else return strdup(output);
+
+toolong:
+       errno = ENAMETOOLONG;
        return 0;
 }