Commit 39e29c4e authored by Chris Lamb's avatar Chris Lamb 👀
Browse files

Ensure readdir(2) returns consistent (and unique) inode numbers. (Closes: #898287)

parent 68887c37
Loading
Loading
Loading
Loading
+8 −5
Original line number Diff line number Diff line
@@ -68,7 +68,7 @@ namespace {
	}

	int wrap (int retval) { return retval == -1 ? -errno : 0; }
	using Dirents = std::vector<std::string>;
	using Dirents = std::vector<std::pair<std::string, ino_t>>;

	// The libc versions of seteuid, etc. set the credentials for all threads.
	// We need to set credentials for a single thread only, so call the syscalls directly.
@@ -270,7 +270,7 @@ int main (int argc, char** argv)

	// Add some of our own hard-coded FUSE options:
	fuse_opt_add_arg(&fargs, "-o");
	fuse_opt_add_arg(&fargs, "atomic_o_trunc,default_permissions"); // XXX: other mount options?
	fuse_opt_add_arg(&fargs, "atomic_o_trunc,default_permissions,use_ino"); // XXX: other mount options?
	if (config.multi_user) {
		fuse_opt_add_arg(&fargs, "-o");
		fuse_opt_add_arg(&fargs, "allow_other");
@@ -429,7 +429,7 @@ int main (int argc, char** argv)
		struct dirent*	dirent_p;
		int		res;
		while ((res = readdir_r(d, &dirent_storage, &dirent_p)) == 0 && dirent_p) {
			dirents->emplace_back(dirent_p->d_name);
			dirents->emplace_back(std::make_pair(dirent_p->d_name, dirent_p->d_ino));
		}
		if (config.sort_dirents) {
			std::sort(dirents->begin(), dirents->end());
@@ -447,14 +447,17 @@ int main (int argc, char** argv)
	};
	disorderfs_fuse_operations.readdir = [] (const char* path, void* buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info* info) {
		Dirents&		dirents = *get_fuse_data<Dirents*>(info);
		struct stat		st;
		memset(&st, 0, sizeof(st));
		if (config.shuffle_dirents) {
			std::random_device	rd;
			std::mt19937		g(rd());
			std::shuffle(dirents.begin(), dirents.end(), g);
		}

		for (const std::string& dirent : dirents) {
			if (filler(buf, dirent.c_str(), nullptr, 0) != 0) {
		for (const auto dirent : dirents) {
			st.st_ino = dirent.second;
			if (filler(buf, dirent.first.c_str(), &st, 0) != 0) {
				return -ENOMEM;
			}
		}

tests/inodes

0 → 100755
+87 −0
Original line number Diff line number Diff line
#!/bin/sh

. ./common

TEMPDIR="$(mktemp -d -t inodes.XXXXXXXXXX)"

trap "Unmount 2>/dev/null; rm -rf ${TEMPDIR}" EXIT

Setup () {
	cat >${TEMPDIR}/inodes.cpp <<EOF
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/syscall.h>

#include <map>
#include <iostream>

struct linux_dirent {
	unsigned long 	d_ino;
	off_t		d_off;
	unsigned short	d_reclen;
	char		d_name[];
};

#define BUF_SIZE 1024

void perror_and_die (const char* s)
{
	std::perror(s);
	std::abort();
}

int
main(int argc, char *argv[])
{
	int				fd, nread, ret = EXIT_SUCCESS;
	char				buf[BUF_SIZE];
	struct stat			st;
	const std::string		dirname = argc > 1 ? argv[1] : ".";
	struct linux_dirent		*d;
	std::map<ino_t, std::string>	inodes;

	if ((fd = open(dirname.c_str(), O_RDONLY | O_DIRECTORY)) == -1)
		perror_and_die("open");

	while ((nread = syscall(SYS_getdents, fd, buf, BUF_SIZE)) > 0) {
		for (int pos = 0; pos < nread; pos += d->d_reclen) {
			d = (struct linux_dirent *) (buf + pos);

			std::string filename = dirname + "/" + std::string(d->d_name);
			if (stat(filename.c_str(), &st) == -1)
				perror_and_die("stat");

			if (d->d_ino != st.st_ino) {
				std::cerr << filename << ": inode from getdents does not match stat: "
					<< d->d_ino << " != " << st.st_ino << std::endl;
				ret = EXIT_FAILURE;
			}

			if (inodes.find(d->d_ino) == inodes.end()) {
				inodes[d->d_ino] = filename;
			} else {
				std::cerr << filename << ": duplicate inode: " << d->d_ino
					<< " used by " << inodes[d->d_ino] << std::endl;
				ret = EXIT_FAILURE;
			}
		}
	}

	if (nread == -1)
		perror_and_die("open");

	exit(ret);
}
EOF
	g++ -Wall -o${TEMPDIR}/inodes ${TEMPDIR}/inodes.cpp || Fail "Could not compile testcase"
}

Setup
Mount
${TEMPDIR}/inodes target || Fail "inodes"
Unmount