| /* |
| 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 30 |
| |
| #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 & 3) != 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; |
| } |