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/** @file
10 *
11 * This example implements a file system with a single file whose
12 * file name changes dynamically to reflect the current time.
13 *
14 * It illustrates the use of the fuse_lowlevel_notify_inval_entry() and
15 * fuse_lowlevel_notify_expire_entry() functions.
16 *
17 * To see the effect, first start the file system with the
18 * ``--no-notify``
19 *
20 *     $ notify_inval_entry --update-interval=1 --timeout 30 --no-notify mnt/
21 *
22 * Observe that `ls` always prints the correct directory contents
23 * (since `readdir` output is not cached)::
24 *
25 *     $ ls mnt; sleep 1; ls mnt; sleep 1; ls mnt
26 *     Time_is_15h_48m_33s  current_time
27 *     Time_is_15h_48m_34s  current_time
28 *     Time_is_15h_48m_35s  current_time
29 *
30 * However, if you try to access a file by name the kernel will
31 * report that it still exists:
32 *
33 *     $ file=$(ls mnt/); echo $file
34 *     Time_is_15h_50m_09s
35 *     $ sleep 5; stat mnt/$file
36 *       File: ‘mnt/Time_is_15h_50m_09s37 *       Size: 32                Blocks: 0          IO Block: 4096   regular file
38 *     Device: 2ah/42d	Inode: 3           Links: 1
39 *     Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
40 *     Access: 1969-12-31 16:00:00.000000000 -0800
41 *     Modify: 1969-12-31 16:00:00.000000000 -0800
42 *     Change: 1969-12-31 16:00:00.000000000 -0800
43 *      Birth: -
44 *
45 * Only once the kernel cache timeout has been reached will the stat
46 * call fail:
47 *
48 *     $ sleep 30; stat mnt/$file
49 *     stat: cannot stat ‘mnt/Time_is_15h_50m_09s’: No such file or directory
50 *
51 * In contrast, if you enable notifications you will be unable to stat
52 * the file as soon as the file system updates its name:
53 *
54 *     $ notify_inval_entry --update-interval=1 --timeout 30 --no-notify mnt/
55 *     $ file=$(ls mnt/); stat mnt/$file
56 *       File: ‘mnt/Time_is_20h_42m_11s57 *       Size: 0                 Blocks: 0          IO Block: 4096   regular empty file
58 *     Device: 2ah/42d	Inode: 2           Links: 1
59 *     Access: (0000/----------)  Uid: (    0/    root)   Gid: (    0/    root)
60 *     Access: 1969-12-31 16:00:00.000000000 -0800
61 *     Modify: 1969-12-31 16:00:00.000000000 -0800
62 *     Change: 1969-12-31 16:00:00.000000000 -0800
63 *      Birth: -
64 *     $ sleep 1; stat mnt/$file
65 *     stat: cannot stat ‘mnt/Time_is_20h_42m_11s’: No such file or directory
66 *
67 * To use the function fuse_lowlevel_notify_expire_entry() instead of
68 * fuse_lowlevel_notify_inval_entry(), use the command line option --only-expire
69 *
70 * ## Compilation ##
71 *
72 *     gcc -Wall notify_inval_entry.c `pkg-config fuse3 --cflags --libs` -o notify_inval_entry
73 *
74 * ## Source code ##
75 * \include notify_inval_entry.c
76 */
77
78
79#define FUSE_USE_VERSION 34
80
81#include <fuse_lowlevel.h>
82#include <stdio.h>
83#include <stdlib.h>
84#include <string.h>
85#include <errno.h>
86#include <fcntl.h>
87#include <assert.h>
88#include <signal.h>
89#include <stddef.h>
90#include <sys/stat.h>
91#include <unistd.h>
92#include <pthread.h>
93
94#define MAX_STR_LEN 128
95static char file_name[MAX_STR_LEN];
96static fuse_ino_t file_ino = 2;
97static int lookup_cnt = 0;
98static pthread_t main_thread;
99
100/* Command line parsing */
101struct options {
102    int no_notify;
103    float timeout;
104    int update_interval;
105    int only_expire;
106};
107static struct options options = {
108    .timeout = 5,
109    .no_notify = 0,
110    .update_interval = 1,
111    .only_expire = 0,
112};
113
114#define OPTION(t, p)                           \
115    { t, offsetof(struct options, p), 1 }
116static const struct fuse_opt option_spec[] = {
117    OPTION("--no-notify", no_notify),
118    OPTION("--update-interval=%d", update_interval),
119    OPTION("--timeout=%f", timeout),
120    OPTION("--only-expire", only_expire),
121    FUSE_OPT_END
122};
123
124static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
125    stbuf->st_ino = ino;
126    if (ino == FUSE_ROOT_ID) {
127        stbuf->st_mode = S_IFDIR | 0755;
128        stbuf->st_nlink = 1;
129    }
130
131    else if (ino == file_ino) {
132        stbuf->st_mode = S_IFREG | 0000;
133        stbuf->st_nlink = 1;
134        stbuf->st_size = 0;
135    }
136
137    else
138        return -1;
139
140    return 0;
141}
142
143static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
144                       const char *name) {
145    struct fuse_entry_param e;
146    memset(&e, 0, sizeof(e));
147
148    if (parent != FUSE_ROOT_ID)
149        goto err_out;
150    else if (strcmp(name, file_name) == 0) {
151        e.ino = file_ino;
152        lookup_cnt++;
153    } else
154        goto err_out;
155
156    e.attr_timeout = options.timeout;
157    e.entry_timeout = options.timeout;
158    if (tfs_stat(e.ino, &e.attr) != 0)
159        goto err_out;
160    fuse_reply_entry(req, &e);
161    return;
162
163err_out:
164    fuse_reply_err(req, ENOENT);
165}
166
167static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
168                        uint64_t nlookup) {
169    (void) req;
170    if(ino == file_ino)
171        lookup_cnt -= nlookup;
172    else
173        assert(ino == FUSE_ROOT_ID);
174    fuse_reply_none(req);
175}
176
177static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
178                        struct fuse_file_info *fi) {
179    struct stat stbuf;
180
181    (void) fi;
182
183    memset(&stbuf, 0, sizeof(stbuf));
184    if (tfs_stat(ino, &stbuf) != 0)
185        fuse_reply_err(req, ENOENT);
186    else
187        fuse_reply_attr(req, &stbuf, options.timeout);
188}
189
190struct dirbuf {
191    char *p;
192    size_t size;
193};
194
195static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
196                       fuse_ino_t ino) {
197    struct stat stbuf;
198    size_t oldsize = b->size;
199    b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
200    b->p = (char *) realloc(b->p, b->size);
201    memset(&stbuf, 0, sizeof(stbuf));
202    stbuf.st_ino = ino;
203    fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
204                      b->size);
205}
206
207#define min(x, y) ((x) < (y) ? (x) : (y))
208
209static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
210                             off_t off, size_t maxsize) {
211    if (off < bufsize)
212        return fuse_reply_buf(req, buf + off,
213                              min(bufsize - off, maxsize));
214    else
215        return fuse_reply_buf(req, NULL, 0);
216}
217
218static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
219                        off_t off, struct fuse_file_info *fi) {
220    (void) fi;
221
222    if (ino != FUSE_ROOT_ID)
223        fuse_reply_err(req, ENOTDIR);
224    else {
225        struct dirbuf b;
226
227        memset(&b, 0, sizeof(b));
228        dirbuf_add(req, &b, file_name, file_ino);
229        reply_buf_limited(req, b.p, b.size, off, size);
230        free(b.p);
231    }
232}
233
234static const struct fuse_lowlevel_ops tfs_oper = {
235    .lookup	= tfs_lookup,
236    .getattr	= tfs_getattr,
237    .readdir	= tfs_readdir,
238    .forget     = tfs_forget,
239};
240
241static void update_fs(void) {
242    time_t t;
243    struct tm *now;
244    ssize_t ret;
245
246    t = time(NULL);
247    now = localtime(&t);
248    assert(now != NULL);
249
250    ret = strftime(file_name, MAX_STR_LEN,
251                   "Time_is_%Hh_%Mm_%Ss", now);
252    assert(ret != 0);
253}
254
255static void* update_fs_loop(void *data) {
256    struct fuse_session *se = (struct fuse_session*) data;
257    char *old_name;
258
259
260    while(!fuse_session_exited(se)) {
261        old_name = strdup(file_name);
262        update_fs();
263
264        if (!options.no_notify && lookup_cnt) {
265            if(options.only_expire) { // expire entry
266                int ret = fuse_lowlevel_notify_expire_entry
267                   (se, FUSE_ROOT_ID, old_name, strlen(old_name));
268
269                // no kernel support
270                if (ret == -ENOSYS) {
271                    printf("fuse_lowlevel_notify_expire_entry not supported by kernel\n");
272                    printf("Exiting...\n");
273
274                    fuse_session_exit(se);
275                    // Make sure to exit now, rather than on next request from userspace
276                    pthread_kill(main_thread, SIGPIPE);
277
278                    break;
279                }
280                // 1) ret == 0: successful expire of an existing entry
281                // 2) ret == -ENOENT: kernel has already expired the entry /
282                //                    entry does not exist anymore in the kernel
283                assert(ret == 0 || ret == -ENOENT);
284            } else { // invalidate entry
285                assert(fuse_lowlevel_notify_inval_entry
286                      (se, FUSE_ROOT_ID, old_name, strlen(old_name)) == 0);
287            }
288        }
289        free(old_name);
290        sleep(options.update_interval);
291    }
292    return NULL;
293}
294
295static void show_help(const char *progname)
296{
297    printf("usage: %s [options] <mountpoint>\n\n", progname);
298    printf("File-system specific options:\n"
299               "    --timeout=<secs>       Timeout for kernel caches\n"
300               "    --update-interval=<secs>  Update-rate of file system contents\n"
301               "    --no-notify            Disable kernel notifications\n"
302               "    --only-expire            Expire entries instead of invalidating them\n"
303               "\n");
304}
305
306int main(int argc, char *argv[]) {
307    struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
308    struct fuse_session *se;
309    struct fuse_cmdline_opts opts;
310    struct fuse_loop_config config;
311    pthread_t updater;
312    int ret = -1;
313
314    if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
315        return 1;
316
317    if (fuse_parse_cmdline(&args, &opts) != 0)
318        return 1;
319    if (opts.show_help) {
320        show_help(argv[0]);
321        fuse_cmdline_help();
322        fuse_lowlevel_help();
323        ret = 0;
324        goto err_out1;
325    } else if (opts.show_version) {
326        printf("FUSE library version %s\n", fuse_pkgversion());
327        fuse_lowlevel_version();
328        ret = 0;
329        goto err_out1;
330    }
331
332    /* Initial contents */
333    update_fs();
334
335    se = fuse_session_new(&args, &tfs_oper,
336                          sizeof(tfs_oper), &se);
337    if (se == NULL)
338        goto err_out1;
339
340    if (fuse_set_signal_handlers(se) != 0)
341        goto err_out2;
342
343    if (fuse_session_mount(se, opts.mountpoint) != 0)
344        goto err_out3;
345
346    fuse_daemonize(opts.foreground);
347
348    // Needed to ensure that the main thread continues/restarts processing as soon
349    // as the fuse session ends (immediately after calling fuse_session_exit() )
350    // and not only on the next request from userspace
351    main_thread = pthread_self();
352
353    /* Start thread to update file contents */
354    ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
355    if (ret != 0) {
356        fprintf(stderr, "pthread_create failed with %s\n",
357                strerror(ret));
358        goto err_out3;
359    }
360
361    /* Block until ctrl+c or fusermount -u */
362    if (opts.singlethread) {
363        ret = fuse_session_loop(se);
364    } else {
365        config.clone_fd = opts.clone_fd;
366        config.max_idle_threads = opts.max_idle_threads;
367        ret = fuse_session_loop_mt(se, &config);
368    }
369
370    fuse_session_unmount(se);
371err_out3:
372    fuse_remove_signal_handlers(se);
373err_out2:
374    fuse_session_destroy(se);
375err_out1:
376    free(opts.mountpoint);
377    fuse_opt_free_args(&args);
378
379    return ret ? 1 : 0;
380}
381
382
383/**
384 * Local Variables:
385 * mode: c
386 * indent-tabs-mode: nil
387 * c-basic-offset: 4
388 * End:
389 */
390