1/* 2 FUSE: Filesystem in Userspace 3 Copyright (C) 2016 Nikolaus Rath <Nikolaus@rath.org> 4 (C) 2017 EditShare LLC <slawek.rudnicki@editshare.com> 5 6 This program can be distributed under the terms of the GNU GPLv2. 7 See the file COPYING. 8 */ 9 10/** @file 11 * 12 * This example implements a file system with two files: 13 * * 'current-time', whose contents change dynamically: 14 * it always contains the current time (same as in 15 * notify_inval_inode.c). 16 * * 'growing', whose size changes dynamically, growing 17 * by 1 byte after each update. This aims to check 18 * if cached file metadata is also invalidated. 19 * 20 * ## Compilation ## 21 * 22 * gcc -Wall invalidate_path.c `pkg-config fuse3 --cflags --libs` -o invalidate_path 23 * 24 * ## Source code ## 25 * \include invalidate_path.c 26 */ 27 28#define FUSE_USE_VERSION 34 29 30#include <fuse.h> 31#include <fuse_lowlevel.h> /* for fuse_cmdline_opts */ 32 33#include <stdio.h> 34#include <stdlib.h> 35#include <string.h> 36#include <errno.h> 37#include <fcntl.h> 38#include <assert.h> 39#include <stddef.h> 40#include <unistd.h> 41#include <pthread.h> 42 43/* We can't actually tell the kernel that there is no 44 timeout, so we just send a big value */ 45#define NO_TIMEOUT 500000 46 47#define MAX_STR_LEN 128 48#define TIME_FILE_NAME "current_time" 49#define TIME_FILE_INO 2 50#define GROW_FILE_NAME "growing" 51#define GROW_FILE_INO 3 52 53static char time_file_contents[MAX_STR_LEN]; 54static size_t grow_file_size; 55 56/* Command line parsing */ 57struct options { 58 int no_notify; 59 int update_interval; 60}; 61static struct options options = { 62 .no_notify = 0, 63 .update_interval = 1, 64}; 65 66#define OPTION(t, p) { t, offsetof(struct options, p), 1 } 67static const struct fuse_opt option_spec[] = { 68 OPTION("--no-notify", no_notify), 69 OPTION("--update-interval=%d", update_interval), 70 FUSE_OPT_END 71}; 72 73static void *xmp_init(struct fuse_conn_info *conn, struct fuse_config *cfg) 74{ 75 (void) conn; 76 cfg->entry_timeout = NO_TIMEOUT; 77 cfg->attr_timeout = NO_TIMEOUT; 78 cfg->negative_timeout = 0; 79 80 return NULL; 81} 82 83static int xmp_getattr(const char *path, 84 struct stat *stbuf, struct fuse_file_info* fi) { 85 (void) fi; 86 if (strcmp(path, "/") == 0) { 87 stbuf->st_ino = 1; 88 stbuf->st_mode = S_IFDIR | 0755; 89 stbuf->st_nlink = 1; 90 } else if (strcmp(path, "/" TIME_FILE_NAME) == 0) { 91 stbuf->st_ino = TIME_FILE_INO; 92 stbuf->st_mode = S_IFREG | 0444; 93 stbuf->st_nlink = 1; 94 stbuf->st_size = strlen(time_file_contents); 95 } else if (strcmp(path, "/" GROW_FILE_NAME) == 0) { 96 stbuf->st_ino = GROW_FILE_INO; 97 stbuf->st_mode = S_IFREG | 0444; 98 stbuf->st_nlink = 1; 99 stbuf->st_size = grow_file_size; 100 } else { 101 return -ENOENT; 102 } 103 104 return 0; 105} 106 107static int xmp_readdir(const char *path, void *buf, fuse_fill_dir_t filler, 108 off_t offset, struct fuse_file_info *fi, 109 enum fuse_readdir_flags flags) { 110 (void) fi; 111 (void) offset; 112 (void) flags; 113 if (strcmp(path, "/") != 0) { 114 return -ENOTDIR; 115 } else { 116 (void) filler; 117 (void) buf; 118 struct stat file_stat; 119 xmp_getattr("/" TIME_FILE_NAME, &file_stat, NULL); 120 filler(buf, TIME_FILE_NAME, &file_stat, 0, 0); 121 xmp_getattr("/" GROW_FILE_NAME, &file_stat, NULL); 122 filler(buf, GROW_FILE_NAME, &file_stat, 0, 0); 123 return 0; 124 } 125} 126 127static int xmp_open(const char *path, struct fuse_file_info *fi) { 128 (void) path; 129 /* Make cache persistent even if file is closed, 130 this makes it easier to see the effects */ 131 fi->keep_cache = 1; 132 return 0; 133} 134 135static int xmp_read(const char *path, char *buf, size_t size, off_t offset, 136 struct fuse_file_info *fi) { 137 (void) fi; 138 (void) offset; 139 if (strcmp(path, "/" TIME_FILE_NAME) == 0) { 140 int file_length = strlen(time_file_contents); 141 int to_copy = offset + size <= file_length 142 ? size 143 : file_length - offset; 144 memcpy(buf, time_file_contents, to_copy); 145 return to_copy; 146 } else { 147 assert(strcmp(path, "/" GROW_FILE_NAME) == 0); 148 int to_copy = offset + size <= grow_file_size 149 ? size 150 : grow_file_size - offset; 151 memset(buf, 'x', to_copy); 152 return to_copy; 153 } 154} 155 156static const struct fuse_operations xmp_oper = { 157 .init = xmp_init, 158 .getattr = xmp_getattr, 159 .readdir = xmp_readdir, 160 .open = xmp_open, 161 .read = xmp_read, 162}; 163 164static void update_fs(void) { 165 static int count = 0; 166 struct tm *now; 167 time_t t; 168 t = time(NULL); 169 now = localtime(&t); 170 assert(now != NULL); 171 172 int time_file_size = strftime(time_file_contents, MAX_STR_LEN, 173 "The current time is %H:%M:%S\n", now); 174 assert(time_file_size != 0); 175 176 grow_file_size = count++; 177} 178 179static int invalidate(struct fuse *fuse, const char *path) { 180 int status = fuse_invalidate_path(fuse, path); 181 if (status == -ENOENT) { 182 return 0; 183 } else { 184 return status; 185 } 186} 187 188static void* update_fs_loop(void *data) { 189 struct fuse *fuse = (struct fuse*) data; 190 191 while (1) { 192 update_fs(); 193 if (!options.no_notify) { 194 assert(invalidate(fuse, "/" TIME_FILE_NAME) == 0); 195 assert(invalidate(fuse, "/" GROW_FILE_NAME) == 0); 196 } 197 sleep(options.update_interval); 198 } 199 return NULL; 200} 201 202static void show_help(const char *progname) 203{ 204 printf("usage: %s [options] <mountpoint>\n\n", progname); 205 printf("File-system specific options:\n" 206 " --update-interval=<secs> Update-rate of file system contents\n" 207 " --no-notify Disable kernel notifications\n" 208 "\n"); 209} 210 211int main(int argc, char *argv[]) { 212 struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 213 struct fuse *fuse; 214 struct fuse_cmdline_opts opts; 215 struct fuse_loop_config config; 216 int res; 217 218 /* Initialize the files */ 219 update_fs(); 220 221 if (fuse_opt_parse(&args, &options, option_spec, NULL) == -1) 222 return 1; 223 224 if (fuse_parse_cmdline(&args, &opts) != 0) 225 return 1; 226 227 if (opts.show_version) { 228 printf("FUSE library version %s\n", fuse_pkgversion()); 229 fuse_lowlevel_version(); 230 res = 0; 231 goto out1; 232 } else if (opts.show_help) { 233 show_help(argv[0]); 234 fuse_cmdline_help(); 235 fuse_lib_help(&args); 236 res = 0; 237 goto out1; 238 } else if (!opts.mountpoint) { 239 fprintf(stderr, "error: no mountpoint specified\n"); 240 res = 1; 241 goto out1; 242 } 243 244 fuse = fuse_new(&args, &xmp_oper, sizeof(xmp_oper), NULL); 245 if (fuse == NULL) { 246 res = 1; 247 goto out1; 248 } 249 250 if (fuse_mount(fuse,opts.mountpoint) != 0) { 251 res = 1; 252 goto out2; 253 } 254 255 if (fuse_daemonize(opts.foreground) != 0) { 256 res = 1; 257 goto out3; 258 } 259 260 pthread_t updater; /* Start thread to update file contents */ 261 int ret = pthread_create(&updater, NULL, update_fs_loop, (void *) fuse); 262 if (ret != 0) { 263 fprintf(stderr, "pthread_create failed with %s\n", strerror(ret)); 264 return 1; 265 }; 266 267 struct fuse_session *se = fuse_get_session(fuse); 268 if (fuse_set_signal_handlers(se) != 0) { 269 res = 1; 270 goto out3; 271 } 272 273 if (opts.singlethread) 274 res = fuse_loop(fuse); 275 else { 276 config.clone_fd = opts.clone_fd; 277 config.max_idle_threads = opts.max_idle_threads; 278 res = fuse_loop_mt(fuse, &config); 279 } 280 if (res) 281 res = 1; 282 283 fuse_remove_signal_handlers(se); 284out3: 285 fuse_unmount(fuse); 286out2: 287 fuse_destroy(fuse); 288out1: 289 free(opts.mountpoint); 290 fuse_opt_free_args(&args); 291 return res; 292} 293