├── slash.h ├── tests ├── Makefile ├── lib │ ├── functions │ └── fuse_tester.c └── test_suite ├── mkfs.sharebox ├── Makefile ├── git-annex.h ├── common.h ├── README.md ├── git-annex.c ├── sharebox.c └── slash.c /slash.h: -------------------------------------------------------------------------------- 1 | #include "common.h" 2 | 3 | void init_slash(dir *); 4 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | all: lib/fuse_tester 2 | ./test_suite 3 | 4 | clean: 5 | rm -f lib/fuse_tester 6 | -------------------------------------------------------------------------------- /mkfs.sharebox: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This shell script initializes a directory for use with sharebox 4 | 5 | usage() 6 | { 7 | echo "Error: $@" 8 | echo "Usage: $0 " 9 | exit 2 10 | } 11 | 12 | [ -n $1 ] || usage "Missing argument" 13 | [ -z $2 ] || usage "Too many arguments" 14 | [ -d $1 ] || usage "$1: Not a directory" 15 | [ -z $(ls -A $1) ] || usage "$1: Directory is not empty" 16 | 17 | mkdir -- $1/files 18 | cd -- $1 19 | git init 20 | git annex init "$USER@$(hostname):$PWD" 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | INSTALL_PREFIX=/usr/local 2 | 3 | CFLAGS=`pkg-config fuse --cflags` -DDEBUG 4 | LDFLAGS=`pkg-config fuse --libs` 5 | 6 | sharebox: sharebox.o git-annex.o slash.o 7 | gcc -g -Wall -o sharebox sharebox.o git-annex.o slash.o $(LDFLAGS) 8 | 9 | sharebox.o: sharebox.c 10 | gcc -g -Wall $(CFLAGS) -c sharebox.c 11 | 12 | git-annex.o: git-annex.c git-annex.h 13 | gcc -g -Wall $(CFLAGS) -c git-annex.c 14 | 15 | slash.o: slash.c slash.h 16 | gcc -g -Wall $(CFLAGS) -c slash.c 17 | 18 | test: sharebox 19 | $(MAKE) -C tests/ 20 | 21 | clean: 22 | rm -f sharebox *.o 23 | $(MAKE) -C tests/ clean 24 | 25 | install: sharebox 26 | install -Dm755 ./sharebox $(INSTALL_PREFIX)/bin/sharebox 27 | install -Dm755 ./mkfs.sharebox $(INSTALL_PREFIX)/sbin/mkfs.sharebox 28 | 29 | uninstall: 30 | rm -f $(INSTALL_PREFIX)/bin/sharebox 31 | rm -f $(INSTALL_PREFIX)/bin/mkfs.sharebox 32 | -------------------------------------------------------------------------------- /tests/lib/functions: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # functions for sharebox test suite 4 | 5 | SUCCESS=0 6 | 7 | sharebox() 8 | { 9 | $PWD/../sharebox $@ 10 | } 11 | 12 | mkfs() 13 | { 14 | [[ $1 == "-t" ]] && [[ $2 == "sharebox" ]] || exit 1 15 | shift 2 16 | $PWD/../mkfs.sharebox $@ 17 | } 18 | 19 | fuse_tester() 20 | { 21 | $PWD/lib/fuse_tester $@ 22 | } 23 | 24 | assert_success() 25 | { 26 | res=$($@ 2>&1) 27 | if [[ $? -ne 0 ]]; then 28 | echo "Error: the command '$@' failed, printing:" 29 | echo "$res" 30 | echo "Press enter to continue" 31 | SUCCESS=1 32 | read anykey 33 | fi 34 | } 35 | 36 | assert_fail() 37 | { 38 | res=$($@ 2>&1) 39 | if [[ $? -eq 0 ]]; then 40 | echo "Error: the command '$@' succeeded, printing:" 41 | echo "$res" 42 | echo "Press enter to continue" 43 | SUCCESS=1 44 | read anykey 45 | fi 46 | } 47 | 48 | clean() 49 | { 50 | chmod -R +w sandbox 51 | rm -rf sandbox 52 | } 53 | -------------------------------------------------------------------------------- /git-annex.h: -------------------------------------------------------------------------------- 1 | /* 2 | * git-annex.h 3 | */ 4 | 5 | #include 6 | 7 | int git_annex_unlock(const char *repodir, const char *path); 8 | int git_annex_add(const char *repodir, const char *path); 9 | int git_annex_get(const char *repodir, const char *path, const char *branch); 10 | int git_add(const char *repodir, const char *path); 11 | int git_commit(const char *repodir, const char *format, ...); 12 | int git_rm(const char *repodir, const char *path); 13 | int git_mv(const char *repodir, const char *old, const char *new); 14 | int git_annexed(const char *repodir, const char *path); 15 | int git_ignored(const char *repodir, const char *path); 16 | 17 | typedef struct namelist namelist; 18 | struct namelist { 19 | char name[FILENAME_MAX]; 20 | namelist* next; 21 | }; 22 | 23 | namelist* git_branches(const char *repodir); 24 | namelist* conflicting_files(const char *repodir, const char *path, 25 | const char *branch); 26 | void free_namelist(namelist *l); 27 | void target(char target[FILENAME_MAX], const char *repodir, 28 | const char *path, const char *branch); 29 | -------------------------------------------------------------------------------- /common.h: -------------------------------------------------------------------------------- 1 | #ifndef __COMMON_H__ 2 | #define __COMMON_H__ 3 | 4 | #define SHAREBOX_VERSION "0.0.1" 5 | #define FUSE_USE_VERSION 26 6 | 7 | #ifdef linux 8 | /* For pread()/pwrite() */ 9 | #define _XOPEN_SOURCE 500 10 | #endif 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | typedef struct dir dir; 29 | struct dir 30 | { 31 | char name[FILENAME_MAX]; 32 | struct fuse_operations operations; 33 | }; 34 | 35 | typedef struct dirlist dirlist; 36 | struct dirlist 37 | { 38 | dir *dir; 39 | dirlist *next; 40 | }; 41 | 42 | struct sharebox 43 | { 44 | pthread_mutex_t rwlock; 45 | const char *reporoot; 46 | bool deep_replicate; 47 | const char *write_callback; 48 | dirlist *dirs; 49 | }; 50 | 51 | extern struct sharebox sharebox; 52 | 53 | #endif /*__COMMON_H__ */ 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | I am no longer developing this project. For a promising alternative, see https://bazil.org/ 2 | 3 | --- 4 | 5 | Here is sharebox, a project of a distributed synchronizing filesystem. 6 | 7 | The problem 8 | =========== 9 | 10 | We want a filesystem that will synchronize arbitrary data between several 11 | machines. This filesystem has to be simple, and to provide as efficiently 12 | as possible a set of minimum features: 13 | 14 | - *Offline access*: it should be possible to modify data while not being 15 | connected to the internet, and get the data synchronized when the 16 | connection is restored. 17 | - *Conflict handling*: if the same document is modified in several places, 18 | the system should keep both versions and yet provide a simple way to 19 | choose between the two versions. Ideally the remote conflicting version 20 | should be shown as a hidden file. Removing this file should resolve the 21 | conflict in favor of the local version. Moving this file as the new 22 | version should resolve the conflict in favor of the remote version. 23 | True merges should be avoided as they could be both confusing and 24 | difficult to handle. Solved conflicts should propagate: if the conflict 25 | between A and B is solved on A, B should follow the same choice. 26 | - *Versioning*: It should be able to automatically keep several versions 27 | of document. 28 | - *Efficient storage*: Versioning must not induce a forever growing size. 29 | It should provide tools to automatically clean the filesystem. 30 | - *Useability*: Users should not have to be aware of the internals. 31 | Preferably, we should avoid creating special commands. Browsing the 32 | history should be done through regular 'ls' and 'cd' commands. Cleaning 33 | old versions of deleting snapshot should be doable through 'rm'. Moving 34 | content between machines should be doable through 'mv'. 35 | - *Battery friendliness*: The filesystem should not force the user to run 36 | updates every time a file is modified. Instead, it should let the user 37 | schedule the synchronizations. Still, callbacks have to be provided in 38 | for those who wish to have these instant updates. 39 | 40 | Planned interface 41 | ================= 42 | 43 | Here is how the user should see the filesystem: 44 | 45 | mnt/ 46 | |-regular files 47 | |-.sharebox/ 48 | |-history/ 49 | |-peers/ 50 | 51 | The test suite gives a sequence of use cases that may be interesting to 52 | browse if you want to understand how sharebox should be used in the end. 53 | 54 | Internal storage 55 | ================ 56 | 57 | Storage should be done through git-annex. Default for each file should be 58 | to be versioned through git-annex. Transfers should happen only when files 59 | are actually opened (through the open() syscall). In order to allow the 60 | user to use git inside the filesystem, the .git directory of sharebox has 61 | to be removed from the tree. This is achieved easily by moving the files 62 | in a subtree: 63 | 64 | fs/ 65 | |-.git/ 66 | |-files/-regular files (links handled through git-annex) 67 | -------------------------------------------------------------------------------- /git-annex.c: -------------------------------------------------------------------------------- 1 | #include "git-annex.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /* 14 | * wrapper around system() to execute a formatted command. Used as: 15 | * fmt_system("mycommand %s", "my args"); 16 | */ 17 | static int fmt_system(const char *format, ...) 18 | { 19 | va_list ap; 20 | size_t ARG_MAX; 21 | char *command; 22 | int status; 23 | 24 | ARG_MAX = sysconf(_SC_ARG_MAX); 25 | command = malloc(ARG_MAX); 26 | 27 | va_start(ap, format); 28 | vsnprintf(command, ARG_MAX, format, ap); 29 | va_end(ap); 30 | 31 | printf("%s\n", command); 32 | status = system(command); 33 | free(command); 34 | return status; 35 | } 36 | 37 | int git_annex_unlock(const char *repodir, const char *path) 38 | { 39 | chdir(repodir); 40 | return fmt_system("git annex unlock -- \"%s\"", 41 | path + strlen(repodir) + 1); 42 | } 43 | 44 | int git_annex_add(const char *repodir, const char *path) 45 | { 46 | chdir(repodir); 47 | return fmt_system("git annex add -- \"%s\"", 48 | path + strlen(repodir) + 1); 49 | } 50 | 51 | int git_annex_get(const char *repodir, const char *path, 52 | const char *branch) 53 | { 54 | int res; 55 | chdir(repodir); 56 | if (branch) 57 | fmt_system("git checkout %s", branch); 58 | res = fmt_system("git annex get -- \"%s\"", 59 | path + strlen(repodir) + 1); 60 | if (branch) 61 | fmt_system("git checkout git-annex"); 62 | return res; 63 | } 64 | 65 | int git_add(const char *repodir, const char *path) 66 | { 67 | chdir(repodir); 68 | return fmt_system("git add -- \"%s\"", 69 | path + strlen(repodir) + 1); 70 | } 71 | 72 | int git_commit(const char *repodir, const char *format, ...) 73 | { 74 | va_list ap; 75 | size_t ARG_MAX; 76 | char *message; 77 | 78 | ARG_MAX = sysconf(_SC_ARG_MAX); 79 | message = malloc(ARG_MAX - 16); 80 | 81 | va_start(ap, format); 82 | vsnprintf(message, ARG_MAX, format, ap); 83 | va_end(ap); 84 | 85 | chdir(repodir); 86 | return fmt_system("git commit -m \"%s\"", message); 87 | } 88 | 89 | int git_rm(const char *repodir, const char *path) 90 | { 91 | chdir(repodir); 92 | return fmt_system("git rm -- \"%s\"", path + strlen(repodir) + 1); 93 | } 94 | 95 | int git_mv(const char *repodir, const char *old, const char *new) 96 | { 97 | chdir(repodir); 98 | return fmt_system("git mv -- \"%s\" \"%s\"", 99 | old + strlen(repodir) + 1, new + strlen(repodir) + 1); 100 | } 101 | 102 | int git_annexed(const char *repodir, const char *path) 103 | { 104 | struct stat st; 105 | char realpathbuf[FILENAME_MAX]; 106 | char gitannexpathbuf[FILENAME_MAX]; 107 | snprintf(gitannexpathbuf, FILENAME_MAX, "%s/.git/annex/objects", repodir); 108 | if (lstat(path, &st) == -1) 109 | perror(path); 110 | if (!S_ISLNK(st.st_mode)) 111 | return 0; 112 | if (realpath(path, realpathbuf) == NULL) 113 | perror(path); 114 | return (strncmp(realpathbuf, gitannexpathbuf, strlen(gitannexpathbuf)) == 0); 115 | } 116 | 117 | int git_ignored(const char *repodir, const char *path) 118 | { 119 | FILE *pipe; 120 | char buf[BUFSIZ], command[256 + FILENAME_MAX], *p; 121 | int res; 122 | 123 | chdir(repodir); 124 | 125 | snprintf(command, sizeof command, 126 | "git ls-files -c -o -d -m --full-name -- \"%s\"", 127 | path + strlen(repodir) + 1); 128 | 129 | res = 1; 130 | if ((pipe = popen(command, "r" )) == NULL) 131 | return -1; 132 | while (fgets(buf, sizeof buf, pipe) != NULL || !feof(pipe)) { 133 | if ((p = strchr(buf, '\n'))) 134 | *p = '\0'; 135 | if (strcmp(buf, path) == 0) 136 | res = 0; 137 | } 138 | if (pclose(pipe) == -1) 139 | return -1; 140 | return res; 141 | } 142 | 143 | namelist* git_branches(const char *repodir) 144 | { 145 | FILE *pipe; 146 | char buf[BUFSIZ], *p; 147 | namelist *res, *curr; 148 | res = curr = NULL; 149 | 150 | chdir(repodir); 151 | if ((pipe = popen("git branch", "r")) == NULL) 152 | return NULL; 153 | 154 | while (fgets(buf, sizeof buf, pipe) != NULL || !feof(pipe)) { 155 | if ((p = strchr(buf, '\n'))) 156 | *p = '\0'; 157 | if (strcmp(buf + 2, "git-annex") != 0) { 158 | namelist *b = malloc(sizeof(namelist)); 159 | strcpy(b->name, buf + 2); 160 | b->next = NULL; 161 | if (curr) { 162 | curr->next = b; 163 | curr = b; 164 | } 165 | else 166 | res = curr = b; 167 | } 168 | } 169 | if (pclose(pipe) == -1) 170 | return NULL; 171 | 172 | return res; 173 | } 174 | 175 | namelist* conflicting_files(const char *repodir, const char *path, 176 | const char *branch) 177 | { 178 | FILE *pipe; 179 | char buf[BUFSIZ], *p, *s; 180 | namelist *res, *curr, *check, *n; 181 | 182 | if (strncmp(branch, "git-annex", strlen("git-annex") == 0)) 183 | return NULL; 184 | 185 | chdir(repodir); 186 | fmt_system("git checkout %s", branch); 187 | fmt_system("git merge git-annex"); 188 | 189 | if ((pipe = popen("git ls-files -u", "r")) == NULL) 190 | return NULL; 191 | 192 | res = curr = NULL; 193 | while (fgets(buf, sizeof buf, pipe) != NULL || !feof(pipe)) { 194 | /* remove trailing \n */ 195 | if ((p = strchr(buf, '\n'))) 196 | *p = '\0'; 197 | /* move after the \t */ 198 | if (!(s = strchr(buf, '\t'))) 199 | break; 200 | n = malloc(sizeof(namelist)); 201 | /* skip the directories */ 202 | if (strstr(s + 1, path) && !strchr((s + 1 + strlen(path)), '/')) { 203 | strncpy(n->name, s + 1, sizeof(n->name)); 204 | } 205 | /* remove if already there */ 206 | for (check = res; check->next != NULL; check = check->next) { 207 | if (strcmp(curr->name, check->name) == 0) { 208 | free(n); 209 | n = NULL; 210 | } 211 | } 212 | if (n) { 213 | if (!res) 214 | res = curr = n; 215 | else { 216 | curr->next = n; 217 | curr = curr->next; 218 | } 219 | } 220 | } 221 | 222 | if (pclose(pipe) == -1) 223 | return NULL; 224 | fmt_system("git reset --hard"); 225 | fmt_system("git checkout git-annex"); 226 | 227 | return res; 228 | } 229 | 230 | void free_namelist(namelist *l) 231 | { 232 | namelist *curr, *next; 233 | if (l != NULL) { 234 | curr = l; 235 | next = l->next; 236 | free(curr); 237 | while (next != NULL) { 238 | curr = next; 239 | next = curr->next; 240 | free(curr); 241 | } 242 | } 243 | } 244 | 245 | void target(char target[FILENAME_MAX], const char *repodir, 246 | const char *path, const char *branch) 247 | { 248 | int res; 249 | chdir(repodir); 250 | fmt_system("git checkout %s", branch); 251 | res = readlink(path, target, FILENAME_MAX - 1); 252 | target[res] = '\0'; 253 | fmt_system("git checkout master"); 254 | } 255 | -------------------------------------------------------------------------------- /sharebox.c: -------------------------------------------------------------------------------- 1 | /* 2 | * sharebox 3 | */ 4 | 5 | #include "common.h" 6 | #include "slash.h" 7 | 8 | /* 9 | * Options parsing 10 | */ 11 | 12 | #define SHAREBOX_OPT(t, p, v) { t, offsetof(struct sharebox, p), v } 13 | 14 | enum { 15 | KEY_HELP, 16 | KEY_VERSION, 17 | }; 18 | 19 | static struct fuse_opt sharebox_opts[] = { 20 | SHAREBOX_OPT("deep_replicate", deep_replicate, false), 21 | SHAREBOX_OPT("write_callback=%s", write_callback, 0), 22 | FUSE_OPT_KEY("-V", KEY_VERSION), 23 | FUSE_OPT_KEY("--version", KEY_VERSION), 24 | FUSE_OPT_KEY("-h", KEY_HELP), 25 | FUSE_OPT_KEY("--help", KEY_HELP), 26 | FUSE_OPT_END 27 | }; 28 | 29 | /* 30 | * FS operations 31 | * 32 | * sharebox-fs embeds more than one filesystem. 33 | * 34 | * "/.sharebox/remotes/" allows to manage the remotes 35 | * "/.sharebox/revisions/" allows to browse the history 36 | * "/.sharebox/unreferenced/" allows to see what files you can trash 37 | * "/" contains the real versionning. 38 | * 39 | * To separate the logic, we match the path of the files we operate on and 40 | * switch to the relevant operation. To do so, we go through the "dirlist" 41 | * attribute of sharebox until we see a directory that match. 42 | * 43 | * A special case for "rename": We refuse moving files outside a 44 | * filesystem. As a consequence, if the files do not both match, it is an 45 | * error. 46 | */ 47 | 48 | struct sharebox sharebox; 49 | 50 | static int sharebox_getattr(const char *path, struct stat *stbuf) 51 | { 52 | dirlist *l; 53 | dir *d; 54 | for (l = sharebox.dirs; l != NULL; l = l->next) { 55 | d = l->dir; 56 | if (strncmp(path, d->name, strlen(d->name)) == 0) 57 | return d->operations.getattr(path, stbuf); 58 | } 59 | return -EACCES; 60 | } 61 | 62 | static int sharebox_access(const char *path, int mask) 63 | { 64 | dirlist *l; 65 | dir *d; 66 | for (l = sharebox.dirs; l != NULL; l = l->next) { 67 | d = l->dir; 68 | if (strncmp(path, d->name, strlen(d->name)) == 0) 69 | return d->operations.access(path, mask); 70 | } 71 | return -EACCES; 72 | } 73 | 74 | static int sharebox_readlink(const char *path, char *buf, size_t size) 75 | { 76 | dirlist *l; 77 | dir *d; 78 | for (l = sharebox.dirs; l != NULL; l = l->next) { 79 | d = l->dir; 80 | if (strncmp(path, d->name, strlen(d->name)) == 0) 81 | return d->operations.readlink(path, buf, size); 82 | } 83 | return -EACCES; 84 | } 85 | 86 | 87 | static int sharebox_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 88 | off_t offset, struct fuse_file_info *fi) 89 | { 90 | dirlist *l; 91 | dir *d; 92 | for (l = sharebox.dirs; l != NULL; l = l->next) { 93 | d = l->dir; 94 | if (strncmp(path, d->name, strlen(d->name)) == 0) 95 | return d->operations.readdir(path, buf, filler, offset, fi); 96 | } 97 | return -EACCES; 98 | } 99 | 100 | static int sharebox_mknod(const char *path, mode_t mode, dev_t rdev) 101 | { 102 | dirlist *l; 103 | dir *d; 104 | for (l = sharebox.dirs; l != NULL; l = l->next) { 105 | d = l->dir; 106 | if (strncmp(path, d->name, strlen(d->name)) == 0) 107 | return d->operations.mknod(path, mode, rdev); 108 | } 109 | return -EACCES; 110 | } 111 | 112 | static int sharebox_mkdir(const char *path, mode_t mode) 113 | { 114 | dirlist *l; 115 | dir *d; 116 | for (l = sharebox.dirs; l != NULL; l = l->next) { 117 | d = l->dir; 118 | if (strncmp(path, d->name, strlen(d->name)) == 0) 119 | return d->operations.mkdir(path, mode); 120 | } 121 | return -EACCES; 122 | } 123 | 124 | static int sharebox_unlink(const char *path) 125 | { 126 | dirlist *l; 127 | dir *d; 128 | for (l = sharebox.dirs; l != NULL; l = l->next) { 129 | d = l->dir; 130 | if (strncmp(path, d->name, strlen(d->name)) == 0) 131 | return d->operations.unlink(path); 132 | } 133 | return -EACCES; 134 | } 135 | 136 | static int sharebox_rmdir(const char *path) 137 | { 138 | dirlist *l; 139 | dir *d; 140 | for (l = sharebox.dirs; l != NULL; l = l->next) { 141 | d = l->dir; 142 | if (strncmp(path, d->name, strlen(d->name)) == 0) 143 | return d->operations.rmdir(path); 144 | } 145 | return -EACCES; 146 | } 147 | 148 | static int sharebox_symlink(const char *target, const char *linkname) 149 | { 150 | dirlist *l; 151 | dir *d; 152 | for (l = sharebox.dirs; l != NULL; l = l->next) { 153 | d = l->dir; 154 | if (strncmp(linkname, d->name, strlen(d->name)) == 0) 155 | return d->operations.symlink(target, linkname); 156 | } 157 | return -EACCES; 158 | } 159 | 160 | static int sharebox_rename(const char *from, const char *to) 161 | { 162 | dirlist *l; 163 | dir *d; 164 | for (l = sharebox.dirs; l != NULL; l = l->next) { 165 | d = l->dir; 166 | /* /!\ we only accept renaming inside the same fs */ 167 | if ((strncmp(from, d->name, strlen(d->name)) == 0) && 168 | (strncmp(to, d->name, strlen(d->name) == 0))) 169 | return d->operations.rename(from, to); 170 | } 171 | return -EACCES; 172 | } 173 | 174 | static int sharebox_chmod(const char *path, mode_t mode) 175 | { 176 | dirlist *l; 177 | dir *d; 178 | for (l = sharebox.dirs; l != NULL; l = l->next) { 179 | d = l->dir; 180 | if (strncmp(path, d->name, strlen(d->name)) == 0) 181 | return d->operations.chmod(path, mode); 182 | } 183 | return -EACCES; 184 | } 185 | 186 | static int sharebox_chown(const char *path, uid_t uid, gid_t gid) 187 | { 188 | dirlist *l; 189 | dir *d; 190 | for (l = sharebox.dirs; l != NULL; l = l->next) { 191 | d = l->dir; 192 | if (strncmp(path, d->name, strlen(d->name)) == 0) 193 | return d->operations.chown(path, uid, gid); 194 | } 195 | return -EACCES; 196 | } 197 | 198 | static int sharebox_truncate(const char *path, off_t size) 199 | { 200 | dirlist *l; 201 | dir *d; 202 | for (l = sharebox.dirs; l != NULL; l = l->next) { 203 | d = l->dir; 204 | if (strncmp(path, d->name, strlen(d->name)) == 0) 205 | return d->operations.truncate(path, size); 206 | } 207 | return -EACCES; 208 | } 209 | 210 | static int sharebox_utimens(const char *path, const struct timespec ts[2]) 211 | { 212 | dirlist *l; 213 | dir *d; 214 | for (l = sharebox.dirs; l != NULL; l = l->next) { 215 | d = l->dir; 216 | if (strncmp(path, d->name, strlen(d->name)) == 0) 217 | return d->operations.utimens(path, ts); 218 | } 219 | return -EACCES; 220 | } 221 | 222 | static int sharebox_open(const char *path, struct fuse_file_info *fi) 223 | { 224 | dirlist *l; 225 | dir *d; 226 | for (l = sharebox.dirs; l != NULL; l = l->next) { 227 | d = l->dir; 228 | if (strncmp(path, d->name, strlen(d->name)) == 0) 229 | return d->operations.open(path, fi); 230 | } 231 | return -EACCES; 232 | } 233 | 234 | static int sharebox_read(const char *path, char *buf, size_t size, off_t offset, 235 | struct fuse_file_info *fi) 236 | { 237 | dirlist *l; 238 | dir *d; 239 | for (l = sharebox.dirs; l != NULL; l = l->next) { 240 | d = l->dir; 241 | if (strncmp(path, d->name, strlen(d->name)) == 0) 242 | return d->operations.read(path, buf, size, offset, fi); 243 | } 244 | return -EACCES; 245 | } 246 | 247 | static int sharebox_write(const char *path, const char *buf, size_t size, 248 | off_t offset, struct fuse_file_info *fi) 249 | { 250 | dirlist *l; 251 | dir *d; 252 | for (l = sharebox.dirs; l != NULL; l = l->next) { 253 | d = l->dir; 254 | if (strncmp(path, d->name, strlen(d->name)) == 0) 255 | return d->operations.write(path, buf, size, offset, fi); 256 | } 257 | return -EACCES; 258 | } 259 | 260 | static int sharebox_release(const char *path, struct fuse_file_info *fi) 261 | { 262 | dirlist *l; 263 | dir *d; 264 | for (l = sharebox.dirs; l != NULL; l = l->next) { 265 | d = l->dir; 266 | if (strncmp(path, d->name, strlen(d->name)) == 0) 267 | return d->operations.release(path, fi); 268 | } 269 | return 0; 270 | } 271 | 272 | static int sharebox_statfs(const char *path, struct statvfs *stbuf) 273 | { 274 | dirlist *l; 275 | dir *d; 276 | for (l = sharebox.dirs; l != NULL; l = l->next) { 277 | d = l->dir; 278 | if (strncmp(path, d->name, strlen(d->name)) == 0) 279 | return d->operations.statfs(path, stbuf); 280 | } 281 | return -EACCES; 282 | } 283 | 284 | static struct fuse_operations sharebox_oper = { 285 | .getattr = sharebox_getattr, 286 | .access = sharebox_access, 287 | .readlink = sharebox_readlink, 288 | .readdir = sharebox_readdir, 289 | .mknod = sharebox_mknod, 290 | .mkdir = sharebox_mkdir, 291 | .symlink = sharebox_symlink, 292 | .unlink = sharebox_unlink, 293 | .rmdir = sharebox_rmdir, 294 | .rename = sharebox_rename, 295 | .chmod = sharebox_chmod, 296 | .chown = sharebox_chown, 297 | .truncate = sharebox_truncate, 298 | .utimens = sharebox_utimens, 299 | .open = sharebox_open, 300 | .read = sharebox_read, 301 | .write = sharebox_write, 302 | .release = sharebox_release, 303 | .statfs = sharebox_statfs, 304 | }; 305 | 306 | static dirlist *init_dirlist() 307 | { 308 | dirlist *l; 309 | dir *slash; 310 | 311 | l = malloc(sizeof (dirlist)); 312 | slash = malloc (sizeof (dir)); 313 | init_slash(slash); 314 | 315 | l->dir = slash; 316 | l->next = NULL; 317 | 318 | return l; 319 | } 320 | 321 | static int 322 | sharebox_opt_proc 323 | (void *data, const char *arg, int key, struct fuse_args *outargs) 324 | { 325 | struct stat st; 326 | char files[FILENAME_MAX]; 327 | switch (key) { 328 | case KEY_HELP: 329 | fprintf(stderr, 330 | "usage: %s [options]\n" 331 | "\n" 332 | "general options:\n" 333 | " -o opt,[opt...] mount options\n" 334 | " -h --help print help\n" 335 | " -V --version print version\n" 336 | "\n" 337 | "sharebox options:\n" 338 | " -o deep_replicate replicate deeply\n" 339 | " -o write_callback program to call when a file has been written\n" 340 | "\n", outargs->argv[0]); 341 | fuse_opt_add_arg(outargs, "-ho"); 342 | fuse_main(outargs->argc, outargs->argv, &sharebox_oper, NULL); 343 | exit(1); 344 | case KEY_VERSION: 345 | fprintf(stderr, "sharebox version %s\n", SHAREBOX_VERSION); 346 | fuse_opt_add_arg(outargs, "--version"); 347 | fuse_main(outargs->argc, outargs->argv, &sharebox_oper, NULL); 348 | exit(0); 349 | case FUSE_OPT_KEY_NONOPT: 350 | if (!sharebox.reporoot) { 351 | if (stat(arg, &st) == -1){ 352 | perror(arg); 353 | exit(1); 354 | } 355 | if ((sharebox.reporoot = realpath(arg, NULL)) == NULL){ 356 | perror(sharebox.reporoot); 357 | exit(1); 358 | } 359 | snprintf(files, FILENAME_MAX, "%s/files", sharebox.reporoot); 360 | if (stat(files, &st) == -1){ 361 | perror(files); 362 | fprintf(stderr, "Missing /files/ (did mkfs do its job?)\n"); 363 | exit(1); 364 | } 365 | return 0; 366 | } 367 | if (!sharebox.dirs) 368 | sharebox.dirs = init_dirlist(); 369 | return 1; 370 | } 371 | return 1; 372 | } 373 | 374 | int main(int argc, char *argv[]) 375 | { 376 | struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 377 | memset(&sharebox, 0, sizeof(sharebox)); 378 | fuse_opt_parse(&args, &sharebox, sharebox_opts, sharebox_opt_proc); 379 | umask(0); 380 | return fuse_main(args.argc, args.argv, &sharebox_oper, NULL); 381 | } 382 | -------------------------------------------------------------------------------- /slash.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Operations that happen in "/" 3 | */ 4 | 5 | #include "slash.h" 6 | #include "git-annex.h" 7 | 8 | // TODO: fix the errnos (save them as soon as they happen) 9 | 10 | /* 11 | * Helpers 12 | */ 13 | 14 | static void fullpath(char fpath[FILENAME_MAX], const char *path) 15 | { 16 | strcpy(fpath, sharebox.reporoot); 17 | strncat(fpath, "/files", FILENAME_MAX); 18 | strncat(fpath, path, FILENAME_MAX); 19 | } 20 | 21 | static int ondisk(const char *lnk) 22 | { 23 | struct stat st; 24 | return (stat(lnk, &st) != -1); 25 | } 26 | 27 | /* 28 | * FS Operations 29 | */ 30 | 31 | static int slash_getattr(const char *path, struct stat *stbuf) 32 | { 33 | int res; 34 | char fpath[FILENAME_MAX]; 35 | fullpath(fpath, path); 36 | 37 | res = lstat(fpath, stbuf); 38 | if (git_annexed(sharebox.reporoot, fpath)) { 39 | if (ondisk(fpath)) { 40 | res = stat(fpath, stbuf); 41 | } else { 42 | stbuf->st_mode &= ~S_IFMT; 43 | stbuf->st_mode |= S_IFREG; /* fake regular file */ 44 | stbuf->st_size = 0; /* fake size = 0 */ 45 | } 46 | stbuf->st_mode |= S_IWUSR; /* fake writable */ 47 | } 48 | if (res == -1) 49 | return -errno; 50 | 51 | return 0; 52 | } 53 | 54 | static int slash_access(const char *path, int mask) 55 | { 56 | int res; 57 | 58 | char fpath[FILENAME_MAX]; 59 | fullpath(fpath, path); 60 | 61 | if (git_annexed(sharebox.reporoot, fpath)) { 62 | if (ondisk(fpath)) 63 | res = access(fpath, mask & ~W_OK); 64 | else 65 | res = -EACCES; 66 | } 67 | else 68 | res = access(fpath, mask); 69 | 70 | if (res == -1) 71 | res = -errno; 72 | 73 | return res; 74 | } 75 | 76 | static int slash_readlink(const char *path, char *buf, size_t size) 77 | { 78 | int res; 79 | 80 | char fpath[FILENAME_MAX]; 81 | fullpath(fpath, path); 82 | 83 | res = readlink(fpath, buf, size - 1); 84 | if (res == -1) 85 | return -errno; 86 | 87 | buf[res] = '\0'; 88 | return 0; 89 | } 90 | 91 | 92 | static int slash_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 93 | off_t offset, struct fuse_file_info *fi) 94 | { 95 | DIR *dp; 96 | struct dirent *de; 97 | (void) offset; 98 | (void) fi; 99 | 100 | char fpath[FILENAME_MAX]; 101 | fullpath(fpath, path); 102 | 103 | dp = opendir(fpath); 104 | if (dp == NULL) 105 | return -errno; 106 | 107 | while ((de = readdir(dp)) != NULL) { 108 | if (filler(buf, de->d_name, NULL, 0)) 109 | break; 110 | } 111 | closedir(dp); 112 | 113 | /* We then list conflicting files */ 114 | /* 115 | namelist *branch, *b; 116 | branch = git_branches(sharebox.reporoot); 117 | for (b = branch; b != NULL; b = b->next) { 118 | namelist *files, *f; 119 | files = conflicting_files(sharebox.reporoot, fpath, b->name); 120 | for (f = files; f != NULL; f = f->next) { 121 | char name[FILENAME_MAX]; 122 | strncpy(name, ".", FILENAME_MAX); 123 | strncat(name, branch->name, FILENAME_MAX); 124 | strncat(name, ".", FILENAME_MAX); 125 | strncat(name, f->name, FILENAME_MAX); 126 | strncat(name, ".conflict", FILENAME_MAX); 127 | if (filler(buf, name, NULL, 0)) 128 | break; 129 | } 130 | free_namelist(files); 131 | } 132 | free_namelist(branch); 133 | */ 134 | 135 | return 0; 136 | } 137 | 138 | static int slash_mknod(const char *path, mode_t mode, dev_t rdev) 139 | { 140 | pthread_mutex_lock(&sharebox.rwlock); 141 | 142 | int res; 143 | 144 | char fpath[FILENAME_MAX]; 145 | fullpath(fpath, path); 146 | 147 | /* On Linux this could just be 'mknod(path, mode, rdev)' but this 148 | is more portable */ 149 | if (S_ISREG(mode)) { 150 | res = open(fpath, O_CREAT | O_EXCL | O_WRONLY, mode); 151 | if (res >= 0) 152 | res = close(res); 153 | } else if (S_ISFIFO(mode)) 154 | res = mkfifo(fpath, mode); 155 | else 156 | res = mknod(fpath, mode, rdev); 157 | 158 | pthread_mutex_unlock(&sharebox.rwlock); 159 | 160 | if (res == -1) 161 | return -errno; 162 | return 0; 163 | } 164 | 165 | static int slash_mkdir(const char *path, mode_t mode) 166 | { 167 | int res; 168 | 169 | char fpath[FILENAME_MAX]; 170 | fullpath(fpath, path); 171 | 172 | res = mkdir(fpath, mode); 173 | 174 | if (res == -1) 175 | return -errno; 176 | return 0; 177 | } 178 | 179 | static int slash_unlink(const char *path) 180 | { 181 | pthread_mutex_lock(&sharebox.rwlock); 182 | 183 | int res; 184 | 185 | char fpath[FILENAME_MAX]; 186 | fullpath(fpath, path); 187 | 188 | res = unlink(fpath); 189 | 190 | if (!git_ignored(sharebox.reporoot, fpath)){ 191 | git_rm(sharebox.reporoot, fpath); 192 | git_commit(sharebox.reporoot, "removed %s", path + 1); 193 | } 194 | 195 | pthread_mutex_unlock(&sharebox.rwlock); 196 | 197 | if (res == -1) 198 | return -errno; 199 | 200 | return 0; 201 | } 202 | 203 | static int slash_rmdir(const char *path) 204 | { 205 | int res; 206 | 207 | char fpath[FILENAME_MAX]; 208 | fullpath(fpath, path); 209 | 210 | res = rmdir(fpath); 211 | if (res == -1) 212 | return -errno; 213 | 214 | return 0; 215 | } 216 | 217 | static int slash_symlink(const char *target, const char *linkname) 218 | { 219 | pthread_mutex_lock(&sharebox.rwlock); 220 | 221 | int res; 222 | 223 | char flinkname[FILENAME_MAX]; 224 | fullpath(flinkname, linkname); 225 | 226 | res = symlink(target, flinkname); 227 | 228 | if (!git_ignored(sharebox.reporoot, flinkname)){ 229 | git_add(sharebox.reporoot, flinkname); 230 | git_commit(sharebox.reporoot, "created symlink %s->%s", linkname + 1, target); 231 | } 232 | 233 | pthread_mutex_unlock(&sharebox.rwlock); 234 | 235 | if (res == -1) 236 | return -errno; 237 | 238 | return 0; 239 | } 240 | 241 | static int slash_rename(const char *from, const char *to) 242 | { 243 | pthread_mutex_lock(&sharebox.rwlock); 244 | 245 | int res; 246 | bool from_ignored; 247 | bool to_ignored; 248 | 249 | char ffrom[FILENAME_MAX]; 250 | char fto[FILENAME_MAX]; 251 | 252 | fullpath(ffrom, from); 253 | fullpath(fto, to); 254 | 255 | /* proceed to rename */ 256 | from_ignored = git_ignored(sharebox.reporoot, ffrom); 257 | res = rename(ffrom, fto); 258 | to_ignored = git_ignored(sharebox.reporoot, fto); 259 | 260 | if (res != -1) { 261 | /* moved ignored to ignored (nothing) */ 262 | 263 | /* moved ignored to non ignored*/ 264 | if (from_ignored && !to_ignored){ 265 | git_annex_add(sharebox.reporoot, fto); 266 | git_add(sharebox.reporoot, fto); /* this ensures links will be added too */ 267 | } 268 | /* moved non ignored to ignored */ 269 | if (!from_ignored && to_ignored){ 270 | git_rm(sharebox.reporoot, ffrom); 271 | } 272 | /* moved non ignored to non ignored */ 273 | if (!from_ignored && !to_ignored){ 274 | git_mv(sharebox.reporoot, ffrom, fto); 275 | } 276 | 277 | git_commit(sharebox.reporoot, "moved %s to %s", from+1, to+1); 278 | } 279 | 280 | pthread_mutex_unlock(&sharebox.rwlock); 281 | 282 | if (res == -1) 283 | return -errno; 284 | 285 | return 0; 286 | } 287 | 288 | static int slash_chmod(const char *path, mode_t mode) 289 | { 290 | pthread_mutex_lock(&sharebox.rwlock); 291 | 292 | int res; 293 | 294 | char fpath[FILENAME_MAX]; 295 | fullpath(fpath, path); 296 | 297 | git_annex_unlock(sharebox.reporoot, fpath); 298 | 299 | res = chmod(fpath, mode); 300 | 301 | git_annex_add(sharebox.reporoot, fpath); 302 | git_commit(sharebox.reporoot, "chmoded %s to %o", path+1, mode); 303 | 304 | pthread_mutex_unlock(&sharebox.rwlock); 305 | 306 | if (res == -1) 307 | return -errno; 308 | 309 | return 0; 310 | } 311 | 312 | static int slash_chown(const char *path, uid_t uid, gid_t gid) 313 | { 314 | pthread_mutex_lock(&sharebox.rwlock); 315 | 316 | int res; 317 | 318 | char fpath[FILENAME_MAX]; 319 | fullpath(fpath, path); 320 | 321 | git_annex_unlock(sharebox.reporoot, fpath); 322 | 323 | res = lchown(fpath, uid, gid); 324 | 325 | git_annex_add(sharebox.reporoot, fpath); 326 | git_commit(sharebox.reporoot, "chmown on %s", path+1); 327 | 328 | pthread_mutex_unlock(&sharebox.rwlock); 329 | 330 | if (res == -1) 331 | return -errno; 332 | 333 | return 0; 334 | } 335 | 336 | static int slash_truncate(const char *path, off_t size) 337 | { 338 | pthread_mutex_lock(&sharebox.rwlock); 339 | 340 | int res; 341 | 342 | char fpath[FILENAME_MAX]; 343 | fullpath(fpath, path); 344 | 345 | git_annex_unlock(sharebox.reporoot, fpath); 346 | 347 | res = truncate(fpath, size); 348 | 349 | git_annex_add(sharebox.reporoot, fpath); 350 | git_commit(sharebox.reporoot, "truncated on %s", path+1); 351 | 352 | pthread_mutex_unlock(&sharebox.rwlock); 353 | 354 | if (res == -1) 355 | return -errno; 356 | 357 | return 0; 358 | } 359 | 360 | static int slash_utimens(const char *path, const struct timespec ts[2]) 361 | { 362 | pthread_mutex_lock(&sharebox.rwlock); 363 | 364 | int res; 365 | struct timeval tv[2]; 366 | char fpath[FILENAME_MAX]; 367 | 368 | tv[0].tv_sec = ts[0].tv_sec; 369 | tv[0].tv_usec = ts[0].tv_nsec / 1000; 370 | tv[1].tv_sec = ts[1].tv_sec; 371 | tv[1].tv_usec = ts[1].tv_nsec / 1000; 372 | 373 | fullpath(fpath, path); 374 | 375 | git_annex_unlock(sharebox.reporoot, fpath); 376 | 377 | res = utimes(fpath, tv); 378 | 379 | git_annex_add(sharebox.reporoot, fpath); 380 | git_commit(sharebox.reporoot, "utimens on %s", path+1); 381 | 382 | pthread_mutex_unlock(&sharebox.rwlock); 383 | 384 | if (res == -1) 385 | return -errno; 386 | 387 | return 0; 388 | } 389 | 390 | static int slash_open(const char *path, struct fuse_file_info *fi) 391 | { 392 | int res; 393 | int flags; 394 | 395 | char fpath[FILENAME_MAX]; 396 | fullpath(fpath, path); 397 | 398 | flags=fi->flags; 399 | 400 | if (git_annexed(sharebox.reporoot, fpath)) 401 | /* Get the file on the fly, remove W_OK if it was requested */ 402 | if (!ondisk(fpath)) 403 | git_annex_get(sharebox.reporoot, fpath, NULL); 404 | if (!ondisk(fpath)) 405 | return -EACCES; 406 | flags &= ~W_OK; 407 | 408 | res = open(fpath, flags); 409 | 410 | if (res == 1) 411 | return -errno; 412 | 413 | close(res); 414 | 415 | return 0; 416 | } 417 | 418 | static int slash_read(const char *path, char *buf, size_t size, off_t offset, 419 | struct fuse_file_info *fi) 420 | { 421 | pthread_mutex_lock(&sharebox.rwlock); 422 | 423 | int fd; 424 | int res; 425 | (void) fi; 426 | 427 | char fpath[FILENAME_MAX]; 428 | fullpath(fpath, path); 429 | 430 | if ((fd = open(fpath, O_RDONLY)) != -1) 431 | if ((res = pread(fd, buf, size, offset)) != -1) 432 | close(fd); 433 | 434 | pthread_mutex_unlock(&sharebox.rwlock); 435 | 436 | if (fd == -1 || res == -1) 437 | return -errno; 438 | return res; 439 | } 440 | 441 | static int slash_write(const char *path, const char *buf, size_t size, 442 | off_t offset, struct fuse_file_info *fi) 443 | { 444 | pthread_mutex_lock(&sharebox.rwlock); 445 | 446 | int fd; 447 | int res; 448 | (void) fi; 449 | 450 | char fpath[FILENAME_MAX]; 451 | fullpath(fpath, path); 452 | 453 | if (git_annexed(sharebox.reporoot, fpath)) 454 | git_annex_unlock(sharebox.reporoot, fpath); 455 | 456 | if ((fd = open(fpath, O_WRONLY)) != -1) 457 | if((res = pwrite(fd, buf, size, offset)) != -1) 458 | close(fd); 459 | 460 | pthread_mutex_unlock(&sharebox.rwlock); 461 | 462 | if (fd == -1 || res == -1) 463 | return -errno; 464 | return res; 465 | } 466 | 467 | static int slash_release(const char *path, struct fuse_file_info *fi) 468 | { 469 | pthread_mutex_lock(&sharebox.rwlock); 470 | 471 | char fpath[FILENAME_MAX]; 472 | fullpath(fpath, path); 473 | 474 | if (!git_ignored(sharebox.reporoot, fpath)){ 475 | git_annex_add(sharebox.reporoot, fpath); 476 | git_commit(sharebox.reporoot, "released %s", path+1); 477 | } 478 | 479 | pthread_mutex_unlock(&sharebox.rwlock); 480 | 481 | return 0; 482 | } 483 | 484 | static int slash_statfs(const char *path, struct statvfs *stbuf) 485 | { 486 | int res; 487 | 488 | char fpath[FILENAME_MAX]; 489 | fullpath(fpath, path); 490 | 491 | res = statvfs(fpath, stbuf); 492 | if (res == -1) 493 | return -errno; 494 | 495 | return 0; 496 | } 497 | 498 | void init_slash(dir *d) 499 | { 500 | strcpy(d->name, "/"); 501 | (d->operations).getattr = slash_getattr; 502 | (d->operations).access = slash_access; 503 | (d->operations).readlink = slash_readlink; 504 | (d->operations).readdir = slash_readdir; 505 | (d->operations).mknod = slash_mknod; 506 | (d->operations).mkdir = slash_mkdir; 507 | (d->operations).symlink = slash_symlink; 508 | (d->operations).unlink = slash_unlink; 509 | (d->operations).rmdir = slash_rmdir; 510 | (d->operations).rename = slash_rename; 511 | (d->operations).chmod = slash_chmod; 512 | (d->operations).chown = slash_chown; 513 | (d->operations).truncate = slash_truncate; 514 | (d->operations).utimens = slash_utimens; 515 | (d->operations).open = slash_open; 516 | (d->operations).read = slash_read; 517 | (d->operations).write = slash_write; 518 | (d->operations).release = slash_release; 519 | (d->operations).statfs = slash_statfs; 520 | } 521 | -------------------------------------------------------------------------------- /tests/test_suite: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | . lib/functions 4 | 5 | fuse() 6 | { 7 | echo "Running tests from fuse" 8 | 9 | # create the filesystem 10 | mkdir -p sandbox/sharebox.fs 11 | mkfs -t sharebox sandbox/sharebox.fs > /dev/null 12 | 13 | # mount it 14 | mkdir -p sandbox/sharebox.mnt 15 | sharebox sandbox/sharebox.fs sandbox/sharebox.mnt 16 | 17 | # run the fuse tests 18 | assert_success fuse_tester $PWD/sandbox/sharebox.mnt 19 | 20 | # unmount 21 | fusermount -u -z sandbox/sharebox.mnt > /dev/null 22 | 23 | clean 24 | } 25 | 26 | sync_no_peers() 27 | { 28 | echo "Missing peer" 29 | 30 | # create the filesystem 31 | mkdir -p sandbox/sharebox.fs 32 | mkfs -t sharebox sandbox/sharebox.fs > /dev/null 33 | 34 | # mount it 35 | mkdir -p sandbox/sharebox.mnt 36 | sharebox sandbox/sharebox.fs sandbox/sharebox.mnt 37 | 38 | # trigger a synchronization 39 | touch sandbox/sharebox.mnt/.sharebox/peers/unexisting 40 | assert_fail test $? -eq 0 41 | 42 | # unmount the filesystems 43 | fusermount -u -z sandbox/sharebox.mnt > /dev/null 44 | 45 | clean 46 | } 47 | 48 | sync_bad_url() 49 | { 50 | echo "Bad url" 51 | 52 | # create the filesystem 53 | mkdir -p sandbox/sharebox.fs 54 | mkfs -t sharebox sandbox/sharebox.fs > /dev/null 55 | 56 | # mount it 57 | mkdir -p sandbox/sharebox.mnt 58 | sharebox sandbox/sharebox.fs sandbox/sharebox.mnt 59 | 60 | # add some peer with a bad url 61 | echo "some_bad_url" > sandbox/sharebox.mnt/.sharebox/peers/some_peer 62 | assert_fail test $? -eq 0 63 | 64 | # unmount the filesystems 65 | fusermount -u -z sandbox/sharebox.mnt > /dev/null 66 | 67 | clean 68 | } 69 | 70 | sync_success() 71 | { 72 | echo "Simple synchronization" 73 | 74 | # create the filesystems 75 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 76 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 77 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 78 | 79 | # mount them 80 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 81 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 82 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 83 | 84 | # add local as a peer of remote 85 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 86 | 87 | # create a file on local side 88 | echo "test_line" > sandbox/local/sharebox.mnt/test_file 89 | 90 | # trigger a synchronization on remote side 91 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 92 | assert_success test $? -eq 0 93 | 94 | # after sync, the file must exist 95 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 96 | 97 | # but diffing should fail because it is recorded as size 0 98 | assert_fail diff sandbox/local/sharebox.mnt/test_file sandbox/remote/sharebox.mnt/test_file 99 | 100 | # diffing should work the second time (the file was opened by the 101 | # first diff, so it has been downloaded) 102 | assert_success diff sandbox/local/sharebox.mnt/test_file sandbox/remote/sharebox.mnt/test_file 103 | 104 | # unmount the filesystems 105 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 106 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 107 | 108 | clean 109 | } 110 | 111 | sync_modify_url() 112 | { 113 | echo "change url" 114 | 115 | # create the filesystems 116 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 117 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 118 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 119 | 120 | # mount them 121 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 122 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 123 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 124 | 125 | # add local with a bad url 126 | echo "bad_url" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 127 | 128 | # create a file on local side 129 | echo "test_line" > sandbox/local/sharebox.mnt/test_file 130 | 131 | # triggering a synchronization on remote side should fail 132 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 133 | assert_fail test $? -eq 0 134 | 135 | # relocate the url 136 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 137 | 138 | # triggering a synchronization on remote side should now succed 139 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 140 | assert_success test $? -eq 0 141 | 142 | # after sync, the file must exist 143 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 144 | 145 | # but diffing should fail because it is recorded as size 0 146 | assert_fail diff sandbox/local/sharebox.mnt/test_file sandbox/remote/sharebox.mnt/test_file 147 | 148 | # diffing should work the second time (the file was opened by the 149 | # first diff, so it has been downloaded) 150 | assert_success diff sandbox/local/sharebox.mnt/test_file sandbox/remote/sharebox.mnt/test_file 151 | 152 | # unmount the filesystems 153 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 154 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 155 | 156 | clean 157 | } 158 | 159 | sync_remove_peer() 160 | { 161 | echo "removing a peer" 162 | 163 | # create the filesystems 164 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 165 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 166 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 167 | 168 | # mount them 169 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 170 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 171 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 172 | 173 | # add local as a peer of remote 174 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 175 | 176 | # trigger a synchronization on remote side 177 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 178 | 179 | # remove the peer "local" on remote side 180 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 181 | 182 | # trigger a synchronization on remote side 183 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 184 | assert_fail test $? -eq 0 185 | 186 | # unmount the filesystems 187 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 188 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 189 | 190 | clean 191 | } 192 | 193 | sync_normal_conflict() 194 | { 195 | echo "synchronization with normal conflict" 196 | 197 | # create the filesystems 198 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 199 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 200 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 201 | 202 | # mount them 203 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 204 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 205 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 206 | 207 | # make them peers 208 | echo "$PWD/sandbox/remote/sharebox.fs/master" > sandbox/local/sharebox.mnt/.sharebox/peers/remote 209 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 210 | 211 | # create a file on local side 212 | touch sandbox/local/sharebox.mnt/test_file 213 | 214 | # trigger a synchronization on remote side 215 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 216 | 217 | # test the file has been created on remote side 218 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 219 | 220 | # operate incompatible modifications on test_file 221 | echo "test_line_local" > sandbox/local/sharebox.mnt/test_file 222 | echo "test_line_remote" > sandbox/remote/sharebox.mnt/test_file 223 | 224 | # import the changes in local 225 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 226 | 227 | # a conflicting file should exist 228 | conflict=$(ls -a1 sandbox/remote/sharebox.mnt | grep ".test_file.remote") 229 | assert_success test $? -eq 0 230 | 231 | # it should contain the content of remote 232 | assert_success grep "test_line_remote" sandbox/local/sharebox.mnt/$conflict 233 | 234 | # unmount the filesystems 235 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 236 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 237 | 238 | clean 239 | } 240 | 241 | sync_resolve_normal_conflict_local() 242 | { 243 | echo "resolving a normal conflict in favor of local" 244 | 245 | # create the filesystems 246 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 247 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 248 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 249 | 250 | # mount them 251 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 252 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 253 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 254 | 255 | # make them peers 256 | echo "$PWD/sandbox/remote/sharebox.fs/master" > sandbox/local/sharebox.mnt/.sharebox/peers/remote 257 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 258 | 259 | # create a file on local side 260 | touch sandbox/local/sharebox.mnt/test_file 261 | 262 | # trigger a synchronization on remote side 263 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 264 | 265 | # test the file has been created on remote side 266 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 267 | 268 | # operate incompatible modifications on test_file 269 | echo "test_line_local" > sandbox/local/sharebox.mnt/test_file 270 | echo "test_line_remote" > sandbox/remote/sharebox.mnt/test_file 271 | 272 | # import the changes in local 273 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 274 | 275 | # a conflicting file should exist, containing the content of remote 276 | conflict=$(ls -a1 sandbox/remote/sharebox.mnt | grep ".test_file.remote") 277 | 278 | # the conflict is solved simply by removing it (local version is kept) 279 | rm sandbox/local/sharebox.mnt/$conflict 280 | 281 | # trigger a synchronization on remote side 282 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 283 | 284 | # the file should contain the local version 285 | assert_success grep "test_line_local" sandbox/remote/sharebox.mnt/test_file 286 | 287 | # unmount the filesystems 288 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 289 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 290 | 291 | clean 292 | } 293 | 294 | sync_resolve_normal_conflict_remote() 295 | { 296 | echo "resolving a normal conflict in favor of remote" 297 | 298 | # create the filesystems 299 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 300 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 301 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 302 | 303 | # mount them 304 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 305 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 306 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 307 | 308 | # make them peers 309 | echo "$PWD/sandbox/remote/sharebox.fs/master" > sandbox/local/sharebox.mnt/.sharebox/peers/remote 310 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 311 | 312 | # create a file on local side 313 | touch sandbox/local/sharebox.mnt/test_file 314 | 315 | # trigger a synchronization on remote side 316 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 317 | 318 | # test the file has been created on remote side 319 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 320 | 321 | # operate incompatible modifications on test_file 322 | echo "test_line_local" > sandbox/local/sharebox.mnt/test_file 323 | echo "test_line_remote" > sandbox/remote/sharebox.mnt/test_file 324 | 325 | # import the changes from local 326 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 327 | 328 | # a conflicting file should exist, containing the content of remote 329 | conflict=$(ls -a1 sandbox/remote/sharebox.mnt | grep ".test_file.remote") 330 | 331 | # the conflict is solved simply by moving it to local 332 | mv sandbox/local/sharebox.mnt/$conflict sandbox/local/sharebox.mnt/test_file 333 | 334 | # trigger a synchronization on remote side 335 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 336 | 337 | # check the content of the file 338 | assert_success grep "test_line_remote" sandbox/remote/sharebox.mnt/test_file 339 | 340 | # unmount the filesystems 341 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 342 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 343 | 344 | clean 345 | } 346 | 347 | sync_resolve_normal_conflict_remote2() 348 | { 349 | echo "resolving a normal conflict in favor of remote (2)" 350 | 351 | # create the filesystems 352 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 353 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 354 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 355 | 356 | # mount them 357 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 358 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 359 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 360 | 361 | # make them peers 362 | echo "$PWD/sandbox/remote/sharebox.fs/master" > sandbox/local/sharebox.mnt/.sharebox/peers/remote 363 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 364 | 365 | # create a file on local side 366 | touch sandbox/local/sharebox.mnt/test_file 367 | 368 | # trigger a synchronization on remote side 369 | echo "get_changes local" > sandbox/remote/sharebox.mnt/.command 370 | 371 | # test the file has been created on remote side 372 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 373 | 374 | # operate incompatible modifications on test_file 375 | echo "test_line_local" > sandbox/local/sharebox.mnt/test_file 376 | echo "test_line_remote" > sandbox/remote/sharebox.mnt/test_file 377 | 378 | # import the changes from local 379 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 380 | 381 | # a conflicting file should exist, containing the content of remote 382 | conflict=$(ls -a1 sandbox/remote/sharebox.mnt | grep ".test_file.remote") 383 | 384 | # the conflict is solved simply by moving it to local 385 | cp sandbox/local/sharebox.mnt/$conflict sandbox/local/sharebox.mnt/test_file 386 | rm sandbox/local/sharebox.mnt/$conflict 387 | 388 | # trigger a synchronization on remote side 389 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 390 | 391 | # check the content of the file 392 | assert_success grep "test_line_remote" sandbox/remote/sharebox.mnt/test_file 393 | 394 | # unmount the filesystems 395 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 396 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 397 | 398 | clean 399 | } 400 | 401 | sync_delete_conflict() 402 | { 403 | echo "synchronization with delete conflict" 404 | 405 | # create the filesystems 406 | mkdir -p sandbox/local/sharebox.fs sandbox/remote/sharebox.fs 407 | mkfs -t sharebox sandbox/local/sharebox.fs > /dev/null 408 | mkfs -t sharebox sandbox/remote/sharebox.fs > /dev/null 409 | 410 | # mount them 411 | mkdir -p sandbox/local/sharebox.mnt sandbox/remote/sharebox.mnt 412 | sharebox sandbox/local/sharebox.fs sandbox/local/sharebox.mnt 413 | sharebox sandbox/remote/sharebox.fs sandbox/remote/sharebox.mnt 414 | 415 | # make them peers 416 | echo "$PWD/sandbox/remote/sharebox.fs/master" > sandbox/local/sharebox.mnt/.sharebox/peers/remote 417 | echo "$PWD/sandbox/local/sharebox.fs/master" > sandbox/remote/sharebox.mnt/.sharebox/peers/local 418 | 419 | echo "test_line" > sandbox/local/sharebox.mnt/test_file 420 | 421 | # import the changes in remote 422 | touch sandbox/remote/sharebox.mnt/.sharebox/peers/local 423 | 424 | assert_success test -e sandbox/remote/sharebox.mnt/test_file 425 | 426 | touch sandbox/remote/sharebox.mnt/test_file 427 | 428 | # removing on local, modifying on remote 429 | echo "test_line_remote" > sandbox/remote/sharebox.mnt/test_file 430 | rm sandbox/local/sharebox.mnt/test_file 431 | 432 | # import the changes from remote 433 | touch > sandbox/local/sharebox.mnt/.sharebox/peers/remote 434 | 435 | # a conflicting file should exist, containing the content of remote 436 | assert_success ls -a1 sandbox/remote/sharebox.mnt | grep ".test_file.remote" 437 | 438 | # unmount the filesystems 439 | fusermount -u -z sandbox/local/sharebox.mnt > /dev/null 440 | fusermount -u -z sandbox/remote/sharebox.mnt > /dev/null 441 | 442 | clean 443 | } 444 | 445 | fuse 446 | sync_success 447 | sync_no_peers 448 | sync_bad_url 449 | sync_modify_url 450 | sync_remove_peer 451 | sync_normal_conflict 452 | sync_resolve_normal_conflict_local 453 | sync_resolve_normal_conflict_remote 454 | sync_resolve_normal_conflict_remote2 455 | sync_delete_conflict 456 | 457 | exit $SUCCESS 458 | -------------------------------------------------------------------------------- /tests/lib/fuse_tester.c: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was shamelessly stolen from the FUSE git repository. 3 | */ 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | static char testfile[1024]; 20 | static char testfile2[1024]; 21 | static char testdir[1024]; 22 | static char testdir2[1024]; 23 | static char testname[256]; 24 | static char testdata[] = "abcdefghijklmnopqrstuvwxyz"; 25 | static char testdata2[] = "1234567890-=qwertyuiop[]\asdfghjkl;'zxcvbnm,./"; 26 | static const char *testdir_files[] = { "f1", "f2", NULL}; 27 | static char zerodata[4096]; 28 | static int testdatalen = sizeof(testdata) - 1; 29 | static int testdata2len = sizeof(testdata2) - 1; 30 | 31 | #define MAX_ENTRIES 1024 32 | 33 | static void test_perror(const char *func, const char *msg) 34 | { 35 | fprintf(stderr, "[%s] %s() - %s: %s\n", testname, func, msg, 36 | strerror(errno)); 37 | } 38 | 39 | static void test_error(const char *func, const char *msg, ...) 40 | __attribute__ ((format (printf, 2, 3))); 41 | 42 | static void start_test(const char *fmt, ...) 43 | __attribute__ ((format (printf, 1, 2))); 44 | 45 | static void test_error(const char *func, const char *msg, ...) 46 | { 47 | va_list ap; 48 | fprintf(stderr, "[%s] %s() - ", testname, func); 49 | va_start(ap, msg); 50 | vfprintf(stderr, msg, ap); 51 | va_end(ap); 52 | fprintf(stderr, "\n"); 53 | } 54 | 55 | static void success(void) 56 | { 57 | fprintf(stderr, "[%s] OK\n", testname); 58 | } 59 | 60 | static void start_test(const char *fmt, ...) 61 | { 62 | va_list ap; 63 | va_start(ap, fmt); 64 | vsprintf(testname, fmt, ap); 65 | va_end(ap); 66 | } 67 | 68 | #define PERROR(msg) test_perror(__FUNCTION__, msg) 69 | #define ERROR(msg, args...) test_error(__FUNCTION__, msg, ##args) 70 | 71 | static int check_size(const char *path, int len) 72 | { 73 | struct stat stbuf; 74 | int res = stat(path, &stbuf); 75 | if (res == -1) { 76 | PERROR("stat"); 77 | return -1; 78 | } 79 | if (stbuf.st_size != len) { 80 | ERROR("length %u instead of %u", (int) stbuf.st_size, 81 | (int) len); 82 | return -1; 83 | } 84 | return 0; 85 | } 86 | 87 | static int fcheck_size(int fd, int len) 88 | { 89 | struct stat stbuf; 90 | int res = fstat(fd, &stbuf); 91 | if (res == -1) { 92 | PERROR("fstat"); 93 | return -1; 94 | } 95 | if (stbuf.st_size != len) { 96 | ERROR("length %u instead of %u", (int) stbuf.st_size, 97 | (int) len); 98 | return -1; 99 | } 100 | return 0; 101 | } 102 | 103 | static int check_type(const char *path, mode_t type) 104 | { 105 | struct stat stbuf; 106 | int res = lstat(path, &stbuf); 107 | if (res == -1) { 108 | PERROR("lstat"); 109 | return -1; 110 | } 111 | if ((stbuf.st_mode & S_IFMT) != type) { 112 | ERROR("type 0%o instead of 0%o", stbuf.st_mode & S_IFMT, type); 113 | return -1; 114 | } 115 | return 0; 116 | } 117 | 118 | static int fcheck_type(int fd, mode_t type) 119 | { 120 | struct stat stbuf; 121 | int res = fstat(fd, &stbuf); 122 | if (res == -1) { 123 | PERROR("fstat"); 124 | return -1; 125 | } 126 | if ((stbuf.st_mode & S_IFMT) != type) { 127 | ERROR("type 0%o instead of 0%o", stbuf.st_mode & S_IFMT, type); 128 | return -1; 129 | } 130 | return 0; 131 | } 132 | 133 | static int check_mode(const char *path, mode_t mode) 134 | { 135 | struct stat stbuf; 136 | int res = lstat(path, &stbuf); 137 | if (res == -1) { 138 | PERROR("lstat"); 139 | return -1; 140 | } 141 | if ((stbuf.st_mode & 07777) != mode) { 142 | ERROR("mode 0%o instead of 0%o", stbuf.st_mode & 07777, mode); 143 | return -1; 144 | } 145 | return 0; 146 | } 147 | 148 | static int fcheck_mode(int fd, mode_t mode) 149 | { 150 | struct stat stbuf; 151 | int res = fstat(fd, &stbuf); 152 | if (res == -1) { 153 | PERROR("fstat"); 154 | return -1; 155 | } 156 | if ((stbuf.st_mode & 07777) != mode) { 157 | ERROR("mode 0%o instead of 0%o", stbuf.st_mode & 07777, mode); 158 | return -1; 159 | } 160 | return 0; 161 | } 162 | 163 | static int check_times(const char *path, time_t atime, time_t mtime) 164 | { 165 | int err = 0; 166 | struct stat stbuf; 167 | int res = lstat(path, &stbuf); 168 | if (res == -1) { 169 | PERROR("lstat"); 170 | return -1; 171 | } 172 | if (stbuf.st_atime != atime) { 173 | ERROR("atime %li instead of %li", stbuf.st_atime, atime); 174 | err--; 175 | } 176 | if (stbuf.st_mtime != mtime) { 177 | ERROR("mtime %li instead of %li", stbuf.st_mtime, mtime); 178 | err--; 179 | } 180 | if (err) 181 | return -1; 182 | 183 | return 0; 184 | } 185 | 186 | static int check_nlink(const char *path, nlink_t nlink) 187 | { 188 | struct stat stbuf; 189 | int res = lstat(path, &stbuf); 190 | if (res == -1) { 191 | PERROR("lstat"); 192 | return -1; 193 | } 194 | if (stbuf.st_nlink != nlink) { 195 | ERROR("nlink %li instead of %li", (long) stbuf.st_nlink, 196 | (long) nlink); 197 | return -1; 198 | } 199 | return 0; 200 | } 201 | 202 | static int fcheck_nlink(int fd, nlink_t nlink) 203 | { 204 | struct stat stbuf; 205 | int res = fstat(fd, &stbuf); 206 | if (res == -1) { 207 | PERROR("fstat"); 208 | return -1; 209 | } 210 | if (stbuf.st_nlink != nlink) { 211 | ERROR("nlink %li instead of %li", (long) stbuf.st_nlink, 212 | (long) nlink); 213 | return -1; 214 | } 215 | return 0; 216 | } 217 | 218 | static int check_nonexist(const char *path) 219 | { 220 | struct stat stbuf; 221 | int res = lstat(path, &stbuf); 222 | if (res == 0) { 223 | ERROR("file should not exist"); 224 | return -1; 225 | } 226 | if (errno != ENOENT) { 227 | ERROR("file should not exist: %s", strerror(errno)); 228 | return -1; 229 | } 230 | return 0; 231 | } 232 | 233 | static int check_buffer(const char *buf, const char *data, unsigned len) 234 | { 235 | if (memcmp(buf, data, len) != 0) { 236 | ERROR("data mismatch"); 237 | return -1; 238 | } 239 | return 0; 240 | } 241 | 242 | static int check_data(const char *path, const char *data, int offset, 243 | unsigned len) 244 | { 245 | char buf[4096]; 246 | int res; 247 | int fd = open(path, O_RDONLY); 248 | if (fd == -1) { 249 | PERROR("open"); 250 | return -1; 251 | } 252 | if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { 253 | PERROR("lseek"); 254 | close(fd); 255 | return -1; 256 | } 257 | while (len) { 258 | int rdlen = len < sizeof(buf) ? len : sizeof(buf); 259 | res = read(fd, buf, rdlen); 260 | if (res == -1) { 261 | PERROR("read"); 262 | close(fd); 263 | return -1; 264 | } 265 | if (res != rdlen) { 266 | ERROR("short read: %u instead of %u", res, rdlen); 267 | close(fd); 268 | return -1; 269 | } 270 | if (check_buffer(buf, data, rdlen) != 0) { 271 | close(fd); 272 | return -1; 273 | } 274 | data += rdlen; 275 | len -= rdlen; 276 | } 277 | res = close(fd); 278 | if (res == -1) { 279 | PERROR("close"); 280 | return -1; 281 | } 282 | return 0; 283 | } 284 | 285 | static int fcheck_data(int fd, const char *data, int offset, 286 | unsigned len) 287 | { 288 | char buf[4096]; 289 | int res; 290 | if (lseek(fd, offset, SEEK_SET) == (off_t) -1) { 291 | PERROR("lseek"); 292 | return -1; 293 | } 294 | while (len) { 295 | int rdlen = len < sizeof(buf) ? len : sizeof(buf); 296 | res = read(fd, buf, rdlen); 297 | if (res == -1) { 298 | PERROR("read"); 299 | return -1; 300 | } 301 | if (res != rdlen) { 302 | ERROR("short read: %u instead of %u", res, rdlen); 303 | return -1; 304 | } 305 | if (check_buffer(buf, data, rdlen) != 0) { 306 | return -1; 307 | } 308 | data += rdlen; 309 | len -= rdlen; 310 | } 311 | return 0; 312 | } 313 | 314 | static int check_dir_contents(const char *path, const char **contents) 315 | { 316 | int i; 317 | int res; 318 | int err = 0; 319 | int found[MAX_ENTRIES]; 320 | const char *cont[MAX_ENTRIES]; 321 | DIR *dp; 322 | 323 | for (i = 0; contents[i]; i++) { 324 | assert(i < MAX_ENTRIES - 3); 325 | found[i] = 0; 326 | cont[i] = contents[i]; 327 | } 328 | found[i] = 0; 329 | cont[i++] = "."; 330 | found[i] = 0; 331 | cont[i++] = ".."; 332 | cont[i] = NULL; 333 | 334 | dp = opendir(path); 335 | if (dp == NULL) { 336 | PERROR("opendir"); 337 | return -1; 338 | } 339 | memset(found, 0, sizeof(found)); 340 | while(1) { 341 | struct dirent *de; 342 | errno = 0; 343 | de = readdir(dp); 344 | if (de == NULL) { 345 | if (errno) { 346 | PERROR("readdir"); 347 | closedir(dp); 348 | return -1; 349 | } 350 | break; 351 | } 352 | for (i = 0; cont[i] != NULL; i++) { 353 | assert(i < MAX_ENTRIES); 354 | if (strcmp(cont[i], de->d_name) == 0) { 355 | if (found[i]) { 356 | ERROR("duplicate entry <%s>", 357 | de->d_name); 358 | err--; 359 | } else 360 | found[i] = 1; 361 | break; 362 | } 363 | } 364 | if (!cont[i]) { 365 | ERROR("unexpected entry <%s>", de->d_name); 366 | err --; 367 | } 368 | } 369 | for (i = 0; cont[i] != NULL; i++) { 370 | if (!found[i]) { 371 | ERROR("missing entry <%s>", cont[i]); 372 | err--; 373 | } 374 | } 375 | res = closedir(dp); 376 | if (res == -1) { 377 | PERROR("closedir"); 378 | return -1; 379 | } 380 | if (err) 381 | return -1; 382 | 383 | return 0; 384 | } 385 | 386 | static int create_file(const char *path, const char *data, int len) 387 | { 388 | int res; 389 | int fd; 390 | 391 | unlink(path); 392 | fd = creat(path, 0644); 393 | if (fd == -1) { 394 | PERROR("creat"); 395 | return -1; 396 | } 397 | if (len) { 398 | res = write(fd, data, len); 399 | if (res == -1) { 400 | PERROR("write"); 401 | close(fd); 402 | return -1; 403 | } 404 | if (res != len) { 405 | ERROR("write is short: %u instead of %u", res, len); 406 | close(fd); 407 | return -1; 408 | } 409 | } 410 | res = close(fd); 411 | if (res == -1) { 412 | PERROR("close"); 413 | return -1; 414 | } 415 | res = check_type(path, S_IFREG); 416 | if (res == -1) 417 | return -1; 418 | res = check_mode(path, 0644); 419 | if (res == -1) 420 | return -1; 421 | res = check_nlink(path, 1); 422 | if (res == -1) 423 | return -1; 424 | res = check_size(path, len); 425 | if (res == -1) 426 | return -1; 427 | 428 | if (len) { 429 | res = check_data(path, data, 0, len); 430 | if (res == -1) 431 | return -1; 432 | } 433 | 434 | return 0; 435 | } 436 | 437 | static int cleanup_dir(const char *path, const char **dir_files, int quiet) 438 | { 439 | int i; 440 | int err = 0; 441 | 442 | for (i = 0; dir_files[i]; i++) { 443 | int res; 444 | char fpath[1024]; 445 | sprintf(fpath, "%s/%s", path, dir_files[i]); 446 | res = unlink(fpath); 447 | if (res == -1 && !quiet) { 448 | PERROR("unlink"); 449 | err --; 450 | } 451 | } 452 | if (err) 453 | return -1; 454 | 455 | return 0; 456 | } 457 | 458 | static int create_dir(const char *path, const char **dir_files) 459 | { 460 | int res; 461 | int i; 462 | 463 | rmdir(path); 464 | res = mkdir(path, 0755); 465 | if (res == -1) { 466 | PERROR("mkdir"); 467 | return -1; 468 | } 469 | res = check_type(path, S_IFDIR); 470 | if (res == -1) 471 | return -1; 472 | res = check_mode(path, 0755); 473 | if (res == -1) 474 | return -1; 475 | 476 | for (i = 0; dir_files[i]; i++) { 477 | char fpath[1024]; 478 | sprintf(fpath, "%s/%s", path, dir_files[i]); 479 | res = create_file(fpath, "", 0); 480 | if (res == -1) { 481 | cleanup_dir(path, dir_files, 1); 482 | return -1; 483 | } 484 | } 485 | res = check_dir_contents(path, dir_files); 486 | if (res == -1) { 487 | cleanup_dir(path, dir_files, 1); 488 | return -1; 489 | } 490 | 491 | return 0; 492 | } 493 | 494 | static int test_truncate(int len) 495 | { 496 | const char *data = testdata; 497 | int datalen = testdatalen; 498 | int res; 499 | 500 | start_test("truncate(%u)", (int) len); 501 | res = create_file(testfile, data, datalen); 502 | if (res == -1) 503 | return -1; 504 | 505 | res = truncate(testfile, len); 506 | if (res == -1) { 507 | PERROR("truncate"); 508 | return -1; 509 | } 510 | res = check_size(testfile, len); 511 | if (res == -1) 512 | return -1; 513 | 514 | if (len > 0) { 515 | if (len <= datalen) { 516 | res = check_data(testfile, data, 0, len); 517 | if (res == -1) 518 | return -1; 519 | } else { 520 | res = check_data(testfile, data, 0, datalen); 521 | if (res == -1) 522 | return -1; 523 | res = check_data(testfile, zerodata, datalen, 524 | len - datalen); 525 | if (res == -1) 526 | return -1; 527 | } 528 | } 529 | res = unlink(testfile); 530 | if (res == -1) { 531 | PERROR("unlink"); 532 | return -1; 533 | } 534 | res = check_nonexist(testfile); 535 | if (res == -1) 536 | return -1; 537 | 538 | success(); 539 | return 0; 540 | } 541 | 542 | static int test_ftruncate(int len, int mode) 543 | { 544 | const char *data = testdata; 545 | int datalen = testdatalen; 546 | int res; 547 | int fd; 548 | 549 | start_test("ftruncate(%u) mode: 0%03o", len, mode); 550 | res = create_file(testfile, data, datalen); 551 | if (res == -1) 552 | return -1; 553 | 554 | fd = open(testfile, O_WRONLY); 555 | if (fd == -1) { 556 | PERROR("open"); 557 | return -1; 558 | } 559 | 560 | res = fchmod(fd, mode); 561 | if (res == -1) { 562 | PERROR("fchmod"); 563 | close(fd); 564 | return -1; 565 | } 566 | res = check_mode(testfile, mode); 567 | if (res == -1) { 568 | close(fd); 569 | return -1; 570 | } 571 | res = ftruncate(fd, len); 572 | if (res == -1) { 573 | PERROR("ftruncate"); 574 | close(fd); 575 | return -1; 576 | } 577 | close(fd); 578 | res = check_size(testfile, len); 579 | if (res == -1) 580 | return -1; 581 | 582 | if (len > 0) { 583 | if (len <= datalen) { 584 | res = check_data(testfile, data, 0, len); 585 | if (res == -1) 586 | return -1; 587 | } else { 588 | res = check_data(testfile, data, 0, datalen); 589 | if (res == -1) 590 | return -1; 591 | res = check_data(testfile, zerodata, datalen, 592 | len - datalen); 593 | if (res == -1) 594 | return -1; 595 | } 596 | } 597 | res = unlink(testfile); 598 | if (res == -1) { 599 | PERROR("unlink"); 600 | return -1; 601 | } 602 | res = check_nonexist(testfile); 603 | if (res == -1) 604 | return -1; 605 | 606 | success(); 607 | return 0; 608 | } 609 | 610 | static int test_utime(void) 611 | { 612 | struct utimbuf utm; 613 | time_t atime = 987631200; 614 | time_t mtime = 123116400; 615 | int res; 616 | 617 | start_test("utime"); 618 | res = create_file(testfile, NULL, 0); 619 | if (res == -1) 620 | return -1; 621 | 622 | utm.actime = atime; 623 | utm.modtime = mtime; 624 | res = utime(testfile, &utm); 625 | if (res == -1) { 626 | PERROR("utime"); 627 | return -1; 628 | } 629 | res = check_times(testfile, atime, mtime); 630 | if (res == -1) { 631 | return -1; 632 | } 633 | res = unlink(testfile); 634 | if (res == -1) { 635 | PERROR("unlink"); 636 | return -1; 637 | } 638 | res = check_nonexist(testfile); 639 | if (res == -1) 640 | return -1; 641 | 642 | success(); 643 | return 0; 644 | } 645 | 646 | static int test_create(void) 647 | { 648 | const char *data = testdata; 649 | int datalen = testdatalen; 650 | int err = 0; 651 | int res; 652 | int fd; 653 | 654 | start_test("create"); 655 | unlink(testfile); 656 | fd = creat(testfile, 0644); 657 | if (fd == -1) { 658 | PERROR("creat"); 659 | return -1; 660 | } 661 | res = write(fd, data, datalen); 662 | if (res == -1) { 663 | PERROR("write"); 664 | close(fd); 665 | return -1; 666 | } 667 | if (res != datalen) { 668 | ERROR("write is short: %u instead of %u", res, datalen); 669 | close(fd); 670 | return -1; 671 | } 672 | res = close(fd); 673 | if (res == -1) { 674 | PERROR("close"); 675 | return -1; 676 | } 677 | res = check_type(testfile, S_IFREG); 678 | if (res == -1) 679 | return -1; 680 | err += check_mode(testfile, 0644); 681 | err += check_nlink(testfile, 1); 682 | err += check_size(testfile, datalen); 683 | err += check_data(testfile, data, 0, datalen); 684 | res = unlink(testfile); 685 | if (res == -1) { 686 | PERROR("unlink"); 687 | return -1; 688 | } 689 | res = check_nonexist(testfile); 690 | if (res == -1) 691 | return -1; 692 | if (err) 693 | return -1; 694 | 695 | success(); 696 | return 0; 697 | } 698 | 699 | static int test_create_unlink(void) 700 | { 701 | const char *data = testdata; 702 | int datalen = testdatalen; 703 | int err = 0; 704 | int res; 705 | int fd; 706 | 707 | start_test("create+unlink"); 708 | unlink(testfile); 709 | fd = open(testfile, O_CREAT | O_RDWR | O_TRUNC, 0644); 710 | if (fd == -1) { 711 | PERROR("creat"); 712 | return -1; 713 | } 714 | res = unlink(testfile); 715 | if (res == -1) { 716 | PERROR("unlink"); 717 | close(fd); 718 | return -1; 719 | } 720 | res = check_nonexist(testfile); 721 | if (res == -1) 722 | return -1; 723 | res = write(fd, data, datalen); 724 | if (res == -1) { 725 | PERROR("write"); 726 | close(fd); 727 | return -1; 728 | } 729 | if (res != datalen) { 730 | ERROR("write is short: %u instead of %u", res, datalen); 731 | close(fd); 732 | return -1; 733 | } 734 | err += fcheck_type(fd, S_IFREG); 735 | err += fcheck_mode(fd, 0644); 736 | err += fcheck_nlink(fd, 0); 737 | err += fcheck_size(fd, datalen); 738 | err += fcheck_data(fd, data, 0, datalen); 739 | res = close(fd); 740 | if (res == -1) { 741 | PERROR("close"); 742 | err--; 743 | } 744 | if (err) 745 | return -1; 746 | 747 | success(); 748 | return 0; 749 | } 750 | 751 | /* Not supported 752 | static int test_mknod(void) 753 | { 754 | int err = 0; 755 | int res; 756 | 757 | start_test("mknod"); 758 | unlink(testfile); 759 | res = mknod(testfile, 0644, 0); 760 | if (res == -1) { 761 | PERROR("mknod"); 762 | return -1; 763 | } 764 | res = check_type(testfile, S_IFREG); 765 | if (res == -1) 766 | return -1; 767 | err += check_mode(testfile, 0644); 768 | err += check_nlink(testfile, 1); 769 | err += check_size(testfile, 0); 770 | res = unlink(testfile); 771 | if (res == -1) { 772 | PERROR("unlink"); 773 | return -1; 774 | } 775 | res = check_nonexist(testfile); 776 | if (res == -1) 777 | return -1; 778 | if (err) 779 | return -1; 780 | 781 | success(); 782 | return 0; 783 | } 784 | */ 785 | 786 | #define test_open(exist, flags, mode) do_test_open(exist, flags, #flags, mode) 787 | 788 | static int do_test_open(int exist, int flags, const char *flags_str, int mode) 789 | { 790 | char buf[4096]; 791 | const char *data = testdata; 792 | int datalen = testdatalen; 793 | unsigned currlen = 0; 794 | int err = 0; 795 | int res; 796 | int fd; 797 | off_t off; 798 | 799 | start_test("open(%s, %s, 0%03o)", exist ? "+" : "-", flags_str, mode); 800 | unlink(testfile); 801 | if (exist) { 802 | res = create_file(testfile, testdata2, testdata2len); 803 | if (res == -1) 804 | return -1; 805 | 806 | currlen = testdata2len; 807 | } 808 | 809 | fd = open(testfile, flags, mode); 810 | if ((flags & O_CREAT) && (flags & O_EXCL) && exist) { 811 | if (fd != -1) { 812 | ERROR("open should have failed"); 813 | close(fd); 814 | return -1; 815 | } else if (errno == EEXIST) 816 | goto succ; 817 | } 818 | if (!(flags & O_CREAT) && !exist) { 819 | if (fd != -1) { 820 | ERROR("open should have failed"); 821 | close(fd); 822 | return -1; 823 | } else if (errno == ENOENT) 824 | goto succ; 825 | } 826 | if (fd == -1) { 827 | PERROR("open"); 828 | return -1; 829 | } 830 | 831 | if (flags & O_TRUNC) 832 | currlen = 0; 833 | 834 | err += check_type(testfile, S_IFREG); 835 | if (exist) 836 | err += check_mode(testfile, 0644); 837 | else 838 | err += check_mode(testfile, mode); 839 | err += check_nlink(testfile, 1); 840 | err += check_size(testfile, currlen); 841 | if (exist && !(flags & O_TRUNC) && (mode & 0400)) 842 | err += check_data(testfile, testdata2, 0, testdata2len); 843 | 844 | res = write(fd, data, datalen); 845 | if ((flags & O_ACCMODE) != O_RDONLY) { 846 | if (res == -1) { 847 | PERROR("write"); 848 | err --; 849 | } else if (res != datalen) { 850 | ERROR("write is short: %u instead of %u", res, datalen); 851 | err --; 852 | } else { 853 | if (datalen > (int) currlen) 854 | currlen = datalen; 855 | 856 | err += check_size(testfile, currlen); 857 | 858 | if (mode & 0400) { 859 | err += check_data(testfile, data, 0, datalen); 860 | if (exist && !(flags & O_TRUNC) && 861 | testdata2len > datalen) 862 | err += check_data(testfile, 863 | testdata2 + datalen, 864 | datalen, 865 | testdata2len - datalen); 866 | } 867 | } 868 | } else { 869 | if (res != -1) { 870 | ERROR("write should have failed"); 871 | err --; 872 | } else if (errno != EBADF) { 873 | PERROR("write"); 874 | err --; 875 | } 876 | } 877 | off = lseek(fd, SEEK_SET, 0); 878 | if (off == (off_t) -1) { 879 | PERROR("lseek"); 880 | err--; 881 | } else if (off != 0) { 882 | ERROR("offset should have returned 0"); 883 | err --; 884 | } 885 | res = read(fd, buf, sizeof(buf)); 886 | if ((flags & O_ACCMODE) != O_WRONLY) { 887 | if (res == -1) { 888 | PERROR("read"); 889 | err--; 890 | } else { 891 | int readsize = 892 | currlen < sizeof(buf) ? currlen : sizeof(buf); 893 | if (res != readsize) { 894 | ERROR("read is short: %i instead of %u", 895 | res, readsize); 896 | err--; 897 | } else { 898 | if ((flags & O_ACCMODE) != O_RDONLY) { 899 | err += check_buffer(buf, data, datalen); 900 | if (exist && !(flags & O_TRUNC) && 901 | testdata2len > datalen) 902 | err += check_buffer(buf + datalen, 903 | testdata2 + datalen, 904 | testdata2len - datalen); 905 | } else if (exist) 906 | err += check_buffer(buf, testdata2, 907 | testdata2len); 908 | } 909 | } 910 | } else { 911 | if (res != -1) { 912 | ERROR("read should have failed"); 913 | err --; 914 | } else if (errno != EBADF) { 915 | PERROR("read"); 916 | err --; 917 | } 918 | } 919 | 920 | res = close(fd); 921 | if (res == -1) { 922 | PERROR("close"); 923 | return -1; 924 | } 925 | res = unlink(testfile); 926 | if (res == -1) { 927 | PERROR("unlink"); 928 | return -1; 929 | } 930 | res = check_nonexist(testfile); 931 | if (res == -1) 932 | return -1; 933 | if (err) 934 | return -1; 935 | 936 | succ: 937 | success(); 938 | return 0; 939 | } 940 | 941 | #define test_open_acc(flags, mode, err) \ 942 | do_test_open_acc(flags, #flags, mode, err) 943 | 944 | static int do_test_open_acc(int flags, const char *flags_str, int mode, int err) 945 | { 946 | const char *data = testdata; 947 | int datalen = testdatalen; 948 | int res; 949 | int fd; 950 | 951 | start_test("open_acc(%s) mode: 0%03o error: '%s'", flags_str, mode, 952 | strerror(err)); 953 | unlink(testfile); 954 | res = create_file(testfile, data, datalen); 955 | if (res == -1) 956 | return -1; 957 | 958 | res = chmod(testfile, mode); 959 | if (res == -1) { 960 | PERROR("chmod"); 961 | return -1; 962 | } 963 | 964 | res = check_mode(testfile, mode); 965 | if (res == -1) 966 | return -1; 967 | 968 | fd = open(testfile, flags); 969 | if (fd == -1) { 970 | if (err != errno) { 971 | PERROR("open"); 972 | return -1; 973 | } 974 | } else { 975 | if (err) { 976 | ERROR("open should have failed"); 977 | close(fd); 978 | return -1; 979 | } 980 | close(fd); 981 | } 982 | success(); 983 | return 0; 984 | } 985 | 986 | static int test_symlink(void) 987 | { 988 | char buf[1024]; 989 | const char *data = testdata; 990 | int datalen = testdatalen; 991 | int linklen = strlen(testfile); 992 | int err = 0; 993 | int res; 994 | 995 | start_test("symlink"); 996 | res = create_file(testfile, data, datalen); 997 | if (res == -1) 998 | return -1; 999 | 1000 | unlink(testfile2); 1001 | res = symlink(testfile, testfile2); 1002 | if (res == -1) { 1003 | PERROR("symlink"); 1004 | return -1; 1005 | } 1006 | res = check_type(testfile2, S_IFLNK); 1007 | if (res == -1) 1008 | return -1; 1009 | err += check_mode(testfile2, 0777); 1010 | err += check_nlink(testfile2, 1); 1011 | res = readlink(testfile2, buf, sizeof(buf)); 1012 | if (res == -1) { 1013 | PERROR("readlink"); 1014 | err--; 1015 | } 1016 | if (res != linklen) { 1017 | ERROR("short readlink: %u instead of %u", res, linklen); 1018 | err--; 1019 | } 1020 | if (memcmp(buf, testfile, linklen) != 0) { 1021 | ERROR("link mismatch"); 1022 | err--; 1023 | } 1024 | err += check_size(testfile2, datalen); 1025 | err += check_data(testfile2, data, 0, datalen); 1026 | res = unlink(testfile2); 1027 | if (res == -1) { 1028 | PERROR("unlink"); 1029 | return -1; 1030 | } 1031 | res = check_nonexist(testfile2); 1032 | if (res == -1) 1033 | return -1; 1034 | if (err) 1035 | return -1; 1036 | 1037 | success(); 1038 | return 0; 1039 | } 1040 | 1041 | static int test_link(void) 1042 | { 1043 | const char *data = testdata; 1044 | int datalen = testdatalen; 1045 | int err = 0; 1046 | int res; 1047 | 1048 | start_test("link"); 1049 | res = create_file(testfile, data, datalen); 1050 | if (res == -1) 1051 | return -1; 1052 | 1053 | unlink(testfile2); 1054 | res = link(testfile, testfile2); 1055 | if (res == -1) { 1056 | PERROR("link"); 1057 | return -1; 1058 | } 1059 | res = check_type(testfile2, S_IFREG); 1060 | if (res == -1) 1061 | return -1; 1062 | err += check_mode(testfile2, 0644); 1063 | err += check_nlink(testfile2, 2); 1064 | err += check_size(testfile2, datalen); 1065 | err += check_data(testfile2, data, 0, datalen); 1066 | res = unlink(testfile); 1067 | if (res == -1) { 1068 | PERROR("unlink"); 1069 | return -1; 1070 | } 1071 | res = check_nonexist(testfile); 1072 | if (res == -1) 1073 | return -1; 1074 | 1075 | err += check_nlink(testfile2, 1); 1076 | res = unlink(testfile2); 1077 | if (res == -1) { 1078 | PERROR("unlink"); 1079 | return -1; 1080 | } 1081 | res = check_nonexist(testfile2); 1082 | if (res == -1) 1083 | return -1; 1084 | if (err) 1085 | return -1; 1086 | 1087 | success(); 1088 | return 0; 1089 | } 1090 | 1091 | static int test_rename_file(void) 1092 | { 1093 | const char *data = testdata; 1094 | int datalen = testdatalen; 1095 | int err = 0; 1096 | int res; 1097 | 1098 | start_test("rename file"); 1099 | res = create_file(testfile, data, datalen); 1100 | if (res == -1) 1101 | return -1; 1102 | 1103 | unlink(testfile2); 1104 | res = rename(testfile, testfile2); 1105 | if (res == -1) { 1106 | PERROR("rename"); 1107 | return -1; 1108 | } 1109 | res = check_nonexist(testfile); 1110 | if (res == -1) 1111 | return -1; 1112 | res = check_type(testfile2, S_IFREG); 1113 | if (res == -1) 1114 | return -1; 1115 | err += check_mode(testfile2, 0644); 1116 | err += check_nlink(testfile2, 1); 1117 | err += check_size(testfile2, datalen); 1118 | err += check_data(testfile2, data, 0, datalen); 1119 | res = unlink(testfile2); 1120 | if (res == -1) { 1121 | PERROR("unlink"); 1122 | return -1; 1123 | } 1124 | res = check_nonexist(testfile2); 1125 | if (res == -1) 1126 | return -1; 1127 | if (err) 1128 | return -1; 1129 | 1130 | success(); 1131 | return 0; 1132 | } 1133 | 1134 | static int test_rename_dir(void) 1135 | { 1136 | int err = 0; 1137 | int res; 1138 | 1139 | start_test("rename dir"); 1140 | res = create_dir(testdir, testdir_files); 1141 | if (res == -1) 1142 | return -1; 1143 | 1144 | rmdir(testdir2); 1145 | res = rename(testdir, testdir2); 1146 | if (res == -1) { 1147 | PERROR("rename"); 1148 | cleanup_dir(testdir, testdir_files, 1); 1149 | return -1; 1150 | } 1151 | res = check_nonexist(testdir); 1152 | if (res == -1) { 1153 | cleanup_dir(testdir, testdir_files, 1); 1154 | return -1; 1155 | } 1156 | res = check_type(testdir2, S_IFDIR); 1157 | if (res == -1) { 1158 | cleanup_dir(testdir2, testdir_files, 1); 1159 | return -1; 1160 | } 1161 | err += check_mode(testdir2, 0755); 1162 | err += check_dir_contents(testdir2, testdir_files); 1163 | err += cleanup_dir(testdir2, testdir_files, 0); 1164 | res = rmdir(testdir2); 1165 | if (res == -1) { 1166 | PERROR("rmdir"); 1167 | return -1; 1168 | } 1169 | res = check_nonexist(testdir2); 1170 | if (res == -1) 1171 | return -1; 1172 | if (err) 1173 | return -1; 1174 | 1175 | success(); 1176 | return 0; 1177 | } 1178 | 1179 | /* Not supported 1180 | static int test_mkfifo(void) 1181 | { 1182 | int res; 1183 | int err = 0; 1184 | 1185 | start_test("mkfifo"); 1186 | unlink(testfile); 1187 | res = mkfifo(testfile, 0644); 1188 | if (res == -1) { 1189 | PERROR("mkfifo"); 1190 | return -1; 1191 | } 1192 | res = check_type(testfile, S_IFIFO); 1193 | if (res == -1) 1194 | return -1; 1195 | err += check_mode(testfile, 0644); 1196 | err += check_nlink(testfile, 1); 1197 | res = unlink(testfile); 1198 | if (res == -1) { 1199 | PERROR("unlink"); 1200 | return -1; 1201 | } 1202 | res = check_nonexist(testfile); 1203 | if (res == -1) 1204 | return -1; 1205 | if (err) 1206 | return -1; 1207 | 1208 | success(); 1209 | return 0; 1210 | } 1211 | */ 1212 | 1213 | static int test_mkdir(void) 1214 | { 1215 | int res; 1216 | int err = 0; 1217 | const char *dir_contents[] = {NULL}; 1218 | 1219 | start_test("mkdir"); 1220 | rmdir(testdir); 1221 | res = mkdir(testdir, 0755); 1222 | if (res == -1) { 1223 | PERROR("mkdir"); 1224 | return -1; 1225 | } 1226 | res = check_type(testdir, S_IFDIR); 1227 | if (res == -1) 1228 | return -1; 1229 | err += check_mode(testdir, 0755); 1230 | err += check_nlink(testdir, 2); 1231 | err += check_dir_contents(testdir, dir_contents); 1232 | res = rmdir(testdir); 1233 | if (res == -1) { 1234 | PERROR("rmdir"); 1235 | return -1; 1236 | } 1237 | res = check_nonexist(testdir); 1238 | if (res == -1) 1239 | return -1; 1240 | if (err) 1241 | return -1; 1242 | 1243 | success(); 1244 | return 0; 1245 | } 1246 | 1247 | int main(int argc, char *argv[]) 1248 | { 1249 | const char *basepath; 1250 | int err = 0; 1251 | 1252 | umask(0); 1253 | if (argc != 2) { 1254 | fprintf(stderr, "usage: %s testdir\n", argv[0]); 1255 | return 1; 1256 | } 1257 | basepath = argv[1]; 1258 | assert(strlen(basepath) < 512); 1259 | if (basepath[0] != '/') { 1260 | fprintf(stderr, "testdir must be an absolute path\n"); 1261 | return 1; 1262 | } 1263 | 1264 | sprintf(testfile, "%s/testfile", basepath); 1265 | sprintf(testfile2, "%s/testfile2", basepath); 1266 | sprintf(testdir, "%s/testdir", basepath); 1267 | sprintf(testdir2, "%s/testdir2", basepath); 1268 | err += test_create(); 1269 | /* err += test_create_unlink(); Does not even pass on xmp */ 1270 | /* err += test_mknod(); Not supported */ 1271 | err += test_symlink(); 1272 | /* err += test_link(); Not supported */ 1273 | /* err += test_mkfifo(); Not supported */ 1274 | err += test_mkdir(); 1275 | err += test_rename_file(); 1276 | err += test_rename_dir(); 1277 | err += test_utime(); 1278 | err += test_truncate(0); 1279 | err += test_truncate(testdatalen / 2); 1280 | err += test_truncate(testdatalen); 1281 | err += test_truncate(testdatalen + 100); 1282 | err += test_ftruncate(0, 0600); 1283 | err += test_ftruncate(testdatalen / 2, 0600); 1284 | err += test_ftruncate(testdatalen, 0600); 1285 | err += test_ftruncate(testdatalen + 100, 0600); 1286 | /* err += test_ftruncate(0, 0400); Does not even pass on xmp */ 1287 | err += test_ftruncate(0, 0200); 1288 | /* err += test_ftruncate(0, 0000); Does not even pass on xmp */ 1289 | err += test_open(0, O_RDONLY, 0); 1290 | err += test_open(1, O_RDONLY, 0); 1291 | err += test_open(1, O_RDWR, 0); 1292 | err += test_open(1, O_WRONLY, 0); 1293 | err += test_open(0, O_RDWR | O_CREAT, 0600); 1294 | err += test_open(1, O_RDWR | O_CREAT, 0600); 1295 | err += test_open(0, O_RDWR | O_CREAT | O_TRUNC, 0600); 1296 | err += test_open(1, O_RDWR | O_CREAT | O_TRUNC, 0600); 1297 | err += test_open(0, O_RDONLY | O_CREAT, 0600); 1298 | err += test_open(0, O_RDONLY | O_CREAT, 0400); 1299 | /* err += test_open(0, O_RDONLY | O_CREAT, 0200); Does not even pass on xmp */ 1300 | /* err += test_open(0, O_RDONLY | O_CREAT, 0000); Does not even pass on xmp */ 1301 | err += test_open(0, O_WRONLY | O_CREAT, 0600); 1302 | /* err += test_open(0, O_WRONLY | O_CREAT, 0400); Does not even pass on xmp */ 1303 | err += test_open(0, O_WRONLY | O_CREAT, 0200); 1304 | /* err += test_open(0, O_WRONLY | O_CREAT, 0000); Does not even pass on xmp */ 1305 | /* err += test_open(0, O_RDWR | O_CREAT, 0400); Does not even pass on xmp */ 1306 | /* err += test_open(0, O_RDWR | O_CREAT, 0200); Does not even pass on xmp */ 1307 | /* err += test_open(0, O_RDWR | O_CREAT, 0000); Does not even pass on xmp */ 1308 | err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0600); 1309 | err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0600); 1310 | /* err += test_open(0, O_RDWR | O_CREAT | O_EXCL, 0000); Does not even pass on xmp */ 1311 | err += test_open(1, O_RDWR | O_CREAT | O_EXCL, 0000); 1312 | err += test_open_acc(O_RDONLY, 0600, 0); 1313 | err += test_open_acc(O_WRONLY, 0600, 0); 1314 | err += test_open_acc(O_RDWR, 0600, 0); 1315 | err += test_open_acc(O_RDONLY, 0400, 0); 1316 | err += test_open_acc(O_RDONLY | O_TRUNC, 0400, EACCES); 1317 | err += test_open_acc(O_WRONLY, 0400, EACCES); 1318 | err += test_open_acc(O_RDWR, 0400, EACCES); 1319 | err += test_open_acc(O_RDONLY, 0200, EACCES); 1320 | err += test_open_acc(O_WRONLY, 0200, 0); 1321 | err += test_open_acc(O_RDWR, 0200, EACCES); 1322 | err += test_open_acc(O_RDONLY, 0000, EACCES); 1323 | err += test_open_acc(O_WRONLY, 0000, EACCES); 1324 | err += test_open_acc(O_RDWR, 0000, EACCES); 1325 | 1326 | unlink(testfile); 1327 | unlink(testfile2); 1328 | rmdir(testdir); 1329 | rmdir(testdir2); 1330 | 1331 | if (err) { 1332 | fprintf(stderr, "%i tests failed\n", -err); 1333 | return 1; 1334 | } 1335 | 1336 | return 0; 1337 | } 1338 | --------------------------------------------------------------------------------