When I unveil(2), fts doesn't behave well. But only in a subtle way.
Enclosed is a demonstration. I found this with openrsync, which unveils
before using fts_open to scan for files.
When run with a directory with only empty subdirectories or just files,
this works fine. But when run with a directory that contains other
non-empty directories, the fts_read fails in the nested directories.
This is on stock OpenBSD 6.4, syspatched, amd64.
For example, consider the following abridged output (to fit into this
e-mail window):
% find ~/tmp/test -ls
drwxr-xr-x 3 /home/kristaps/tmp/test
drwxr-xr-x 3 /home/kristaps/tmp/test/test2
-rw-r--r-- 1 /home/kristaps/tmp/test/test2/test2
drwxr-xr-x 2 /home/kristaps/tmp/test/test2/test3
-rw-r--r-- 1 /home/kristaps/tmp/test/test1
% gcc -W -Wall -Wextra -g foo.c
% ./a.out /home/kristaps/tmp/test/
a.out: /home/kristaps/tmp/test/
a.out: /home/kristaps/tmp/test/test2
a.out: /home/kristaps/tmp/test/test2/test2
a.out: /home/kristaps/tmp/test/test2/test3
a.out: /home/kristaps/tmp/test/test2/test3
a.out: /home/kristaps/tmp/test/test2
a.out: /home/kristaps/tmp/test/test1
a.out: /home/kristaps/tmp/test/
a.out: TRYING AGAIN.
a.out: /home/kristaps/tmp/test/
a.out: /home/kristaps/tmp/test/test2
a.out: /home/kristaps/tmp/test/test2/test2: no stat
a.out: ...but regular stat works
So the first nested child fails (regardless of whether it's a file or
directory, by the way). But a regular stat still works.
The same happens if I use unveil("/", "r").
#include <sys/types.h>
#include <sys/stat.h>
#include <err.h>
#include <errno.h>
#include <fts.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
char *root[2];
struct stat st;
FTS *fts;
FTSENT *ent;
if (2 != argc)
return EXIT_FAILURE;
root[0] = argv[1];
root[1] = NULL;
if (-1 == lstat(root[0], &st))
err(EXIT_FAILURE, "%s", root[0]);
else if ( ! S_ISDIR(st.st_mode))
errx(EXIT_FAILURE, "%s: not a directory", root[0]);
fts = fts_open(root, FTS_PHYSICAL, NULL);
if (NULL == fts)
err(EXIT_FAILURE, "%s: fts_open", root[0]);
while (NULL != (ent = fts_read(fts))) {
if (FTS_NS == ent->fts_info) {
errno = ent->fts_errno;
err(EXIT_FAILURE, "%s: no stat", ent->fts_path);
}
warnx("%s", ent->fts_path);
errno = 0;
}
if (NULL != ent)
err(EXIT_FAILURE, "%s: fts_read", root[0]);
fts_close(fts);
warnx("TRYING AGAIN.");
if (-1 == unveil(root[0], "r"))
err(EXIT_FAILURE, "%s: unveil", root[0]);
fts = fts_open(root, FTS_PHYSICAL, NULL);
if (NULL == fts)
err(EXIT_FAILURE, "%s: fts_open", root[0]);
while (NULL != (ent = fts_read(fts))) {
if (FTS_NS == ent->fts_info) {
errno = ent->fts_errno;
warnx("%s: no stat", ent->fts_path);
if (-1 == lstat(ent->fts_path, &st))
errx(EXIT_FAILURE, "%s: really no stat", ent->fts_path);
errx(EXIT_FAILURE, "...but regular stat works: %s", ent->fts_path);
}
warnx("%s", ent->fts_path);
errno = 0;
}
if (NULL != ent)
err(EXIT_FAILURE, "%s: fts_read", root[0]);
fts_close(fts);
return EXIT_SUCCESS;
}