From: Nikita Shubin Date: Sat, 22 Feb 2025 05:12:42 +0000 (+0300) Subject: guse: add example X-Git-Url: http://git.maquefel.me/?a=commitdiff_plain;h=refs%2Fheads%2Fnshubin%2Fguse;p=qemu-gpiodev%2Flibfuse.git guse: add example Signed-off-by: Nikita Shubin --- diff --git a/example/guse.c b/example/guse.c new file mode 100644 index 0000000..97b389d --- /dev/null +++ b/example/guse.c @@ -0,0 +1,900 @@ +/* + GUSE example: GPIO character device in Userspace + Copyright (C) 2025 Nikita Shubin + + This program can be distributed under the terms of the GNU GPLv2. + See the file COPYING. + +*/ + +/** @file + * + * This example demonstrates how to implement a character device in + * userspace ("GUSE"). This is only allowed for root. The character + * device should appear in /dev under the specified name. It can be + * tested with the guse_client.c program. + * + * Mount the file system with: + * + * guse -f --name=mydevice + * + * You should now have a new /dev/mydevice character device. To "unmount" it, + * kill the "guse" process. + * + * To compile this example, run + * + * gcc -Wall guse.c `pkg-config fuse3 --cflags --libs` -o guse + * + * ## Source code ## + * \include guse.c + */ + + +#define FUSE_USE_VERSION 31 +#define GUSE_LINES_NUM 8 + +#include +#include +#include +#include +#include +#include +#include +#include // for ffs +#include +#include +#include +#include + +#include + +#include "ioctl.h" + +#define BIT(nr) (1UL << (nr)) +#define BIT_ULL(nr) (1ULL << (nr)) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define _ffs(x) _Generic((x), \ + char: ffs, \ + int: ffs, \ + long int: ffsl, \ + long long int: ffsll, \ + default: ffsll)(x) + +#define __ffs(x, size) (_ffs(x) == 0 ? size : _ffs(x) - 1) + +#define __popcount(x) _Generic((x), \ + char: __builtin_popcount, \ + int: __builtin_popcount, \ + unsigned int: __builtin_popcount, \ + unsigned long: __builtin_popcountl, \ + default: __builtin_popcountl)(x) + +#define BITS_PER_BYTE 8 +#define BITS_PER_TYPE(type) (sizeof(type) * BITS_PER_BYTE) +#define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d)) +#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_TYPE(long)) +#define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE) + +#define BIT_MASK(nr) (nr == BITS_PER_TYPE(1ULL) ? 0x0 : ~((1ULL << nr) - 1)) + +#define first_set_bit_from(addr, bit, size) __ffs((addr) & BIT_MASK(bit), size) + +#define for_each_set_bit(bit, addr, size) \ + for ((bit) = __ffs((addr), (size)); \ + (bit) < (size); \ + (bit) = first_set_bit_from((addr), (bit + 1), (size))) + +#define for_each_set_bit_from(bit, addr, size) \ + for ((bit) = first_set_bit_from((addr), (bit), (size)); \ + (bit) < (size); \ + (bit) = first_set_bit_from((addr), (bit), (size))) + +static pthread_mutex_t guse_mutex; +static pthread_attr_t attr; +static pthread_t guse_producer; +static _Atomic bool guse_stop = false; + +static void *gusexmp_buf; +static size_t gusexmp_size; + +#define GUSE_MAX_WATCH 64 +#define GUSE_MAX_EVENTS 64 + +static uint8_t num_watched; +static struct watch_request { + uint64_t inode; + struct fuse_pollhandle *ph; + + uint64_t watched_lines; + + uint32_t num_lines; + uint32_t offsets[GPIO_V2_LINES_MAX]; + + uint32_t num_events; + struct gpio_v2_line_info_changed events[GUSE_MAX_EVENTS]; +} watch_requests[GUSE_MAX_WATCH]; + +static struct watch_request *find_watch(uint64_t inode) +{ + int i; + struct watch_request *req = NULL; + + for (i = 0; i < GUSE_MAX_WATCH; i++) { + req = &watch_requests[i]; + if (req->inode == inode) + return req; + } + + return NULL; +} + +static struct watch_request *allocate_watch(uint64_t inode) +{ + int i; + struct watch_request *req = NULL; + + for (i = 0; i < GUSE_MAX_WATCH; i++) { + req = &watch_requests[i]; + if (!req->inode) { + req->inode = inode; + num_watched++; + return req; + } + } + + return NULL; +} + +static void free_watch(struct watch_request *req) +{ + req->inode = 0; + if (req->ph) + fuse_pollhandle_destroy(req->ph); + req->ph = NULL; + req->watched_lines = 0; + req->num_lines = 0; + req->num_events = 0; + num_watched--; +} + +#define GUSE_MAX_LINEREQ 64 + +static uint8_t num_requests; +static struct line_request { + uint64_t inode; + struct fuse_pollhandle *ph; + + uint64_t fallen_mask; + uint64_t risen_mask; + uint64_t config_mask; + uint64_t mask; + + uint32_t num_lines; + uint32_t offsets[GPIO_V2_LINES_MAX]; + + uint32_t num_events; + struct gpio_v2_line_event events[GUSE_MAX_EVENTS]; +} line_requests[GUSE_MAX_LINEREQ]; + +static struct line_request *find_linereq(uint64_t inode) +{ + int i; + struct line_request *req = NULL; + + for (i = 0; i < GUSE_MAX_LINEREQ; i++) { + req = &line_requests[i]; + if (req->inode == inode) + return req; + } + + return NULL; +} + +static struct line_request *allocate_linereq(uint64_t inode) +{ + int i; + struct line_request *req = NULL; + + for (i = 0; i < GUSE_MAX_LINEREQ; i++) { + req = &line_requests[i]; + if (!req->inode) { + req->inode = inode; + num_requests++; + return req; + } + } + + return NULL; +} + +static void free_linereq(struct line_request *req) +{ + req->inode = 0; + if (req->ph) + fuse_pollhandle_destroy(req->ph); + req->ph = NULL; + req->fallen_mask = 0; + req->risen_mask = 0; + req->config_mask = 0; + req->num_lines = 0; + req->num_events = 0; + num_requests--; +} + +static uint64_t timespec_to_ns(struct timespec ts) +{ + return (uint64_t)ts.tv_nsec + 1000000000LL * (uint64_t)ts.tv_sec; +} + +static const char *usage = +"usage: gusexmp [options]\n" +"\n" +"options:\n" +" --help|-h print this help message\n" +" --maj=MAJ|-M MAJ device major number\n" +" --min=MIN|-m MIN device minor number\n" +" --name=NAME|-n NAME device name (mandatory)\n" +" -d -o debug enable debug output (implies -f)\n" +" -f foreground operation\n" +" -s disable multi-threaded operation\n" +"\n"; + +static void *guse_producer_thread(void *data); + +static void gusexmp_init(void *userdata, struct fuse_conn_info *conn) +{ + (void)userdata; + + /* Disable the receiving and processing of FUSE_INTERRUPT requests */ + conn->no_interrupt = 1; + + errno = pthread_create(&guse_producer, &attr, guse_producer_thread, NULL); + if (errno) { + perror("pthread_create"); + } +} + +static void gusexmp_destroy(void *private_data) +{ + (void)private_data; + + guse_stop = true; + + pthread_join(guse_producer, NULL); +} + +static void gusexmp_open(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + printf("%s: %lu\n", __func__, ino); + + fuse_reply_open(req, fi); +} + +static void gusexmp_release(fuse_req_t req, fuse_ino_t ino, + struct fuse_file_info *fi) +{ + struct line_request *lreq = find_linereq(ino); + struct line_request *wreq = find_watch(ino); + + pthread_mutex_lock(&guse_mutex); + + if (lreq) { + printf("%s: lreq %lu\n", __func__, ino); + + for (int i = 0; i < num_watched; i++) { + struct watch_request *wreq = &watch_requests[i]; + unsigned events = 0; + struct timespec ts; + uint64_t ts_ns; + + timespec_get(&ts, TIME_UTC); + ts_ns = timespec_to_ns(ts); + + for (int j = 0; j < lreq->num_lines; j++) { + if (BIT(lreq->offsets[j]) & wreq->watched_lines) { + struct gpio_v2_line_info_changed *event = &wreq->events[wreq->num_events++]; + + event->timestamp_ns = ts_ns; + event->event_type = GPIO_V2_LINE_CHANGED_RELEASED; + + event->info.offset = lreq->offsets[j]; + + events++; + } + } + + if (events && wreq->ph) { + fuse_notify_poll(wreq->ph); + fuse_pollhandle_destroy(wreq->ph); + wreq->ph= NULL; + } + + } + + free_linereq(lreq); + } + + if (wreq) { + printf("%s: wreq %lu\n", __func__, ino); + free_watch(wreq); + } + + pthread_mutex_unlock(&guse_mutex); + + fuse_reply_err(req, 0); +} + +static void gusexmp_read(fuse_req_t req, fuse_ino_t ino, size_t size, off_t off, + struct fuse_file_info *fi) +{ + struct line_request *lreq = find_linereq(ino); + struct watch_request *wreq = find_watch(ino); + + (void)fi; + + if (!lreq && !wreq) { + fuse_reply_err(req, ENOSYS); + return; + } + + pthread_mutex_lock(&guse_mutex); + + if (lreq) { + printf("%s: lreq num_events: %lu\n", __func__, lreq->num_events); + if (lreq->num_events) { + fuse_reply_buf(req, &lreq->events, sizeof(lreq->events[0]) * lreq->num_events); + lreq->num_events = 0; + } else + fuse_reply_buf(req, NULL, 0); + } + + if (wreq) { + printf("%s: wreq num_events: %lu\n", __func__, wreq->num_events); + if (wreq->num_events) { + fuse_reply_buf(req, &wreq->events, sizeof(wreq->events[0]) * wreq->num_events); + wreq->num_events = 0; + } else + fuse_reply_buf(req, NULL, 0); + } + + pthread_mutex_unlock(&guse_mutex); +} + +static const struct gpiochip_info info = { + .name = "GUSE", + .label = "GUSE_EXAMPLE", + .lines = GUSE_LINES_NUM, +}; + +#define LINE_INFO(_offset, _name, _consumer, _flags, _changed, _value) \ + { \ + .name = _name, \ + .consumer = _consumer, \ + .offset = _offset, \ + .flags = _flags, \ + .padding[0] = _changed, \ + .padding[1] = _value, \ + } + +static struct gpio_v2_line_info line_info[GUSE_LINES_NUM] = { + LINE_INFO(0, "guse0", "", GPIO_V2_LINE_FLAG_OUTPUT, 1, 0), + LINE_INFO(1, "guse1", "", GPIO_V2_LINE_FLAG_OUTPUT, 0, 0), + LINE_INFO(2, "guse2", "", GPIO_V2_LINE_FLAG_OUTPUT, 0, 0), + LINE_INFO(3, "guse3", "", GPIO_V2_LINE_FLAG_OUTPUT, 0, 0), + LINE_INFO(4, "guse4", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0), + LINE_INFO(5, "guse5", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0), + LINE_INFO(6, "guse6", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0), + LINE_INFO(7, "guse7", "", GPIO_V2_LINE_FLAG_INPUT, 0, 0), +}; + +static inline int set_value(unsigned idx, uint8_t value) +{ + if (idx > ARRAY_SIZE(line_info)) + return -1; + + line_info[idx].padding[1] = value; + + return 0; +} + +static inline int get_value(unsigned idx) +{ + if (idx > ARRAY_SIZE(line_info)) + return -1; + + return line_info[idx].padding[1]; +} + +static void gusexmp_lineinfo(fuse_req_t req, const void *in_buf) +{ + struct gpio_v2_line_info *in = (struct gpio_v2_line_info *)in_buf; + uint32_t offset = in->offset; + + if (offset > info.lines) { + fuse_reply_err(req, EINVAL); + return; + } + + fuse_reply_ioctl(req, 0, &line_info[offset], sizeof(line_info[0])); +} + +static int gpio_v2_line_config_output_value(const struct gpio_v2_line_config *lc, + unsigned int line_idx) +{ + unsigned int i; + uint64_t mask = BIT_ULL(line_idx); + + for (i = 0; i < lc->num_attrs; i++) { + if ((lc->attrs[i].attr.id == GPIO_V2_LINE_ATTR_ID_OUTPUT_VALUES) && + (lc->attrs[i].mask & mask)) + return !!(lc->attrs[i].attr.values & mask); + } + + return 0; +} + +/* GPIO_V2_LINE_FLAG_INPUT & GPIO_V2_LINE_EDGE_FLAGS */ +static void gusexmp_linerequest(fuse_req_t req, fuse_ino_t ino, + const void *in_buf) +{ + struct gpio_v2_line_request *in = (struct gpio_v2_line_request *)in_buf; + struct gpio_v2_line_request request = { 0 }; + struct line_request *lreq; + uint64_t mask = 0; + int i, j, ret = EINVAL; + + memcpy(&request, in, sizeof(request)); + + /* line request not available for the device itself */ + if (ino & GUSE_DEVICE_INODE_FLAG) + goto out_fail; + + /* sanity check */ + if (in->num_lines > GUSE_LINES_NUM) + goto out_fail; + + /* TODO: forbid switching OUTPUTS to INPUTS */ + lreq = allocate_linereq(ino); + if (!req) { + ret = EMFILE; + goto out_fail; + } + + for (i = 0; i < in->num_lines; i++) { + mask |= BIT_ULL(request.offsets[i]); + } + + memcpy(lreq->offsets, request.offsets, sizeof(request.offsets)); + + if (request.config.flags & GPIO_V2_LINE_FLAG_INPUT) { + if (request.config.flags & GPIO_V2_LINE_FLAG_EDGE_RISING) + lreq->risen_mask = mask; + + if (request.config.flags & GPIO_V2_LINE_FLAG_EDGE_FALLING) + lreq->fallen_mask = mask; + } else if (request.config.flags & GPIO_V2_LINE_FLAG_OUTPUT) { + lreq->mask = mask; + } + + lreq->num_lines = in->num_lines; + fuse_reply_ioctl(req, 0, &request, sizeof(request)); + + /* notify watch */ + for (i = 0; i < num_watched; i++) { + struct watch_request *wreq = &watch_requests[i]; + uint64_t watch_mask = wreq->watched_lines & mask; + unsigned int bit = 0; + struct timespec ts; + uint64_t ts_ns; + + timespec_get(&ts, TIME_UTC); + ts_ns = timespec_to_ns(ts); + + pthread_mutex_lock(&guse_mutex); + + for_each_set_bit(bit, watch_mask, GUSE_LINES_NUM) { + struct gpio_v2_line_info_changed *event = &wreq->events[wreq->num_events++]; + + event->timestamp_ns = ts_ns; + event->event_type = GPIO_V2_LINE_CHANGED_REQUESTED; + + event->info.offset = bit; + } + + if (bit && wreq->ph) { + fuse_notify_poll(wreq->ph); + fuse_pollhandle_destroy(wreq->ph); + wreq->ph= NULL; + } + + pthread_mutex_unlock(&guse_mutex); + } + + return; + +out_fail: + fuse_reply_err(req, ret); +} + +static void guseexmp_linevalues(fuse_req_t req, fuse_ino_t ino, + const void *in_buf, bool is_read) +{ + struct gpio_v2_line_values *in_val = (struct gpio_v2_line_values *)in_buf; + struct gpio_v2_line_values request; + struct line_request *lreq = NULL; + unsigned int bit = 0; + uint64_t bits = in_val->bits; + uint64_t mask = in_val->mask; + + lreq = find_linereq(ino); + if (!lreq) { + fuse_reply_err(req, EINVAL); + return; + } + + printf("%s: %lu bits=0x%x mask=0x%x\n", __func__, ino, bits, mask); + + if (is_read) { + bits = 0; + } + + for_each_set_bit(bit, mask, lreq->num_lines) { + uint32_t idx = lreq->offsets[bit]; + + if (is_read) { + int val = get_value(idx); + if (val >= 0) + bits |= val << bit; + printf("%s: get_value(%u, %u)\n", __func__, idx, val); + } else { + uint8_t val = !!(bits & BIT_ULL(bit)); + set_value(idx, val); + printf("%s: set_value(%u, %u)\n", __func__, idx, val); + } + } + + request.bits = bits; + request.mask = mask; + + printf("%s: request bits=0x%x mask=0x%x\n", __func__, bits, mask); + + fuse_reply_ioctl(req, 0, &request, sizeof(request)); +} + +/* +ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=0} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0 +ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=1} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0 +ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=2} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0 +ioctl(3, GPIO_V2_GET_LINEINFO_WATCH_IOCTL, {offset=3} => {name="", consumer="?", flags=GPIO_V2_LINE_FLAG_USED|GPIO_V2_LINE_FLAG_OUTPUT, num_attrs=0}) = 0 +*/ +static void guseexmp_linewatch(fuse_req_t req, fuse_ino_t ino, + const void *in_buf, bool is_watch) +{ + struct gpio_v2_line_info *in = (struct gpio_v2_line_info *)in_buf; + struct gpio_v2_line_info request = { 0 }; + struct watch_request *wreq = NULL; + int i, ret = EINVAL; + + /* sanity check */ + if (in->offset> GUSE_LINES_NUM) + goto out_fail; + + wreq = find_watch(ino); + if (!wreq) { + wreq = allocate_watch(ino); + } + + if (!wreq) { + ret = EMFILE; + goto out_fail; + } + + if (is_watch) { + wreq->watched_lines |= BIT_ULL(in->offset); + } else { + wreq->watched_lines &= ~BIT_ULL(in->offset); + } + + printf("%s: %lu offset=%u watched_lines=0x%x watch=%u\n", + __func__, ino, in->offset, wreq->watched_lines, is_watch); + + request.offset = in->offset; + fuse_reply_ioctl(req, 0, &request, sizeof(request)); + + return; + +out_fail: + fuse_reply_err(req, ret); +} + +/* */ +static void gusexmp_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, + void *arg, struct fuse_file_info *fi, unsigned flags, + const void *in_buf, size_t in_bufsz, size_t out_bufsz) +{ + bool is_read = false, is_watch = false; + + (void)fi; + + if (flags & FUSE_IOCTL_COMPAT) { + fuse_reply_err(req, ENOSYS); + return; + } + + switch(cmd) { + case GPIO_GET_CHIPINFO_IOCTL: + fuse_reply_ioctl(req, 0, &info, sizeof(info)); + break; + case GPIO_V2_GET_LINEINFO_IOCTL: + gusexmp_lineinfo(req, in_buf); + break; + /* + * GPIO_V2_GET_LINE_IOCTL is also processed by guse module. + */ + case GPIO_V2_GET_LINE_IOCTL: + gusexmp_linerequest(req, ino, in_buf); + break; + case GPIO_V2_LINE_GET_VALUES_IOCTL: + is_read = true; + /* fallthrough */ + case GPIO_V2_LINE_SET_VALUES_IOCTL: + guseexmp_linevalues(req, ino, in_buf, is_read); + break; + case GPIO_V2_GET_LINEINFO_WATCH_IOCTL: + is_watch = true; + case GPIO_GET_LINEINFO_UNWATCH_IOCTL: + guseexmp_linewatch(req, ino, in_buf, is_watch); + break; + case GPIO_V2_LINE_SET_CONFIG_IOCTL: + default: + fuse_reply_err(req, EINVAL); + } +} + +static void gusexmp_poll(fuse_req_t req, fuse_ino_t ino, struct fuse_file_info *fi, + struct fuse_pollhandle *ph) +{ + struct line_request *lreq = NULL; + struct watch_request *wreq = NULL; + struct fuse_pollhandle *oldph = NULL; + uint32_t watched = 0; + + pthread_mutex_lock(&guse_mutex); + + if (ino & GUSE_DEVICE_INODE_FLAG) { + wreq = find_watch(ino); + if (wreq) { + oldph = wreq->ph; + watched = wreq->num_events; + } + } else { + lreq = find_linereq(ino); + if (lreq) { + oldph = lreq->ph; + watched = lreq->num_events; + } + } + + printf("%s: %lu\n", __func__, ino); + + if (!lreq && !wreq) + fuse_reply_poll(req, POLLERR); + + if (ph != NULL) { + if (oldph) + fuse_pollhandle_destroy(oldph); + + if (lreq) + lreq->ph = ph; + + if (wreq) + wreq->ph = ph; + } + + if (watched) + fuse_reply_poll(req, POLLIN); + else + fuse_reply_poll(req, 0); + + pthread_mutex_unlock(&guse_mutex); +} + +static void *guse_producer_thread(void *data) +{ + const struct timespec interval = { 1, 0 }; + unsigned idx = 0, nr = 1; + unsigned toggle = 0; + struct timespec ts; + + (void) data; + while (!guse_stop) { + enum gpio_v2_line_event_id event_id = GPIO_V2_LINE_EVENT_RISING_EDGE; + int i, j; + uint64_t ts_ns; + + timespec_get(&ts, TIME_UTC); + ts_ns = timespec_to_ns(ts); + + if (toggle++ % 2) + event_id = GPIO_V2_LINE_EVENT_FALLING_EDGE; + + pthread_mutex_lock(&guse_mutex); + + for (i = 0; i < GUSE_LINES_NUM; i++) { + uint32_t line_bit = BIT(i); + + /* generate fake config conditions */ + if (line_info[i].padding[0]) { + for (j = 0; j < num_watched; j++) { + struct watch_request *wreq = &watch_requests[j]; + + if (wreq->watched_lines & line_bit) { + struct gpio_v2_line_info_changed *event = &wreq->events[wreq->num_events++]; + + event->timestamp_ns = ts_ns; + event->event_type = GPIO_V2_LINE_CHANGED_CONFIG; + + event->info.offset = i; + + printf("%s: push event GPIO_V2_LINE_CHANGED_CONFIG\n", __func__); + } + } + } + + /* produce event for each input */ + if (line_info[i].flags & GPIO_V2_LINE_FLAG_OUTPUT) + continue; + + for (j = 0; j < num_requests; j++) { + struct line_request *lreq = &line_requests[j]; + uint64_t mask; + + if (event_id == GPIO_V2_LINE_EVENT_RISING_EDGE) { + mask = lreq->risen_mask; + } else { + mask = lreq->fallen_mask; + } + + if (line_bit & mask) { + if (lreq->num_events >= GUSE_MAX_LINEREQ) { + fprintf(stderr, "Error: GUSE_MAX_LINEREQ\n"); + continue; + } + + struct gpio_v2_line_event *event = &lreq->events[lreq->num_events++]; + + event->timestamp_ns = ts_ns; + event->offset = i; + event->id = event_id; + } + } + } + + for (i = 0; i < num_requests; i++) { + struct line_request *lreq = &line_requests[i]; + + if (lreq->num_events && lreq->ph) { + fuse_notify_poll(lreq->ph); + fuse_pollhandle_destroy(lreq->ph); + lreq->ph= NULL; + } + } + + for (i = 0; i < num_watched; i++) { + struct watch_request *wreq = &watch_requests[i]; + + if (wreq->num_events && wreq->ph) { + fuse_notify_poll(wreq->ph); + fuse_pollhandle_destroy(wreq->ph); + wreq->ph= NULL; + } + } + + pthread_mutex_unlock(&guse_mutex); + + nanosleep(&interval, NULL); + } + + return NULL; +} + +struct gusexmp_param { + unsigned major; + unsigned minor; + char *dev_name; + int is_help; +}; + +#define GUSEXMP_OPT(t, p) { t, offsetof(struct gusexmp_param, p), 1 } + +static const struct fuse_opt gusexmp_opts[] = { + GUSEXMP_OPT("-M %u", major), + GUSEXMP_OPT("--maj=%u", major), + GUSEXMP_OPT("-m %u", minor), + GUSEXMP_OPT("--min=%u", minor), + GUSEXMP_OPT("-n %s", dev_name), + GUSEXMP_OPT("--name=%s", dev_name), + FUSE_OPT_KEY("-h", 0), + FUSE_OPT_KEY("--help", 0), + FUSE_OPT_END +}; + +static int gusexmp_process_arg(void *data, const char *arg, int key, + struct fuse_args *outargs) +{ + struct gusexmp_param *param = data; + + (void)outargs; + (void)arg; + + switch (key) { + case 0: + param->is_help = 1; + fprintf(stderr, "%s", usage); + return fuse_opt_add_arg(outargs, "-ho"); + default: + return 1; + } +} + +static const struct guse_cdev_lowlevel_ops gusexmp_clop = { + .init = gusexmp_init, + .destroy = gusexmp_destroy, + .open = gusexmp_open, + .release = gusexmp_release, + .read = gusexmp_read, + .ioctl = gusexmp_ioctl, + .poll = gusexmp_poll, +}; + +int main(int argc, char **argv) +{ + struct fuse_args args = FUSE_ARGS_INIT(argc, argv); + struct gusexmp_param param = { 0, 0, NULL, 0 }; + char dev_name[128] = "DEVNAME="; + const char *dev_info_argv[] = { dev_name }; + struct guse_info ci; + int ret = 1; + + if (fuse_opt_parse(&args, ¶m, gusexmp_opts, gusexmp_process_arg)) { + printf("failed to parse option\n"); + free(param.dev_name); + goto out; + } + + if (!param.is_help) { + if (!param.dev_name) { + fprintf(stderr, "Error: device name missing\n"); + goto out; + } + strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME=")); + free(param.dev_name); + } + + errno = pthread_mutex_init(&guse_mutex, NULL); + if (errno) { + perror("pthread_mutex_init"); + return 1; + } + + errno = pthread_attr_init(&attr); + if (errno) { + perror("pthread_attr_init"); + return 1; + } + + memset(&ci, 0, sizeof(ci)); + ci.dev_major = param.major; + ci.dev_minor = param.minor; + ci.dev_info_argc = 1; + ci.dev_info_argv = dev_info_argv; + + ret = guse_lowlevel_main(args.argc, args.argv, &ci, &gusexmp_clop, NULL); + +out: + fuse_opt_free_args(&args); + return ret; +} diff --git a/example/meson.build b/example/meson.build index 5c02cc1..0b82779 100644 --- a/example/meson.build +++ b/example/meson.build @@ -1,7 +1,8 @@ examples = [ 'passthrough', 'passthrough_fh', 'hello', 'hello_ll', 'printcap', 'ioctl_client', 'poll_client', - 'ioctl', 'cuse', 'cuse_client' ] + 'ioctl', 'cuse', 'cuse_client', + 'guse' ] if not platform.endswith('bsd') and platform != 'dragonfly' examples += [ 'passthrough_ll', 'hello_ll_uds' ]