chdir()ing into a directory with +r-x fails, so we should manually use the
directory name as a prefix rather than chdir()ing into it.

Also adds new parameters to mkent for the prefix, and for dictating whether
or not a permission denied error when stat()ing shall be fatal or not. This
allows errors stat()ing children of `foo` to be reported and non-fatal, while
a permission denied error on `foo` itself would be fatal.

Also adds a valid flag to ent to hide stale/default stat so that unknown
permissions and user/groups can be displayed as such.
---
 ls.c | 89 +++++++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 61 insertions(+), 28 deletions(-)

diff --git a/ls.c b/ls.c
index b716aba..076f6b7 100644
--- a/ls.c
+++ b/ls.c
@@ -10,11 +10,13 @@
 #include <string.h>
 #include <time.h>
 #include <unistd.h>
+#include <errno.h>
 
 #include "utf.h"
 #include "util.h"
 
 struct entry {
+       char    valid;
        char   *name;
        mode_t  mode, tmode;
        nlink_t nlink;
@@ -58,15 +60,33 @@ static int showdirs;
 static void ls(const char *, const struct entry *, int);
 
 static void
-mkent(struct entry *ent, char *path, int dostat, int follow)
+mkent(struct entry *ent, char *prefix, char *name, int dostat, int follow,
+      int fatal)
 {
        struct stat st;
+       char path[PATH_MAX];
 
-       ent->name = path;
+       ent->valid = 0;
+       ent->name = name;
        if (!dostat)
                return;
-       if ((follow ? stat : lstat)(path, &st) < 0)
-               eprintf("%s %s:", follow ? "stat" : "lstat", path);
+
+       if (prefix != NULL)
+               snprintf(path, PATH_MAX, "%s/%s", prefix, name);
+       else
+               strncpy(path, name, PATH_MAX);
+
+       if ((follow ? stat : lstat)(path, &st) < 0) {
+               if (errno == EACCES && !fatal) {
+                       weprintf("%s %s:", follow ? "stat" : "lstat", path);
+                       return;
+               } else {
+                       eprintf("%s %s:", follow ? "stat" : "lstat", path);
+               }
+       } else {
+               ent->valid = 1;
+       }
+
        ent->mode  = st.st_mode;
        ent->nlink = st.st_nlink;
        ent->uid   = st.st_uid;
@@ -130,12 +150,13 @@ printname(const char *name)
 }
 
 static void
-output(const struct entry *ent)
+output(const char *prefix, const struct entry *ent)
 {
        struct group *gr;
        struct passwd *pw;
        struct tm *tm;
        ssize_t len;
+       char path[PATH_MAX];
        char *fmt, buf[BUFSIZ], pwname[_SC_LOGIN_NAME_MAX],
             grname[_SC_LOGIN_NAME_MAX], mode[] = "----------";
 
@@ -163,6 +184,14 @@ output(const struct entry *ent)
        else
                mode[0] = '?';
 
+       if (!ent->valid) {
+               printf("%c????????? ? ? ? ? ? ", mode[0]);
+               printname(ent->name);
+               fputs(indicator(ent->mode), stdout);
+               putchar('\n');
+               return;
+       }
+
        if (ent->mode & S_IRUSR) mode[1] = 'r';
        if (ent->mode & S_IWUSR) mode[2] = 'w';
        if (ent->mode & S_IXUSR) mode[3] = 'x';
@@ -208,7 +237,8 @@ output(const struct entry *ent)
        printname(ent->name);
        fputs(indicator(ent->mode), stdout);
        if (S_ISLNK(ent->mode)) {
-               if ((len = readlink(ent->name, buf, sizeof(buf) - 1)) < 0)
+               snprintf(path, PATH_MAX, "%s/%s", prefix, ent->name);
+               if ((len = readlink(path, buf, sizeof(buf) - 1)) < 0)
                        eprintf("readlink %s:", ent->name);
                buf[len] = '\0';
                printf(" -> %s%s", buf, indicator(ent->tmode));
@@ -247,13 +277,15 @@ lsdir(const char *path, const struct entry *dir)
        size_t i, n = 0;
        char prefix[PATH_MAX];
 
-       if (!(dp = opendir(dir->name))) {
+       if (snprintf(prefix, PATH_MAX, "%s%s", path, dir->name) >=
+                   PATH_MAX)
+                       eprintf("path too long: %s%s\n", path, dir->name);
+
+       if (!(dp = opendir(prefix))) {
                ret = 1;
-               weprintf("opendir %s%s:", path, dir->name);
+               weprintf("opendir %s:", prefix);
                return;
        }
-       if (chdir(dir->name) < 0)
-               eprintf("chdir %s:", dir->name);
 
        while ((d = readdir(dp))) {
                if (d->d_name[0] == '.' && !aflag && !Aflag)
@@ -264,8 +296,12 @@ lsdir(const char *path, const struct entry *dir)
                                continue;
 
                ents = ereallocarray(ents, ++n, sizeof(*ents));
-               mkent(&ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
-                   lflag || pflag || Rflag || sort, Lflag);
+               ent = &ents[n - 1];
+               mkent(ent, prefix, estrdup(d->d_name), Fflag || iflag ||
+                   lflag || pflag || Rflag || sort, Lflag, 0);
+               if (!ent->valid) {
+                       ent->mode = DTTOIF(d->d_type);
+               }
        }
 
        closedir(dp);
@@ -279,13 +315,9 @@ lsdir(const char *path, const struct entry *dir)
                puts(":");
        }
        for (i = 0; i < n; i++)
-               output(&ents[i]);
+               output(dir->name, &ents[i]);
 
        if (Rflag) {
-               if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >=
-                   PATH_MAX)
-                       eprintf("path too long: %s%s\n", path, dir->name);
-
                for (i = 0; i < n; i++) {
                        ent = &ents[i];
                        if (strcmp(ent->name, ".") == 0 ||
@@ -328,10 +360,10 @@ static void
 ls(const char *path, const struct entry *ent, int listdir)
 {
        int treeind;
-       char cwd[PATH_MAX];
+       char correct_path[PATH_MAX];
 
        if (!listdir) {
-               output(ent);
+               output(path, ent);
        } else if (S_ISDIR(ent->mode) ||
            (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
                if ((treeind = visit(ent)) < 0) {
@@ -340,19 +372,20 @@ ls(const char *path, const struct entry *ent, int listdir)
                        return;
                }
 
-               if (!getcwd(cwd, PATH_MAX))
-                       eprintf("getcwd:");
-
                if (first)
                        first = 0;
                else
                        putchar('\n');
 
-               lsdir(path, ent);
-               tree[treeind].ino = 0;
 
-               if (chdir(cwd) < 0)
-                       eprintf("chdir %s:", cwd);
+               if (strlen(path) == 0 || path[strlen(path) - 1] == '/') {
+                       lsdir(path, ent);
+               } else {
+                       snprintf(correct_path, sizeof(correct_path), "%s/", 
path);
+                       lsdir(correct_path, ent);
+               }
+
+               tree[treeind].ino = 0;
        }
 }
 
@@ -446,7 +479,7 @@ main(int argc, char *argv[])
        case 0: /* fallthrough */
                *--argv = ".", ++argc;
        case 1:
-               mkent(&ent, argv[0], 1, Hflag || Lflag);
+               mkent(&ent, NULL, argv[0], 1, Hflag || Lflag, 1);
                ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
                    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
                     !(dflag || Fflag || lflag)));
@@ -454,7 +487,7 @@ main(int argc, char *argv[])
                break;
        default:
                for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) {
-                       mkent(&ent, argv[i], 1, Hflag || Lflag);
+                       mkent(&ent, NULL, argv[i], 1, Hflag || Lflag, 1);
 
                        if ((!dflag && S_ISDIR(ent.mode)) ||
                            (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
-- 
2.17.1


Reply via email to