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.
24 struct fuse_req {
fuse_reqfuse_req25 fuse_req()
26 : fReplyResult(0),
27 fReplyBuf(NULL)
28 {
29 sem_init(&fSyncSem, 0, 0);
30 }
31
~fuse_reqfuse_req32 ~fuse_req() {
33 sem_destroy(&fSyncSem);
34 }
35
Waitfuse_req36 void Wait() {
37 sem_wait(&fSyncSem);
38 }
39
Notifyfuse_req40 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
65 void
fuse_ll_init(const fuse_lowlevel_ops * ops,void * userdata,struct fuse_conn_info * conn)66 fuse_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
74 void
fuse_ll_destroy(const fuse_lowlevel_ops * ops,void * userdata)75 fuse_ll_destroy(const fuse_lowlevel_ops* ops, void* userdata)
76 {
77 if (ops->destroy != NULL) {
78 ops->destroy(userdata);
79 }
80 }
81
82
83 int
fuse_ll_lookup(const fuse_lowlevel_ops * ops,fuse_ino_t parent,const char * name,struct stat * st)84 fuse_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
99 int
fuse_ll_getattr(const fuse_lowlevel_ops * ops,fuse_ino_t ino,struct stat * st)100 fuse_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
113 int
fuse_ll_setattr(const fuse_lowlevel_ops * ops,fuse_ino_t ino,const struct stat * attr,int to_set)114 fuse_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
128 int
fuse_ll_readlink(const fuse_lowlevel_ops * ops,fuse_ino_t ino,char * buffer,size_t size)129 fuse_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
142 int
fuse_ll_mkdir(const fuse_lowlevel_ops * ops,fuse_ino_t parent,const char * name,mode_t mode)143 fuse_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
156 int
fuse_ll_unlink(const fuse_lowlevel_ops * ops,fuse_ino_t parent,const char * name)157 fuse_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
169 int
fuse_ll_rmdir(const fuse_lowlevel_ops * ops,fuse_ino_t parent,const char * name)170 fuse_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
182 int
fuse_ll_symlink(const fuse_lowlevel_ops * ops,const char * link,fuse_ino_t parent,const char * name)183 fuse_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
196 int
fuse_ll_rename(const fuse_lowlevel_ops * ops,fuse_ino_t parent,const char * name,fuse_ino_t newparent,const char * newname)197 fuse_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
210 int
fuse_ll_link(const fuse_lowlevel_ops * ops,fuse_ino_t ino,fuse_ino_t newparent,const char * newname)211 fuse_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
224 int
fuse_ll_open(const fuse_lowlevel_ops * ops,fuse_ino_t ino,fuse_file_info * ffi)225 fuse_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
238 int
fuse_ll_read(const fuse_lowlevel_ops * ops,fuse_ino_t ino,char * buffer,size_t bufferSize,off_t position,fuse_file_info * ffi)239 fuse_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
253 int
fuse_ll_write(const fuse_lowlevel_ops * ops,fuse_ino_t ino,const char * buf,size_t size,off_t off,struct fuse_file_info * fi)254 fuse_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
267 int
fuse_ll_flush(const fuse_lowlevel_ops * ops,fuse_ino_t ino,fuse_file_info * ffi)268 fuse_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
280 int
fuse_ll_release(const fuse_lowlevel_ops * ops,fuse_ino_t ino,fuse_file_info * ffi)281 fuse_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
293 int
fuse_ll_fsync(const fuse_lowlevel_ops * ops,fuse_ino_t ino,int datasync,fuse_file_info * ffi)294 fuse_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
306 int
fuse_ll_opendir(const fuse_lowlevel_ops * ops,fuse_ino_t inode,struct fuse_file_info * ffi)307 fuse_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
326 int
fuse_ll_readdir(const fuse_lowlevel_ops * ops,fuse_ino_t ino,void * cookie,char * buffer,size_t bufferSize,ReadDirBufferFiller filler,off_t pos,fuse_file_info * ffi)327 fuse_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
346 int
fuse_ll_releasedir(const fuse_lowlevel_ops * ops,fuse_ino_t ino,struct fuse_file_info * fi)347 fuse_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
359 int
fuse_ll_statfs(const fuse_lowlevel_ops * ops,fuse_ino_t inode,struct statvfs * stat)360 fuse_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
373 int
fuse_ll_getxattr(const fuse_lowlevel_ops * ops,fuse_ino_t ino,const char * name,char * buffer,size_t size)374 fuse_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
388 int
fuse_ll_listxattr(const fuse_lowlevel_ops * ops,fuse_ino_t ino,char * buffer,size_t size)389 fuse_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
402 int
fuse_ll_access(const fuse_lowlevel_ops * ops,fuse_ino_t ino,int mask)403 fuse_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
415 int
fuse_ll_create(const fuse_lowlevel_ops * ops,fuse_ino_t parent,const char * name,mode_t mode,struct fuse_file_info * fi,fuse_ino_t & ino)416 fuse_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
434 int
fuse_reply_attr(fuse_req_t req,const struct stat * attr,double attr_timeout)435 fuse_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
443 int
fuse_reply_create(fuse_req_t req,const struct fuse_entry_param * e,const struct fuse_file_info * fi)444 fuse_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
452 int
fuse_reply_readlink(fuse_req_t req,const char * link)453 fuse_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
462 int
fuse_reply_open(fuse_req_t req,const struct fuse_file_info * f)463 fuse_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
471 int
fuse_reply_buf(fuse_req_t req,const char * buf,size_t size)472 fuse_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
484 int
fuse_reply_entry(fuse_req_t req,const struct fuse_entry_param * e)485 fuse_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
493 int
fuse_reply_err(fuse_req_t req,int err)494 fuse_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
503 int
fuse_reply_statfs(fuse_req_t req,const struct statvfs * stat)504 fuse_reply_statfs(fuse_req_t req, const struct statvfs* stat)
505 {
506 *req->fReplyStat = *stat;
507 req->Notify();
508 return 0;
509 }
510
511
512 int
fuse_reply_write(fuse_req_t req,size_t count)513 fuse_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
fuse_add_direntry(fuse_req_t req,char * buf,size_t bufsize,const char * name,const struct stat * stbuf,off_t off)523 size_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
fuse_session_add_chan(struct fuse_session * se,struct fuse_chan * ch)541 void fuse_session_add_chan(struct fuse_session* se, struct fuse_chan* ch)
542 {
543 }
544
fuse_session_remove_chan(struct fuse_chan * ch)545 void fuse_session_remove_chan(struct fuse_chan* ch)
546 {
547 }
548
fuse_session_destroy(struct fuse_session * se)549 void fuse_session_destroy(struct fuse_session* se)
550 {
551 }
552
fuse_session_exit(struct fuse_session * se)553 void fuse_session_exit(struct fuse_session* se)
554 {
555 }
556