• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
35 struct 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 }
47 static 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 };
53 static int got_write;
54 
55 pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
56 pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
57 static int write_start, write_done;
58 
tfs_init(void * userdata,struct fuse_conn_info * conn)59 static 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 
tfs_stat(fuse_ino_t ino,struct stat * stbuf)69 static 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 
tfs_lookup(fuse_req_t req,fuse_ino_t parent,const char * name)88 static 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 
105 err_out:
106     fuse_reply_err(req, ENOENT);
107 }
108 
tfs_getattr(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)109 static 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 
tfs_open(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)122 static 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 
tfs_write(fuse_req_t req,fuse_ino_t ino,const char * buf,size_t size,off_t off,struct fuse_file_info * fi)135 static 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 
169 static 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 
close_rofd(void * data)177 static 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 
run_fs(void * data)196 static 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 
test_fs(char * mountpoint)202 static 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 
main(int argc,char * argv[])242 int 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