1/* 2 FUSE: Filesystem in Userspace 3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> 4 5 This program can be distributed under the terms of the GNU GPLv2. 6 See the file COPYING. 7*/ 8 9 10#define FUSE_USE_VERSION 30 11 12#include <fuse_config.h> 13#include <fuse_lowlevel.h> 14#include <stdio.h> 15#include <stdlib.h> 16#include <string.h> 17#include <errno.h> 18#include <fcntl.h> 19#include <assert.h> 20#include <stddef.h> 21#include <unistd.h> 22#include <sys/stat.h> 23#include <pthread.h> 24 25#ifndef __linux__ 26#include <limits.h> 27#else 28#include <linux/limits.h> 29#endif 30 31#define FILE_INO 2 32#define FILE_NAME "write_me" 33 34/* Command line parsing */ 35struct options { 36 int writeback; 37 int data_size; 38 int delay_ms; 39} options = { 40 .writeback = 0, 41 .data_size = 2048, 42 .delay_ms = 0, 43}; 44 45#define OPTION(t, p) \ 46 { t, offsetof(struct options, p), 1 } 47static const struct fuse_opt option_spec[] = { 48 OPTION("writeback_cache", writeback), 49 OPTION("--data-size=%d", data_size), 50 OPTION("--delay_ms=%d", delay_ms), 51 FUSE_OPT_END 52}; 53static int got_write; 54 55pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 56pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; 57static int write_start, write_done; 58 59static void tfs_init (void *userdata, struct fuse_conn_info *conn) 60{ 61 (void) userdata; 62 63 if(options.writeback) { 64 assert(conn->capable & FUSE_CAP_WRITEBACK_CACHE); 65 conn->want |= FUSE_CAP_WRITEBACK_CACHE; 66 } 67} 68 69static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) { 70 stbuf->st_ino = ino; 71 if (ino == FUSE_ROOT_ID) { 72 stbuf->st_mode = S_IFDIR | 0755; 73 stbuf->st_nlink = 1; 74 } 75 76 else if (ino == FILE_INO) { 77 stbuf->st_mode = S_IFREG | 0222; 78 stbuf->st_nlink = 1; 79 stbuf->st_size = 0; 80 } 81 82 else 83 return -1; 84 85 return 0; 86} 87 88static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, 89 const char *name) { 90 struct fuse_entry_param e; 91 memset(&e, 0, sizeof(e)); 92 93 if (parent != FUSE_ROOT_ID) 94 goto err_out; 95 else if (strcmp(name, FILE_NAME) == 0) 96 e.ino = FILE_INO; 97 else 98 goto err_out; 99 100 if (tfs_stat(e.ino, &e.attr) != 0) 101 goto err_out; 102 fuse_reply_entry(req, &e); 103 return; 104 105err_out: 106 fuse_reply_err(req, ENOENT); 107} 108 109static void tfs_getattr(fuse_req_t req, fuse_ino_t ino, 110 struct fuse_file_info *fi) { 111 struct stat stbuf; 112 113 (void) fi; 114 115 memset(&stbuf, 0, sizeof(stbuf)); 116 if (tfs_stat(ino, &stbuf) != 0) 117 fuse_reply_err(req, ENOENT); 118 else 119 fuse_reply_attr(req, &stbuf, 5); 120} 121 122static void tfs_open(fuse_req_t req, fuse_ino_t ino, 123 struct fuse_file_info *fi) { 124 if (ino == FUSE_ROOT_ID) 125 fuse_reply_err(req, EISDIR); 126 else { 127 assert(ino == FILE_INO); 128 /* Test close(rofd) does not block waiting for pending writes */ 129 fi->noflush = !options.writeback && options.delay_ms && 130 (fi->flags & O_ACCMODE) == O_RDONLY; 131 fuse_reply_open(req, fi); 132 } 133} 134 135static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf, 136 size_t size, off_t off, struct fuse_file_info *fi) { 137 (void) fi; (void) buf; (void) off; 138 size_t expected; 139 140 assert(ino == FILE_INO); 141 expected = options.data_size; 142 if(options.writeback) 143 expected *= 2; 144 145 if(size != expected) 146 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!", 147 expected, size); 148 else 149 got_write = 1; 150 151 /* Simulate waiting for pending writes */ 152 if (options.delay_ms) { 153 pthread_mutex_lock(&lock); 154 write_start = 1; 155 pthread_cond_signal(&cond); 156 pthread_mutex_unlock(&lock); 157 158 usleep(options.delay_ms * 1000); 159 160 pthread_mutex_lock(&lock); 161 write_done = 1; 162 pthread_cond_signal(&cond); 163 pthread_mutex_unlock(&lock); 164 } 165 166 fuse_reply_write(req, size); 167} 168 169static struct fuse_lowlevel_ops tfs_oper = { 170 .init = tfs_init, 171 .lookup = tfs_lookup, 172 .getattr = tfs_getattr, 173 .open = tfs_open, 174 .write = tfs_write, 175}; 176 177static void* close_rofd(void *data) { 178 int rofd = (int)(long) data; 179 180 /* Wait for first write to start */ 181 pthread_mutex_lock(&lock); 182 while (!write_start && !write_done) 183 pthread_cond_wait(&cond, &lock); 184 pthread_mutex_unlock(&lock); 185 186 close(rofd); 187 printf("rofd closed. write_start: %d write_done: %d\n", write_start, write_done); 188 189 /* First write should not have been completed */ 190 if (write_done) 191 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n"); 192 193 return NULL; 194} 195 196static void* run_fs(void *data) { 197 struct fuse_session *se = (struct fuse_session*) data; 198 assert(fuse_session_loop(se) == 0); 199 return NULL; 200} 201 202static void test_fs(char *mountpoint) { 203 char fname[PATH_MAX]; 204 char *buf; 205 size_t dsize = options.data_size; 206 int fd, rofd; 207 pthread_t rofd_thread; 208 209 buf = malloc(dsize); 210 assert(buf != NULL); 211 assert((fd = open("/dev/urandom", O_RDONLY)) != -1); 212 assert(read(fd, buf, dsize) == dsize); 213 close(fd); 214 215 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, 216 mountpoint) > 0); 217 fd = open(fname, O_WRONLY); 218 if (fd == -1) { 219 perror(fname); 220 assert(0); 221 } 222 223 if (options.delay_ms) { 224 /* Verify that close(rofd) does not block waiting for pending writes */ 225 rofd = open(fname, O_RDONLY); 226 assert(pthread_create(&rofd_thread, NULL, close_rofd, (void *)(long)rofd) == 0); 227 /* Give close_rofd time to start */ 228 usleep(options.delay_ms * 1000); 229 } 230 231 assert(write(fd, buf, dsize) == dsize); 232 assert(write(fd, buf, dsize) == dsize); 233 free(buf); 234 close(fd); 235 236 if (options.delay_ms) { 237 printf("rwfd closed. write_start: %d write_done: %d\n", write_start, write_done); 238 assert(pthread_join(rofd_thread, NULL) == 0); 239 } 240} 241 242int main(int argc, char *argv[]) { 243 struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 244 struct fuse_session *se; 245 struct fuse_cmdline_opts fuse_opts; 246 pthread_t fs_thread; 247 248 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0); 249 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0); 250#ifndef __FreeBSD__ 251 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0); 252#endif 253 se = fuse_session_new(&args, &tfs_oper, 254 sizeof(tfs_oper), NULL); 255 fuse_opt_free_args(&args); 256 assert (se != NULL); 257 assert(fuse_set_signal_handlers(se) == 0); 258 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0); 259 260 /* Start file-system thread */ 261 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0); 262 263 /* Write test data */ 264 test_fs(fuse_opts.mountpoint); 265 free(fuse_opts.mountpoint); 266 267 /* Stop file system */ 268 fuse_session_exit(se); 269 fuse_session_unmount(se); 270 assert(pthread_join(fs_thread, NULL) == 0); 271 272 assert(got_write == 1); 273 fuse_remove_signal_handlers(se); 274 fuse_session_destroy(se); 275 276 printf("Test completed successfully.\n"); 277 return 0; 278} 279 280 281/** 282 * Local Variables: 283 * mode: c 284 * indent-tabs-mode: nil 285 * c-basic-offset: 4 286 * End: 287 */ 288