xref: /third_party/libfuse/example/cuse.c (revision 6881f68f)
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, &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
323out:
324	fuse_opt_free_args(&args);
325	return ret;
326}
327