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" 81static char file_contents[MAX_STR_LEN]; 82static int lookup_cnt = 0; 83static size_t file_size; 84 85/* Keep track if we ever stored data (==1), and 86 received it back correctly (==2) */ 87static int retrieve_status = 0; 88 89/* Command line parsing */ 90struct options { 91 int no_notify; 92 int update_interval; 93}; 94static 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 } 101static 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 107static 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 126static 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 146err_out: 147 fuse_reply_err(req, ENOENT); 148} 149 150static 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 160static 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 173struct dirbuf { 174 char *p; 175 size_t size; 176}; 177 178static 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 192static 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 201static 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 217static 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 237static 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 245static 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 272static 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 282static 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 294static 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 340static 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 349int 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); 410err_out3: 411 fuse_remove_signal_handlers(se); 412err_out2: 413 fuse_session_destroy(se); 414err_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