#define _GNU_SOURCE #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include static char testfile[1024]; static char testfile2[1024]; static char testdir[1024]; static char testdir2[1024]; static char subfile[1024]; static char testfile_r[1024]; static char testfile2_r[1024]; static char testdir_r[1024]; static char testdir2_r[1024]; static char subfile_r[1024]; 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 = 1; static unsigned int select_test = 0; static unsigned int skip_test = 0; #define MAX_ENTRIES 1024 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 void success(void) { fprintf(stderr, "%s OK\n", testname); } static void __start_test(const char *fmt, ...) { unsigned int n; va_list ap; n = sprintf(testname, "%3i [", testnum++); va_start(ap, fmt); n += vsprintf(testname + n, fmt, ap); va_end(ap); sprintf(testname + n, "]"); } #define start_test(msg, args...) { \ if ((select_test && testnum != select_test) || \ (testnum == skip_test)) { \ testnum++; \ 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 check_size(const char *path, int len) { struct stat stbuf; int res = stat(path, &stbuf); if (res == -1) { PERROR("stat"); return -1; } if (stbuf.st_size != len) { ERROR("length %u instead of %u", (int) stbuf.st_size, (int) len); return -1; } return 0; } static int fcheck_size(int fd, int len) { struct stat stbuf; int res = fstat(fd, &stbuf); if (res == -1) { PERROR("fstat"); return -1; } if (stbuf.st_size != len) { ERROR("length %u instead of %u", (int) stbuf.st_size, (int) len); 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; } if ((stbuf.st_mode & S_IFMT) != type) { ERROR("type 0%o instead of 0%o", stbuf.st_mode & S_IFMT, type); return -1; } return 0; } static int fcheck_type(int fd, mode_t type) { struct stat stbuf; int res = fstat(fd, &stbuf); if (res == -1) { PERROR("fstat"); return -1; } if ((stbuf.st_mode & S_IFMT) != type) { ERROR("type 0%o instead of 0%o", stbuf.st_mode & S_IFMT, type); 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; } if ((stbuf.st_mode & 07777) != mode) { ERROR("mode 0%o instead of 0%o", stbuf.st_mode & 07777, mode); return -1; } return 0; } static int fcheck_mode(int fd, mode_t mode) { struct stat stbuf; int res = fstat(fd, &stbuf); if (res == -1) { PERROR("fstat"); return -1; } if ((stbuf.st_mode & 07777) != mode) { ERROR("mode 0%o instead of 0%o", stbuf.st_mode & 07777, mode); return -1; } return 0; } 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 check_nlink(const char *path, nlink_t nlink) { struct stat stbuf; int res = lstat(path, &stbuf); if (res == -1) { PERROR("lstat"); return -1; } if (stbuf.st_nlink != nlink) { ERROR("nlink %li instead of %li", (long) stbuf.st_nlink, (long) nlink); return -1; } return 0; } static int fcheck_nlink(int fd, nlink_t nlink) { struct stat stbuf; int res = fstat(fd, &stbuf); if (res == -1) { PERROR("fstat"); return -1; } if (stbuf.st_nlink != nlink) { ERROR("nlink %li instead of %li", (long) stbuf.st_nlink, (long) nlink); return -1; } return 0; } 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]; int res; if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { PERROR("lseek"); return -1; } while (len) { int rdlen = len < sizeof(buf) ? len : sizeof(buf); 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]; } found[i] = 0; cont[i++] = "."; found[i] = 0; cont[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; } 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 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[1024]; sprintf(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[1024]; sprintf(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_file(testfile, data, datalen); if (res == -1) return -1; res = truncate(testfile, len); if (res == -1) { PERROR("truncate"); return -1; } res = check_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_file(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_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_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; struct dirent *de; 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"); 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; if (err) return -1; success(); return 0; } #else static int test_copy_file_range(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_file(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) 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; } err += fcheck_type(fd, S_IFREG); err += fcheck_mode(fd, 0644); err += fcheck_nlink(fd, 0); err += fcheck_size(fd, datalen); err += fcheck_data(fd, data, 0, datalen); res = close(fd); if (res == -1) { PERROR("close"); err--; } if (err) return -1; success(); return 0; } #ifndef __FreeBSD__ 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; } #endif #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 & 0400)) 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 & 0400) { 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_file(testfile, data, datalen); if (res == -1) return -1; res = chmod(testfile, mode); if (res == -1) { PERROR("chmod"); return -1; } res = check_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); } 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_file(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; 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_file(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_file(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_file(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[1024], path2[1024]; 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) { 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 } #ifndef __FreeBSD__ 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; } #endif 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; } #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; } int main(int argc, char *argv[]) { const char *basepath; const char *realpath; int err = 0; int a; int is_root; umask(0); if (argc < 2 || argc > 4) { fprintf(stderr, "usage: %s testdir [:realdir] [[-]test#]\n", argv[0]); return 1; } basepath = argv[1]; realpath = basepath; for (a = 2; a < argc; a++) { char *endptr; char *arg = argv[a]; if (arg[0] == ':') { realpath = arg + 1; } else { if (arg[0] == '-') { arg++; skip_test = strtoul(arg, &endptr, 10); } else { select_test = strtoul(arg, &endptr, 10); } if (arg[0] == '\0' || *endptr != '\0') { fprintf(stderr, "invalid number: '%s'\n", arg); return 1; } } } assert(strlen(basepath) < 512); assert(strlen(realpath) < 512); if (basepath[0] != '/') { fprintf(stderr, "testdir must be an absolute path\n"); return 1; } sprintf(testfile, "%s/testfile", basepath); sprintf(testfile2, "%s/testfile2", basepath); sprintf(testdir, "%s/testdir", basepath); sprintf(testdir2, "%s/testdir2", basepath); sprintf(subfile, "%s/subfile", testdir2); sprintf(testfile_r, "%s/testfile", realpath); sprintf(testfile2_r, "%s/testfile2", realpath); sprintf(testdir_r, "%s/testdir", realpath); sprintf(testdir2_r, "%s/testdir2", realpath); sprintf(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(); #ifndef __FreeBSD__ err += test_mknod(); err += test_mkfifo(); #endif err += test_mkdir(); err += test_rename_file(); err += test_rename_dir(); err += test_rename_dir_loop(); err += test_seekdir(); 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(); unlink(testfile); unlink(testfile2); rmdir(testdir); rmdir(testdir2); if (err) { fprintf(stderr, "%i tests failed\n", -err); return 1; } return 0; }