/* * Copyright 2022, Adrien Destugues * Distributed under terms of the MIT license. */ #include "FUSELowLevel.h" #include #include #include #include #include #define ROUNDDOWN(a, b) (((a) / (b)) * (b)) #define ROUNDUP(a, b) ROUNDDOWN((a) + (b) - 1, b) // Reimplement fuse_req in our own way. In libfuse, the requests are communicated to the kernel, // but in our case, they remain entirely inside userlandfs_server. This means we can use a much // simpler system, by passing pointers directly between userlandfs and the libfuse client code, // and synchronizing them with a semaphore to wait for the replies. struct fuse_req { fuse_req() : fReplyResult(0), fReplyBuf(NULL) { sem_init(&fSyncSem, 0, 0); } ~fuse_req() { sem_destroy(&fSyncSem); } void Wait() { sem_wait(&fSyncSem); } void Notify() { sem_post(&fSyncSem); } sem_t fSyncSem; ssize_t fReplyResult; ReadDirBufferFiller fRequestFiller; void* fRequestCookie; // The reply can contain various things, depending on which function was called union { struct stat* fReplyAttr; struct fuse_entry_param fReplyEntry; struct fuse_file_info* fReplyOpen; struct statvfs* fReplyStat; char* fReplyBuf; }; }; // #pragma mark - Convenience functions for calling fuse lowlevel filesstems easily void fuse_ll_init(const fuse_lowlevel_ops* ops, void* userdata, struct fuse_conn_info* conn) { if (ops->init != NULL) { ops->init(userdata, conn); } } void fuse_ll_destroy(const fuse_lowlevel_ops* ops, void* userdata) { if (ops->destroy != NULL) { ops->destroy(userdata); } } int fuse_ll_lookup(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name, struct stat* st) { if (ops->lookup == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->lookup(&request, parent, name); request.Wait(); *st = request.fReplyEntry.attr; st->st_ino = request.fReplyEntry.ino; return request.fReplyResult; } int fuse_ll_getattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct stat* st) { if (ops->getattr == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyAttr = st; ops->getattr(&request, ino, NULL); request.Wait(); return request.fReplyResult; } int fuse_ll_setattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const struct stat *attr, int to_set) { if (ops->setattr == NULL) return B_NOT_SUPPORTED; fuse_req request; //request.fReplyAttr = attr; ops->setattr(&request, ino, const_cast(attr), to_set, NULL); request.Wait(); return request.fReplyResult; } int fuse_ll_readlink(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size) { if (ops->readlink == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyBuf = buffer; ops->readlink(&request, ino); request.Wait(); return request.fReplyResult; } int fuse_ll_mkdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name, mode_t mode) { if (ops->mkdir == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->mkdir(&request, parent, name, mode); request.Wait(); return request.fReplyResult; } int fuse_ll_unlink(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name) { if (ops->unlink == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->unlink(&request, parent, name); request.Wait(); return request.fReplyResult; } int fuse_ll_rmdir(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name) { if (ops->rmdir == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->rmdir(&request, parent, name); request.Wait(); return request.fReplyResult; } int fuse_ll_symlink(const fuse_lowlevel_ops* ops, const char* link, fuse_ino_t parent, const char* name) { if (ops->symlink == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->symlink(&request, link, parent, name); request.Wait(); return request.fReplyResult; } int fuse_ll_rename(const fuse_lowlevel_ops* ops, fuse_ino_t parent, const char *name, fuse_ino_t newparent, const char *newname) { if (ops->rename == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->rename(&request, parent, name, newparent, newname); request.Wait(); return request.fReplyResult; } int fuse_ll_link(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_ino_t newparent, const char *newname) { if (ops->link == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->link(&request, ino, newparent, newname); request.Wait(); return request.fReplyResult; } int fuse_ll_open(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi) { if (ops->open == NULL) return 0; fuse_req request; request.fReplyOpen = ffi; ops->open(&request, ino, ffi); request.Wait(); return request.fReplyResult; } 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) { if (ops->read == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyBuf = buffer; ops->read(&request, ino, bufferSize, position, ffi); request.Wait(); return request.fReplyResult; } 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) { if (ops->write == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->write(&request, ino, buf, size, off, fi); request.Wait(); return request.fReplyResult; } int fuse_ll_flush(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi) { if (ops->flush == NULL) return 0; fuse_req request; ops->flush(&request, ino, ffi); request.Wait(); return request.fReplyResult; } int fuse_ll_release(const fuse_lowlevel_ops* ops, fuse_ino_t ino, fuse_file_info* ffi) { if (ops->release == NULL) return 0; fuse_req request; ops->release(&request, ino, ffi); request.Wait(); return request.fReplyResult; } int fuse_ll_fsync(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int datasync, fuse_file_info* ffi) { if (ops->fsync == NULL) return 0; fuse_req request; ops->fsync(&request, ino, datasync, ffi); request.Wait(); return request.fReplyResult; } int fuse_ll_opendir(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct fuse_file_info* ffi) { // intentioanlly check for readdir here. Some filesystems do not need an opendir, but still // implement readdir. However if readdir is not implemented, there is no point in trying to // open a directory. if (ops->readdir == NULL) return B_NOT_SUPPORTED; if (ops->opendir) { fuse_req request; ops->opendir(&request, inode, ffi); request.Wait(); return request.fReplyResult; } return 0; } 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) { if (ops->readdir == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyBuf = buffer; request.fRequestFiller = filler; request.fRequestCookie = cookie; ops->readdir(&request, ino, bufferSize, pos, ffi); request.Wait(); return request.fReplyResult; } int fuse_ll_releasedir(const fuse_lowlevel_ops* ops, fuse_ino_t ino, struct fuse_file_info *fi) { if (ops->releasedir == NULL) return 0; fuse_req request; ops->releasedir(&request, ino, fi); request.Wait(); return request.fReplyResult; } int fuse_ll_statfs(const fuse_lowlevel_ops* ops, fuse_ino_t inode, struct statvfs* stat) { if (ops->statfs == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyStat = stat; ops->statfs(&request, inode); request.Wait(); return request.fReplyResult; } int fuse_ll_getxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, const char *name, char* buffer, size_t size) { if (ops->getxattr == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyBuf = buffer; ops->getxattr(&request, ino, name, size); request.Wait(); return request.fReplyResult; } int fuse_ll_listxattr(const fuse_lowlevel_ops* ops, fuse_ino_t ino, char* buffer, size_t size) { if (ops->listxattr == NULL) return B_NOT_SUPPORTED; fuse_req request; request.fReplyBuf = (char*)buffer; ops->listxattr(&request, ino, size); request.Wait(); return request.fReplyResult; } int fuse_ll_access(const fuse_lowlevel_ops* ops, fuse_ino_t ino, int mask) { if (ops->access == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->access(&request, ino, mask); request.Wait(); return request.fReplyResult; } 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) { // TODO if the create op is missing, we could try using mknod + open instead if (ops->create == NULL) return B_NOT_SUPPORTED; fuse_req request; ops->create(&request, parent, name, mode, fi); request.Wait(); ino = request.fReplyEntry.ino; return request.fReplyResult; } //#pragma mark - lowlevel replies handling int fuse_reply_attr(fuse_req_t req, const struct stat *attr, double attr_timeout) { *req->fReplyAttr = *attr; req->Notify(); return 0; } int fuse_reply_create(fuse_req_t req, const struct fuse_entry_param* e, const struct fuse_file_info* fi) { req->fReplyEntry = *e; req->Notify(); return 0; } int fuse_reply_readlink(fuse_req_t req, const char* link) { strlcpy(req->fReplyBuf, link, req->fReplyResult); req->fReplyResult = strlen(link); req->Notify(); return 0; } int fuse_reply_open(fuse_req_t req, const struct fuse_file_info* f) { *req->fReplyOpen = *f; req->Notify(); return 0; } int fuse_reply_buf(fuse_req_t req, const char *buf, size_t size) { if (req->fReplyBuf && req->fReplyBuf != buf) memcpy(req->fReplyBuf, buf, size); req->fReplyResult = size; req->Notify(); return 0; } int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param *e) { req->fReplyEntry = *e; req->Notify(); return 0; } int fuse_reply_err(fuse_req_t req, int err) { assert(err >= 0); req->fReplyResult = -err; req->Notify(); return 0; } int fuse_reply_statfs(fuse_req_t req, const struct statvfs* stat) { *req->fReplyStat = *stat; req->Notify(); return 0; } int fuse_reply_write(fuse_req_t req, size_t count) { req->fReplyResult = count; req->Notify(); return 0; } // return: size of the entry (no matter if it was added to the buffer or not) // params: pointer to where to store the entry, size size_t fuse_add_direntry(fuse_req_t req, char *buf, size_t bufsize, const char *name, const struct stat *stbuf, off_t off) { size_t entryLen = offsetof(struct dirent, d_name) + strlen(name) + 1; // align the entry length, so the next dirent will be aligned entryLen = ROUNDUP(entryLen, 8); if (stbuf != NULL) { req->fRequestFiller(req->fRequestCookie, buf, bufsize, name, stbuf, off); } return entryLen; } // #pragma mark - Stubs for FUSE functions called by client code, that we don't need void fuse_session_add_chan(struct fuse_session* se, struct fuse_chan* ch) { } void fuse_session_remove_chan(struct fuse_chan* ch) { } void fuse_session_destroy(struct fuse_session* se) { } void fuse_session_exit(struct fuse_session* se) { }