mirror of
https://github.com/libfuse/libfuse.git
synced 2024-11-27 06:04:27 +08:00
295 lines
6.3 KiB
C
295 lines
6.3 KiB
C
/*
|
|
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 GPLv2.
|
|
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 <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 up to 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 const 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;
|
|
}
|