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