1/*
2 * Copyright 2022, Adrien Destugues <pulkomandy@pulkomandy.tk>
3 * Distributed under terms of the MIT license.
4 */
5
6#include "FUSELowLevel.h"
7
8#include <assert.h>
9#include <dirent.h>
10#include <stdio.h>
11#include <string.h>
12
13#include <Errors.h>
14
15#define ROUNDDOWN(a, b) (((a) / (b)) * (b))
16#define ROUNDUP(a, b)   ROUNDDOWN((a) + (b) - 1, b)
17
18
19
20// Reimplement fuse_req in our own way. In libfuse, the requests are communicated to the kernel,
21// but in our case, they remain entirely inside userlandfs_server. This means we can use a much
22// simpler system, by passing pointers directly between userlandfs and the libfuse client code,
23// and synchronizing them with a semaphore to wait for the replies.
24struct fuse_req {
25	fuse_req()
26		: fReplyResult(0),
27		fReplyBuf(NULL)
28	{
29		sem_init(&fSyncSem, 0, 0);
30	}
31
32	~fuse_req() {
33		sem_destroy(&fSyncSem);
34	}
35
36	void Wait() {
37		sem_wait(&fSyncSem);
38	}
39
40	void Notify() {
41		sem_post(&fSyncSem);
42	}
43
44	sem_t fSyncSem;
45	ssize_t fReplyResult;
46
47	ReadDirBufferFiller fRequestFiller;
48	void* fRequestCookie;
49
50	// The reply can contain various things, depending on which function was called
51	union {
52		struct stat* fReplyAttr;
53		struct fuse_entry_param fReplyEntry;
54		struct fuse_file_info* fReplyOpen;
55		struct statvfs* fReplyStat;
56
57		char* fReplyBuf;
58	};
59};
60
61
62// #pragma mark - Convenience functions for calling fuse lowlevel filesstems easily
63
64
65void
66fuse_ll_init(const fuse_lowlevel_ops* ops, void* userdata, struct fuse_conn_info* conn)
67{
68	if (ops->init != NULL) {
69		ops->init(userdata, conn);
70	}
71}
72
73
74void
75fuse_ll_destroy(const fuse_lowlevel_ops* ops, void* userdata)
76{
77	if (ops->destroy != NULL) {
78		ops->destroy(userdata);
79	}
80}
81
82
83int
84fuse_ll_lookup(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
85	struct stat* st)
86{
87	if (ops->lookup == NULL)
88		return B_NOT_SUPPORTED;
89
90	fuse_req request;
91	ops->lookup(&request, parent, name);
92	request.Wait();
93	*st = request.fReplyEntry.attr;
94	st->st_ino = request.fReplyEntry.ino;
95	return request.fReplyResult;
96}
97
98
99int
100fuse_ll_getattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct stat* st)
101{
102	if (ops->getattr == NULL)
103		return B_NOT_SUPPORTED;
104
105	fuse_req request;
106	request.fReplyAttr = st;
107	ops->getattr(&request, ino, NULL);
108	request.Wait();
109	return request.fReplyResult;
110}
111
112
113int
114fuse_ll_setattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const struct stat *attr,
115	int to_set)
116{
117	if (ops->setattr == NULL)
118		return B_NOT_SUPPORTED;
119
120	fuse_req request;
121	//request.fReplyAttr = attr;
122	ops->setattr(&request, ino, const_cast<struct stat*>(attr), to_set, NULL);
123	request.Wait();
124	return request.fReplyResult;
125}
126
127
128int
129fuse_ll_readlink(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size)
130{
131	if (ops->readlink == NULL)
132		return B_NOT_SUPPORTED;
133
134	fuse_req request;
135	request.fReplyBuf = buffer;
136	ops->readlink(&request, ino);
137	request.Wait();
138	return request.fReplyResult;
139}
140
141
142int
143fuse_ll_mkdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
144	mode_t mode)
145{
146	if (ops->mkdir == NULL)
147		return B_NOT_SUPPORTED;
148
149	fuse_req request;
150	ops->mkdir(&request, parent, name, mode);
151	request.Wait();
152	return request.fReplyResult;
153}
154
155
156int
157fuse_ll_unlink(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name)
158{
159	if (ops->unlink == NULL)
160		return B_NOT_SUPPORTED;
161
162	fuse_req request;
163	ops->unlink(&request, parent, name);
164	request.Wait();
165	return request.fReplyResult;
166}
167
168
169int
170fuse_ll_rmdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name)
171{
172	if (ops->rmdir == NULL)
173		return B_NOT_SUPPORTED;
174
175	fuse_req request;
176	ops->rmdir(&request, parent, name);
177	request.Wait();
178	return request.fReplyResult;
179}
180
181
182int
183fuse_ll_symlink(const fuse_lowlevel_ops* ops, const char* link, fuse_ino_t parent,
184	const char* name)
185{
186	if (ops->symlink == NULL)
187		return B_NOT_SUPPORTED;
188
189	fuse_req request;
190	ops->symlink(&request, link, parent, name);
191	request.Wait();
192	return request.fReplyResult;
193}
194
195
196int
197fuse_ll_rename(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
198	fuse_ino_t newparent, const char *newname)
199{
200	if (ops->rename == NULL)
201		return B_NOT_SUPPORTED;
202
203	fuse_req request;
204	ops->rename(&request, parent, name, newparent, newname);
205	request.Wait();
206	return request.fReplyResult;
207}
208
209
210int
211fuse_ll_link(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_ino_t newparent,
212	const char *newname)
213{
214	if (ops->link == NULL)
215		return B_NOT_SUPPORTED;
216
217	fuse_req request;
218	ops->link(&request, ino, newparent, newname);
219	request.Wait();
220	return request.fReplyResult;
221}
222
223
224int
225fuse_ll_open(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi)
226{
227	if (ops->open == NULL)
228		return 0;
229
230	fuse_req request;
231	request.fReplyOpen = ffi;
232	ops->open(&request, ino, ffi);
233	request.Wait();
234	return request.fReplyResult;
235}
236
237
238int
239fuse_ll_read(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t bufferSize,
240	off_t position, fuse_file_info* ffi)
241{
242	if (ops->read == NULL)
243		return B_NOT_SUPPORTED;
244
245	fuse_req request;
246	request.fReplyBuf = buffer;
247	ops->read(&request, ino, bufferSize, position, ffi);
248	request.Wait();
249	return request.fReplyResult;
250}
251
252
253int
254fuse_ll_write(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *buf,
255	size_t size, off_t off, struct fuse_file_info *fi)
256{
257	if (ops->write == NULL)
258		return B_NOT_SUPPORTED;
259
260	fuse_req request;
261	ops->write(&request, ino, buf, size, off, fi);
262	request.Wait();
263	return request.fReplyResult;
264}
265
266
267int
268fuse_ll_flush(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi)
269{
270	if (ops->flush == NULL)
271		return 0;
272
273	fuse_req request;
274	ops->flush(&request, ino, ffi);
275	request.Wait();
276	return request.fReplyResult;
277}
278
279
280int
281fuse_ll_release(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi)
282{
283	if (ops->release == NULL)
284		return 0;
285
286	fuse_req request;
287	ops->release(&request, ino, ffi);
288	request.Wait();
289	return request.fReplyResult;
290}
291
292
293int
294fuse_ll_fsync(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int datasync, fuse_file_info* ffi)
295{
296	if (ops->fsync == NULL)
297		return 0;
298
299	fuse_req request;
300	ops->fsync(&request, ino, datasync, ffi);
301	request.Wait();
302	return request.fReplyResult;
303}
304
305
306int
307fuse_ll_opendir(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct fuse_file_info* ffi)
308{
309	// intentioanlly check for readdir here. Some filesystems do not need an opendir, but still
310	// implement readdir. However if readdir is not implemented, there is no point in trying to
311	// open a directory.
312	if (ops->readdir == NULL)
313		return B_NOT_SUPPORTED;
314
315	if (ops->opendir) {
316		fuse_req request;
317		ops->opendir(&request, inode, ffi);
318		request.Wait();
319		return request.fReplyResult;
320	}
321
322	return 0;
323}
324
325
326int
327fuse_ll_readdir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, void* cookie, char* buffer,
328	size_t bufferSize, ReadDirBufferFiller filler, off_t pos, fuse_file_info* ffi)
329{
330	if (ops->readdir == NULL)
331		return B_NOT_SUPPORTED;
332
333	fuse_req request;
334	request.fReplyBuf = buffer;
335
336	request.fRequestFiller = filler;
337	request.fRequestCookie = cookie;
338
339	ops->readdir(&request, ino, bufferSize, pos, ffi);
340
341	request.Wait();
342	return request.fReplyResult;
343}
344
345
346int
347fuse_ll_releasedir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi)
348{
349	if (ops->releasedir == NULL)
350		return 0;
351
352	fuse_req request;
353	ops->releasedir(&request, ino, fi);
354	request.Wait();
355	return request.fReplyResult;
356}
357
358
359int
360fuse_ll_statfs(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct statvfs* stat)
361{
362	if (ops->statfs == NULL)
363		return B_NOT_SUPPORTED;
364
365	fuse_req request;
366	request.fReplyStat = stat;
367	ops->statfs(&request, inode);
368	request.Wait();
369	return request.fReplyResult;
370}
371
372
373int
374fuse_ll_getxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *name,
375	char* buffer, size_t size)
376{
377	if (ops->getxattr == NULL)
378		return B_NOT_SUPPORTED;
379
380	fuse_req request;
381	request.fReplyBuf = buffer;
382	ops->getxattr(&request, ino, name, size);
383	request.Wait();
384	return request.fReplyResult;
385}
386
387
388int
389fuse_ll_listxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size)
390{
391	if (ops->listxattr == NULL)
392		return B_NOT_SUPPORTED;
393
394	fuse_req request;
395	request.fReplyBuf = (char*)buffer;
396	ops->listxattr(&request, ino, size);
397	request.Wait();
398	return request.fReplyResult;
399}
400
401
402int
403fuse_ll_access(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int mask)
404{
405	if (ops->access == NULL)
406		return B_NOT_SUPPORTED;
407
408	fuse_req request;
409	ops->access(&request, ino, mask);
410	request.Wait();
411	return request.fReplyResult;
412}
413
414
415int
416fuse_ll_create(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name,
417	mode_t mode, struct fuse_file_info *fi, fuse_ino_t& ino)
418{
419	// TODO if the create op is missing, we could try using mknod + open instead
420	if (ops->create == NULL)
421		return B_NOT_SUPPORTED;
422
423	fuse_req request;
424	ops->create(&request, parent, name, mode, fi);
425	request.Wait();
426	ino = request.fReplyEntry.ino;
427	return request.fReplyResult;
428}
429
430
431//#pragma mark - lowlevel replies handling
432
433
434int
435fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout)
436{
437	*req->fReplyAttr = *attr;
438	req->Notify();
439	return 0;
440}
441
442
443int
444fuse_reply_create(fuse_req_t req, const struct fuse_entry_param* e, const struct fuse_file_info* fi)
445{
446	req->fReplyEntry = *e;
447	req->Notify();
448	return 0;
449}
450
451
452int
453fuse_reply_readlink(fuse_req_t req, const char* link)
454{
455	strlcpy(req->fReplyBuf, link, req->fReplyResult);
456	req->fReplyResult = strlen(link);
457	req->Notify();
458	return 0;
459}
460
461
462int
463fuse_reply_open(fuse_req_t req, const struct fuse_file_info* f)
464{
465	*req->fReplyOpen = *f;
466	req->Notify();
467	return 0;
468}
469
470
471int
472fuse_reply_buf(fuse_req_t req, const char *buf, size_t size)
473{
474	if (req->fReplyBuf && req->fReplyBuf != buf)
475		memcpy(req->fReplyBuf, buf, size);
476
477	req->fReplyResult = size;
478
479	req->Notify();
480	return 0;
481}
482
483
484int
485fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e)
486{
487	req->fReplyEntry = *e;
488	req->Notify();
489	return 0;
490}
491
492
493int
494fuse_reply_err(fuse_req_t req, int err)
495{
496	assert(err >= 0);
497	req->fReplyResult = -err;
498	req->Notify();
499	return 0;
500}
501
502
503int
504fuse_reply_statfs(fuse_req_t req, const struct statvfs* stat)
505{
506	*req->fReplyStat = *stat;
507	req->Notify();
508	return 0;
509}
510
511
512int
513fuse_reply_write(fuse_req_t req, size_t count)
514{
515	req->fReplyResult = count;
516	req->Notify();
517	return 0;
518}
519
520
521// return: size of the entry (no matter if it was added to the buffer or not)
522// params: pointer to where to store the entry, size
523size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name,
524	const struct stat *stbuf, off_t off)
525{
526	size_t entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1;
527	// align the entry length, so the next dirent will be aligned
528	entryLen = ROUNDUP(entryLen, 8);
529
530	if (stbuf != NULL) {
531		req->fRequestFiller(req->fRequestCookie, buf, bufsize, name, stbuf, off);
532	}
533
534	return entryLen;
535}
536
537
538// #pragma mark - Stubs for FUSE functions called by client code, that we don't need
539
540
541void fuse_session_add_chan(struct fuse_session* se, struct fuse_chan* ch)
542{
543}
544
545void fuse_session_remove_chan(struct fuse_chan* ch)
546{
547}
548
549void fuse_session_destroy(struct fuse_session* se)
550{
551}
552
553void fuse_session_exit(struct fuse_session* se)
554{
555}
556