/* FUSE: Filesystem in Userspace Copyright (C) 2016 Nikolaus Rath (C) 2017 EditShare LLC This program can be distributed under the terms of the GNU GPL. See the file COPYING. */ /** @file * * This example implements a file system with two files: * * 'current-time', whose contents change dynamically: * it always contains the current time (same as in * notify_inval_inode.c). * * 'growing', whose size changes dynamically, growing * by 1 byte after each update. This aims to check * if cached file metadata is also invalidated. * * ## Compilation ## * * gcc -Wall @file `pkg-config fuse3 --cflags --libs` -o invalidate_path * * ## Source code ## * \include @file */ #define FUSE_USE_VERSION 31 #include #include /* for fuse_cmdline_opts */ #include #include #include #include #include #include #include #include #include /* We can't actually tell the kernel that there is no timeout, so we just send a big value */ #define NO_TIMEOUT 500000 #define MAX_STR_LEN 128 #define TIME_FILE_NAME "current_time" #define TIME_FILE_INO 2 #define GROW_FILE_NAME "growing" #define GROW_FILE_INO 3 static char time_file_contents[MAX_STR_LEN]; static size_t grow_file_size; /* Command line parsing */ struct options { int no_notify; int update_interval; }; static struct options options = { .no_notify = 0, .update_interval = 1, }; #define OPTION(t, p) { t, offsetof(struct options, p), 1 } static const struct fuse_opt option_spec[] = { OPTION("--no-notify", no_notify), OPTION("--update-interval=%d", update_interval), FUSE_OPT_END }; static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) { (void) conn; cfg->entry_timeout = NO_TIMEOUT; cfg->attr_timeout = NO_TIMEOUT; cfg->negative_timeout = 0; return NULL; } static int xmp_getattr(const char *path, struct stat *stbuf, struct fuse_file_info* fi) { (void) fi; if (strcmp(path, "/") == 0) { stbuf->st_ino = 1; stbuf->st_mode = S_IFDIR | 0755; stbuf->st_nlink = 1; } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) { stbuf->st_ino = TIME_FILE_INO; stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = strlen(time_file_contents); } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) { stbuf->st_ino = GROW_FILE_INO; stbuf->st_mode = S_IFREG | 0444; stbuf->st_nlink = 1; stbuf->st_size = grow_file_size; } else { return -ENOENT; } return 0; } static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, off_t offset, struct fuse_file_info *fi, enum fuse_readdir_flags flags) { (void) fi; (void) offset; (void) flags; if (strcmp(path, "/") != 0) { return -ENOTDIR; } else { (void) filler; (void) buf; struct stat file_stat; xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL); filler(buf, TIME_FILE_NAME, &file_stat, 0, 0); xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL); filler(buf, GROW_FILE_NAME, &file_stat, 0, 0); return 0; } } static int xmp_open(const char *path, struct fuse_file_info *fi) { (void) path; /* Make cache persistent even if file is closed, this makes it easier to see the effects */ fi->keep_cache = 1; return 0; } static int xmp_read(const char *path, char *buf, size_t size, off_t offset, struct fuse_file_info *fi) { (void) fi; (void) offset; if (strcmp(path, "/" TIME_FILE_NAME) == 0) { int file_length = strlen(time_file_contents); int to_copy = offset + size <= file_length ? size : file_length - offset; memcpy(buf, time_file_contents, to_copy); return to_copy; } else { assert(strcmp(path, "/" GROW_FILE_NAME) == 0); int to_copy = offset + size <= grow_file_size ? size : grow_file_size - offset; memset(buf, 'x', to_copy); return to_copy; } } static struct fuse_operations xmp_oper = { .init = xmp_init, .getattr = xmp_getattr, .readdir = xmp_readdir, .open = xmp_open, .read = xmp_read, }; static void update_fs(void) { static int count = 0; struct tm *now; time_t t; t = time(NULL); now = localtime(&t); assert(now != NULL); int time_file_size = strftime(time_file_contents, MAX_STR_LEN, "The current time is %H:%M:%S\n", now); assert(time_file_size != 0); grow_file_size = count++; } static int invalidate(struct fuse *fuse, const char *path) { int status = fuse_invalidate_path(fuse, path); if (status == -ENOENT) { return 0; } else { return status; } } static void* update_fs_loop(void *data) { struct fuse *fuse = (struct fuse*) data; while (1) { update_fs(); if (!options.no_notify) { assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0); assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0); } sleep(options.update_interval); } return NULL; } static void show_help(const char *progname) { printf("usage: %s [options] \n\n", progname); printf("File-system specific options:\n" " --update-interval= Update-rate of file system contents\n" " --no-notify Disable kernel notifications\n" "\n"); } int main(int argc, char *argv[]) { struct fuse_args args = FUSE_ARGS_INIT(argc, argv); struct fuse *fuse; struct fuse_cmdline_opts opts; int res; /* Initialize the files */ update_fs(); if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) return 1; if (fuse_parse_cmdline(&args, &opts) != 0) return 1; if (opts.show_version) { printf("FUSE library version %s\n", fuse_pkgversion()); fuse_lowlevel_version(); res = 0; goto out1; } else if (opts.show_help) { show_help(argv[0]); fuse_cmdline_help(); fuse_lib_help(&args); res = 0; goto out1; } else if (!opts.mountpoint) { fprintf(stderr, "error: no mountpoint specified\n"); res = 1; goto out1; } fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL); if (fuse == NULL) { res = 1; goto out1; } if (fuse_mount(fuse,opts.mountpoint) != 0) { res = 1; goto out2; } if (fuse_daemonize(opts.foreground) != 0) { res = 1; goto out3; } pthread_t updater; /* Start thread to update file contents */ int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse); if (ret != 0) { fprintf(stderr, "pthread_create failed with %s\n", strerror(ret)); return 1; }; struct fuse_session *se = fuse_get_session(fuse); if (fuse_set_signal_handlers(se) != 0) { res = 1; goto out3; } if (opts.singlethread) res = fuse_loop(fuse); else res = fuse_loop_mt(fuse, opts.clone_fd); if (res) res = 1; fuse_remove_signal_handlers(se); out3: fuse_unmount(fuse); out2: fuse_destroy(fuse); out1: free(opts.mountpoint); fuse_opt_free_args(&args); return res; }