xref: /third_party/libfuse/lib/cuse_lowlevel.c (revision 6881f68f)
1/*
2  CUSE: Character device in Userspace
3  Copyright (C) 2008       SUSE Linux Products GmbH
4  Copyright (C) 2008       Tejun Heo <teheo@suse.de>
5
6  This program can be distributed under the terms of the GNU LGPLv2.
7  See the file COPYING.LIB.
8*/
9
10#include "fuse_config.h"
11#include "cuse_lowlevel.h"
12#include "fuse_kernel.h"
13#include "fuse_i.h"
14#include "fuse_opt.h"
15
16#include <stdio.h>
17#include <string.h>
18#include <stdlib.h>
19#include <stddef.h>
20#include <errno.h>
21#include <unistd.h>
22
23struct cuse_data {
24	struct cuse_lowlevel_ops	clop;
25	unsigned			max_read;
26	unsigned			dev_major;
27	unsigned			dev_minor;
28	unsigned			flags;
29	unsigned			dev_info_len;
30	char				dev_info[];
31};
32
33static struct cuse_lowlevel_ops *req_clop(fuse_req_t req)
34{
35	return &req->se->cuse_data->clop;
36}
37
38static void cuse_fll_open(fuse_req_t req, fuse_ino_t ino,
39			  struct fuse_file_info *fi)
40{
41	(void)ino;
42	req_clop(req)->open(req, fi);
43}
44
45static void cuse_fll_read(fuse_req_t req, fuse_ino_t ino, size_t size,
46			  off_t off, struct fuse_file_info *fi)
47{
48	(void)ino;
49	req_clop(req)->read(req, size, off, fi);
50}
51
52static void cuse_fll_write(fuse_req_t req, fuse_ino_t ino, const char *buf,
53			   size_t size, off_t off, struct fuse_file_info *fi)
54{
55	(void)ino;
56	req_clop(req)->write(req, buf, size, off, fi);
57}
58
59static void cuse_fll_flush(fuse_req_t req, fuse_ino_t ino,
60			   struct fuse_file_info *fi)
61{
62	(void)ino;
63	req_clop(req)->flush(req, fi);
64}
65
66static void cuse_fll_release(fuse_req_t req, fuse_ino_t ino,
67			     struct fuse_file_info *fi)
68{
69	(void)ino;
70	req_clop(req)->release(req, fi);
71}
72
73static void cuse_fll_fsync(fuse_req_t req, fuse_ino_t ino, int datasync,
74			   struct fuse_file_info *fi)
75{
76	(void)ino;
77	req_clop(req)->fsync(req, datasync, fi);
78}
79
80static void cuse_fll_ioctl(fuse_req_t req, fuse_ino_t ino, unsigned int cmd, void *arg,
81			   struct fuse_file_info *fi, unsigned int flags,
82			   const void *in_buf, size_t in_bufsz, size_t out_bufsz)
83{
84	(void)ino;
85	req_clop(req)->ioctl(req, cmd, arg, fi, flags, in_buf, in_bufsz,
86			     out_bufsz);
87}
88
89static void cuse_fll_poll(fuse_req_t req, fuse_ino_t ino,
90			  struct fuse_file_info *fi, struct fuse_pollhandle *ph)
91{
92	(void)ino;
93	req_clop(req)->poll(req, fi, ph);
94}
95
96static size_t cuse_pack_info(int argc, const char **argv, char *buf)
97{
98	size_t size = 0;
99	int i;
100
101	for (i = 0; i < argc; i++) {
102		size_t len;
103
104		len = strlen(argv[i]) + 1;
105		size += len;
106		if (buf) {
107			memcpy(buf, argv[i], len);
108			buf += len;
109		}
110	}
111
112	return size;
113}
114
115static struct cuse_data *cuse_prep_data(const struct cuse_info *ci,
116					const struct cuse_lowlevel_ops *clop)
117{
118	struct cuse_data *cd;
119	size_t dev_info_len;
120
121	dev_info_len = cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv,
122				      NULL);
123
124	if (dev_info_len > CUSE_INIT_INFO_MAX) {
125		fuse_log(FUSE_LOG_ERR, "cuse: dev_info (%zu) too large, limit=%u\n",
126			dev_info_len, CUSE_INIT_INFO_MAX);
127		return NULL;
128	}
129
130	cd = calloc(1, sizeof(*cd) + dev_info_len);
131	if (!cd) {
132		fuse_log(FUSE_LOG_ERR, "cuse: failed to allocate cuse_data\n");
133		return NULL;
134	}
135
136	memcpy(&cd->clop, clop, sizeof(cd->clop));
137	cd->max_read = 131072;
138	cd->dev_major = ci->dev_major;
139	cd->dev_minor = ci->dev_minor;
140	cd->dev_info_len = dev_info_len;
141	cd->flags = ci->flags;
142	cuse_pack_info(ci->dev_info_argc, ci->dev_info_argv, cd->dev_info);
143
144	return cd;
145}
146
147struct fuse_session *cuse_lowlevel_new(struct fuse_args *args,
148				       const struct cuse_info *ci,
149				       const struct cuse_lowlevel_ops *clop,
150				       void *userdata)
151{
152	struct fuse_lowlevel_ops lop;
153	struct cuse_data *cd;
154	struct fuse_session *se;
155
156	cd = cuse_prep_data(ci, clop);
157	if (!cd)
158		return NULL;
159
160	memset(&lop, 0, sizeof(lop));
161	lop.init	= clop->init;
162	lop.destroy	= clop->destroy;
163	lop.open	= clop->open		? cuse_fll_open		: NULL;
164	lop.read	= clop->read		? cuse_fll_read		: NULL;
165	lop.write	= clop->write		? cuse_fll_write	: NULL;
166	lop.flush	= clop->flush		? cuse_fll_flush	: NULL;
167	lop.release	= clop->release		? cuse_fll_release	: NULL;
168	lop.fsync	= clop->fsync		? cuse_fll_fsync	: NULL;
169	lop.ioctl	= clop->ioctl		? cuse_fll_ioctl	: NULL;
170	lop.poll	= clop->poll		? cuse_fll_poll		: NULL;
171
172	se = fuse_session_new(args, &lop, sizeof(lop), userdata);
173	if (!se) {
174		free(cd);
175		return NULL;
176	}
177	se->cuse_data = cd;
178
179	return se;
180}
181
182static int cuse_reply_init(fuse_req_t req, struct cuse_init_out *arg,
183			   char *dev_info, unsigned dev_info_len)
184{
185	struct iovec iov[3];
186
187	iov[1].iov_base = arg;
188	iov[1].iov_len = sizeof(struct cuse_init_out);
189	iov[2].iov_base = dev_info;
190	iov[2].iov_len = dev_info_len;
191
192	return fuse_send_reply_iov_nofree(req, 0, iov, 3);
193}
194
195void cuse_lowlevel_init(fuse_req_t req, fuse_ino_t nodeid, const void *inarg)
196{
197	struct fuse_init_in *arg = (struct fuse_init_in *) inarg;
198	struct cuse_init_out outarg;
199	struct fuse_session *se = req->se;
200	struct cuse_data *cd = se->cuse_data;
201	size_t bufsize = se->bufsize;
202	struct cuse_lowlevel_ops *clop = req_clop(req);
203
204	(void) nodeid;
205	if (se->debug) {
206		fuse_log(FUSE_LOG_DEBUG, "CUSE_INIT: %u.%u\n", arg->major, arg->minor);
207		fuse_log(FUSE_LOG_DEBUG, "flags=0x%08x\n", arg->flags);
208	}
209	se->conn.proto_major = arg->major;
210	se->conn.proto_minor = arg->minor;
211	se->conn.capable = 0;
212	se->conn.want = 0;
213
214	if (arg->major < 7) {
215		fuse_log(FUSE_LOG_ERR, "cuse: unsupported protocol version: %u.%u\n",
216			arg->major, arg->minor);
217		fuse_reply_err(req, EPROTO);
218		return;
219	}
220
221	if (bufsize < FUSE_MIN_READ_BUFFER) {
222		fuse_log(FUSE_LOG_ERR, "cuse: warning: buffer size too small: %zu\n",
223			bufsize);
224		bufsize = FUSE_MIN_READ_BUFFER;
225	}
226
227	bufsize -= 4096;
228	if (bufsize < se->conn.max_write)
229		se->conn.max_write = bufsize;
230
231	se->got_init = 1;
232	if (se->op.init)
233		se->op.init(se->userdata, &se->conn);
234
235	memset(&outarg, 0, sizeof(outarg));
236	outarg.major = FUSE_KERNEL_VERSION;
237	outarg.minor = FUSE_KERNEL_MINOR_VERSION;
238	outarg.flags = cd->flags;
239	outarg.max_read = cd->max_read;
240	outarg.max_write = se->conn.max_write;
241	outarg.dev_major = cd->dev_major;
242	outarg.dev_minor = cd->dev_minor;
243
244	if (se->debug) {
245		fuse_log(FUSE_LOG_DEBUG, "   CUSE_INIT: %u.%u\n",
246			outarg.major, outarg.minor);
247		fuse_log(FUSE_LOG_DEBUG, "   flags=0x%08x\n", outarg.flags);
248		fuse_log(FUSE_LOG_DEBUG, "   max_read=0x%08x\n", outarg.max_read);
249		fuse_log(FUSE_LOG_DEBUG, "   max_write=0x%08x\n", outarg.max_write);
250		fuse_log(FUSE_LOG_DEBUG, "   dev_major=%u\n", outarg.dev_major);
251		fuse_log(FUSE_LOG_DEBUG, "   dev_minor=%u\n", outarg.dev_minor);
252		fuse_log(FUSE_LOG_DEBUG, "   dev_info: %.*s\n", cd->dev_info_len,
253			cd->dev_info);
254	}
255
256	cuse_reply_init(req, &outarg, cd->dev_info, cd->dev_info_len);
257
258	if (clop->init_done)
259		clop->init_done(se->userdata);
260
261	fuse_free_req(req);
262}
263
264struct fuse_session *cuse_lowlevel_setup(int argc, char *argv[],
265					 const struct cuse_info *ci,
266					 const struct cuse_lowlevel_ops *clop,
267					 int *multithreaded, void *userdata)
268{
269	const char *devname = "/dev/cuse";
270	static const struct fuse_opt kill_subtype_opts[] = {
271		FUSE_OPT_KEY("subtype=",  FUSE_OPT_KEY_DISCARD),
272		FUSE_OPT_END
273	};
274	struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
275	struct fuse_session *se;
276	struct fuse_cmdline_opts opts;
277	int fd;
278	int res;
279
280	if (fuse_parse_cmdline(&args, &opts) == -1)
281		return NULL;
282	*multithreaded = !opts.singlethread;
283
284	/* Remove subtype= option */
285	res = fuse_opt_parse(&args, NULL, kill_subtype_opts, NULL);
286	if (res == -1)
287		goto out1;
288
289	/*
290	 * Make sure file descriptors 0, 1 and 2 are open, otherwise chaos
291	 * would ensue.
292	 */
293	do {
294		fd = open("/dev/null", O_RDWR);
295		if (fd > 2)
296			close(fd);
297	} while (fd >= 0 && fd <= 2);
298
299	se = cuse_lowlevel_new(&args, ci, clop, userdata);
300	if (se == NULL)
301		goto out1;
302
303	fd = open(devname, O_RDWR);
304	if (fd == -1) {
305		if (errno == ENODEV || errno == ENOENT)
306			fuse_log(FUSE_LOG_ERR, "cuse: device not found, try 'modprobe cuse' first\n");
307		else
308			fuse_log(FUSE_LOG_ERR, "cuse: failed to open %s: %s\n",
309				devname, strerror(errno));
310		goto err_se;
311	}
312	se->fd = fd;
313
314	res = fuse_set_signal_handlers(se);
315	if (res == -1)
316		goto err_se;
317
318	res = fuse_daemonize(opts.foreground);
319	if (res == -1)
320		goto err_sig;
321
322	fuse_opt_free_args(&args);
323	return se;
324
325err_sig:
326	fuse_remove_signal_handlers(se);
327err_se:
328	fuse_session_destroy(se);
329out1:
330	free(opts.mountpoint);
331	fuse_opt_free_args(&args);
332	return NULL;
333}
334
335void cuse_lowlevel_teardown(struct fuse_session *se)
336{
337	fuse_remove_signal_handlers(se);
338	fuse_session_destroy(se);
339}
340
341int cuse_lowlevel_main(int argc, char *argv[], const struct cuse_info *ci,
342		       const struct cuse_lowlevel_ops *clop, void *userdata)
343{
344	struct fuse_session *se;
345	int multithreaded;
346	int res;
347
348	se = cuse_lowlevel_setup(argc, argv, ci, clop, &multithreaded,
349				 userdata);
350	if (se == NULL)
351		return 1;
352
353	if (multithreaded) {
354		struct fuse_loop_config *config = fuse_loop_cfg_create();
355		res = fuse_session_loop_mt(se, config);
356		fuse_loop_cfg_destroy(config);
357	}
358	else
359		res = fuse_session_loop(se);
360
361	cuse_lowlevel_teardown(se);
362	if (res == -1)
363		return 1;
364
365	return 0;
366}
367