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 
47 static void *cusexmp_buf;
48 static size_t cusexmp_size;
49 
50 static 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 
cusexmp_resize(size_t new_size)63 static 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 
cusexmp_expand(size_t new_size)83 static 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 
cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)90 static void cusexmp_open(fuse_req_t req, struct fuse_file_info *fi)
91 {
92 	fuse_reply_open(req, fi);
93 }
94 
cusexmp_read(fuse_req_t req, size_t size, off_t off, struct fuse_file_info *fi)95 static 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 
cusexmp_write(fuse_req_t req, const char *buf, size_t size, off_t off, struct fuse_file_info *fi)108 static 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 
fioc_do_rw(fuse_req_t req, void *addr, const void *in_buf, size_t in_bufsz, size_t out_bufsz, int is_read)122 static 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 
cusexmp_ioctl(fuse_req_t req, int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz)197 static 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 
244 struct 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 
253 static 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 
cusexmp_process_arg(void *data, const char *arg, int key, struct fuse_args *outargs)265 static 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 
283 static 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 
main(int argc, char **argv)290 int 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, &param, 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 
323 out:
324 	fuse_opt_free_args(&args);
325 	return ret;
326 }
327