• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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  *  contents change dynamically: it always contains the current time.
13  *
14  *  While notify_inval_inode.c uses fuse_lowlevel_notify_inval_inode()
15  *  to let the kernel know that it has to invalidate the cache, this
16  *  example actively pushes the updated data into the kernel cache
17  *  using fuse_lowlevel_notify_store().
18  *
19  *  To see the effect, first start the file system with the
20  *  ``--no-notify`` option:
21  *
22  *      $ notify_store_retrieve --update-interval=1 --no-notify mnt/
23  *
24  *  Observe that the output never changes, even though the file system
25  *  updates it once per second. This is because the contents are cached
26  *  in the kernel:
27  *
28  *      $ for i in 1 2 3 4 5; do
29  *      >     cat mnt/current_time
30  *      >     sleep 1
31  *      > done
32  *      The current time is 15:58:18
33  *      The current time is 15:58:18
34  *      The current time is 15:58:18
35  *      The current time is 15:58:18
36  *      The current time is 15:58:18
37  *
38  *  If you instead enable the notification functions, the changes become
39  *  visible:
40  *
41  *      $ notify_store_retrieve --update-interval=1 mnt/
42  *      $ for i in 1 2 3 4 5; do
43  *      >     cat mnt/current_time
44  *      >     sleep 1
45  *      > done
46  *      The current time is 15:58:40
47  *      The current time is 15:58:41
48  *      The current time is 15:58:42
49  *      The current time is 15:58:43
50  *      The current time is 15:58:44
51  *
52  * ## Compilation ##
53  *
54  *     gcc -Wall notify_store_retrieve.c `pkg-config fuse3 --cflags --libs` -o notify_store_retrieve
55  *
56  * ## Source code ##
57  * \include notify_store_retrieve.c
58  */
59 
60 
61 #define FUSE_USE_VERSION 34
62 
63 #include <fuse_lowlevel.h>
64 #include <stdio.h>
65 #include <stdlib.h>
66 #include <string.h>
67 #include <errno.h>
68 #include <fcntl.h>
69 #include <assert.h>
70 #include <stddef.h>
71 #include <unistd.h>
72 #include <pthread.h>
73 
74 /* We can't actually tell the kernel that there is no
75    timeout, so we just send a big value */
76 #define NO_TIMEOUT 500000
77 
78 #define MAX_STR_LEN 128
79 #define FILE_INO 2
80 #define FILE_NAME "current_time"
81 static char file_contents[MAX_STR_LEN];
82 static int lookup_cnt = 0;
83 static size_t file_size;
84 
85 /* Keep track if we ever stored data (==1), and
86    received it back correctly (==2) */
87 static int retrieve_status = 0;
88 
89 /* Command line parsing */
90 struct options {
91     int no_notify;
92     int update_interval;
93 };
94 static struct options options = {
95     .no_notify = 0,
96     .update_interval = 1,
97 };
98 
99 #define OPTION(t, p)                           \
100     { t, offsetof(struct options, p), 1 }
101 static const struct fuse_opt option_spec[] = {
102     OPTION("--no-notify", no_notify),
103     OPTION("--update-interval=%d", update_interval),
104     FUSE_OPT_END
105 };
106 
tfs_stat(fuse_ino_t ino,struct stat * stbuf)107 static int tfs_stat(fuse_ino_t ino, struct stat *stbuf) {
108     stbuf->st_ino = ino;
109     if (ino == FUSE_ROOT_ID) {
110         stbuf->st_mode = S_IFDIR | 0755;
111         stbuf->st_nlink = 1;
112     }
113 
114     else if (ino == FILE_INO) {
115         stbuf->st_mode = S_IFREG | 0444;
116         stbuf->st_nlink = 1;
117         stbuf->st_size = file_size;
118     }
119 
120     else
121         return -1;
122 
123     return 0;
124 }
125 
tfs_lookup(fuse_req_t req,fuse_ino_t parent,const char * name)126 static void tfs_lookup(fuse_req_t req, fuse_ino_t parent,
127                        const char *name) {
128     struct fuse_entry_param e;
129     memset(&e, 0, sizeof(e));
130 
131     if (parent != FUSE_ROOT_ID)
132         goto err_out;
133     else if (strcmp(name, FILE_NAME) == 0) {
134         e.ino = FILE_INO;
135         lookup_cnt++;
136     } else
137         goto err_out;
138 
139     e.attr_timeout = NO_TIMEOUT;
140     e.entry_timeout = NO_TIMEOUT;
141     if (tfs_stat(e.ino, &e.attr) != 0)
142         goto err_out;
143     fuse_reply_entry(req, &e);
144     return;
145 
146 err_out:
147     fuse_reply_err(req, ENOENT);
148 }
149 
tfs_forget(fuse_req_t req,fuse_ino_t ino,uint64_t nlookup)150 static void tfs_forget (fuse_req_t req, fuse_ino_t ino,
151                         uint64_t nlookup) {
152     (void) req;
153     if(ino == FILE_INO)
154         lookup_cnt -= nlookup;
155     else
156         assert(ino == FUSE_ROOT_ID);
157     fuse_reply_none(req);
158 }
159 
tfs_getattr(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)160 static void tfs_getattr(fuse_req_t req, fuse_ino_t ino,
161                         struct fuse_file_info *fi) {
162     struct stat stbuf;
163 
164     (void) fi;
165 
166     memset(&stbuf, 0, sizeof(stbuf));
167     if (tfs_stat(ino, &stbuf) != 0)
168         fuse_reply_err(req, ENOENT);
169     else
170         fuse_reply_attr(req, &stbuf, NO_TIMEOUT);
171 }
172 
173 struct dirbuf {
174     char *p;
175     size_t size;
176 };
177 
dirbuf_add(fuse_req_t req,struct dirbuf * b,const char * name,fuse_ino_t ino)178 static void dirbuf_add(fuse_req_t req, struct dirbuf *b, const char *name,
179                        fuse_ino_t ino) {
180     struct stat stbuf;
181     size_t oldsize = b->size;
182     b->size += fuse_add_direntry(req, NULL, 0, name, NULL, 0);
183     b->p = (char *) realloc(b->p, b->size);
184     memset(&stbuf, 0, sizeof(stbuf));
185     stbuf.st_ino = ino;
186     fuse_add_direntry(req, b->p + oldsize, b->size - oldsize, name, &stbuf,
187                       b->size);
188 }
189 
190 #define min(x, y) ((x) < (y) ? (x) : (y))
191 
reply_buf_limited(fuse_req_t req,const char * buf,size_t bufsize,off_t off,size_t maxsize)192 static int reply_buf_limited(fuse_req_t req, const char *buf, size_t bufsize,
193                              off_t off, size_t maxsize) {
194     if (off < bufsize)
195         return fuse_reply_buf(req, buf + off,
196                               min(bufsize - off, maxsize));
197     else
198         return fuse_reply_buf(req, NULL, 0);
199 }
200 
tfs_readdir(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)201 static void tfs_readdir(fuse_req_t req, fuse_ino_t ino, size_t size,
202                         off_t off, struct fuse_file_info *fi) {
203     (void) fi;
204 
205     if (ino != FUSE_ROOT_ID)
206         fuse_reply_err(req, ENOTDIR);
207     else {
208         struct dirbuf b;
209 
210         memset(&b, 0, sizeof(b));
211         dirbuf_add(req, &b, FILE_NAME, FILE_INO);
212         reply_buf_limited(req, b.p, b.size, off, size);
213         free(b.p);
214     }
215 }
216 
tfs_open(fuse_req_t req,fuse_ino_t ino,struct fuse_file_info * fi)217 static void tfs_open(fuse_req_t req, fuse_ino_t ino,
218                      struct fuse_file_info *fi) {
219 
220     /* Make cache persistent even if file is closed,
221        this makes it easier to see the effects */
222     fi->keep_cache = 1;
223 
224     if (ino == FUSE_ROOT_ID)
225         fuse_reply_err(req, EISDIR);
226     else if ((fi->flags & O_ACCMODE) != O_RDONLY)
227         fuse_reply_err(req, EACCES);
228     else if (ino == FILE_INO)
229         fuse_reply_open(req, fi);
230     else {
231         // This should not happen
232         fprintf(stderr, "Got open for non-existing inode!\n");
233         fuse_reply_err(req, ENOENT);
234     }
235 }
236 
tfs_read(fuse_req_t req,fuse_ino_t ino,size_t size,off_t off,struct fuse_file_info * fi)237 static void tfs_read(fuse_req_t req, fuse_ino_t ino, size_t size,
238                      off_t off, struct fuse_file_info *fi) {
239     (void) fi;
240 
241     assert(ino == FILE_INO);
242     reply_buf_limited(req, file_contents, file_size, off, size);
243 }
244 
tfs_retrieve_reply(fuse_req_t req,void * cookie,fuse_ino_t ino,off_t offset,struct fuse_bufvec * data)245 static void tfs_retrieve_reply(fuse_req_t req, void *cookie, fuse_ino_t ino,
246                                off_t offset, struct fuse_bufvec *data) {
247     struct fuse_bufvec bufv;
248     char buf[MAX_STR_LEN];
249     char *expected;
250     ssize_t ret;
251 
252     assert(ino == FILE_INO);
253     assert(offset == 0);
254     expected = (char*) cookie;
255 
256     bufv.count = 1;
257     bufv.idx = 0;
258     bufv.off = 0;
259     bufv.buf[0].size = MAX_STR_LEN;
260     bufv.buf[0].mem = buf;
261     bufv.buf[0].flags = 0;
262 
263     ret = fuse_buf_copy(&bufv, data, 0);
264     assert(ret > 0);
265     assert(strncmp(buf, expected, ret) == 0);
266     free(expected);
267     retrieve_status = 2;
268     fuse_reply_none(req);
269 }
270 
271 
272 static const struct fuse_lowlevel_ops tfs_oper = {
273     .lookup	= tfs_lookup,
274     .getattr	= tfs_getattr,
275     .readdir	= tfs_readdir,
276     .open	= tfs_open,
277     .read	= tfs_read,
278     .forget     = tfs_forget,
279     .retrieve_reply = tfs_retrieve_reply,
280 };
281 
update_fs(void)282 static void update_fs(void) {
283     struct tm *now;
284     time_t t;
285     t = time(NULL);
286     now = localtime(&t);
287     assert(now != NULL);
288 
289     file_size = strftime(file_contents, MAX_STR_LEN,
290                          "The current time is %H:%M:%S\n", now);
291     assert(file_size != 0);
292 }
293 
update_fs_loop(void * data)294 static void* update_fs_loop(void *data) {
295     struct fuse_session *se = (struct fuse_session*) data;
296     struct fuse_bufvec bufv;
297     int ret;
298 
299     while(1) {
300         update_fs();
301         if (!options.no_notify && lookup_cnt) {
302             /* Only send notification if the kernel
303                is aware of the inode */
304             bufv.count = 1;
305             bufv.idx = 0;
306             bufv.off = 0;
307             bufv.buf[0].size = file_size;
308             bufv.buf[0].mem = file_contents;
309             bufv.buf[0].flags = 0;
310 
311             /* This shouldn't fail, but apparently it sometimes
312                does - see https://github.com/libfuse/libfuse/issues/105 */
313             ret = fuse_lowlevel_notify_store(se, FILE_INO, 0, &bufv, 0);
314             if (-ret == ENODEV) {
315                 // File system was unmounted
316                 break;
317             }
318             else if (ret != 0) {
319                 fprintf(stderr, "ERROR: fuse_lowlevel_notify_store() failed with %s (%d)\n",
320                         strerror(-ret), -ret);
321                 abort();
322             }
323 
324             /* To make sure that everything worked correctly, ask the
325                kernel to send us back the stored data */
326             ret = fuse_lowlevel_notify_retrieve(se, FILE_INO, MAX_STR_LEN,
327                                                 0, (void*) strdup(file_contents));
328             if (-ret == ENODEV) { // File system was unmounted
329                 break;
330             }
331             assert(ret == 0);
332             if(retrieve_status == 0)
333                 retrieve_status = 1;
334         }
335         sleep(options.update_interval);
336     }
337     return NULL;
338 }
339 
show_help(const char * progname)340 static void show_help(const char *progname)
341 {
342     printf("usage: %s [options] <mountpoint>\n\n", progname);
343     printf("File-system specific options:\n"
344                "    --update-interval=<secs>  Update-rate of file system contents\n"
345                "    --no-notify            Disable kernel notifications\n"
346                "\n");
347 }
348 
main(int argc,char * argv[])349 int main(int argc, char *argv[]) {
350     struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
351     struct fuse_session *se;
352     struct fuse_cmdline_opts opts;
353     struct fuse_loop_config config;
354     pthread_t updater;
355     int ret = -1;
356 
357     if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1)
358         return 1;
359 
360     if (fuse_parse_cmdline(&args, &opts) != 0)
361         return 1;
362     if (opts.show_help) {
363         show_help(argv[0]);
364         fuse_cmdline_help();
365         fuse_lowlevel_help();
366         ret = 0;
367         goto err_out1;
368     } else if (opts.show_version) {
369         printf("FUSE library version %s\n", fuse_pkgversion());
370         fuse_lowlevel_version();
371         ret = 0;
372         goto err_out1;
373     }
374 
375     /* Initial contents */
376     update_fs();
377 
378     se = fuse_session_new(&args, &tfs_oper,
379                           sizeof(tfs_oper), NULL);
380     if (se == NULL)
381         goto err_out1;
382 
383     if (fuse_set_signal_handlers(se) != 0)
384         goto err_out2;
385 
386     if (fuse_session_mount(se, opts.mountpoint) != 0)
387         goto err_out3;
388 
389     fuse_daemonize(opts.foreground);
390 
391     /* Start thread to update file contents */
392     ret = pthread_create(&updater, NULL, update_fs_loop, (void *)se);
393     if (ret != 0) {
394         fprintf(stderr, "pthread_create failed with %s\n",
395                 strerror(ret));
396         goto err_out3;
397     }
398 
399     /* Block until ctrl+c or fusermount -u */
400     if (opts.singlethread)
401         ret = fuse_session_loop(se);
402     else {
403         config.clone_fd = opts.clone_fd;
404         config.max_idle_threads = opts.max_idle_threads;
405         ret = fuse_session_loop_mt(se, &config);
406     }
407 
408     assert(retrieve_status != 1);
409     fuse_session_unmount(se);
410 err_out3:
411     fuse_remove_signal_handlers(se);
412 err_out2:
413     fuse_session_destroy(se);
414 err_out1:
415     free(opts.mountpoint);
416     fuse_opt_free_args(&args);
417 
418     return ret ? 1 : 0;
419 }
420 
421 
422 /**
423  * Local Variables:
424  * mode: c
425  * indent-tabs-mode: nil
426  * c-basic-offset: 4
427  * End:
428  */
429