libfuse
test_write_cache.c
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 GPL2.txt.
7*/
8
9#define FUSE_USE_VERSION 30
10
11/* Not really needed - just to test build with FUSE_USE_VERSION == 30 */
12#include <fuse.h>
13
14#include <fuse_config.h>
15#include <fuse_lowlevel.h>
16#include <stdio.h>
17#include <stdlib.h>
18#include <string.h>
19#include <errno.h>
20#include <fcntl.h>
21#include <assert.h>
22#include <stddef.h>
23#include <unistd.h>
24#include <sys/stat.h>
25#include <pthread.h>
26#include <stdatomic.h>
27
28#ifndef __linux__
29#include <limits.h>
30#else
31#include <linux/limits.h>
32#endif
33
34#define FILE_INO 2
35#define FILE_NAME "write_me"
36
37/* Command line parsing */
38struct options {
39 int writeback;
40 int data_size;
41 int delay_ms;
42} options = {
43 .writeback = 0,
44 .data_size = 2048,
45 .delay_ms = 0,
46};
47
48#define WRITE_SYSCALLS 64
49
50#define OPTION(t, p) { t, offsetof(struct options, p), 1 }
51static const struct fuse_opt option_spec[] = {
52 OPTION("writeback_cache", writeback),
53 OPTION("--data-size=%d", data_size), OPTION("--delay_ms=%d", delay_ms),
55};
56static int got_write;
57static atomic_int write_cnt;
58
59pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
60pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
61static int write_start, write_done;
62
63static void tfs_init(void *userdata, struct fuse_conn_info *conn)
64{
65 (void)userdata;
66
67 if (options.writeback) {
70 }
71}
72
73static int tfs_stat(fuse_ino_t ino, struct stat *stbuf)
74{
75 stbuf->st_ino = ino;
76 if (ino == FUSE_ROOT_ID) {
77 stbuf->st_mode = S_IFDIR | 0755;
78 stbuf->st_nlink = 1;
79 }
80
81 else if (ino == FILE_INO) {
82 stbuf->st_mode = S_IFREG | 0222;
83 stbuf->st_nlink = 1;
84 stbuf->st_size = 0;
85 }
86
87 else
88 return -1;
89
90 return 0;
91}
92
93static void tfs_lookup(fuse_req_t req, fuse_ino_t parent, const char *name)
94{
95 struct fuse_entry_param e;
96
97 memset(&e, 0, sizeof(e));
98
99 if (parent != FUSE_ROOT_ID)
100 goto err_out;
101 else if (strcmp(name, FILE_NAME) == 0)
102 e.ino = FILE_INO;
103 else
104 goto err_out;
105
106 if (tfs_stat(e.ino, &e.attr) != 0)
107 goto err_out;
108 fuse_reply_entry(req, &e);
109 return;
110
111err_out:
112 fuse_reply_err(req, ENOENT);
113}
114
115static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
116 struct fuse_file_info *fi)
117{
118 struct stat stbuf;
119
120 (void)fi;
121
122 memset(&stbuf, 0, sizeof(stbuf));
123 if (tfs_stat(ino, &stbuf) != 0)
124 fuse_reply_err(req, ENOENT);
125 else
126 fuse_reply_attr(req, &stbuf, 5);
127}
128
129static void tfs_open(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi)
130{
131 if (ino == FUSE_ROOT_ID)
132 fuse_reply_err(req, EISDIR);
133 else {
134 assert(ino == FILE_INO);
135 /* Test close(rofd) does not block waiting for pending writes */
136 fi->noflush = !options.writeback && options.delay_ms &&
137 (fi->flags & O_ACCMODE) == O_RDONLY;
138 fuse_reply_open(req, fi);
139 }
140}
141
142static void tfs_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
143 size_t size, off_t off, struct fuse_file_info *fi)
144{
145 (void)fi;
146 (void)buf;
147 (void)off;
148 size_t expected;
149
150 assert(ino == FILE_INO);
151 expected = options.data_size;
152 if (options.writeback)
153 expected *= 2;
154
155 write_cnt++;
156
157 if (size != expected && !options.writeback)
158 fprintf(stderr, "ERROR: Expected %zd bytes, got %zd\n!",
159 expected, size);
160 else
161 got_write = 1;
162
163 /* Simulate waiting for pending writes */
164 if (options.delay_ms) {
165 pthread_mutex_lock(&lock);
166 write_start = 1;
167 pthread_cond_signal(&cond);
168 pthread_mutex_unlock(&lock);
169
170 usleep(options.delay_ms * 1000);
171
172 pthread_mutex_lock(&lock);
173 write_done = 1;
174 pthread_cond_signal(&cond);
175 pthread_mutex_unlock(&lock);
176 }
177
178 fuse_reply_write(req, size);
179}
180
181static struct fuse_lowlevel_ops tfs_oper = {
182 .init = tfs_init,
183 .lookup = tfs_lookup,
184 .getattr = tfs_getattr,
185 .open = tfs_open,
186 .write = tfs_write,
187};
188
189static void *close_rofd(void *data)
190{
191 int rofd = (int)(long)data;
192
193 /* Wait for first write to start */
194 pthread_mutex_lock(&lock);
195 while (!write_start && !write_done)
196 pthread_cond_wait(&cond, &lock);
197 pthread_mutex_unlock(&lock);
198
199 close(rofd);
200 printf("rofd closed. write_start: %d write_done: %d\n", write_start,
201 write_done);
202
203 /* First write should not have been completed */
204 if (write_done)
205 fprintf(stderr, "ERROR: close(rofd) blocked on write!\n");
206
207 return NULL;
208}
209
210static void *run_fs(void *data)
211{
212 struct fuse_session *se = (struct fuse_session *)data;
213
214 assert(fuse_session_loop(se) == 0);
215 return NULL;
216}
217
218static void test_fs(char *mountpoint)
219{
220 char fname[PATH_MAX];
221 char *buf;
222 const size_t iosize = options.data_size;
223 const size_t dsize = options.data_size * WRITE_SYSCALLS;
224 int fd, rofd;
225 pthread_t rofd_thread;
226 off_t off = 0;
227
228 buf = malloc(dsize);
229 assert(buf != NULL);
230 assert((fd = open("/dev/urandom", O_RDONLY)) != -1);
231 assert(read(fd, buf, dsize) == dsize);
232 close(fd);
233
234 assert(snprintf(fname, PATH_MAX, "%s/" FILE_NAME, mountpoint) > 0);
235 fd = open(fname, O_WRONLY);
236 if (fd == -1) {
237 perror(fname);
238 assert(0);
239 }
240
241 if (options.delay_ms) {
242 /* Verify that close(rofd) does not block waiting for pending writes */
243 rofd = open(fname, O_RDONLY);
244 assert(pthread_create(&rofd_thread, NULL, close_rofd,
245 (void *)(long)rofd) == 0);
246 /* Give close_rofd time to start */
247 usleep(options.delay_ms * 1000);
248 }
249
250 for (int cnt = 0; cnt < WRITE_SYSCALLS; cnt++) {
251 assert(pwrite(fd, buf + off, iosize, off) == iosize);
252 off += iosize;
253 assert(off <= dsize);
254 }
255 free(buf);
256 close(fd);
257
258 if (options.delay_ms) {
259 printf("rwfd closed. write_start: %d write_done: %d\n",
260 write_start, write_done);
261 assert(pthread_join(rofd_thread, NULL) == 0);
262 }
263}
264
265int main(int argc, char *argv[])
266{
267 struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
268 struct fuse_session *se;
269 struct fuse_cmdline_opts fuse_opts;
270 pthread_t fs_thread;
271
272 assert(fuse_opt_parse(&args, &options, option_spec, NULL) == 0);
273 assert(fuse_parse_cmdline(&args, &fuse_opts) == 0);
274#ifndef __FreeBSD__
275 assert(fuse_opt_add_arg(&args, "-oauto_unmount") == 0);
276#endif
277 se = fuse_session_new(&args, &tfs_oper, sizeof(tfs_oper), NULL);
278 fuse_opt_free_args(&args);
279 assert(se != NULL);
280 assert(fuse_set_signal_handlers(se) == 0);
281 assert(fuse_session_mount(se, fuse_opts.mountpoint) == 0);
282
283 /* Start file-system thread */
284 assert(pthread_create(&fs_thread, NULL, run_fs, (void *)se) == 0);
285
286 /* Write test data */
287 test_fs(fuse_opts.mountpoint);
288 free(fuse_opts.mountpoint);
289
290 /* Stop file system */
293 assert(pthread_join(fs_thread, NULL) == 0);
294
295 assert(got_write == 1);
296
297 /*
298 * when writeback cache is enabled, kernel side can merge requests, but
299 * memory pressure, system 'sync' might trigger data flushes before - flush
300 * might happen in between write syscalls - merging subpage writes into
301 * a single page and pages into large fuse requests might or might not work.
302 * Though we can expect that that at least some (but maybe all) write
303 * system calls can be merged.
304 */
305 if (options.writeback)
306 assert(write_cnt < WRITE_SYSCALLS);
307 else
308 assert(write_cnt == WRITE_SYSCALLS);
309
312
313 printf("Test completed successfully.\n");
314 return 0;
315}
316
bool fuse_set_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
int fuse_set_signal_handlers(struct fuse_session *se)
#define FUSE_CAP_WRITEBACK_CACHE
bool fuse_get_feature_flag(struct fuse_conn_info *conn, uint64_t flag)
void fuse_remove_signal_handlers(struct fuse_session *se)
void fuse_session_destroy(struct fuse_session *se)
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info *fi)
void fuse_session_exit(struct fuse_session *se)
int fuse_reply_err(fuse_req_t req, int err)
struct fuse_req * fuse_req_t
int fuse_session_loop(struct fuse_session *se)
Definition fuse_loop.c:19
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
void fuse_session_unmount(struct fuse_session *se)
int fuse_reply_write(fuse_req_t req, size_t count)
int fuse_session_mount(struct fuse_session *se, const char *mountpoint)
uint64_t fuse_ino_t
int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
int fuse_opt_add_arg(struct fuse_args *args, const char *arg)
Definition fuse_opt.c:55
void fuse_opt_free_args(struct fuse_args *args)
Definition fuse_opt.c:34
int fuse_opt_parse(struct fuse_args *args, void *data, const struct fuse_opt opts[], fuse_opt_proc_t proc)
Definition fuse_opt.c:398
#define FUSE_ARGS_INIT(argc, argv)
Definition fuse_opt.h:123
#define FUSE_OPT_END
Definition fuse_opt.h:104
#define FUSE_ROOT_ID
char ** argv
Definition fuse_opt.h:114
fuse_ino_t ino
uint32_t noflush
Definition fuse_common.h:99
void(* init)(void *userdata, struct fuse_conn_info *conn)