From c97da47da6121b3a73262b3dc0981aa0105ab1a4 Mon Sep 17 00:00:00 2001 From: "aynic.os" Date: Sat, 8 Jul 2023 18:14:42 +0000 Subject: [PATCH] fuse3 implementation --- AUTHORS | 1 + src/πfs.c | 695 +++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 588 insertions(+), 108 deletions(-) diff --git a/AUTHORS b/AUTHORS index 107c25a..5d1bccf 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1 +1,2 @@ Philip Langdale +Yann Autissier diff --git a/src/πfs.c b/src/πfs.c index 8aba540..eec21f4 100644 --- a/src/πfs.c +++ b/src/πfs.c @@ -1,4 +1,5 @@ /* Copyright (C) 2012 Philip Langdale + * Copyright (C) 2022 Yann Autissier * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,9 +16,18 @@ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ -#define FUSE_USE_VERSION 26 +#ifdef linux +/* For pread()/pwrite()/utimensat() */ +#define _XOPEN_SOURCE 700 +#endif + +#define PIFS_VERSION "0.0.1" +#define FUSE_USE_VERSION 31 + +#include #include #include +#include #include #include #include @@ -25,144 +35,312 @@ #include #include #include +#ifdef HAVE_SETXATTR #include +#endif // HAVE_SETXATTR #include -#include -#include -struct options { +struct options +{ + char *dir; char *mdd; + char *log; + int help; + int version; } options; -/** macro to define options */ -#define PIFS_OPT_KEY(t, p, v) { t, offsetof(struct options, p), v } - -static struct fuse_opt pifs_opts[] = -{ - PIFS_OPT_KEY("mdd=%s", mdd, 0), +/* macro to define options */ +#define PIFS_OPTIONS(t, p, v) { t, offsetof(struct options, p), v } +static struct fuse_opt pifs_opts[] = { + PIFS_OPTIONS("-h", help, 1), + PIFS_OPTIONS("--help", help, 1), + PIFS_OPTIONS("log=%s", log, 0), + PIFS_OPTIONS("mdd=%s", mdd, 0), + PIFS_OPTIONS("-V", version, 1), + PIFS_OPTIONS("--version", version, 1), + FUSE_OPT_END }; -#define FULL_PATH(path) \ - char full_path[PATH_MAX]; \ - snprintf(full_path, PATH_MAX, "%s%s", options.mdd, path); \ - printf("full_path: %s\n", full_path); +/* define debug infos in log file */ +#define FUSE_LOG(ret,...) \ + fuse_log(FUSE_LOG_INFO,"pid=%-6d clock=%-8ld %-16s %-6d %-32s",getpid(),clock(),__FUNCTION__,ret,ret < 0 ? strerror(errno) : ""); \ + fuse_log(FUSE_LOG_INFO,__VA_ARGS__); \ -static int pifs_getattr(const char *path, struct stat *buf) +/* define metadata file path */ +#define MDD_PATH(path) \ + char mdd_path[PATH_MAX]; \ + snprintf(mdd_path, PATH_MAX, "%s%s", options.mdd, path); \ + +/** Get file attributes + * + * Similar to stat(). The 'st_dev' and 'st_blksize' fields are + * ignored. The 'st_ino' field is ignored except if the 'use_ino' + * mount option is given. In that case it is passed to userspace, + * but libfuse and the kernel will still assign a different + * inode for internal use (called the "nodeid"). + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * int (*getattr) (const char *, struct stat *, struct fuse_file_info *fi); + * + * struct stat { + * dev_t st_dev; ID of device containing file + * ino_t st_ino; inode number + * mode_t st_mode; protection + * nlink_t st_nlink; number of hard links + * uid_t st_uid; user ID of owner + * gid_t st_gid; group ID of owner + * dev_t st_rdev; device ID (if special file) + * off_t st_size; total size, in bytes + * blksize_t st_blksize; blocksize for file system I/O + * blkcnt_t st_blocks; number of 512B blocks allocated + * time_t st_atime; time of last access + * time_t st_mtime; time of last modification + * time_t st_ctime; time of last status change + * }; +**/ +static int pifs_getattr(const char *path, struct stat *buf, struct fuse_file_info *info) { - FULL_PATH(path); - int ret = lstat(full_path, buf); - buf->st_size /= 2; + MDD_PATH(path); + int ret = lstat(mdd_path, buf); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +/** Read the target of a symbolic link + * + * The buffer should be filled with a null terminated string. The + * buffer size argument includes the space for the terminating + * null character. If the linkname is too long to fit in the + * buffer, it should be truncated. The return value should be 0 + * for success. +**/ static int pifs_readlink(const char *path, char *buf, size_t bufsiz) { - FULL_PATH(path); - int ret = readlink(full_path, buf, bufsiz - 1); + MDD_PATH(path); + int ret = readlink(mdd_path, buf, bufsiz - 1); if (ret == -1) { + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return -errno; } buf[ret] = '\0'; + FUSE_LOG(ret,"mdd=%s, buf=%s, bufsiz=%zu\n",mdd_path,buf,bufsiz); return 0; } +/** Create a file node + * + * This is called for creation of all non-directory, non-symlink + * nodes. If the filesystem defines a create() method, then for + * regular files that will be called instead. +**/ static int pifs_mknod(const char *path, mode_t mode, dev_t dev) { - FULL_PATH(path); - int ret = mknod(full_path, mode, dev); + MDD_PATH(path); + int ret = mknod(mdd_path, mode, dev); + FUSE_LOG(ret,"mdd=%s, mode=%o\n",mdd_path,mode); return ret == -1 ? -errno : ret; } +/** Create a directory + * + * Note that the mode argument may not have the type specification + * bits set, i.e. S_ISDIR(mode) can be false. To obtain the + * correct directory type bits use mode|S_IFDIR +**/ static int pifs_mkdir(const char *path, mode_t mode) { - FULL_PATH(path); - int ret = mkdir(full_path, mode | S_IFDIR); + MDD_PATH(path); + int ret = mkdir(mdd_path, mode | S_IFDIR); + FUSE_LOG(ret,"mdd=%s, mode=%o\n",mdd_path,mode); return ret == -1 ? -errno : ret; } +/** Remove a file **/ static int pifs_unlink(const char *path) { - FULL_PATH(path); - int ret = unlink(full_path); + MDD_PATH(path); + int ret = unlink(mdd_path); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +/** Remove a directory **/ static int pifs_rmdir(const char *path) { - FULL_PATH(path); - int ret = rmdir(full_path); + MDD_PATH(path); + int ret = rmdir(mdd_path); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +/** Create a symbolic link **/ static int pifs_symlink(const char *oldpath, const char *newpath) { - FULL_PATH(newpath); - int ret = symlink(oldpath, full_path); + MDD_PATH(newpath); + int ret = symlink(oldpath, mdd_path); + FUSE_LOG(ret,"mdd=%s, old=%s\n",mdd_path,oldpath); return ret == -1 ? -errno : ret; } -static int pifs_rename(const char *oldpath, const char *newpath) +/** Rename a file + * + * *flags* may be `RENAME_EXCHANGE` or `RENAME_NOREPLACE`. If + * RENAME_NOREPLACE is specified, the filesystem must not + * overwrite *newname* if it exists and return an error + * instead. If `RENAME_EXCHANGE` is specified, the filesystem + * must atomically exchange the two files, i.e. both must + * exist and neither may be deleted. +**/ +static int pifs_rename(const char *oldpath, const char *newpath, unsigned int flags) { - FULL_PATH(newpath); - int ret = rename(oldpath, full_path); + MDD_PATH(newpath); + char old_path[PATH_MAX]; + snprintf(old_path, PATH_MAX, "%s%s", options.mdd, oldpath); + if ((flags == 0) && (access(mdd_path, R_OK) == 0)) { + FUSE_LOG(-1,"mdd=%s, old=%s, flags=%o\n",mdd_path,old_path,flags); + return -1; + } + int ret = rename(old_path, mdd_path); + FUSE_LOG(ret,"mdd=%s, old=%s, flags=%o\n",mdd_path,old_path,flags); return ret == -1 ? -errno : ret; } +/** Create a hard link to a file **/ static int pifs_link(const char *oldpath, const char *newpath) { - FULL_PATH(newpath); - int ret = link(oldpath, full_path); + MDD_PATH(newpath); + int ret = link(oldpath, mdd_path); + FUSE_LOG(ret,"mdd=%s, old=%s\n",mdd_path,oldpath); return ret == -1 ? -errno : ret; } -static int pifs_chmod(const char *path, mode_t mode) +/** Change the permission bits of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * int chmod(const char *, mode_t, struct fuse_file_info *fi); +**/ +static int pifs_chmod(const char *path, mode_t mode, struct fuse_file_info *info) { - FULL_PATH(path); - int ret = chmod(full_path, mode); + MDD_PATH(path); + int ret = chmod(mdd_path, mode); + FUSE_LOG(ret,"mdd=%s, mode=%o\n",mdd_path,mode); return ret == -1 ? -errno : ret; } -static int pifs_chown(const char *path, uid_t owner, gid_t group) +/** Change the owner and group of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * int chown(const char *, uid_t, gid_t, struct fuse_file_info *fi); + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. +**/ +static int pifs_chown(const char *path, uid_t owner, gid_t group, struct fuse_file_info *info) { - FULL_PATH(path); - int ret = chown(full_path, owner, group); + MDD_PATH(path); + int ret = chown(mdd_path, owner, group); + FUSE_LOG(ret,"mdd=%s, owner=%d, group=%d\n",mdd_path,owner,group); return ret == -1 ? -errno : ret; } -static int pifs_truncate(const char *path, off_t length) +/** Change the size of a file + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + int truncate(const char *, off_t, struct fuse_file_info *fi); + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. +**/ +static int pifs_truncate(const char *path, off_t length, struct fuse_file_info *info) { - FULL_PATH(path); - int ret = truncate(full_path, length * 2); - return ret == -1 ? -errno : ret; -} - -static int pifs_utime(const char *path, struct utimbuf *times) -{ - FULL_PATH(path); - int ret = utime(full_path, times); + MDD_PATH(path); + int ret = truncate(mdd_path, length); + FUSE_LOG(ret,"mdd=%s, length=%jd\n",mdd_path,(intmax_t)length); return ret == -1 ? -errno : ret; } +/** Open a file + * + * Open flags are available in fi->flags. The following rules + * apply. + * + * - Creation (O_CREAT, O_EXCL, O_NOCTTY) flags will be + * filtered out / handled by the kernel. + * + * - Access modes (O_RDONLY, O_WRONLY, O_RDWR, O_EXEC, O_SEARCH) + * should be used by the filesystem to check if the operation is + * permitted. If the ``-o default_permissions`` mount option is + * given, this check is already done by the kernel before calling + * open() and may thus be omitted by the filesystem. + * + * - When writeback caching is enabled, the kernel may send + * read requests even for files opened with O_WRONLY. The + * filesystem should be prepared to handle this. + * + * - When writeback caching is disabled, the filesystem is + * expected to properly handle the O_APPEND flag and ensure + * that each write is appending to the end of the file. + * + * - When writeback caching is enabled, the kernel will + * handle O_APPEND. However, unless all changes to the file + * come through the kernel this will not work reliably. The + * filesystem should thus either ignore the O_APPEND flag + * (and let the kernel handle it), or return an error + * (indicating that reliably O_APPEND is not available). + * + * Filesystem may store an arbitrary file handle (pointer, + * index, etc) in fi->fh, and use this in other all other file + * operations (read, write, flush, release, fsync). + * + * Filesystem may also implement stateless file I/O and not store + * anything in fi->fh. + * + * There are also some flags (direct_io, keep_cache) which the + * filesystem may set in fi, to change the way the file is opened. + * See fuse_file_info structure in for more details. + * + * If this request is answered with an error code of ENOSYS + * and FUSE_CAP_NO_OPEN_SUPPORT is set in + * `fuse_conn_info.capable`, this is treated as success and + * future calls to open will also succeed without being send + * to the filesystem process. +**/ static int pifs_open(const char *path, struct fuse_file_info *info) { - FULL_PATH(path); - int ret = open(full_path, info->flags); - info->fh = ret; + MDD_PATH(path); + int ret = open(mdd_path, info->flags); + FUSE_LOG(ret,"mdd=%s, flags=%o\n",mdd_path,info->flags); + if (ret != -1) info->fh = ret; return ret == -1 ? -errno : 0; } +/** Read data from an open file + * + * Read should return exactly the number of bytes requested except + * on EOF or error, otherwise the rest of the data will be + * substituted with zeroes. An exception to this is when the + * 'direct_io' mount option is specified, in which case the return + * value of the read system call will reflect the return value of + * this operation. +**/ static int pifs_read(const char *path, char *buf, size_t count, off_t offset, struct fuse_file_info *info) { - char buffer[5]; + char buffer[4096]; + FILE *fp; int size = 0; - int ret = lseek(info->fh, offset * 2, SEEK_SET); + int ret = lseek(info->fh, offset, SEEK_SET); if (ret == -1) { return -errno; } dup2(info->fh, STDIN_FILENO); - FILE *fp = popen("ipfs cat", "r"); + // fp = popen("ipfs cat", "r"); + fp = popen("cat", "r"); if (fp == 0) { perror("popen(3) failed"); return -1; @@ -182,11 +360,20 @@ static int pifs_read(const char *path, char *buf, size_t count, off_t offset, return size; } +/** Write data to an open file + * + * Write should return exactly the number of bytes requested + * except on error. An exception to this is when the 'direct_io' + * mount option is specified (see read operation). + * + * Unless FUSE_CAP_HANDLE_KILLPRIV is disabled, this method is + * expected to reset the setuid and setgid bits. +**/ static int pifs_write(const char *path, const char *buf, size_t count, off_t offset, struct fuse_file_info *info) { int fd[2]; - int ret = lseek(info->fh, offset * 2, SEEK_SET); + int ret = lseek(info->fh, offset, SEEK_SET); if (ret == -1) { return -errno; } @@ -204,8 +391,8 @@ static int pifs_write(const char *path, const char *buf, size_t count, dup2(info->fh, STDOUT_FILENO); close(fd[0]); close(fd[1]); - // ret = execlp("ipfs", "ipfs", "add", "-q", "-s", "rabin-262144-524288-1048576", NULL); - ret = execlp("ipfs", "ipfs", "add", "-q", NULL); + // ret = execlp("ipfs", "ipfs", "add", "-q", NULL); + ret = execlp("cat", "cat", NULL); if (ret == -1) { return -errno; } @@ -223,67 +410,169 @@ static int pifs_write(const char *path, const char *buf, size_t count, return count; } +/** Get file system statistics + * + * The 'f_favail', 'f_fsid' and 'f_flag' fields are ignored +**/ static int pifs_statfs(const char *path, struct statvfs *buf) { - FULL_PATH(path); - int ret = statvfs(full_path, buf); + MDD_PATH(path); + int ret = statvfs(mdd_path, buf); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +/** Possibly flush cached data + * + * BIG NOTE: This is not equivalent to fsync(). It's not a + * request to sync dirty data. + * + * Flush is called on each close() of a file descriptor, as opposed to + * release which is called on the close of the last file descriptor for + * a file. Under Linux, errors returned by flush() will be passed to + * userspace as errors from close(), so flush() is a good place to write + * back any cached dirty data. However, many applications ignore errors + * on close(), and on non-Linux systems, close() may succeed even if flush() + * returns an error. For these reasons, filesystems should not assume + * that errors returned by flush will ever be noticed or even + * delivered. + * + * NOTE: The flush() method may be called more than once for each + * open(). This happens if more than one file descriptor refers to an + * open file handle, e.g. due to dup(), dup2() or fork() calls. It is + * not possible to determine if a flush is final, so each flush should + * be treated equally. Multiple write-flush sequences are relatively + * rare, so this shouldn't be a problem. + * + * Filesystems shouldn't assume that flush will be called at any + * particular point. It may be called more times than expected, or not + * at all. + * + * [close]: http://pubs.opengroup.org/onlinepubs/9699919799/functions/close.html +**/ +int pifs_flush(const char *, struct fuse_file_info *info); + +/** Release an open file + * + * Release is called when there are no more references to an open + * file: all file descriptors are closed and all memory mappings + * are unmapped. + * + * For every open() call there will be exactly one release() call + * with the same flags and file handle. It is possible to + * have a file opened more than once, in which case only the last + * release will mean, that no more reads/writes will happen on the + * file. The return value of release is ignored. +**/ static int pifs_release(const char *path, struct fuse_file_info *info) { + MDD_PATH(path); int ret = close(info->fh); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +/** Synchronize file contents + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. +**/ static int pifs_fsync(const char *path, int datasync, struct fuse_file_info *info) { + MDD_PATH(path); int ret = datasync ? fdatasync(info->fh) : fsync(info->fh); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +#ifdef HAVE_SETXATTR +/** Set extended attributes **/ static int pifs_setxattr(const char *path, const char *name, const char *value, size_t size, int flags) { - FULL_PATH(path); - int ret = setxattr(full_path, name, value, size, flags); + MDD_PATH(path); + int ret = setxattr(mdd_path, name, value, size, flags); + FUSE_LOG(ret,"mdd=%s, name=%s, value=%s, size=%zu, flags=%o\n",mdd_path,name,value,size,flags); return ret == -1 ? -errno : ret; } +/** Get extended attributes **/ static int pifs_getxattr(const char *path, const char *name, char *value, size_t size) { - FULL_PATH(path); - int ret = getxattr(full_path, name, value, size); + MDD_PATH(path); + int ret = getxattr(mdd_path, name, value, size); + FUSE_LOG(ret,"mdd=%s, name=%s, value=%s, size=%zu\n",mdd_path,name,value,size); return ret == -1 ? -errno : ret; } +/** List extended attributes **/ static int pifs_listxattr(const char *path, char *list, size_t size) { - FULL_PATH(path); - int ret = listxattr(full_path, list, size); + MDD_PATH(path); + int ret = listxattr(mdd_path, list, size); + FUSE_LOG(ret,"mdd=%s, list=%s, size=%zu\n",mdd_path,list,size); return ret == -1 ? -errno : ret; } +/** Remove extended attributes **/ static int pifs_removexattr(const char *path, const char *name) { - FULL_PATH(path); - int ret = removexattr(full_path, name); + MDD_PATH(path); + int ret = removexattr(mdd_path, name); + FUSE_LOG(ret,"mdd=%s, name=%s\n",mdd_path,name); return ret == -1 ? -errno : ret; } +#endif // HAVE_SETXATTR +/** Open directory + * + * Unless the 'default_permissions' mount option is given, + * this method should check if opendir is permitted for this + * directory. Optionally opendir may also return an arbitrary + * filehandle in the fuse_file_info structure, which will be + * passed to readdir, releasedir and fsyncdir. +**/ static int pifs_opendir(const char *path, struct fuse_file_info *info) { - FULL_PATH(path); - DIR *dir = opendir(full_path); + MDD_PATH(path); + DIR *dir = opendir(mdd_path); + if (!dir){ + FUSE_LOG(-1,"mdd=%s\n",mdd_path); + return -errno; + } + FUSE_LOG(0,"mdd=%s\n",mdd_path); info->fh = (uint64_t) dir; return !dir ? -errno : 0; } +/** Read directory + * + * The filesystem may choose between two modes of operation: + * + * 1) The readdir implementation ignores the offset parameter, and + * passes zero to the filler function's offset. The filler + * function will not return '1' (unless an error happens), so the + * whole directory is read in a single readdir operation. + * + * 2) The readdir implementation keeps track of the offsets of the + * directory entries. It uses the offset parameter and always + * passes non-zero offset to the filler function. When the buffer + * is full (or an error happens) the filler function will return + * '1'. + * + * When FUSE_READDIR_PLUS is not set, only some parameters of the + * fill function (the fuse_fill_dir_t parameter) are actually used: + * The file type (which is part of stat::st_mode) is used. And if + * fuse_config::use_ino is set, the inode (stat::st_ino) is also + * used. The other fields are ignored when FUSE_READDIR_PLUS is not + * set. +**/ static int pifs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, - off_t offset, struct fuse_file_info *info) + off_t offset, struct fuse_file_info *info, enum fuse_readdir_flags flags) { + MDD_PATH(path); DIR *dir = (DIR *) info->fh; if (offset) { seekdir(dir, offset); @@ -295,80 +584,209 @@ static int pifs_readdir(const char *path, void *buf, fuse_fill_dir_t filler, struct dirent *de = readdir(dir); if (!de) { if (errno) { + FUSE_LOG(-1,"mdd=%s\n",mdd_path); return -errno; } else { break; } } - ret = filler(buf, de->d_name, NULL, de->d_off); + ret = filler(buf, de->d_name, NULL, de->d_off, 0); + FUSE_LOG(ret,"mdd=%s, name=%s\n",mdd_path,de->d_name); } while (ret == 0); return 0; } +/** Release directory + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. +**/ static int pifs_releasedir(const char *path, struct fuse_file_info *info) { + MDD_PATH(path); int ret = closedir((DIR *)info->fh); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); return ret == -1 ? -errno : ret; } +/** Synchronize directory contents + * + * If the directory has been removed after the call to opendir, the + * path parameter will be NULL. + * + * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data +**/ static int pifs_fsyncdir(const char *path, int datasync, struct fuse_file_info *info) { + MDD_PATH(path); int fd = dirfd((DIR *)info->fh); if (fd == -1) { + FUSE_LOG(-1,"mdd=%s\n",mdd_path); return -errno; } int ret = datasync ? fdatasync(fd) : fsync(fd); + FUSE_LOG(ret,"mdd=%s, datasync=%d\n",mdd_path,datasync); return ret == -1 ? -errno : ret; } +/** + * Initialize filesystem + * + * The return value will passed in the `private_data` field of + * `struct fuse_context` to all file operations, and as a + * parameter to the destroy() method. It overrides the initial + * value provided to fuse_main() / fuse_new(). +**/ +void *pifs_init(struct fuse_conn_info *conn, + struct fuse_config *cfg); + +/** + * Clean up filesystem + * + * Called on filesystem exit. +**/ +void pifs_destroy(void *private_data); + +/** + * Check file access permissions + * + * This will be called for the access() system call. If the + * 'default_permissions' mount option is given, this method is not + * called. + * + * This method is not called under Linux kernel versions 2.4.x +**/ static int pifs_access(const char *path, int mode) { - FULL_PATH(path); - int ret = access(full_path, mode); + MDD_PATH(path); + int ret = access(mdd_path, mode); + FUSE_LOG(ret,"mdd=%s, mode=%d\n",mdd_path,mode); return ret == -1 ? -errno : ret; } +/** + * Create and open a file + * + * If the file does not exist, first create it with the specified + * mode, and then open it. + * + * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the mknod() and open() methods + * will be called instead. +**/ static int pifs_create(const char *path, mode_t mode, struct fuse_file_info *info) { - FULL_PATH(path); - int ret = creat(full_path, mode); + MDD_PATH(path); + int ret = creat(mdd_path, mode); + FUSE_LOG(ret,"mdd=%s, mode=%o\n",mdd_path,mode); info->fh = ret; return ret == -1 ? -errno : 0; } -static int pifs_ftruncate(const char *path, off_t length, - struct fuse_file_info *info) -{ - int ret = ftruncate(info->fh, length * 2); - return ret == -1 ? -errno : ret; -} - -static int pifs_fgetattr(const char *path, struct stat *buf, - struct fuse_file_info *info) -{ - int ret = fstat(info->fh, buf); - return ret == -1 ? -errno : ret; -} - +/** + * Perform POSIX file locking operation + * + * The cmd argument will be either F_GETLK, F_SETLK or F_SETLKW. + * + * For the meaning of fields in 'struct flock' see the man page + * for fcntl(2). The l_whence field will always be set to + * SEEK_SET. + * + * For checking lock ownership, the 'fuse_file_info->owner' + * argument must be used. + * + * For F_GETLK operation, the library will first check currently + * held locks, and if a conflicting lock is found it will return + * information without calling this method. This ensures, that + * for local locks the l_pid field is correctly filled in. The + * results may not be accurate in case of race conditions and in + * the presence of hard links, but it's unlikely that an + * application would rely on accurate GETLK results in these + * cases. If a conflicting lock is not found, this method will be + * called, and the filesystem may fill out l_pid by a meaningful + * value, or it may leave this field zero. + * + * For F_SETLK and F_SETLKW the l_pid field will be set to the pid + * of the process performing the locking operation. + * + * Note: if this method is not implemented, the kernel will still + * allow file locking to work locally. Hence it is only + * interesting for network filesystems and similar. + * + * struct flock { + * ... + * short l_type; * Type of lock: F_RDLCK, + * F_WRLCK, F_UNLCK + * short l_whence; * How to interpret l_start: + * SEEK_SET, SEEK_CUR, SEEK_END + * off_t l_start; * Starting offset for lock + * off_t l_len; * Number of bytes to lock + * pid_t l_pid; * PID of process blocking our lock + * (F_GETLK only) + * ... + * }; + * + * F_SETLK (struct flock *) + * Acquire a lock (when l_type is F_RDLCK or F_WRLCK) or release + * a lock (when l_type is F_UNLCK) on the bytes specified by the + * l_whence, l_start, and l_len fields of lock. If a conflicting + * lock is held by another process, this call returns -1 and sets + * errno to EACCES or EAGAIN. + * F_SETLKW (struct flock *) + * As for F_SETLK, but if a conflicting lock is held on the file, + * then wait for that lock to be released. If a signal is caught + * while waiting, then the call is interrupted and (after the + * signal handler has returned) returns immediately (with return + * value -1 and errno set to EINTR; see signal(7)). + * F_GETLK (struct flock *) + * On input to this call, lock describes a lock we would like to + * place on the file. If the lock could be placed, fcntl() does + * not actually place it, but returns F_UNLCK in the l_type field + * of lock and leaves the other fields of the structure unchanged. + * If one or more incompatible locks would prevent this lock being + * placed, then fcntl() returns details about one of these locks + * in the l_type, l_whence, l_start, and l_len fields of lock and + * sets l_pid to be the PID of the process holding that lock. +**/ static int pifs_lock(const char *path, struct fuse_file_info *info, int cmd, struct flock *lock) { + MDD_PATH(path); int ret = fcntl(info->fh, cmd, lock); + FUSE_LOG(ret,"mdd=%s, cmd=%d(%s), lock=%d(%s)\n",mdd_path, + cmd,(cmd==F_GETLK?"F_GETLK":(cmd==F_SETLK?"F_SETLK":"F_SETLKW")), + lock->l_type,(lock->l_type == F_RDLCK? "F_RDLCK":(lock->l_type == F_WRLCK?"F_WRLCK":"F_UNLCK"))); return ret == -1 ? -errno : ret; } -static int pifs_utimens(const char *path, const struct timespec times[2]) +/** + * Change the access and modification times of a file with + * nanosecond resolution + * + * This supersedes the old utime() interface. New applications + * should use this. + * + * `fi` will always be NULL if the file is not currently open, but + * may also be NULL if the file is open. + * + * See the utimensat(2) man page for details. +**/ +static int pifs_utimens(const char *path, const struct timespec times[2], struct fuse_file_info *info) { + MDD_PATH(path); DIR *dir = opendir(options.mdd); if (!dir) { + FUSE_LOG(-1,"mdd=%s\n",options.mdd); return -errno; } int ret = utimensat(dirfd(dir), basename((char *) path), times, 0); + FUSE_LOG(ret,"mdd=%s\n",mdd_path); closedir(dir); return ret == -1 ? -errno : ret; } @@ -386,33 +804,48 @@ static struct fuse_operations pifs_ops = { .chmod = pifs_chmod, .chown = pifs_chown, .truncate = pifs_truncate, - .utime = pifs_utime, + .utimens = pifs_utimens, .open = pifs_open, .read = pifs_read, .write = pifs_write, .statfs = pifs_statfs, .release = pifs_release, .fsync = pifs_fsync, +#ifdef HAVE_SETXATTR .setxattr = pifs_setxattr, .getxattr = pifs_getxattr, .listxattr = pifs_listxattr, .removexattr = pifs_removexattr, +#endif // HAVE_SETXATTR .opendir = pifs_opendir, .readdir = pifs_readdir, .releasedir = pifs_releasedir, .fsyncdir = pifs_fsyncdir, .access = pifs_access, .create = pifs_create, - .ftruncate = pifs_ftruncate, - .fgetattr = pifs_fgetattr, .lock = pifs_lock, - .utimens = pifs_utimens, - .flag_nullpath_ok = 1, }; +static void pifs_log(enum fuse_log_level level, const char *fmt, va_list ap) +{ + FILE *fp = fopen(options.log, "a"); + vfprintf(fp, fmt, ap); + fclose(fp); +} + +static void usage(const char *progname) +{ + printf("Usage: %s [options] \n" + "pifs options:\n" + " -o log= log file to trace fuse calls\n" + " -o mdd= metadata directory to store ipfs hashes\n" + "\n", progname); +} + int main (int argc, char *argv[]) { int ret; + FILE *fp; struct fuse_args args = FUSE_ARGS_INIT(argc, argv); memset(&options, 0, sizeof(struct options)); @@ -420,20 +853,66 @@ int main (int argc, char *argv[]) return -1; } - if (!options.mdd) { - fprintf(stderr, - "%s: Metadata directory must be specified with -o mdd=\n", - argv[0]); - return -1; - } + if (options.help) { + usage(argv[0]); + assert(fuse_opt_add_arg(&args, "--help") == 0); + args.argv[0][0] = '\0'; + } else if (options.version) { + fprintf(stdout, "pifs version %s\n", PIFS_VERSION); + return 0; + } else { + + options.dir = args.argv[1]; + if (!options.mdd) { + fprintf(stderr, + "%s: Metadata directory must be specified with -o mdd=\n", + argv[0]); + return 1; + } + + if (access(options.mdd, R_OK | W_OK | X_OK) == -1) { + fprintf(stderr, "%s: Cannot access metadata directory '%s': %s\n", + argv[0], options.mdd, strerror(errno)); + return 1; + } + + if (options.log != NULL) { + if ((fp = fopen(options.log, "w")) == NULL) { + fprintf(stderr, "%s: Cannot write to log file '%s': %s\n", + argv[0], options.log, strerror(errno)); + return 1; + } + fclose(fp); + fuse_set_log_func((fuse_log_func_t) pifs_log); + FUSE_LOG(0,"bin=%s, dir=%s, mdd=%s, log=%s\n", args.argv[0], options.dir, options.mdd ,options.log); + } + + if (options.dir == NULL) { + fprintf(stderr, "%s: No mountpoint specified\n", argv[0]); + return 2; + } else if (strcmp(options.dir,"-o") == 0) { + fprintf(stderr, "%s: Invalid option argument '%s'\n", + argv[0], args.argv[2]); + return 3; + } else if (access(options.dir, W_OK | X_OK) == -1) { + fprintf(stderr, "%s: Cannot mount directory '%s': %s\n", + argv[0], options.dir, strerror(errno)); + return 4; + } - if (access(options.mdd, R_OK | W_OK | X_OK) == -1) { - fprintf(stderr, "%s: Cannot access metadata directory '%s': %s\n", - argv[0], options.mdd, strerror(errno)); - return -1; } ret = fuse_main(args.argc, args.argv, &pifs_ops, NULL); +/* The following error codes may be returned from fuse_main(): + * 1: Invalid option arguments + * 2: No mount point specified + * 3: FUSE setup failed + * 4: Mounting failed + * 5: Failed to daemonize (detach from session) + * 6: Failed to set up signal handlers + * 7: An error occurred during the life of the file system + * @return 0 on success, nonzero on failure + */ fuse_opt_free_args(&args); return ret; }