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