Permalink
Cannot retrieve contributors at this time
| /* | |
| FUSE fsel: FUSE select example | |
| Copyright (C) 2008 SUSE Linux Products GmbH | |
| Copyright (C) 2008 Tejun Heo <teheo@suse.de> | |
| This program can be distributed under the terms of the GNU GPL. | |
| See the file COPYING. | |
| */ | |
| /** @file | |
| * | |
| * This example illustrates how to write a FUSE file system that | |
| * supports polling for changes that don't come through the kernel. It | |
| * can be tested with the poll_client.c program. | |
| * | |
| * Compile with: | |
| * | |
| * gcc -Wall poll.c `pkg-config fuse3 --cflags --libs` -o poll | |
| * | |
| * ## Source code ## | |
| * \include poll.c | |
| */ | |
| #define FUSE_USE_VERSION 31 | |
| #include <config.h> | |
| #include <fuse.h> | |
| #include <unistd.h> | |
| #include <ctype.h> | |
| #include <string.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <errno.h> | |
| #include <time.h> | |
| #include <pthread.h> | |
| #include <poll.h> | |
| /* | |
| * fsel_open_mask is used to limit the number of opens to 1 per file. | |
| * This is to use file index (0-F) as fh as poll support requires | |
| * unique fh per open file. Lifting this would require proper open | |
| * file management. | |
| */ | |
| static unsigned fsel_open_mask; | |
| static const char fsel_hex_map[] = "0123456789ABCDEF"; | |
| static struct fuse *fsel_fuse; /* needed for poll notification */ | |
| #define FSEL_CNT_MAX 10 /* each file can store upto 10 chars */ | |
| #define FSEL_FILES 16 | |
| static pthread_mutex_t fsel_mutex; /* protects notify_mask and cnt array */ | |
| static unsigned fsel_poll_notify_mask; /* poll notification scheduled? */ | |
| static struct fuse_pollhandle *fsel_poll_handle[FSEL_FILES]; /* poll notify handles */ | |
| static unsigned fsel_cnt[FSEL_FILES]; /* nbytes stored in each file */ | |
| static int fsel_path_index(const char *path) | |
| { | |
| char ch = path[1]; | |
| if (strlen(path) != 2 || path[0] != '/' || !isxdigit(ch) || islower(ch)) | |
| return -1; | |
| return ch <= '9' ? ch - '0' : ch - 'A' + 10; | |
| } | |
| static int fsel_getattr(const char *path, struct stat *stbuf, | |
| struct fuse_file_info *fi) | |
| { | |
| (void) fi; | |
| int idx; | |
| memset(stbuf, 0, sizeof(struct stat)); | |
| if (strcmp(path, "/") == 0) { | |
| stbuf->st_mode = S_IFDIR | 0555; | |
| stbuf->st_nlink = 2; | |
| return 0; | |
| } | |
| idx = fsel_path_index(path); | |
| if (idx < 0) | |
| return -ENOENT; | |
| stbuf->st_mode = S_IFREG | 0444; | |
| stbuf->st_nlink = 1; | |
| stbuf->st_size = fsel_cnt[idx]; | |
| return 0; | |
| } | |
| static int fsel_readdir(const char *path, void *buf, fuse_fill_dir_t filler, | |
| off_t offset, struct fuse_file_info *fi, | |
| enum fuse_readdir_flags flags) | |
| { | |
| char name[2] = { }; | |
| int i; | |
| (void) offset; | |
| (void) fi; | |
| (void) flags; | |
| if (strcmp(path, "/") != 0) | |
| return -ENOENT; | |
| for (i = 0; i < FSEL_FILES; i++) { | |
| name[0] = fsel_hex_map[i]; | |
| filler(buf, name, NULL, 0, 0); | |
| } | |
| return 0; | |
| } | |
| static int fsel_open(const char *path, struct fuse_file_info *fi) | |
| { | |
| int idx = fsel_path_index(path); | |
| if (idx < 0) | |
| return -ENOENT; | |
| if ((fi->flags & O_ACCMODE) != O_RDONLY) | |
| return -EACCES; | |
| if (fsel_open_mask & (1 << idx)) | |
| return -EBUSY; | |
| fsel_open_mask |= (1 << idx); | |
| /* | |
| * fsel files are nonseekable somewhat pipe-like files which | |
| * gets filled up periodically by producer thread and consumed | |
| * on read. Tell FUSE as such. | |
| */ | |
| fi->fh = idx; | |
| fi->direct_io = 1; | |
| fi->nonseekable = 1; | |
| return 0; | |
| } | |
| static int fsel_release(const char *path, struct fuse_file_info *fi) | |
| { | |
| int idx = fi->fh; | |
| (void) path; | |
| fsel_open_mask &= ~(1 << idx); | |
| return 0; | |
| } | |
| static int fsel_read(const char *path, char *buf, size_t size, off_t offset, | |
| struct fuse_file_info *fi) | |
| { | |
| int idx = fi->fh; | |
| (void) path; | |
| (void) offset; | |
| pthread_mutex_lock(&fsel_mutex); | |
| if (fsel_cnt[idx] < size) | |
| size = fsel_cnt[idx]; | |
| printf("READ %X transferred=%zu cnt=%u\n", idx, size, fsel_cnt[idx]); | |
| fsel_cnt[idx] -= size; | |
| pthread_mutex_unlock(&fsel_mutex); | |
| memset(buf, fsel_hex_map[idx], size); | |
| return size; | |
| } | |
| static int fsel_poll(const char *path, struct fuse_file_info *fi, | |
| struct fuse_pollhandle *ph, unsigned *reventsp) | |
| { | |
| static unsigned polled_zero; | |
| int idx = fi->fh; | |
| (void) path; | |
| /* | |
| * Poll notification requires pointer to struct fuse which | |
| * can't be obtained when using fuse_main(). As notification | |
| * happens only after poll is called, fill it here from | |
| * fuse_context. | |
| */ | |
| if (!fsel_fuse) { | |
| struct fuse_context *cxt = fuse_get_context(); | |
| if (cxt) | |
| fsel_fuse = cxt->fuse; | |
| } | |
| pthread_mutex_lock(&fsel_mutex); | |
| if (ph != NULL) { | |
| struct fuse_pollhandle *oldph = fsel_poll_handle[idx]; | |
| if (oldph) | |
| fuse_pollhandle_destroy(oldph); | |
| fsel_poll_notify_mask |= (1 << idx); | |
| fsel_poll_handle[idx] = ph; | |
| } | |
| if (fsel_cnt[idx]) { | |
| *reventsp |= POLLIN; | |
| printf("POLL %X cnt=%u polled_zero=%u\n", | |
| idx, fsel_cnt[idx], polled_zero); | |
| polled_zero = 0; | |
| } else | |
| polled_zero++; | |
| pthread_mutex_unlock(&fsel_mutex); | |
| return 0; | |
| } | |
| static struct fuse_operations fsel_oper = { | |
| .getattr = fsel_getattr, | |
| .readdir = fsel_readdir, | |
| .open = fsel_open, | |
| .release = fsel_release, | |
| .read = fsel_read, | |
| .poll = fsel_poll, | |
| }; | |
| static void *fsel_producer(void *data) | |
| { | |
| const struct timespec interval = { 0, 250000000 }; | |
| unsigned idx = 0, nr = 1; | |
| (void) data; | |
| while (1) { | |
| int i, t; | |
| pthread_mutex_lock(&fsel_mutex); | |
| /* | |
| * This is the main producer loop which is executed | |
| * ever 500ms. On each iteration, it fills one byte | |
| * to 1, 2 or 4 files and sends poll notification if | |
| * requested. | |
| */ | |
| for (i = 0, t = idx; i < nr; | |
| i++, t = (t + FSEL_FILES / nr) % FSEL_FILES) { | |
| if (fsel_cnt[t] == FSEL_CNT_MAX) | |
| continue; | |
| fsel_cnt[t]++; | |
| if (fsel_fuse && (fsel_poll_notify_mask & (1 << t))) { | |
| struct fuse_pollhandle *ph; | |
| printf("NOTIFY %X\n", t); | |
| ph = fsel_poll_handle[t]; | |
| fuse_notify_poll(ph); | |
| fuse_pollhandle_destroy(ph); | |
| fsel_poll_notify_mask &= ~(1 << t); | |
| fsel_poll_handle[t] = NULL; | |
| } | |
| } | |
| idx = (idx + 1) % FSEL_FILES; | |
| if (idx == 0) | |
| nr = (nr * 2) % 7; /* cycle through 1, 2 and 4 */ | |
| pthread_mutex_unlock(&fsel_mutex); | |
| nanosleep(&interval, NULL); | |
| } | |
| return NULL; | |
| } | |
| int main(int argc, char *argv[]) | |
| { | |
| pthread_t producer; | |
| pthread_attr_t attr; | |
| int ret; | |
| errno = pthread_mutex_init(&fsel_mutex, NULL); | |
| if (errno) { | |
| perror("pthread_mutex_init"); | |
| return 1; | |
| } | |
| errno = pthread_attr_init(&attr); | |
| if (errno) { | |
| perror("pthread_attr_init"); | |
| return 1; | |
| } | |
| errno = pthread_create(&producer, &attr, fsel_producer, NULL); | |
| if (errno) { | |
| perror("pthread_create"); | |
| return 1; | |
| } | |
| ret = fuse_main(argc, argv, &fsel_oper, NULL); | |
| pthread_cancel(producer); | |
| pthread_join(producer, NULL); | |
| return ret; | |
| } |