File completion, where you want to present the output of readdir() with
file-type hints, is a great place to use fstatat() and avoid string
manipulation.
The first diff is for ftpd's NLIST support, which is used by ftp clients'
filename completion. Note this corrects(?) a quirk in NLIST's behavior:
currently if you ask for the NLIST of a path beginning with "./", ftpd
will silently suppress the "./" prefix on the returned paths:
ftp> nlist ftp
150 Opening ASCII mode data connection for 'file list'.
ftp/rfc
226 Transfer complete.
ftp> nlist ./ftp
150 Opening ASCII mode data connection for 'file list'.
ftp/rfc
226 Transfer complete.
ftp>
I believe that was a side-effect of how argumentless NLIST was supported:
with this diff it only suppresses the "./" prefix if you do NLIST or
"NLIST ." Or maybe send_file_list() should just take an argument that
suppresses the prefix, for use by bare NLIST?
The second is in mg's filename completion on filesystems which don't store
file-type info in the directory entry: ext2, cd9660, udf, and nfs.
Tested against a udf filesystem.
Index: libexec/ftpd/ftpd.c
===================================================================
RCS file: /data/src/openbsd/src/libexec/ftpd/ftpd.c,v
retrieving revision 1.216
diff -u -p -r1.216 ftpd.c
--- libexec/ftpd/ftpd.c 4 May 2016 19:48:08 -0000 1.216
+++ libexec/ftpd/ftpd.c 28 Jun 2016 06:59:10 -0000
@@ -2635,6 +2635,7 @@ send_file_list(char *whichf)
int simple = 0;
volatile int freeglob = 0;
glob_t gl;
+ size_t prefixlen;
if (strpbrk(whichf, "~{[*?") != NULL) {
memset(&gl, 0, sizeof(gl));
@@ -2694,9 +2695,11 @@ send_file_list(char *whichf)
if ((dirp = opendir(dirname)) == NULL)
continue;
+ if (dirname[0] == '.' && dirname[1] == '\0')
+ prefixlen = 0;
+ else
+ prefixlen = strlen(dirname) + 1;
while ((dir = readdir(dirp)) != NULL) {
- char nbuf[PATH_MAX];
-
if (recvurg) {
myoob();
recvurg = 0;
@@ -2710,14 +2713,12 @@ send_file_list(char *whichf)
dir->d_namlen == 2)
continue;
- snprintf(nbuf, sizeof(nbuf), "%s/%s", dirname,
- dir->d_name);
-
/*
* We have to do a stat to insure it's
* not a directory or special file.
*/
- if (simple || (stat(nbuf, &st) == 0 &&
+ if (simple ||
+ (fstatat(dirfd(dirp), dir->d_name, &st, 0) == 0 &&
S_ISREG(st.st_mode))) {
if (dout == NULL) {
dout = dataconn("file list", (off_t)-1,
@@ -2726,13 +2727,14 @@ send_file_list(char *whichf)
goto out;
transflag++;
}
- if (nbuf[0] == '.' && nbuf[1] == '/')
- fprintf(dout, "%s%s\n", &nbuf[2],
- type == TYPE_A ? "\r" : "");
- else
- fprintf(dout, "%s%s\n", nbuf,
- type == TYPE_A ? "\r" : "");
- byte_count += strlen(nbuf) + 1;
+
+ if (prefixlen) {
+ fprintf(dout, "%s/", dirname);
+ byte_count += prefixlen;
+ }
+ fprintf(dout, "%s%s\n", dir->d_name,
+ type == TYPE_A ? "\r" : "");
+ byte_count += dir->d_namlen + 1;
}
}
(void) closedir(dirp);
Index: usr.bin/mg/fileio.c
===================================================================
RCS file: /data/src/openbsd/src/usr.bin/mg/fileio.c,v
retrieving revision 1.100
diff -u -p -r1.100 fileio.c
--- usr.bin/mg/fileio.c 26 Jan 2016 18:02:51 -0000 1.100
+++ usr.bin/mg/fileio.c 28 Jun 2016 07:15:01 -0000
@@ -526,14 +526,8 @@ make_file_list(char *buf)
} else if (dent->d_type == DT_LNK ||
dent->d_type == DT_UNKNOWN) {
struct stat statbuf;
- char statname[NFILEN + 2];
- statbuf.st_mode = 0;
- ret = snprintf(statname, sizeof(statname), "%s/%s",
- dir, dent->d_name);
- if (ret < 0 || ret > sizeof(statname) - 1)
- continue;
- if (stat(statname, &statbuf) < 0)
+ if (fstatat(dirfd(dirp), dent->d_name, &statbuf, 0) < 0)
continue;
if (S_ISDIR(statbuf.st_mode))
isdir = 1;