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;

}

Reply via email to