On 2018-07-08, David Phillips <da...@sighup.nz> wrote:
> As a side note to this patch: it's been sitting in my queue for
> the better part of a year while I internally debated the
> untidiness of not being allowed to chdir. It's an unfortunate
> result of implementing this behaviour, but it's behaviour
> that other implementations (namely GNU, but not Solaris) support.
>
> Let me know any feedback or oversights on my part,
> David

Hi David,

Rather than keeping track of the full prefix for `mkent` and `output`,
what do you think about just tracking the directory fd, and using the
*at family of functions to work relative to that directory? I attached
a patch to show what I'm thinking. It looks like this might even
simplify some of the code.

Either way, I think the changes to avoid chdir and to show unknown
entries (stat failure) should be separate commits.
From 7bd0f40e2166788a91f0015cb7386e44f105fad8 Mon Sep 17 00:00:00 2001
From: Michael Forney <mfor...@mforney.org>
Date: Tue, 25 Sep 2018 18:23:47 -0700
Subject: [PATCH] ls: Use *at functions instead of changing working directory

---
 ls.c | 52 ++++++++++++++++++++++------------------------------
 1 file changed, 22 insertions(+), 30 deletions(-)

diff --git a/ls.c b/ls.c
index b716aba..7535c0b 100644
--- a/ls.c
+++ b/ls.c
@@ -3,6 +3,7 @@
 #include <sys/types.h>
 
 #include <dirent.h>
+#include <fcntl.h>
 #include <grp.h>
 #include <pwd.h>
 #include <stdio.h>
@@ -55,18 +56,18 @@ static int first = 1;
 static char sort = 0;
 static int showdirs;
 
-static void ls(const char *, const struct entry *, int);
+static void ls(int, const char *, const struct entry *, int);
 
 static void
-mkent(struct entry *ent, char *path, int dostat, int follow)
+mkent(int dirfd, struct entry *ent, char *path, int dostat, int follow)
 {
 	struct stat st;
 
 	ent->name = path;
 	if (!dostat)
 		return;
-	if ((follow ? stat : lstat)(path, &st) < 0)
-		eprintf("%s %s:", follow ? "stat" : "lstat", path);
+	if (fstatat(dirfd, path, &st, follow ? 0 : AT_SYMLINK_NOFOLLOW) < 0)
+		eprintf("stat %s:", path);
 	ent->mode  = st.st_mode;
 	ent->nlink = st.st_nlink;
 	ent->uid   = st.st_uid;
@@ -130,7 +131,7 @@ printname(const char *name)
 }
 
 static void
-output(const struct entry *ent)
+output(int dirfd, const struct entry *ent)
 {
 	struct group *gr;
 	struct passwd *pw;
@@ -208,7 +209,7 @@ 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)
+		if ((len = readlinkat(dirfd, ent->name, buf, sizeof(buf) - 1)) < 0)
 			eprintf("readlink %s:", ent->name);
 		buf[len] = '\0';
 		printf(" -> %s%s", buf, indicator(ent->tmode));
@@ -239,7 +240,7 @@ entcmp(const void *va, const void *vb)
 }
 
 static void
-lsdir(const char *path, const struct entry *dir)
+lsdir(int dirfd, const char *path, const struct entry *dir)
 {
 	DIR *dp;
 	struct entry *ent, *ents = NULL;
@@ -247,13 +248,12 @@ lsdir(const char *path, const struct entry *dir)
 	size_t i, n = 0;
 	char prefix[PATH_MAX];
 
-	if (!(dp = opendir(dir->name))) {
+	dirfd = openat(dirfd, dir->name, O_RDONLY|O_DIRECTORY);
+	if (dirfd < 0 || !(dp = fdopendir(dirfd))) {
 		ret = 1;
 		weprintf("opendir %s%s:", path, dir->name);
 		return;
 	}
-	if (chdir(dir->name) < 0)
-		eprintf("chdir %s:", dir->name);
 
 	while ((d = readdir(dp))) {
 		if (d->d_name[0] == '.' && !aflag && !Aflag)
@@ -264,12 +264,10 @@ 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 ||
+		mkent(dirfd, &ents[n - 1], estrdup(d->d_name), Fflag || iflag ||
 		    lflag || pflag || Rflag || sort, Lflag);
 	}
 
-	closedir(dp);
-
 	if (!Uflag)
 		qsort(ents, n, sizeof(*ents), entcmp);
 
@@ -279,7 +277,7 @@ lsdir(const char *path, const struct entry *dir)
 		puts(":");
 	}
 	for (i = 0; i < n; i++)
-		output(&ents[i]);
+		output(dirfd, &ents[i]);
 
 	if (Rflag) {
 		if (snprintf(prefix, PATH_MAX, "%s%s/", path, dir->name) >=
@@ -294,13 +292,14 @@ lsdir(const char *path, const struct entry *dir)
 			if (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode) && !Lflag)
 				continue;
 
-			ls(prefix, ent, 1);
+			ls(dirfd, prefix, ent, 1);
 		}
 	}
 
 	for (i = 0; i < n; ++i)
 		free(ents[i].name);
 	free(ents);
+	closedir(dp);
 }
 
 static int
@@ -325,13 +324,12 @@ visit(const struct entry *ent)
 }
 
 static void
-ls(const char *path, const struct entry *ent, int listdir)
+ls(int dirfd, const char *path, const struct entry *ent, int listdir)
 {
 	int treeind;
-	char cwd[PATH_MAX];
 
 	if (!listdir) {
-		output(ent);
+		output(dirfd, ent);
 	} else if (S_ISDIR(ent->mode) ||
 	    (S_ISLNK(ent->mode) && S_ISDIR(ent->tmode))) {
 		if ((treeind = visit(ent)) < 0) {
@@ -340,19 +338,13 @@ 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);
+		lsdir(dirfd, path, ent);
 		tree[treeind].ino = 0;
-
-		if (chdir(cwd) < 0)
-			eprintf("chdir %s:", cwd);
 	}
 }
 
@@ -446,15 +438,15 @@ main(int argc, char *argv[])
 	case 0: /* fallthrough */
 		*--argv = ".", ++argc;
 	case 1:
-		mkent(&ent, argv[0], 1, Hflag || Lflag);
-		ls("", &ent, (!dflag && S_ISDIR(ent.mode)) ||
+		mkent(AT_FDCWD, &ent, argv[0], 1, Hflag || Lflag);
+		ls(AT_FDCWD, "", &ent, (!dflag && S_ISDIR(ent.mode)) ||
 		    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
 		     !(dflag || Fflag || lflag)));
 
 		break;
 	default:
 		for (i = ds = fs = 0, fents = dents = NULL; i < argc; ++i) {
-			mkent(&ent, argv[i], 1, Hflag || Lflag);
+			mkent(AT_FDCWD, &ent, argv[i], 1, Hflag || Lflag);
 
 			if ((!dflag && S_ISDIR(ent.mode)) ||
 			    (S_ISLNK(ent.mode) && S_ISDIR(ent.tmode) &&
@@ -473,12 +465,12 @@ main(int argc, char *argv[])
 		qsort(dents, ds, sizeof(ent), entcmp);
 
 		for (i = 0; i < fs; ++i)
-			ls("", &fents[i], 0);
+			ls(AT_FDCWD, "", &fents[i], 0);
 		free(fents);
 		if (fs && ds)
 			putchar('\n');
 		for (i = 0; i < ds; ++i)
-			ls("", &dents[i], 1);
+			ls(AT_FDCWD, "", &dents[i], 1);
 		free(dents);
 	}
 
-- 
2.17.1

Reply via email to