changeset 708:50d759f8b371

Remove readlink -m for being poorly defined ("readlink -m /dev/null/and/more" answers what question, exactly?), rewrite xabspath() to work right and not depend on realpath, fix subtle longstanding bug in llist_traverse().
author Rob Landley <rob@landley.net>
date Thu, 22 Nov 2012 21:18:09 -0600
parents 977e19296b3f
children 6164dcc7384d
files lib/lib.c lib/lib.h lib/llist.c toys/other/readlink.c
diffstat 4 files changed, 130 insertions(+), 37 deletions(-) [+]
line wrap: on
line diff
--- a/lib/lib.c	Tue Nov 20 09:21:52 2012 -0600
+++ b/lib/lib.c	Thu Nov 22 21:18:09 2012 -0600
@@ -323,37 +323,134 @@
   if(stat(path, st)) perror_exit("Can't stat %s", path);
 }
 
-// Cannonicalize path, even to file with one or more missing components at end
-char *xabspath(char *path, unsigned missing) 
+// Split a path into linked list of components, tracking head and tail of list.
+// Filters out // entries with no contents.
+struct string_list **splitpath(char *path, struct string_list **list)
 {
-  char *apath, *temp, *slash;
-  int i=0;
+  char *new = path;
+
+  *list = 0;
+  do {
+    int len;
 
-  // If this isn't an absolute path, make it one with cwd.
-  if (path[0]!='/') {
-    char *temp=xgetcwd();
-    apath = xmsprintf("%s/%s", temp, path);
+    if (*path && *path != '/') continue;
+    len = path-new;
+    if (len > 0) {
+      *list = xmalloc(sizeof(struct string_list) + len + 1);
+      (*list)->next = 0;
+      strncpy((*list)->str, new, len);
+      (*list)->str[len] = 0;
+      list = &(*list)->next;
+    }
+    new = path+1;
+  } while (*path++);
+
+  return list;
+}
+
+// Cannonicalize path, even to file with one or more missing components at end.
+// if exact, require last path component to exist
+char *xabspath(char *path, int exact) 
+{
+  struct string_list *todo, *done = 0;
+  int try = 9999, dirfd = open("/", 0);;
+  char buf[4096], *ret;
+
+  // If this isn't an absolute path, start with cwd.
+  if (*path != '/') {
+    char *temp = xgetcwd();
+
+    splitpath(path, splitpath(temp, &todo));
     free(temp);
-  } else apath = path;
-  slash = apath+strlen(apath);
+  } else splitpath(path, &todo);
+
+  // Iterate through path components
+  while (todo) {
+    struct string_list *new = llist_pop(&todo), **tail;
+    ssize_t len;
+
+    if (!try--) {
+      errno = ELOOP;
+      goto error;
+    }
+
+    // Removable path componenents.
+    if (!strcmp(new->str, ".") || !strcmp(new->str, "..")) {
+      if (new->str[1] && done) free(llist_pop(&done));
+      free(new);
+      continue;
+    }
+
+    // Is this a symlink?
+    len=readlinkat(dirfd, new->str, buf, 4096);
+    if (len>4095) goto error;
+    if (len<1) {
+      int fd;
 
-  for (;;) {
-    temp = realpath(apath, NULL);
-    if (i) *slash = '/';
-    if (temp || ++i > missing) break;
-    while (slash>apath) if (*--slash == '/') break;
-    *slash=0;
-    free(temp);
+      // Not a symlink: add to linked list, move dirfd, fail if error
+      if ((exact || todo) && errno != EINVAL) goto error;
+      new->next = done;
+      done = new;
+      fd = openat(dirfd, new->str, O_DIRECTORY);
+      if (fd == -1 && (exact || todo)) goto error;
+      close(dirfd);
+      dirfd = fd;
+      continue;
+    }
+
+    // If this symlink is to an absolute path, discard existing resolved path
+    buf[len] = 0;
+    if (*buf == '/') {
+      llist_traverse(done, free);
+      done=0;
+      close(dirfd);
+      dirfd = open("/", 0);
+    }
+    free(new);
+
+    // prepend components of new path. Note symlink to "/" will leave new NULL
+    tail = splitpath(buf, &new);
+
+    // symlink to "/" will return null and leave tail alone
+    if (new) {
+      *tail = todo;
+      todo = new;
+    }
+  }
+  close(dirfd);
+
+  // At this point done has the path, in reverse order. Reverse list while
+  // calculating buffer length.
+
+  try = 2;
+  while (done) {
+    struct string_list *temp = llist_pop(&done);;
+
+    if (todo) try++;
+    try += strlen(temp->str);
+    temp->next = todo;
+    todo = temp;
   }
 
-  if (i && temp) {
-    slash = xmsprintf("%s%s", temp, slash);
-    free(temp);
-    temp = slash;
+  // Assemble return buffer
+
+  ret = xmalloc(try);
+  *ret = '/';
+  ret [try = 1] = 0;
+  while (todo) {
+    if (try>1) ret[try++] = '/';
+    try = stpcpy(ret+try, todo->str) - ret;
+    free(llist_pop(&todo));
   }
 
-  if (path != apath) free(apath); 
-  return temp;
+  return ret;
+
+error:
+  close(dirfd);
+  llist_traverse(todo, free);
+  llist_traverse(done, free);
+
+  return NULL;
 }
 
 // Resolve all symlinks, returning malloc() memory.
--- a/lib/lib.h	Tue Nov 20 09:21:52 2012 -0600
+++ b/lib/lib.h	Thu Nov 22 21:18:09 2012 -0600
@@ -116,7 +116,7 @@
 char *xreadfile(char *name);
 char *xgetcwd(void);
 void xstat(char *path, struct stat *st);
-char *xabspath(char *path, unsigned missing);
+char *xabspath(char *path, int exact);
 char *xrealpath(char *path);
 void xchdir(char *path);
 void xmkpath(char *path, int mode);
--- a/lib/llist.c	Tue Nov 20 09:21:52 2012 -0600
+++ b/lib/llist.c	Thu Nov 22 21:18:09 2012 -0600
@@ -8,12 +8,14 @@
 // Call a function (such as free()) on each element of a linked list.
 void llist_traverse(void *list, void (*using)(void *data))
 {
+  void *old = list;
+
   while (list) {
     void *pop = llist_pop(&list);
     using(pop);
 
     // End doubly linked list too.
-    if (list==pop) break;
+    if (old == list) break;
   }
 }
 
--- a/toys/other/readlink.c	Tue Nov 20 09:21:52 2012 -0600
+++ b/toys/other/readlink.c	Thu Nov 22 21:18:09 2012 -0600
@@ -2,7 +2,7 @@
  *
  * Copyright 2007 Rob Landley <rob@landley.net>
 
-USE_READLINK(NEWTOY(readlink, "<1>1femnq[-fem]", TOYFLAG_BIN))
+USE_READLINK(NEWTOY(readlink, "<1>1fenq[-fe]", TOYFLAG_BIN))
 
 config READLINK
   bool "readlink"
@@ -14,9 +14,8 @@
 
     Options for producing cannonical paths (all symlinks/./.. resolved):
 
-    -e	cannonical path to existing file (fail if does not exist)
-    -f	cannonical path to creatable file (fail if directory does not exist)
-    -m	cannonical path
+    -e	cannonical path to existing entry (fail if nothing there)
+    -f	full path (fail if location does not exist)
     -n	no trailing newline
     -q	quiet (no output, just error code)
 */
@@ -30,14 +29,9 @@
 
   // Calculating full cannonical path?
 
-  if (toys.optflags & (FLAG_f|FLAG_e|FLAG_m)) {
-    unsigned u = 0;
-
-    if (toys.optflags & FLAG_f) u++;
-    if (toys.optflags & FLAG_m) u=999999999;
-
-    s = xabspath(*toys.optargs, u);
-  } else s = xreadlink(*toys.optargs);
+  if (toys.optflags & (FLAG_f|FLAG_e))
+    s = xabspath(*toys.optargs, toys.optflags & FLAG_e);
+  else s = xreadlink(*toys.optargs);
 
   if (s) {
     if (!(toys.optflags & FLAG_q))