mirror of
https://github.com/libfuse/libfuse.git
synced 2026-07-07 14:47:41 +08:00
3897ca7d33
Found by githubs codeql, basepath might be too large for the destination buffer - strict the size. Signed-off-by: Bernd Schubert <bernd@bsbernd.com>
2252 lines
43 KiB
C
2252 lines
43 KiB
C
#define _GNU_SOURCE
|
|
#include "fuse_config.h"
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <utime.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <time.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/un.h>
|
|
|
|
#ifndef ALLPERMS
|
|
# define ALLPERMS (S_ISUID|S_ISGID|S_ISVTX|S_IRWXU|S_IRWXG|S_IRWXO)/* 07777 */
|
|
#endif
|
|
|
|
|
|
static const char *basepath;
|
|
static const char *basepath_r;
|
|
static char testfile[1024];
|
|
static char testfile2[1024];
|
|
static char testdir[1024];
|
|
static char testdir2[1024];
|
|
static char testsock[1024];
|
|
static char subfile[1280];
|
|
|
|
static char testfile_r[1024];
|
|
static char testfile2_r[1024];
|
|
static char testdir_r[1024];
|
|
static char testdir2_r[1024];
|
|
static char subfile_r[1280];
|
|
|
|
static char testname[256];
|
|
static char testdata[] = "abcdefghijklmnopqrstuvwxyz";
|
|
static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./";
|
|
static const char *testdir_files[] = { "f1", "f2", NULL};
|
|
static long seekdir_offsets[4];
|
|
static char zerodata[4096];
|
|
static int testdatalen = sizeof(testdata) - 1;
|
|
static int testdata2len = sizeof(testdata2) - 1;
|
|
static unsigned int testnum = 0;
|
|
static unsigned int select_test = 0;
|
|
static unsigned int skip_test = 0;
|
|
static unsigned int unlinked_test = 0;
|
|
|
|
#define MAX_ENTRIES 1024
|
|
#define MAX_TESTS 100
|
|
|
|
static struct test {
|
|
int fd;
|
|
struct stat stat;
|
|
} tests[MAX_TESTS];
|
|
|
|
static void test_perror(const char *func, const char *msg)
|
|
{
|
|
fprintf(stderr, "%s %s() - %s: %s\n", testname, func, msg,
|
|
strerror(errno));
|
|
}
|
|
|
|
static void test_error(const char *func, const char *msg, ...)
|
|
__attribute__ ((format (printf, 2, 3)));
|
|
|
|
static void __start_test(const char *fmt, ...)
|
|
__attribute__ ((format (printf, 1, 2)));
|
|
|
|
static void test_error(const char *func, const char *msg, ...)
|
|
{
|
|
va_list ap;
|
|
fprintf(stderr, "%s %s() - ", testname, func);
|
|
va_start(ap, msg);
|
|
vfprintf(stderr, msg, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
static int is_dot_or_dotdot(const char *name) {
|
|
return name[0] == '.' &&
|
|
(name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
|
|
}
|
|
|
|
static void success(void)
|
|
{
|
|
fprintf(stderr, "%s OK\n", testname);
|
|
}
|
|
|
|
#define this_test (&tests[testnum-1])
|
|
#define next_test (&tests[testnum])
|
|
|
|
static void __start_test(const char *fmt, ...)
|
|
{
|
|
unsigned int n;
|
|
va_list ap;
|
|
n = snprintf(testname, sizeof(testname), "%3u [", testnum);
|
|
va_start(ap, fmt);
|
|
if (n < sizeof(testname))
|
|
n += vsnprintf(testname + n, sizeof(testname) - n, fmt, ap);
|
|
va_end(ap);
|
|
if (n < sizeof(testname))
|
|
snprintf(testname + n, sizeof(testname) - n, "]");
|
|
// Use dedicated testfile per test
|
|
snprintf(testfile, sizeof(testfile), "%s/testfile.%u", basepath, testnum);
|
|
snprintf(testfile_r, sizeof(testfile_r), "%s/testfile.%u", basepath_r, testnum);
|
|
if (testnum > MAX_TESTS) {
|
|
fprintf(stderr, "%s - too many tests\n", testname);
|
|
exit(1);
|
|
}
|
|
this_test->fd = -1;
|
|
}
|
|
|
|
#define start_test(msg, args...) { \
|
|
testnum++; \
|
|
if ((select_test && testnum != select_test) || \
|
|
(testnum == skip_test)) { \
|
|
return 0; \
|
|
} \
|
|
__start_test(msg, ##args); \
|
|
}
|
|
|
|
#define PERROR(msg) test_perror(__FUNCTION__, msg)
|
|
#define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args)
|
|
|
|
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
|
|
|
|
static int st_check_size(struct stat *st, int len)
|
|
{
|
|
if (st->st_size != len) {
|
|
ERROR("length %u instead of %u", (int) st->st_size,
|
|
(int) len);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_size(const char *path, int len)
|
|
{
|
|
struct stat stbuf;
|
|
int res = stat(path, &stbuf);
|
|
if (res == -1) {
|
|
PERROR("stat");
|
|
return -1;
|
|
}
|
|
return st_check_size(&stbuf, len);
|
|
}
|
|
|
|
static int check_testfile_size(const char *path, int len)
|
|
{
|
|
this_test->stat.st_size = len;
|
|
return check_size(path, len);
|
|
}
|
|
|
|
static int st_check_type(const struct stat *st, mode_t type)
|
|
{
|
|
if ((st->st_mode & S_IFMT) != type) {
|
|
ERROR("type 0%o instead of 0%o", st->st_mode & S_IFMT, type);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_type(const char *path, mode_t type)
|
|
{
|
|
struct stat stbuf;
|
|
int res = lstat(path, &stbuf);
|
|
if (res == -1) {
|
|
PERROR("lstat");
|
|
return -1;
|
|
}
|
|
return st_check_type(&stbuf, type);
|
|
}
|
|
|
|
static int st_check_mode(const struct stat *st, mode_t mode)
|
|
{
|
|
if ((st->st_mode & ALLPERMS) != mode) {
|
|
ERROR("mode 0%o instead of 0%o", st->st_mode & ALLPERMS,
|
|
mode);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_mode(const char *path, mode_t mode)
|
|
{
|
|
struct stat stbuf;
|
|
int res = lstat(path, &stbuf);
|
|
if (res == -1) {
|
|
PERROR("lstat");
|
|
return -1;
|
|
}
|
|
return st_check_mode(&stbuf, mode);
|
|
}
|
|
|
|
static int check_testfile_mode(const char *path, mode_t mode)
|
|
{
|
|
this_test->stat.st_mode &= ~ALLPERMS;
|
|
this_test->stat.st_mode |= mode;
|
|
return check_mode(path, mode);
|
|
}
|
|
|
|
static int check_times(const char *path, time_t atime, time_t mtime)
|
|
{
|
|
int err = 0;
|
|
struct stat stbuf;
|
|
int res = lstat(path, &stbuf);
|
|
if (res == -1) {
|
|
PERROR("lstat");
|
|
return -1;
|
|
}
|
|
if (stbuf.st_atime != atime) {
|
|
ERROR("atime %li instead of %li", stbuf.st_atime, atime);
|
|
err--;
|
|
}
|
|
if (stbuf.st_mtime != mtime) {
|
|
ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
|
|
err--;
|
|
}
|
|
if (err)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if 0
|
|
static int fcheck_times(int fd, time_t atime, time_t mtime)
|
|
{
|
|
int err = 0;
|
|
struct stat stbuf;
|
|
int res = fstat(fd, &stbuf);
|
|
if (res == -1) {
|
|
PERROR("fstat");
|
|
return -1;
|
|
}
|
|
if (stbuf.st_atime != atime) {
|
|
ERROR("atime %li instead of %li", stbuf.st_atime, atime);
|
|
err--;
|
|
}
|
|
if (stbuf.st_mtime != mtime) {
|
|
ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime);
|
|
err--;
|
|
}
|
|
if (err)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int st_check_nlink(struct stat *st, nlink_t nlink)
|
|
{
|
|
if (st->st_nlink != nlink) {
|
|
ERROR("nlink %li instead of %li", (long) st->st_nlink,
|
|
(long) nlink);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_nlink(const char *path, nlink_t nlink)
|
|
{
|
|
struct stat stbuf;
|
|
int res = lstat(path, &stbuf);
|
|
if (res == -1) {
|
|
PERROR("lstat");
|
|
return -1;
|
|
}
|
|
return st_check_nlink(&stbuf, nlink);
|
|
}
|
|
|
|
static int fcheck_stat(int fd, int flags, struct stat *st)
|
|
{
|
|
struct stat stbuf;
|
|
int res = fstat(fd, &stbuf);
|
|
if (res == -1) {
|
|
if (flags & O_PATH) {
|
|
// With O_PATH fd, the server does not have to keep
|
|
// the inode alive so FUSE inode may be stale or bad
|
|
if (errno == ESTALE || errno == EIO ||
|
|
errno == ENOENT || errno == EBADF)
|
|
return 0;
|
|
}
|
|
PERROR("fstat");
|
|
return -1;
|
|
}
|
|
|
|
int err = 0;
|
|
err += st_check_type(&stbuf, st->st_mode & S_IFMT);
|
|
err += st_check_mode(&stbuf, st->st_mode & ALLPERMS);
|
|
err += st_check_size(&stbuf, st->st_size);
|
|
err += st_check_nlink(&stbuf, st->st_nlink);
|
|
|
|
return err;
|
|
}
|
|
|
|
static int check_nonexist(const char *path)
|
|
{
|
|
struct stat stbuf;
|
|
int res = lstat(path, &stbuf);
|
|
if (res == 0) {
|
|
ERROR("file should not exist");
|
|
return -1;
|
|
}
|
|
if (errno != ENOENT) {
|
|
ERROR("file should not exist: %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_buffer(const char *buf, const char *data, unsigned len)
|
|
{
|
|
if (memcmp(buf, data, len) != 0) {
|
|
ERROR("data mismatch");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_data(const char *path, const char *data, int offset,
|
|
unsigned len)
|
|
{
|
|
char buf[4096];
|
|
int res;
|
|
int fd = open(path, O_RDONLY);
|
|
if (fd == -1) {
|
|
PERROR("open");
|
|
return -1;
|
|
}
|
|
if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
|
|
PERROR("lseek");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
while (len) {
|
|
int rdlen = len < sizeof(buf) ? len : sizeof(buf);
|
|
res = read(fd, buf, rdlen);
|
|
if (res == -1) {
|
|
PERROR("read");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if (res != rdlen) {
|
|
ERROR("short read: %u instead of %u", res, rdlen);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if (check_buffer(buf, data, rdlen) != 0) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
data += rdlen;
|
|
len -= rdlen;
|
|
}
|
|
res = close(fd);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int fcheck_data(int fd, const char *data, int offset,
|
|
unsigned len)
|
|
{
|
|
char buf[4096];
|
|
if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {
|
|
PERROR("lseek");
|
|
return -1;
|
|
}
|
|
while (len) {
|
|
int rdlen = len < sizeof(buf) ? len : sizeof(buf);
|
|
int res = read(fd, buf, rdlen);
|
|
if (res == -1) {
|
|
PERROR("read");
|
|
return -1;
|
|
}
|
|
if (res != rdlen) {
|
|
ERROR("short read: %u instead of %u", res, rdlen);
|
|
return -1;
|
|
}
|
|
if (check_buffer(buf, data, rdlen) != 0) {
|
|
return -1;
|
|
}
|
|
data += rdlen;
|
|
len -= rdlen;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int check_dir_contents(const char *path, const char **contents)
|
|
{
|
|
int i;
|
|
int res;
|
|
int err = 0;
|
|
int found[MAX_ENTRIES];
|
|
const char *cont[MAX_ENTRIES];
|
|
DIR *dp;
|
|
|
|
for (i = 0; contents[i]; i++) {
|
|
assert(i < MAX_ENTRIES - 3);
|
|
found[i] = 0;
|
|
cont[i] = contents[i];
|
|
}
|
|
cont[i] = NULL;
|
|
|
|
dp = opendir(path);
|
|
if (dp == NULL) {
|
|
PERROR("opendir");
|
|
return -1;
|
|
}
|
|
memset(found, 0, sizeof(found));
|
|
while(1) {
|
|
struct dirent *de;
|
|
errno = 0;
|
|
de = readdir(dp);
|
|
if (de == NULL) {
|
|
if (errno) {
|
|
PERROR("readdir");
|
|
closedir(dp);
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
if (is_dot_or_dotdot(de->d_name))
|
|
continue;
|
|
for (i = 0; cont[i] != NULL; i++) {
|
|
assert(i < MAX_ENTRIES);
|
|
if (strcmp(cont[i], de->d_name) == 0) {
|
|
if (found[i]) {
|
|
ERROR("duplicate entry <%s>",
|
|
de->d_name);
|
|
err--;
|
|
} else
|
|
found[i] = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!cont[i]) {
|
|
ERROR("unexpected entry <%s>", de->d_name);
|
|
err --;
|
|
}
|
|
}
|
|
for (i = 0; cont[i] != NULL; i++) {
|
|
if (!found[i]) {
|
|
ERROR("missing entry <%s>", cont[i]);
|
|
err--;
|
|
}
|
|
}
|
|
res = closedir(dp);
|
|
if (res == -1) {
|
|
PERROR("closedir");
|
|
return -1;
|
|
}
|
|
if (err)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int create_file(const char *path, const char *data, int len)
|
|
{
|
|
int res;
|
|
int fd;
|
|
|
|
unlink(path);
|
|
fd = creat(path, 0644);
|
|
if (fd == -1) {
|
|
PERROR("creat");
|
|
return -1;
|
|
}
|
|
if (len) {
|
|
res = write(fd, data, len);
|
|
if (res == -1) {
|
|
PERROR("write");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if (res != len) {
|
|
ERROR("write is short: %u instead of %u", res, len);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
}
|
|
res = close(fd);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
return -1;
|
|
}
|
|
res = check_type(path, S_IFREG);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_mode(path, 0644);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_nlink(path, 1);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_size(path, len);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
if (len) {
|
|
res = check_data(path, data, 0, len);
|
|
if (res == -1)
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int create_path_fd(const char *path, const char *data, int len)
|
|
{
|
|
int path_fd;
|
|
int res;
|
|
|
|
res = create_file(path, data, len);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
path_fd = open(path, O_PATH);
|
|
if (path_fd == -1)
|
|
PERROR("open(O_PATH)");
|
|
|
|
return path_fd;
|
|
}
|
|
|
|
// Can be called once per test
|
|
static int create_testfile(const char *path, const char *data, int len)
|
|
{
|
|
struct test *t = this_test;
|
|
struct stat *st = &t->stat;
|
|
int res, fd;
|
|
|
|
if (t->fd > 0) {
|
|
ERROR("testfile already created");
|
|
return -1;
|
|
}
|
|
|
|
fd = create_path_fd(path, data, len);
|
|
if (fd == -1)
|
|
return -1;
|
|
|
|
t->fd = fd;
|
|
|
|
res = fstat(fd, st);
|
|
if (res == -1) {
|
|
PERROR("fstat");
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_unlinked_testfile(int fd)
|
|
{
|
|
struct stat *st = &this_test->stat;
|
|
|
|
st->st_nlink = 0;
|
|
return fcheck_stat(fd, O_PATH, st);
|
|
}
|
|
|
|
// Check recorded testfiles after all tests completed
|
|
static int check_unlinked_testfiles(void)
|
|
{
|
|
int res, err = 0;
|
|
int num = testnum;
|
|
|
|
if (!unlinked_test)
|
|
return 0;
|
|
|
|
testnum = 0;
|
|
while (testnum < num) {
|
|
int fd = next_test->fd;
|
|
start_test("check_unlinked_testfile");
|
|
if (fd == -1)
|
|
continue;
|
|
|
|
err += check_unlinked_testfile(fd);
|
|
res = close(fd);
|
|
if (res == -1) {
|
|
PERROR("close(test_fd)");
|
|
err--;
|
|
}
|
|
}
|
|
|
|
if (err) {
|
|
fprintf(stderr, "%i unlinked testfile checks failed\n", -err);
|
|
return 1;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static int cleanup_dir(const char *path, const char **dir_files, int quiet)
|
|
{
|
|
int i;
|
|
int err = 0;
|
|
|
|
for (i = 0; dir_files[i]; i++) {
|
|
int res;
|
|
char fpath[1280];
|
|
snprintf(fpath, sizeof(fpath), "%s/%s", path, dir_files[i]);
|
|
res = unlink(fpath);
|
|
if (res == -1 && !quiet) {
|
|
PERROR("unlink");
|
|
err --;
|
|
}
|
|
}
|
|
if (err)
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int create_dir(const char *path, const char **dir_files)
|
|
{
|
|
int res;
|
|
int i;
|
|
|
|
rmdir(path);
|
|
res = mkdir(path, 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
return -1;
|
|
}
|
|
res = check_type(path, S_IFDIR);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_mode(path, 0755);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
for (i = 0; dir_files[i]; i++) {
|
|
char fpath[1280];
|
|
snprintf(fpath, sizeof(fpath), "%s/%s", path, dir_files[i]);
|
|
res = create_file(fpath, "", 0);
|
|
if (res == -1) {
|
|
cleanup_dir(path, dir_files, 1);
|
|
return -1;
|
|
}
|
|
}
|
|
res = check_dir_contents(path, dir_files);
|
|
if (res == -1) {
|
|
cleanup_dir(path, dir_files, 1);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int test_truncate(int len)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int res;
|
|
|
|
start_test("truncate(%u)", (int) len);
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
res = truncate(testfile, len);
|
|
if (res == -1) {
|
|
PERROR("truncate");
|
|
return -1;
|
|
}
|
|
res = check_testfile_size(testfile, len);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
if (len > 0) {
|
|
if (len <= datalen) {
|
|
res = check_data(testfile, data, 0, len);
|
|
if (res == -1)
|
|
return -1;
|
|
} else {
|
|
res = check_data(testfile, data, 0, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_data(testfile, zerodata, datalen,
|
|
len - datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
}
|
|
}
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_ftruncate(int len, int mode)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int res;
|
|
int fd;
|
|
|
|
start_test("ftruncate(%u) mode: 0%03o", len, mode);
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
fd = open(testfile, O_WRONLY);
|
|
if (fd == -1) {
|
|
PERROR("open");
|
|
return -1;
|
|
}
|
|
|
|
res = fchmod(fd, mode);
|
|
if (res == -1) {
|
|
PERROR("fchmod");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
res = check_testfile_mode(testfile, mode);
|
|
if (res == -1) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
res = ftruncate(fd, len);
|
|
if (res == -1) {
|
|
PERROR("ftruncate");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
res = check_testfile_size(testfile, len);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
if (len > 0) {
|
|
if (len <= datalen) {
|
|
res = check_data(testfile, data, 0, len);
|
|
if (res == -1)
|
|
return -1;
|
|
} else {
|
|
res = check_data(testfile, data, 0, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_data(testfile, zerodata, datalen,
|
|
len - datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
}
|
|
}
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_seekdir(void)
|
|
{
|
|
int i;
|
|
int res;
|
|
DIR *dp;
|
|
const struct dirent *de = NULL;
|
|
|
|
start_test("seekdir");
|
|
res = create_dir(testdir, testdir_files);
|
|
if (res == -1)
|
|
return res;
|
|
|
|
dp = opendir(testdir);
|
|
if (dp == NULL) {
|
|
PERROR("opendir");
|
|
return -1;
|
|
}
|
|
|
|
/* Remember dir offsets */
|
|
for (i = 0; i < ARRAY_SIZE(seekdir_offsets); i++) {
|
|
seekdir_offsets[i] = telldir(dp);
|
|
errno = 0;
|
|
de = readdir(dp);
|
|
if (de == NULL) {
|
|
if (errno) {
|
|
PERROR("readdir");
|
|
goto fail;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Walk until the end of directory */
|
|
while (de)
|
|
de = readdir(dp);
|
|
|
|
/* Start from the last valid dir offset and seek backwards */
|
|
for (i--; i >= 0; i--) {
|
|
seekdir(dp, seekdir_offsets[i]);
|
|
de = readdir(dp);
|
|
if (de == NULL) {
|
|
ERROR("Unexpected end of directory after seekdir()");
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
closedir(dp);
|
|
res = cleanup_dir(testdir, testdir_files, 0);
|
|
if (!res)
|
|
success();
|
|
return res;
|
|
fail:
|
|
closedir(dp);
|
|
cleanup_dir(testdir, testdir_files, 1);
|
|
return -1;
|
|
}
|
|
|
|
#ifdef HAVE_COPY_FILE_RANGE
|
|
static int test_copy_file_range(void)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int err = 0;
|
|
int res;
|
|
int fd_in, fd_out;
|
|
off_t pos_in = 0, pos_out = 0;
|
|
|
|
start_test("copy_file_range");
|
|
unlink(testfile);
|
|
fd_in = open(testfile, O_CREAT | O_RDWR, 0644);
|
|
if (fd_in == -1) {
|
|
PERROR("creat");
|
|
return -1;
|
|
}
|
|
res = write(fd_in, data, datalen);
|
|
if (res == -1) {
|
|
PERROR("write");
|
|
close(fd_in);
|
|
return -1;
|
|
}
|
|
if (res != datalen) {
|
|
ERROR("write is short: %u instead of %u", res, datalen);
|
|
close(fd_in);
|
|
return -1;
|
|
}
|
|
|
|
unlink(testfile2);
|
|
fd_out = creat(testfile2, 0644);
|
|
if (fd_out == -1) {
|
|
PERROR("creat");
|
|
close(fd_in);
|
|
return -1;
|
|
}
|
|
res = copy_file_range(fd_in, &pos_in, fd_out, &pos_out, datalen, 0);
|
|
if (res == -1) {
|
|
PERROR("copy_file_range");
|
|
close(fd_in);
|
|
close(fd_out);
|
|
return -1;
|
|
}
|
|
if (res != datalen) {
|
|
ERROR("copy is short: %u instead of %u", res, datalen);
|
|
close(fd_in);
|
|
close(fd_out);
|
|
return -1;
|
|
}
|
|
|
|
res = close(fd_in);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
close(fd_out);
|
|
return -1;
|
|
}
|
|
res = close(fd_out);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
return -1;
|
|
}
|
|
|
|
err = check_data(testfile2, data, 0, datalen);
|
|
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
res = unlink(testfile2);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile2);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
#else
|
|
static int test_copy_file_range(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#ifdef HAVE_STATX
|
|
static int test_statx(void)
|
|
{
|
|
struct statx sb;
|
|
char msg[] = "hi";
|
|
size_t msg_size = sizeof(msg);
|
|
struct timespec tp;
|
|
int res;
|
|
|
|
memset(&sb, 0, sizeof(sb));
|
|
unlink(testfile);
|
|
|
|
start_test("statx");
|
|
|
|
res = create_testfile(testfile, msg, msg_size);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
res = statx(-1, testfile, AT_EMPTY_PATH,
|
|
STATX_BASIC_STATS | STATX_BTIME, &sb);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
if (sb.stx_size != msg_size)
|
|
return -1;
|
|
|
|
clock_gettime(CLOCK_REALTIME, &tp);
|
|
|
|
if (sb.stx_btime.tv_sec > tp.tv_sec)
|
|
return -1;
|
|
|
|
if (sb.stx_btime.tv_sec == tp.tv_sec &&
|
|
sb.stx_btime.tv_nsec >= tp.tv_nsec)
|
|
return -1;
|
|
|
|
unlink(testfile);
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
#else
|
|
static int test_statx(void)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int test_utime(void)
|
|
{
|
|
struct utimbuf utm;
|
|
time_t atime = 987631200;
|
|
time_t mtime = 123116400;
|
|
int res;
|
|
|
|
start_test("utime");
|
|
res = create_testfile(testfile, NULL, 0);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
utm.actime = atime;
|
|
utm.modtime = mtime;
|
|
res = utime(testfile, &utm);
|
|
if (res == -1) {
|
|
PERROR("utime");
|
|
return -1;
|
|
}
|
|
res = check_times(testfile, atime, mtime);
|
|
if (res == -1) {
|
|
return -1;
|
|
}
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_create(void)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int err = 0;
|
|
int res;
|
|
int fd;
|
|
|
|
start_test("create");
|
|
unlink(testfile);
|
|
fd = creat(testfile, 0644);
|
|
if (fd == -1) {
|
|
PERROR("creat");
|
|
return -1;
|
|
}
|
|
res = write(fd, data, datalen);
|
|
if (res == -1) {
|
|
PERROR("write");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if (res != datalen) {
|
|
ERROR("write is short: %u instead of %u", res, datalen);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
res = close(fd);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
return -1;
|
|
}
|
|
res = check_type(testfile, S_IFREG);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile, 0644);
|
|
err += check_nlink(testfile, 1);
|
|
err += check_size(testfile, datalen);
|
|
err += check_data(testfile, data, 0, datalen);
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_create_unlink(void)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int err = 0;
|
|
int res;
|
|
int fd;
|
|
|
|
start_test("create+unlink");
|
|
unlink(testfile);
|
|
fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644);
|
|
if (fd == -1) {
|
|
PERROR("creat");
|
|
return -1;
|
|
}
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
res = write(fd, data, datalen);
|
|
if (res == -1) {
|
|
PERROR("write");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
if (res != datalen) {
|
|
ERROR("write is short: %u instead of %u", res, datalen);
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
struct stat st = {
|
|
.st_mode = S_IFREG | 0644,
|
|
.st_size = datalen,
|
|
};
|
|
err = fcheck_stat(fd, O_RDWR, &st);
|
|
err += fcheck_data(fd, data, 0, datalen);
|
|
res = close(fd);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
err--;
|
|
}
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_mknod(void)
|
|
{
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("mknod");
|
|
unlink(testfile);
|
|
res = mknod(testfile, 0644, 0);
|
|
if (res == -1) {
|
|
PERROR("mknod");
|
|
return -1;
|
|
}
|
|
res = check_type(testfile, S_IFREG);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile, 0644);
|
|
err += check_nlink(testfile, 1);
|
|
err += check_size(testfile, 0);
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
#define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode)
|
|
|
|
static int do_test_open(int exist, int flags, const char *flags_str, int mode)
|
|
{
|
|
char buf[4096];
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
unsigned currlen = 0;
|
|
int err = 0;
|
|
int res;
|
|
int fd;
|
|
off_t off;
|
|
|
|
start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode);
|
|
unlink(testfile);
|
|
if (exist) {
|
|
res = create_file(testfile_r, testdata2, testdata2len);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
currlen = testdata2len;
|
|
}
|
|
|
|
fd = open(testfile, flags, mode);
|
|
if ((flags & O_CREAT) && (flags & O_EXCL) && exist) {
|
|
if (fd != -1) {
|
|
ERROR("open should have failed");
|
|
close(fd);
|
|
return -1;
|
|
} else if (errno == EEXIST)
|
|
goto succ;
|
|
}
|
|
if (!(flags & O_CREAT) && !exist) {
|
|
if (fd != -1) {
|
|
ERROR("open should have failed");
|
|
close(fd);
|
|
return -1;
|
|
} else if (errno == ENOENT)
|
|
goto succ;
|
|
}
|
|
if (fd == -1) {
|
|
PERROR("open");
|
|
return -1;
|
|
}
|
|
|
|
if (flags & O_TRUNC)
|
|
currlen = 0;
|
|
|
|
err += check_type(testfile, S_IFREG);
|
|
if (exist)
|
|
err += check_mode(testfile, 0644);
|
|
else
|
|
err += check_mode(testfile, mode);
|
|
err += check_nlink(testfile, 1);
|
|
err += check_size(testfile, currlen);
|
|
if (exist && !(flags & O_TRUNC) && (mode & S_IRUSR))
|
|
err += check_data(testfile, testdata2, 0, testdata2len);
|
|
|
|
res = write(fd, data, datalen);
|
|
if ((flags & O_ACCMODE) != O_RDONLY) {
|
|
if (res == -1) {
|
|
PERROR("write");
|
|
err --;
|
|
} else if (res != datalen) {
|
|
ERROR("write is short: %u instead of %u", res, datalen);
|
|
err --;
|
|
} else {
|
|
if (datalen > (int) currlen)
|
|
currlen = datalen;
|
|
|
|
err += check_size(testfile, currlen);
|
|
|
|
if (mode & S_IRUSR) {
|
|
err += check_data(testfile, data, 0, datalen);
|
|
if (exist && !(flags & O_TRUNC) &&
|
|
testdata2len > datalen)
|
|
err += check_data(testfile,
|
|
testdata2 + datalen,
|
|
datalen,
|
|
testdata2len - datalen);
|
|
}
|
|
}
|
|
} else {
|
|
if (res != -1) {
|
|
ERROR("write should have failed");
|
|
err --;
|
|
} else if (errno != EBADF) {
|
|
PERROR("write");
|
|
err --;
|
|
}
|
|
}
|
|
off = lseek(fd, SEEK_SET, 0);
|
|
if (off == (off_t) -1) {
|
|
PERROR("lseek");
|
|
err--;
|
|
} else if (off != 0) {
|
|
ERROR("offset should have returned 0");
|
|
err --;
|
|
}
|
|
res = read(fd, buf, sizeof(buf));
|
|
if ((flags & O_ACCMODE) != O_WRONLY) {
|
|
if (res == -1) {
|
|
PERROR("read");
|
|
err--;
|
|
} else {
|
|
int readsize =
|
|
currlen < sizeof(buf) ? currlen : sizeof(buf);
|
|
if (res != readsize) {
|
|
ERROR("read is short: %i instead of %u",
|
|
res, readsize);
|
|
err--;
|
|
} else {
|
|
if ((flags & O_ACCMODE) != O_RDONLY) {
|
|
err += check_buffer(buf, data, datalen);
|
|
if (exist && !(flags & O_TRUNC) &&
|
|
testdata2len > datalen)
|
|
err += check_buffer(buf + datalen,
|
|
testdata2 + datalen,
|
|
testdata2len - datalen);
|
|
} else if (exist)
|
|
err += check_buffer(buf, testdata2,
|
|
testdata2len);
|
|
}
|
|
}
|
|
} else {
|
|
if (res != -1) {
|
|
ERROR("read should have failed");
|
|
err --;
|
|
} else if (errno != EBADF) {
|
|
PERROR("read");
|
|
err --;
|
|
}
|
|
}
|
|
|
|
res = close(fd);
|
|
if (res == -1) {
|
|
PERROR("close");
|
|
return -1;
|
|
}
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_nonexist(testfile_r);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
succ:
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
#define test_open_acc(flags, mode, err) \
|
|
do_test_open_acc(flags, #flags, mode, err)
|
|
|
|
static int do_test_open_acc(int flags, const char *flags_str, int mode, int err)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int res;
|
|
int fd;
|
|
|
|
start_test("open_acc(%s) mode: 0%03o message: '%s'", flags_str, mode,
|
|
strerror(err));
|
|
unlink(testfile);
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
res = chmod(testfile, mode);
|
|
if (res == -1) {
|
|
PERROR("chmod");
|
|
return -1;
|
|
}
|
|
|
|
res = check_testfile_mode(testfile, mode);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
fd = open(testfile, flags);
|
|
if (fd == -1) {
|
|
if (err != errno) {
|
|
PERROR("open");
|
|
return -1;
|
|
}
|
|
} else {
|
|
if (err) {
|
|
ERROR("open should have failed");
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
}
|
|
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_nonexist(testfile_r);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_symlink(void)
|
|
{
|
|
char buf[1024];
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int linklen = strlen(testfile);
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("symlink");
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
unlink(testfile2);
|
|
res = symlink(testfile, testfile2);
|
|
if (res == -1) {
|
|
PERROR("symlink");
|
|
return -1;
|
|
}
|
|
res = check_type(testfile2, S_IFLNK);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile2, 0777);
|
|
err += check_nlink(testfile2, 1);
|
|
res = readlink(testfile2, buf, sizeof(buf));
|
|
if (res == -1) {
|
|
PERROR("readlink");
|
|
err--;
|
|
}
|
|
if (res != linklen) {
|
|
ERROR("short readlink: %u instead of %u", res, linklen);
|
|
err--;
|
|
}
|
|
if (memcmp(buf, testfile, linklen) != 0) {
|
|
ERROR("link mismatch");
|
|
err--;
|
|
}
|
|
err += check_size(testfile2, datalen);
|
|
err += check_data(testfile2, data, 0, datalen);
|
|
res = unlink(testfile2);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile2);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_link(void)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("link");
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
unlink(testfile2);
|
|
res = link(testfile, testfile2);
|
|
if (res == -1) {
|
|
PERROR("link");
|
|
return -1;
|
|
}
|
|
res = check_type(testfile2, S_IFREG);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile2, 0644);
|
|
err += check_nlink(testfile2, 2);
|
|
err += check_size(testfile2, datalen);
|
|
err += check_data(testfile2, data, 0, datalen);
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
err += check_nlink(testfile2, 1);
|
|
res = unlink(testfile2);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile2);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_link2(void)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("link-unlink-link");
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
unlink(testfile2);
|
|
res = link(testfile, testfile2);
|
|
if (res == -1) {
|
|
PERROR("link");
|
|
return -1;
|
|
}
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
res = link(testfile2, testfile);
|
|
if (res == -1) {
|
|
PERROR("link");
|
|
}
|
|
res = check_type(testfile, S_IFREG);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile, 0644);
|
|
err += check_nlink(testfile, 2);
|
|
err += check_size(testfile, datalen);
|
|
err += check_data(testfile, data, 0, datalen);
|
|
|
|
res = unlink(testfile2);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
err += check_nlink(testfile, 1);
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_rename_file(void)
|
|
{
|
|
const char *data = testdata;
|
|
int datalen = testdatalen;
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("rename file");
|
|
res = create_testfile(testfile, data, datalen);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
unlink(testfile2);
|
|
res = rename(testfile, testfile2);
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
res = check_type(testfile2, S_IFREG);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile2, 0644);
|
|
err += check_nlink(testfile2, 1);
|
|
err += check_size(testfile2, datalen);
|
|
err += check_data(testfile2, data, 0, datalen);
|
|
res = unlink(testfile2);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile2);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_rename_dir(void)
|
|
{
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("rename dir");
|
|
res = create_dir(testdir, testdir_files);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
rmdir(testdir2);
|
|
res = rename(testdir, testdir2);
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
cleanup_dir(testdir, testdir_files, 1);
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testdir);
|
|
if (res == -1) {
|
|
cleanup_dir(testdir, testdir_files, 1);
|
|
return -1;
|
|
}
|
|
res = check_type(testdir2, S_IFDIR);
|
|
if (res == -1) {
|
|
cleanup_dir(testdir2, testdir_files, 1);
|
|
return -1;
|
|
}
|
|
err += check_mode(testdir2, 0755);
|
|
err += check_dir_contents(testdir2, testdir_files);
|
|
err += cleanup_dir(testdir2, testdir_files, 0);
|
|
res = rmdir(testdir2);
|
|
if (res == -1) {
|
|
PERROR("rmdir");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testdir2);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_rename_dir_loop(void)
|
|
{
|
|
#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path)
|
|
#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2)
|
|
|
|
char path[1280], path2[1280];
|
|
int err = 0;
|
|
int res;
|
|
|
|
start_test("rename dir loop");
|
|
|
|
res = create_dir(testdir, testdir_files);
|
|
if (res == -1)
|
|
return -1;
|
|
|
|
res = mkdir(PATH("a"), 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a"), PATH2("a"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
errno = 0;
|
|
res = rename(PATH("a"), PATH2("a/b"));
|
|
if (res == 0 || errno != EINVAL) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = mkdir(PATH("a/b"), 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
goto fail;
|
|
}
|
|
|
|
res = mkdir(PATH("a/b/c"), 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
goto fail;
|
|
}
|
|
|
|
errno = 0;
|
|
res = rename(PATH("a"), PATH2("a/b/c"));
|
|
if (res == 0 || errno != EINVAL) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
errno = 0;
|
|
res = rename(PATH("a"), PATH2("a/b/c/a"));
|
|
if (res == 0 || errno != EINVAL) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
errno = 0;
|
|
res = rename(PATH("a/b/c"), PATH2("a"));
|
|
if (res == 0 || errno != ENOTEMPTY) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = open(PATH("a/foo"), O_CREAT, 0644);
|
|
if (res == -1) {
|
|
PERROR("open");
|
|
goto fail;
|
|
}
|
|
close(res);
|
|
|
|
res = rename(PATH("a/foo"), PATH2("a/bar"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/bar"), PATH2("a/foo"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/foo"), PATH2("a/b/bar"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/b/bar"), PATH2("a/foo"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/foo"), PATH2("a/b/c/bar"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/b/c/bar"), PATH2("a/foo"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = open(PATH("a/bar"), O_CREAT, 0644);
|
|
if (res == -1) {
|
|
PERROR("open");
|
|
goto fail;
|
|
}
|
|
close(res);
|
|
|
|
res = rename(PATH("a/foo"), PATH2("a/bar"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
unlink(PATH("a/bar"));
|
|
|
|
res = rename(PATH("a/b"), PATH2("a/d"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/d"), PATH2("a/b"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = mkdir(PATH("a/d"), 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/b"), PATH2("a/d"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = rename(PATH("a/d"), PATH2("a/b"));
|
|
if (res == -1) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
res = mkdir(PATH("a/d"), 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
goto fail;
|
|
}
|
|
|
|
res = mkdir(PATH("a/d/e"), 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
goto fail;
|
|
}
|
|
|
|
errno = 0;
|
|
res = rename(PATH("a/b"), PATH2("a/d"));
|
|
if (res == 0 || (errno != ENOTEMPTY && errno != EEXIST)) {
|
|
PERROR("rename");
|
|
goto fail;
|
|
}
|
|
|
|
rmdir(PATH("a/d/e"));
|
|
rmdir(PATH("a/d"));
|
|
|
|
rmdir(PATH("a/b/c"));
|
|
rmdir(PATH("a/b"));
|
|
rmdir(PATH("a"));
|
|
|
|
err += cleanup_dir(testdir, testdir_files, 0);
|
|
res = rmdir(testdir);
|
|
if (res == -1) {
|
|
PERROR("rmdir");
|
|
goto fail;
|
|
}
|
|
res = check_nonexist(testdir);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
|
|
fail:
|
|
unlink(PATH("a/bar"));
|
|
|
|
rmdir(PATH("a/d/e"));
|
|
rmdir(PATH("a/d"));
|
|
|
|
rmdir(PATH("a/b/c"));
|
|
rmdir(PATH("a/b"));
|
|
rmdir(PATH("a"));
|
|
|
|
cleanup_dir(testdir, testdir_files, 1);
|
|
rmdir(testdir);
|
|
|
|
return -1;
|
|
|
|
#undef PATH2
|
|
#undef PATH
|
|
}
|
|
|
|
static int test_mkfifo(void)
|
|
{
|
|
int res;
|
|
int err = 0;
|
|
|
|
start_test("mkfifo");
|
|
unlink(testfile);
|
|
res = mkfifo(testfile, 0644);
|
|
if (res == -1) {
|
|
PERROR("mkfifo");
|
|
return -1;
|
|
}
|
|
res = check_type(testfile, S_IFIFO);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testfile, 0644);
|
|
err += check_nlink(testfile, 1);
|
|
res = unlink(testfile);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testfile);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_mkdir(void)
|
|
{
|
|
int res;
|
|
int err = 0;
|
|
const char *dir_contents[] = {NULL};
|
|
|
|
start_test("mkdir");
|
|
rmdir(testdir);
|
|
res = mkdir(testdir, 0755);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
return -1;
|
|
}
|
|
res = check_type(testdir, S_IFDIR);
|
|
if (res == -1)
|
|
return -1;
|
|
err += check_mode(testdir, 0755);
|
|
/* Some file systems (like btrfs) don't track link
|
|
count for directories */
|
|
//err += check_nlink(testdir, 2);
|
|
err += check_dir_contents(testdir, dir_contents);
|
|
res = rmdir(testdir);
|
|
if (res == -1) {
|
|
PERROR("rmdir");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testdir);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_socket(void)
|
|
{
|
|
struct sockaddr_un su;
|
|
int fd;
|
|
int res;
|
|
int err = 0;
|
|
const size_t test_sock_len = strlen(testsock) + 1;
|
|
|
|
start_test("socket");
|
|
if (test_sock_len > sizeof(su.sun_path)) {
|
|
fprintf(stderr, "Need to shorten mount point by %zu chars\n",
|
|
strlen(testsock) + 1 - sizeof(su.sun_path));
|
|
return -1;
|
|
}
|
|
unlink(testsock);
|
|
fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (fd < 0) {
|
|
PERROR("socket");
|
|
return -1;
|
|
}
|
|
su.sun_family = AF_UNIX;
|
|
|
|
strncpy(su.sun_path, testsock, test_sock_len);
|
|
su.sun_path[sizeof(su.sun_path) - 1] = '\0';
|
|
res = bind(fd, (struct sockaddr*)&su, sizeof(su));
|
|
if (res == -1) {
|
|
PERROR("bind");
|
|
return -1;
|
|
}
|
|
|
|
res = check_type(testsock, S_IFSOCK);
|
|
if (res == -1) {
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
err += check_nlink(testsock, 1);
|
|
close(fd);
|
|
res = unlink(testsock);
|
|
if (res == -1) {
|
|
PERROR("unlink");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testsock);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
#define test_create_ro_dir(flags) \
|
|
do_test_create_ro_dir(flags, #flags)
|
|
|
|
static int do_test_create_ro_dir(int flags, const char *flags_str)
|
|
{
|
|
int res;
|
|
int err = 0;
|
|
int fd;
|
|
|
|
start_test("open(%s) in read-only directory", flags_str);
|
|
rmdir(testdir);
|
|
res = mkdir(testdir, 0555);
|
|
if (res == -1) {
|
|
PERROR("mkdir");
|
|
return -1;
|
|
}
|
|
fd = open(subfile, flags, 0644);
|
|
if (fd != -1) {
|
|
close(fd);
|
|
unlink(subfile);
|
|
ERROR("open should have failed");
|
|
err--;
|
|
} else {
|
|
res = check_nonexist(subfile);
|
|
if (res == -1)
|
|
err--;
|
|
}
|
|
unlink(subfile);
|
|
res = rmdir(testdir);
|
|
if (res == -1) {
|
|
PERROR("rmdir");
|
|
return -1;
|
|
}
|
|
res = check_nonexist(testdir);
|
|
if (res == -1)
|
|
return -1;
|
|
if (err)
|
|
return -1;
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
#ifndef __FreeBSD__
|
|
/* this tests open with O_TMPFILE
|
|
note that this will only work with the fuse low level api
|
|
you will get ENOTSUP with the high level api */
|
|
static int test_create_tmpfile(void)
|
|
{
|
|
rmdir(testdir);
|
|
int res = mkdir(testdir, 0777);
|
|
if (res)
|
|
return -1;
|
|
|
|
start_test("create tmpfile");
|
|
|
|
int fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
|
|
if(fd == -1) {
|
|
if (errno == ENOTSUP) {
|
|
/* don't bother if we're working on an old kernel
|
|
or on the high level API */
|
|
return 0;
|
|
}
|
|
|
|
PERROR("open O_TMPFILE | O_RDWR");
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
|
|
fd = open(testdir, O_TMPFILE | O_WRONLY | O_EXCL, S_IRUSR | S_IWUSR);
|
|
if(fd == -1){
|
|
PERROR("open with O_TMPFILE | O_WRONLY | O_EXCL");
|
|
return -1;
|
|
};
|
|
close(fd);
|
|
|
|
fd = open(testdir, O_TMPFILE | O_RDONLY, S_IRUSR);
|
|
if (fd != -1) {
|
|
ERROR("open with O_TMPFILE | O_RDONLY succeeded");
|
|
return -1;
|
|
}
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
|
|
static int test_create_and_link_tmpfile(void)
|
|
{
|
|
/* skip this test for now since the github runner will fail in the linkat call below */
|
|
return 0;
|
|
|
|
rmdir(testdir);
|
|
unlink(testfile);
|
|
|
|
int res = mkdir(testdir, 0777);
|
|
if (res)
|
|
return -1;
|
|
|
|
start_test("create and link tmpfile");
|
|
|
|
int fd = open(testdir, O_TMPFILE | O_RDWR | O_EXCL, S_IRUSR | S_IWUSR);
|
|
if(fd == -1) {
|
|
if (errno == ENOTSUP) {
|
|
/* don't bother if we're working on an old kernel
|
|
or on the high level API */
|
|
return 0;
|
|
}
|
|
PERROR("open with O_TMPFILE | O_RDWR | O_EXCL");
|
|
return -1;
|
|
}
|
|
|
|
if (!linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
|
|
ERROR("linkat succeeded on a tmpfile opened with O_EXCL");
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
|
|
fd = open(testdir, O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);
|
|
if(fd == -1) {
|
|
PERROR("open O_TMPFILE");
|
|
return -1;
|
|
}
|
|
|
|
if (check_nonexist(testfile)) {
|
|
return -1;
|
|
}
|
|
|
|
if (linkat(fd, "", AT_FDCWD, testfile, AT_EMPTY_PATH)) {
|
|
PERROR("linkat tempfile");
|
|
return -1;
|
|
}
|
|
close(fd);
|
|
|
|
if (check_nlink(testfile, 1)) {
|
|
return -1;
|
|
}
|
|
unlink(testfile);
|
|
|
|
success();
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
int err = 0;
|
|
int a;
|
|
int is_root;
|
|
|
|
umask(0);
|
|
if (argc < 2 || argc > 4) {
|
|
fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#] [-u]\n", argv[0]);
|
|
return 1;
|
|
}
|
|
basepath = argv[1];
|
|
basepath_r = basepath;
|
|
for (a = 2; a < argc; a++) {
|
|
char *endptr;
|
|
char *arg = argv[a];
|
|
if (arg[0] == ':') {
|
|
basepath_r = arg + 1;
|
|
} else {
|
|
if (arg[0] == '-') {
|
|
arg++;
|
|
if (arg[0] == 'u') {
|
|
unlinked_test = 1;
|
|
endptr = arg + 1;
|
|
} else {
|
|
skip_test = strtoul(arg, &endptr, 10);
|
|
}
|
|
} else {
|
|
select_test = strtoul(arg, &endptr, 10);
|
|
}
|
|
if (arg[0] == '\0' || *endptr != '\0') {
|
|
fprintf(stderr, "invalid option: '%s'\n", argv[a]);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
if (basepath[0] != '/') {
|
|
fprintf(stderr, "testdir must be an absolute path\n");
|
|
return 1;
|
|
}
|
|
if (strlen(basepath) > 1000) {
|
|
fprintf(stderr, "testdir path is too long\n");
|
|
return 1;
|
|
}
|
|
if (strlen(basepath_r) > 1000) {
|
|
fprintf(stderr, "realdir path is too long\n");
|
|
return 1;
|
|
}
|
|
|
|
snprintf(testfile, sizeof(testfile), "%s/testfile", basepath);
|
|
snprintf(testfile2, sizeof(testfile2), "%s/testfile2", basepath);
|
|
snprintf(testdir, sizeof(testdir), "%s/testdir", basepath);
|
|
snprintf(testdir2, sizeof(testdir2), "%s/testdir2", basepath);
|
|
snprintf(subfile, sizeof(subfile), "%s/subfile", testdir2);
|
|
snprintf(testsock, sizeof(testsock), "%s/testsock", basepath);
|
|
|
|
snprintf(testfile_r, sizeof(testfile_r), "%s/testfile", basepath_r);
|
|
snprintf(testfile2_r, sizeof(testfile2_r), "%s/testfile2", basepath_r);
|
|
snprintf(testdir_r, sizeof(testdir_r), "%s/testdir", basepath_r);
|
|
snprintf(testdir2_r, sizeof(testdir2_r), "%s/testdir2", basepath_r);
|
|
snprintf(subfile_r, sizeof(subfile_r), "%s/subfile", testdir2_r);
|
|
|
|
is_root = (geteuid() == 0);
|
|
|
|
err += test_create();
|
|
err += test_create_unlink();
|
|
err += test_symlink();
|
|
err += test_link();
|
|
err += test_link2();
|
|
err += test_mknod();
|
|
err += test_mkfifo();
|
|
err += test_mkdir();
|
|
err += test_rename_file();
|
|
err += test_rename_dir();
|
|
err += test_rename_dir_loop();
|
|
err += test_seekdir();
|
|
err += test_socket();
|
|
err += test_utime();
|
|
err += test_truncate(0);
|
|
err += test_truncate(testdatalen / 2);
|
|
err += test_truncate(testdatalen);
|
|
err += test_truncate(testdatalen + 100);
|
|
err += test_ftruncate(0, 0600);
|
|
err += test_ftruncate(testdatalen / 2, 0600);
|
|
err += test_ftruncate(testdatalen, 0600);
|
|
err += test_ftruncate(testdatalen + 100, 0600);
|
|
err += test_ftruncate(0, 0400);
|
|
err += test_ftruncate(0, 0200);
|
|
err += test_ftruncate(0, 0000);
|
|
err += test_open(0, O_RDONLY, 0);
|
|
err += test_open(1, O_RDONLY, 0);
|
|
err += test_open(1, O_RDWR, 0);
|
|
err += test_open(1, O_WRONLY, 0);
|
|
err += test_open(0, O_RDWR | O_CREAT, 0600);
|
|
err += test_open(1, O_RDWR | O_CREAT, 0600);
|
|
err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600);
|
|
err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600);
|
|
err += test_open(0, O_RDONLY | O_CREAT, 0600);
|
|
err += test_open(0, O_RDONLY | O_CREAT, 0400);
|
|
err += test_open(0, O_RDONLY | O_CREAT, 0200);
|
|
err += test_open(0, O_RDONLY | O_CREAT, 0000);
|
|
err += test_open(0, O_WRONLY | O_CREAT, 0600);
|
|
err += test_open(0, O_WRONLY | O_CREAT, 0400);
|
|
err += test_open(0, O_WRONLY | O_CREAT, 0200);
|
|
err += test_open(0, O_WRONLY | O_CREAT, 0000);
|
|
err += test_open(0, O_RDWR | O_CREAT, 0400);
|
|
err += test_open(0, O_RDWR | O_CREAT, 0200);
|
|
err += test_open(0, O_RDWR | O_CREAT, 0000);
|
|
err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000);
|
|
err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000);
|
|
err += test_open_acc(O_RDONLY, 0600, 0);
|
|
err += test_open_acc(O_WRONLY, 0600, 0);
|
|
err += test_open_acc(O_RDWR, 0600, 0);
|
|
err += test_open_acc(O_RDONLY, 0400, 0);
|
|
err += test_open_acc(O_WRONLY, 0200, 0);
|
|
if(!is_root) {
|
|
err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES);
|
|
err += test_open_acc(O_WRONLY, 0400, EACCES);
|
|
err += test_open_acc(O_RDWR, 0400, EACCES);
|
|
err += test_open_acc(O_RDONLY, 0200, EACCES);
|
|
err += test_open_acc(O_RDWR, 0200, EACCES);
|
|
err += test_open_acc(O_RDONLY, 0000, EACCES);
|
|
err += test_open_acc(O_WRONLY, 0000, EACCES);
|
|
err += test_open_acc(O_RDWR, 0000, EACCES);
|
|
}
|
|
err += test_create_ro_dir(O_CREAT);
|
|
err += test_create_ro_dir(O_CREAT | O_EXCL);
|
|
err += test_create_ro_dir(O_CREAT | O_WRONLY);
|
|
err += test_create_ro_dir(O_CREAT | O_TRUNC);
|
|
err += test_copy_file_range();
|
|
err += test_statx();
|
|
#ifndef __FreeBSD__
|
|
err += test_create_tmpfile();
|
|
err += test_create_and_link_tmpfile();
|
|
#endif
|
|
|
|
unlink(testfile2);
|
|
unlink(testsock);
|
|
rmdir(testdir);
|
|
rmdir(testdir2);
|
|
|
|
if (err) {
|
|
fprintf(stderr, "%i tests failed\n", -err);
|
|
return 1;
|
|
}
|
|
|
|
return check_unlinked_testfiles();
|
|
}
|