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