1/* 2 CUSE example: Character device in Userspace 3 Copyright (C) 2008-2009 SUSE Linux Products GmbH 4 Copyright (C) 2008-2009 Tejun Heo <tj@kernel.org> 5 6 This program can be distributed under the terms of the GNU GPLv2. 7 See the file COPYING. 8 9*/ 10 11/** @file 12 * 13 * This example demonstrates how to implement a character device in 14 * userspace ("CUSE"). This is only allowed for root. The character 15 * device should appear in /dev under the specified name. It can be 16 * tested with the cuse_client.c program. 17 * 18 * Mount the file system with: 19 * 20 * cuse -f --name=mydevice 21 * 22 * You should now have a new /dev/mydevice character device. To "unmount" it, 23 * kill the "cuse" process. 24 * 25 * To compile this example, run 26 * 27 * gcc -Wall cuse.c `pkg-config fuse3 --cflags --libs` -o cuse 28 * 29 * ## Source code ## 30 * \include cuse.c 31 */ 32 33 34#define FUSE_USE_VERSION 31 35 36#include <cuse_lowlevel.h> 37#include <fuse_opt.h> 38#include <stddef.h> 39#include <stdio.h> 40#include <stdlib.h> 41#include <string.h> 42#include <unistd.h> 43#include <errno.h> 44 45#include "ioctl.h" 46 47static void *cusexmp_buf; 48static size_t cusexmp_size; 49 50static const char *usage = 51"usage: cusexmp [options]\n" 52"\n" 53"options:\n" 54" --help|-h print this help message\n" 55" --maj=MAJ|-M MAJ device major number\n" 56" --min=MIN|-m MIN device minor number\n" 57" --name=NAME|-n NAME device name (mandatory)\n" 58" -d -o debug enable debug output (implies -f)\n" 59" -f foreground operation\n" 60" -s disable multi-threaded operation\n" 61"\n"; 62 63static int cusexmp_resize(size_t new_size) 64{ 65 void *new_buf; 66 67 if (new_size == cusexmp_size) 68 return 0; 69 70 new_buf = realloc(cusexmp_buf, new_size); 71 if (!new_buf && new_size) 72 return -ENOMEM; 73 74 if (new_size > cusexmp_size) 75 memset(new_buf + cusexmp_size, 0, new_size - cusexmp_size); 76 77 cusexmp_buf = new_buf; 78 cusexmp_size = new_size; 79 80 return 0; 81} 82 83static int cusexmp_expand(size_t new_size) 84{ 85 if (new_size > cusexmp_size) 86 return cusexmp_resize(new_size); 87 return 0; 88} 89 90static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi) 91{ 92 fuse_reply_open(req, fi); 93} 94 95static void cusexmp_read(fuse_req_t req, size_t size, off_t off, 96 struct fuse_file_info *fi) 97{ 98 (void)fi; 99 100 if (off >= cusexmp_size) 101 off = cusexmp_size; 102 if (size > cusexmp_size - off) 103 size = cusexmp_size - off; 104 105 fuse_reply_buf(req, cusexmp_buf + off, size); 106} 107 108static void cusexmp_write(fuse_req_t req, const char *buf, size_t size, 109 off_t off, struct fuse_file_info *fi) 110{ 111 (void)fi; 112 113 if (cusexmp_expand(off + size)) { 114 fuse_reply_err(req, ENOMEM); 115 return; 116 } 117 118 memcpy(cusexmp_buf + off, buf, size); 119 fuse_reply_write(req, size); 120} 121 122static void fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf, 123 size_t in_bufsz, size_t out_bufsz, int is_read) 124{ 125 const struct fioc_rw_arg *arg; 126 struct iovec in_iov[2], out_iov[3], iov[3]; 127 size_t cur_size; 128 129 /* read in arg */ 130 in_iov[0].iov_base = addr; 131 in_iov[0].iov_len = sizeof(*arg); 132 if (!in_bufsz) { 133 fuse_reply_ioctl_retry(req, in_iov, 1, NULL, 0); 134 return; 135 } 136 arg = in_buf; 137 in_buf += sizeof(*arg); 138 in_bufsz -= sizeof(*arg); 139 140 /* prepare size outputs */ 141 out_iov[0].iov_base = 142 addr + offsetof(struct fioc_rw_arg, prev_size); 143 out_iov[0].iov_len = sizeof(arg->prev_size); 144 145 out_iov[1].iov_base = 146 addr + offsetof(struct fioc_rw_arg, new_size); 147 out_iov[1].iov_len = sizeof(arg->new_size); 148 149 /* prepare client buf */ 150 if (is_read) { 151 out_iov[2].iov_base = arg->buf; 152 out_iov[2].iov_len = arg->size; 153 if (!out_bufsz) { 154 fuse_reply_ioctl_retry(req, in_iov, 1, out_iov, 3); 155 return; 156 } 157 } else { 158 in_iov[1].iov_base = arg->buf; 159 in_iov[1].iov_len = arg->size; 160 if (arg->size && !in_bufsz) { 161 fuse_reply_ioctl_retry(req, in_iov, 2, out_iov, 2); 162 return; 163 } 164 } 165 166 /* we're all set */ 167 cur_size = cusexmp_size; 168 iov[0].iov_base = &cur_size; 169 iov[0].iov_len = sizeof(cur_size); 170 171 iov[1].iov_base = &cusexmp_size; 172 iov[1].iov_len = sizeof(cusexmp_size); 173 174 if (is_read) { 175 size_t off = arg->offset; 176 size_t size = arg->size; 177 178 if (off >= cusexmp_size) 179 off = cusexmp_size; 180 if (size > cusexmp_size - off) 181 size = cusexmp_size - off; 182 183 iov[2].iov_base = cusexmp_buf + off; 184 iov[2].iov_len = size; 185 fuse_reply_ioctl_iov(req, size, iov, 3); 186 } else { 187 if (cusexmp_expand(arg->offset + in_bufsz)) { 188 fuse_reply_err(req, ENOMEM); 189 return; 190 } 191 192 memcpy(cusexmp_buf + arg->offset, in_buf, in_bufsz); 193 fuse_reply_ioctl_iov(req, in_bufsz, iov, 2); 194 } 195} 196 197static void cusexmp_ioctl(fuse_req_t req, int cmd, void *arg, 198 struct fuse_file_info *fi, unsigned flags, 199 const void *in_buf, size_t in_bufsz, size_t out_bufsz) 200{ 201 int is_read = 0; 202 203 (void)fi; 204 205 if (flags & FUSE_IOCTL_COMPAT) { 206 fuse_reply_err(req, ENOSYS); 207 return; 208 } 209 210 switch (cmd) { 211 case FIOC_GET_SIZE: 212 if (!out_bufsz) { 213 struct iovec iov = { arg, sizeof(size_t) }; 214 215 fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1); 216 } else 217 fuse_reply_ioctl(req, 0, &cusexmp_size, 218 sizeof(cusexmp_size)); 219 break; 220 221 case FIOC_SET_SIZE: 222 if (!in_bufsz) { 223 struct iovec iov = { arg, sizeof(size_t) }; 224 225 fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0); 226 } else { 227 cusexmp_resize(*(size_t *)in_buf); 228 fuse_reply_ioctl(req, 0, NULL, 0); 229 } 230 break; 231 232 case FIOC_READ: 233 is_read = 1; 234 /* fall through */ 235 case FIOC_WRITE: 236 fioc_do_rw(req, arg, in_buf, in_bufsz, out_bufsz, is_read); 237 break; 238 239 default: 240 fuse_reply_err(req, EINVAL); 241 } 242} 243 244struct cusexmp_param { 245 unsigned major; 246 unsigned minor; 247 char *dev_name; 248 int is_help; 249}; 250 251#define CUSEXMP_OPT(t, p) { t, offsetof(struct cusexmp_param, p), 1 } 252 253static const struct fuse_opt cusexmp_opts[] = { 254 CUSEXMP_OPT("-M %u", major), 255 CUSEXMP_OPT("--maj=%u", major), 256 CUSEXMP_OPT("-m %u", minor), 257 CUSEXMP_OPT("--min=%u", minor), 258 CUSEXMP_OPT("-n %s", dev_name), 259 CUSEXMP_OPT("--name=%s", dev_name), 260 FUSE_OPT_KEY("-h", 0), 261 FUSE_OPT_KEY("--help", 0), 262 FUSE_OPT_END 263}; 264 265static int cusexmp_process_arg(void *data, const char *arg, int key, 266 struct fuse_args *outargs) 267{ 268 struct cusexmp_param *param = data; 269 270 (void)outargs; 271 (void)arg; 272 273 switch (key) { 274 case 0: 275 param->is_help = 1; 276 fprintf(stderr, "%s", usage); 277 return fuse_opt_add_arg(outargs, "-ho"); 278 default: 279 return 1; 280 } 281} 282 283static const struct cuse_lowlevel_ops cusexmp_clop = { 284 .open = cusexmp_open, 285 .read = cusexmp_read, 286 .write = cusexmp_write, 287 .ioctl = cusexmp_ioctl, 288}; 289 290int main(int argc, char **argv) 291{ 292 struct fuse_args args = FUSE_ARGS_INIT(argc, argv); 293 struct cusexmp_param param = { 0, 0, NULL, 0 }; 294 char dev_name[128] = "DEVNAME="; 295 const char *dev_info_argv[] = { dev_name }; 296 struct cuse_info ci; 297 int ret = 1; 298 299 if (fuse_opt_parse(&args, ¶m, cusexmp_opts, cusexmp_process_arg)) { 300 printf("failed to parse option\n"); 301 free(param.dev_name); 302 goto out; 303 } 304 305 if (!param.is_help) { 306 if (!param.dev_name) { 307 fprintf(stderr, "Error: device name missing\n"); 308 goto out; 309 } 310 strncat(dev_name, param.dev_name, sizeof(dev_name) - sizeof("DEVNAME=")); 311 free(param.dev_name); 312 } 313 314 memset(&ci, 0, sizeof(ci)); 315 ci.dev_major = param.major; 316 ci.dev_minor = param.minor; 317 ci.dev_info_argc = 1; 318 ci.dev_info_argv = dev_info_argv; 319 ci.flags = CUSE_UNRESTRICTED_IOCTL; 320 321 ret = cuse_lowlevel_main(args.argc, args.argv, &ci, &cusexmp_clop, NULL); 322 323out: 324 fuse_opt_free_args(&args); 325 return ret; 326} 327