diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..872d38f0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +*.class + +# Package Files # +*.jar +*.war +*.ear + +# Eclipse Settings Files # +.settings +.project +.classpath + +# Maven # +target/ +pom.xml.versionsBackup + +# IntelliJ Settings Files (https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems) # +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries +.idea/**/libraries/ +*.iml diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..4b48d0d5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "libfuse"] + path = libfuse + url = https://github.com/libfuse/libfuse.git + branch = fuse-2_9_bugfix diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..5c98b428 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,2 @@ +# Default ignored files +/workspace.xml \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..8c8e8619 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..318711de --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,14 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations/HelloFileSystem.xml b/.idea/runConfigurations/HelloFileSystem.xml new file mode 100644 index 00000000..3ce482d6 --- /dev/null +++ b/.idea/runConfigurations/HelloFileSystem.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/bindings/pom.xml b/bindings/pom.xml new file mode 100644 index 00000000..96ec66a9 --- /dev/null +++ b/bindings/pom.xml @@ -0,0 +1,35 @@ + + + + fuse-panama + de.skymatic + 0.1.0-SNAPSHOT + + 4.0.0 + + fuse-panama-bindings + + + + de.skymatic + fuse + 1.0 + system + ${basedir}/../ffi/fuse.jar + + + + org.slf4j + slf4j-api + 1.7.29 + + + org.slf4j + slf4j-simple + 1.7.29 + + + + \ No newline at end of file diff --git a/bindings/src/main/java/de/skymatic/fusepanama/AbstractFuseFileSystem.java b/bindings/src/main/java/de/skymatic/fusepanama/AbstractFuseFileSystem.java new file mode 100644 index 00000000..3eb48a95 --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/AbstractFuseFileSystem.java @@ -0,0 +1,112 @@ +package de.skymatic.fusepanama; + +import java.foreign.Scope; +import java.foreign.memory.Callback; +import java.foreign.memory.Pointer; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +import com.github.libfuse.fuse_common_h; +import com.github.libfuse.fuse_h; +import com.github.libfuse.fuse_lib; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractFuseFileSystem implements AutoCloseable, FuseOperations { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractFuseFileSystem.class); + + protected final Scope scope; + protected final fuse_h.fuse_operations fuseOperations; + private final Set notImplementedMethods; + private Pointer fuseHandle; + + public AbstractFuseFileSystem() { + this.scope = Scope.globalScope().fork(); + this.fuseOperations = scope.allocateStruct(fuse_h.fuse_operations.class); + this.notImplementedMethods = Arrays.stream(getClass().getMethods()) + .filter(method -> method.getAnnotation(NotImplemented.class) != null) + .map(Method::getName) + .collect(Collectors.toSet()); + fuseOperations.getattr$set(allocateIfImplemented("getattr", this::getattr)); + fuseOperations.readlink$set(allocateIfImplemented("readlink", this::readlink)); + fuseOperations.getdir$set(allocateIfImplemented("getdir", this::getdir)); + fuseOperations.mknod$set(allocateIfImplemented("mknod", this::mknod)); + fuseOperations.mkdir$set(allocateIfImplemented("mkdir", this::mkdir)); + fuseOperations.unlink$set(allocateIfImplemented("unlink", this::unlink)); + fuseOperations.rmdir$set(allocateIfImplemented("rmdir", this::rmdir)); + fuseOperations.symlink$set(allocateIfImplemented("symlink", this::symlink)); + fuseOperations.rename$set(allocateIfImplemented("rename", this::rename)); + fuseOperations.link$set(allocateIfImplemented("link", this::link)); + fuseOperations.chmod$set(allocateIfImplemented("chmod", this::chmod)); + fuseOperations.chown$set(allocateIfImplemented("chown", this::chown)); + fuseOperations.truncate$set(allocateIfImplemented("truncate", this::truncate)); + fuseOperations.utime$set(allocateIfImplemented("utime", this::utime)); + fuseOperations.open$set(allocateIfImplemented("open", this::open)); + fuseOperations.read$set(allocateIfImplemented("read", this::read)); + fuseOperations.write$set(allocateIfImplemented("write", this::write)); + fuseOperations.statfs$set(allocateIfImplemented("statfs", this::statfs)); + fuseOperations.flush$set(allocateIfImplemented("flush", this::flush)); + fuseOperations.release$set(allocateIfImplemented("release", this::release)); + fuseOperations.fsync$set(allocateIfImplemented("fsync", this::fsync)); + fuseOperations.setxattr$set(allocateIfImplemented("setxattr", this::setxattr)); + fuseOperations.getxattr$set(allocateIfImplemented("getxattr", this::getxattr)); + fuseOperations.listxattr$set(allocateIfImplemented("listxattr", this::listxattr)); + fuseOperations.removexattr$set(allocateIfImplemented("removexattr", this::removexattr)); + fuseOperations.opendir$set(allocateIfImplemented("opendir", this::opendir)); + fuseOperations.readdir$set(allocateIfImplemented("readdir", this::readdir)); + fuseOperations.releasedir$set(allocateIfImplemented("releasedir", this::releasedir)); + fuseOperations.fsyncdir$set(allocateIfImplemented("fsyncdir", this::fsyncdir)); + fuseOperations.init$set(allocateIfImplemented("init", this::init)); + fuseOperations.destroy$set(allocateIfImplemented("destroy", this::destroy)); + fuseOperations.access$set(allocateIfImplemented("access", this::access)); + fuseOperations.create$set(allocateIfImplemented("create", this::create)); + fuseOperations.ftruncate$set(allocateIfImplemented("ftruncate", this::ftruncate)); + fuseOperations.fgetattr$set(allocateIfImplemented("fgetattr", this::fgetattr)); + fuseOperations.lock$set(allocateIfImplemented("lock", this::lock)); + fuseOperations.utimens$set(allocateIfImplemented("utimens", this::utimens)); + fuseOperations.bmap$set(allocateIfImplemented("bmap", this::bmap)); + fuseOperations.ioctl$set(allocateIfImplemented("ioctl", this::ioctl)); + fuseOperations.poll$set(allocateIfImplemented("poll", this::poll)); + fuseOperations.write_buf$set(allocateIfImplemented("writeBuf", this::writeBuf)); + fuseOperations.read_buf$set(allocateIfImplemented("readBuf", this::readBuf)); + fuseOperations.flock$set(allocateIfImplemented("flock", this::flock)); + fuseOperations.fallocate$set(allocateIfImplemented("fallocate", this::fallocate)); + } + + private Callback allocateIfImplemented(String methodName, T callback) { + if (notImplementedMethods.contains(methodName)) { + return Callback.ofNull(); + } else { + return scope.allocateCallback(callback); + } + } + + @Override + public Pointer init(Pointer conn) { + this.fuseHandle = fuse_lib.fuse_get_context().get().fuse$get(); + LOG.debug("init()"); + return Pointer.ofNull(); + } + + protected Pointer getFuseHandle() { + return fuseHandle; + } + + public fuse_h.fuse_operations getFuseOperations() { + return fuseOperations; + } + + @Override + public void destroy(Pointer pointer) { + this.fuseHandle = Pointer.ofNull(); + LOG.debug("destroy()"); + } + + @Override + public void close() { + scope.close(); + } +} diff --git a/bindings/src/main/java/de/skymatic/fusepanama/Errno.java b/bindings/src/main/java/de/skymatic/fusepanama/Errno.java new file mode 100644 index 00000000..7cf582ac --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/Errno.java @@ -0,0 +1,21 @@ +package de.skymatic.fusepanama; + +/** + * Error codes extracted from + * errno.h (linux) + * and errno.h (darwin) + */ +public interface Errno { + + boolean IS_MAC = System.getProperty("os.name").toLowerCase().contains("mac"); + + /** + * No such file or directory + */ + int ENOENT = 2; + + /** + * Invalid system call number + */ + int ENOSYS = IS_MAC ? 78 : 38; +} diff --git a/bindings/src/main/java/de/skymatic/fusepanama/Fuse.java b/bindings/src/main/java/de/skymatic/fusepanama/Fuse.java new file mode 100644 index 00000000..6a4dbf2e --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/Fuse.java @@ -0,0 +1,42 @@ +package de.skymatic.fusepanama; + +import java.foreign.Scope; +import java.foreign.memory.Pointer; +import java.util.concurrent.ThreadLocalRandom; + +import com.github.libfuse.fuse_lib; +import com.github.libfuse.fuse_opt_h; +import com.github.libfuse.fuse_opt_lib; + +public class Fuse { + + /** + * Mounts the given file system at the given mount point. + *

+ * This method blocks until unmounted. + */ + public static void mount(AbstractFuseFileSystem fs, String mountPoint, boolean debug) { + try (Scope scope = Scope.globalScope().fork()) { + var fuse_args = scope.allocateStruct(fuse_opt_h.fuse_args.class); + fuse_args.argc$set(0); + fuse_args.argv$set(Pointer.ofNull()); + fuse_args.allocated$set(0); + fuse_opt_lib.fuse_opt_add_arg(fuse_args.ptr(), scope.allocateCString("fusefs-" + ThreadLocalRandom.current().nextInt())); + fuse_opt_lib.fuse_opt_add_arg(fuse_args.ptr(), scope.allocateCString("-f")); + if (debug) { + fuse_opt_lib.fuse_opt_add_arg(fuse_args.ptr(), scope.allocateCString("-d")); + } + fuse_opt_lib.fuse_opt_add_arg(fuse_args.ptr(), scope.allocateCString(mountPoint)); + + int result = fuse_lib.fuse_main_real(fuse_args.argc$get(), fuse_args.argv$get(), fs.fuseOperations.ptr(), FuseOperations.SIZEOF_FUSE_OPERATIONS, Pointer.ofNull()); + + fuse_opt_lib.fuse_opt_free_args(fuse_args.ptr()); + + if (result != 0) { + throw new RuntimeException("mount failed with return code " + result); + } + } + } + + +} diff --git a/bindings/src/main/java/de/skymatic/fusepanama/FuseOperations.java b/bindings/src/main/java/de/skymatic/fusepanama/FuseOperations.java new file mode 100644 index 00000000..82635baa --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/FuseOperations.java @@ -0,0 +1,523 @@ +package de.skymatic.fusepanama; + +import java.foreign.memory.Callback; +import java.foreign.memory.LayoutType; +import java.foreign.memory.Pointer; + +import com.github.libfuse.fuse_common_h; +import com.github.libfuse.fuse_h; +import usr.include.sys._types._timespec_h; +import usr.include.sys.fcntl_h; +import usr.include.sys.stat_h; +import usr.include.sys.statvfs_h; +import usr.include.utime_h; + +public interface FuseOperations { + + long SIZEOF_FUSE_OPERATIONS = LayoutType.ofStruct(fuse_h.fuse_operations.class).bytesSize(); + + /** + * 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. + */ + int getattr(Pointer path, Pointer buf); + + /** + * 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. + */ + int readlink(Pointer path, Pointer buf, long len); + + /** + * @deprecated use {@link #readdir(Pointer, Pointer, Callback, long, Pointer) readdir()} instead + */ + int getdir(Pointer path, Pointer fuse_dirhandlePointer, Callback fi2Callback); + + /** + * 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. + */ + int mknod(Pointer path, short mode, int rdev); + + /** + * 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 + */ + int mkdir(Pointer path, short mode); + + /** + * Remove a file + */ + int unlink(Pointer path); + + /** + * Remove a directory + */ + int rmdir(Pointer path); + + /** + * Create a symbolic link + */ + int symlink(Pointer linkname, Pointer target); + + /** + * Rename a file + */ + int rename(Pointer oldpath, Pointer newpath); + + /** + * Create a hard link to a file + */ + int link(Pointer linkname, Pointer target); + + /** + * Change the permission bits of a file + */ + int chmod(Pointer path, short mode); + + /** + * Change the owner and group of a file + */ + int chown(Pointer path, int uid, int gid); + + /** + * Change the size of a file + */ + int truncate(Pointer path, long size); + + /** + * Change the access and/or modification times of a file + * + * @deprecated use {@link #utimens(Pointer, Pointer) utimens()} instead. + */ + int utime(Pointer path, Pointer utimbufPointer); + + /** + * File open operation + *

+ * No creation (O_CREAT, O_EXCL) and by default also no + * truncation (O_TRUNC) flags will be passed to open(). If an + * application specifies O_TRUNC, fuse first calls truncate() + * and then open(). Only if 'atomic_o_trunc' has been + * specified and kernel version is 2.6.24 or later, O_TRUNC is + * passed on to open. + *

+ * Unless the 'default_permissions' mount option is given, + * open should check if the operation is permitted for the + * given flags. Optionally open may also return an arbitrary + * filehandle in the fuse_file_info structure, which will be + * passed to all file operations. + *

+ * Changed in version 2.2 + */ + int open(Pointer path, Pointer fi); + + /** + * 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. + *

+ * Changed in version 2.2 + */ + int read(Pointer path, Pointer buf, long size, long offset, Pointer fi); + + /** + * 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). + *

+ * Changed in version 2.2 + */ + int write(Pointer path, Pointer buf, long size, long offset, Pointer fi); + + /** + * Get file system statistics + *

+ * The 'f_frsize', 'f_favail', 'f_fsid' and 'f_flag' fields are ignored + *

+ * Replaced 'struct statfs' parameter with 'struct statvfs' in + * version 2.5 + */ + int statfs(Pointer path, Pointer buf); + + /** + * 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. So if a + * filesystem wants to return write errors in close() and the file + * has cached dirty data, this is a good place to write back data + * and return any errors. Since many applications ignore close() + * errors this is not always useful. + *

+ * NOTE: The flush() method may be called more than once for each + * open(). This happens if more than one file descriptor refers + * to an opened file 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 always be called + * after some writes, or that if will be called at all. + *

+ * Changed in version 2.2 + */ + int flush(Pointer path, Pointer fi); + + /** + * 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 descriptor. 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. + *

+ * Changed in version 2.2 + */ + int release(Pointer path, Pointer fi); + + /** + * Synchronize file contents + *

+ * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data. + *

+ * Changed in version 2.2 + */ + int fsync(Pointer path, int datasync, Pointer fi); + + /** + * Set extended attributes + */ + int setxattr(Pointer path, Pointer name, Pointer value, long size, int flags); + + /** + * Get extended attributes + */ + int getxattr(Pointer path, Pointer name, Pointer value, long size); + + /** + * List extended attributes + */ + int listxattr(Pointer path, Pointer list, long size); + + /** + * Remove extended attributes + */ + int removexattr(Pointer path, Pointer name); + + /** + * 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, closedir and fsyncdir. + *

+ * Introduced in version 2.3 + */ + int opendir(Pointer path, Pointer fi); + + /** + * Read directory + *

+ * This supersedes the old getdir() interface. New applications + * should use this. + *

+ * 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. This + * works just like the old getdir() method. + *

+ * 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'. + *

+ * Introduced in version 2.3 + */ + int readdir(Pointer path, Pointer buf, Callback filler, long offset, Pointer fi); + + /** + * Release directory + *

+ * Introduced in version 2.3 + */ + int releasedir(Pointer path, Pointer fi); + + /** + * Synchronize directory contents + *

+ * If the datasync parameter is non-zero, then only the user data + * should be flushed, not the meta data + *

+ * Introduced in version 2.3 + */ + int fsyncdir(Pointer path, int datasync, Pointer fi); + + /** + * Initialize filesystem + *

+ * The return value will passed in the private_data field of + * fuse_context to all file operations and as a parameter to the + * destroy() method. + *

+ * Introduced in version 2.3 + * Changed in version 2.6 + */ + Pointer init(Pointer conn); + + /** + * Clean up filesystem + *

+ * Called on filesystem exit. + *

+ * Introduced in version 2.3 + */ + void destroy(Pointer pointer); + + /** + * 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 + *

+ * Introduced in version 2.5 + */ + int access(Pointer path, int mask); + + /** + * 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. + *

+ * Introduced in version 2.5 + */ + int create(Pointer path, short mode, Pointer fi); + + /** + * Change the size of an open file + *

+ * This method is called instead of the truncate() method if the + * truncation was invoked from an ftruncate() system call. + *

+ * If this method is not implemented or under Linux kernel + * versions earlier than 2.6.15, the truncate() method will be + * called instead. + *

+ * Introduced in version 2.5 + */ + int ftruncate(Pointer path, long size, Pointer fi); + + /** + * Get attributes from an open file + *

+ * This method is called instead of the getattr() method if the + * file information is available. + *

+ * Currently this is only called after the create() method if that + * is implemented (see above). Later it may be called for + * invocations of fstat() too. + *

+ * Introduced in version 2.5 + */ + int fgetattr(Pointer path, Pointer buf, Pointer fi); + + /** + * 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. + *

+ * Introduced in version 2.6 + */ + int lock(Pointer path, Pointer fi, int cmd, Pointer lock); + + /** + * Change the access and modification times of a file with + * nanosecond resolution + *

+ * This supersedes the old utime() interface. New applications + * should use this. + *

+ * See the utimensat(2) man page for details. + *

+ * Introduced in version 2.6 + */ + int utimens(Pointer path, Pointer<_timespec_h.timespec> tv); + + /** + * Map block index within file to block index within device + *

+ * Note: This makes sense only for block device backed filesystems + * mounted with the 'blkdev' option + *

+ * Introduced in version 2.6 + */ + int bmap(Pointer path, long blocksize, Pointer idx); + + /** + * Ioctl + *

+ * flags will have FUSE_IOCTL_COMPAT set for 32bit ioctls in + * 64bit environment. The size and direction of data is + * determined by _IOC_*() decoding of cmd. For _IOC_NONE, + * data will be NULL, for _IOC_WRITE data is out area, for + * _IOC_READ in area and if both are set in/out area. In all + * non-NULL cases, the area is of _IOC_SIZE(cmd) bytes. + *

+ * If flags has FUSE_IOCTL_DIR then the fuse_file_info refers to a + * directory file handle. + *

+ * Introduced in version 2.8 + */ + int ioctl(Pointer path, int cmd, Pointer arg, Pointer fi, int flags, Pointer data); + + /** + * Poll for IO readiness events + *

+ * Note: If ph is non-NULL, the client should notify + * when IO readiness events occur by calling + * fuse_notify_poll() with the specified ph. + *

+ * Regardless of the number of times poll with a non-NULL ph + * is received, single notification is enough to clear all. + * Notifying more times incurs overhead but doesn't harm + * correctness. + *

+ * The callee is responsible for destroying ph with + * fuse_pollhandle_destroy() when no longer in use. + *

+ * Introduced in version 2.8 + */ + int poll(Pointer path, Pointer fi, Pointer ph, Pointer reventsp); + + /** + * Write contents of buffer to an open file + *

+ * Similar to the write() method, but data is supplied in a + * generic buffer. Use fuse_buf_copy() to transfer data to + * the destination. + *

+ * Introduced in version 2.9 + */ + int writeBuf(Pointer path, Pointer buf, long offset, Pointer fi); + + /** + * Store data from an open file in a buffer + *

+ * Similar to the read() method, but data is stored and + * returned in a generic buffer. + *

+ * No actual copying of data has to take place, the source + * file descriptor may simply be stored in the buffer for + * later data transfer. + *

+ * The buffer must be allocated dynamically and stored at the + * location pointed to by bufp. If the buffer contains memory + * regions, they too must be allocated using malloc(). The + * allocated memory will be freed by the caller. + *

+ * Introduced in version 2.9 + */ + int readBuf(Pointer path, Pointer> bufp, long size, long offset, Pointer fi); + + /** + * Perform BSD file locking operation + *

+ * The op argument will be either LOCK_SH, LOCK_EX or LOCK_UN + *

+ * Nonblocking requests will be indicated by ORing LOCK_NB to + * the above operations + *

+ * For more information see the flock(2) manual page. + *

+ * Additionally fi->owner will be set to a value unique to + * this open file. This same value will be supplied to + * ->release() when the file is released. + *

+ * 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. + *

+ * Introduced in version 2.9 + */ + int flock(Pointer path, Pointer fi, int op); + + /** + * Allocates space for an open file + *

+ * This function ensures that required space is allocated for specified + * file. If this function returns success then any subsequent write + * request to specified range is guaranteed not to fail because of lack + * of space on the file system media. + *

+ * Introduced in version 2.9.1 + */ + int fallocate(Pointer path, int mode, long offset, long length, Pointer fi); +} diff --git a/bindings/src/main/java/de/skymatic/fusepanama/NotImplemented.java b/bindings/src/main/java/de/skymatic/fusepanama/NotImplemented.java new file mode 100644 index 00000000..71ef1906 --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/NotImplemented.java @@ -0,0 +1,13 @@ +package de.skymatic.fusepanama; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(value = ElementType.METHOD) +public @interface NotImplemented { +} diff --git a/bindings/src/main/java/de/skymatic/fusepanama/StubFuseFileSystem.java b/bindings/src/main/java/de/skymatic/fusepanama/StubFuseFileSystem.java new file mode 100644 index 00000000..1cfd37ca --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/StubFuseFileSystem.java @@ -0,0 +1,273 @@ +package de.skymatic.fusepanama; + +import java.foreign.memory.Callback; +import java.foreign.memory.Pointer; + +import com.github.libfuse.fuse_common_h; +import com.github.libfuse.fuse_h; +import usr.include.sys._types._timespec_h; +import usr.include.sys.fcntl_h; +import usr.include.sys.stat_h; +import usr.include.sys.statvfs_h; +import usr.include.utime_h; + +/** + * Implements all fuse operations with a default implementation, that returns -{@link Errno#ENOSYS}, + * with the exception of {@link #open(Pointer, Pointer) open}, {@link #release(Pointer, Pointer) release}, + * {@link #opendir(Pointer, Pointer) opendir}, {@link #releasedir(Pointer, Pointer) releasedir} + * and {@link #statfs(Pointer, Pointer) statfs}, which return 0. + */ +public class StubFuseFileSystem extends AbstractFuseFileSystem { + + @Override + @NotImplemented + public int getattr(Pointer path, Pointer buf) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int readlink(Pointer path, Pointer buf, long len) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int getdir(Pointer path, Pointer fuse_dirhandlePointer, Callback fi2Callback) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int mknod(Pointer path, short mode, int rdev) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int mkdir(Pointer path, short mode) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int unlink(Pointer path) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int rmdir(Pointer path) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int symlink(Pointer linkname, Pointer target) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int rename(Pointer oldpath, Pointer newpath) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int link(Pointer linkname, Pointer target) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int chmod(Pointer path, short mode) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int chown(Pointer path, int uid, int gid) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int truncate(Pointer path, long size) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int utime(Pointer path, Pointer utimbufPointer) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int open(Pointer path, Pointer fi) { + return 0; + } + + @Override + @NotImplemented + public int read(Pointer path, Pointer buf, long size, long offset, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int write(Pointer path, Pointer buf, long size, long offset, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int statfs(Pointer path, Pointer buf) { + return 0; + } + + @Override + @NotImplemented + public int flush(Pointer path, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int release(Pointer path, Pointer fi) { + return 0; + } + + @Override + @NotImplemented + public int fsync(Pointer path, int datasync, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int setxattr(Pointer path, Pointer name, Pointer value, long size, int flags) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int getxattr(Pointer path, Pointer name, Pointer value, long size) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int listxattr(Pointer path, Pointer list, long size) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int removexattr(Pointer path, Pointer name) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int opendir(Pointer path, Pointer fi) { + return 0; + } + + @Override + @NotImplemented + public int readdir(Pointer path, Pointer buf, Callback filler, long offset, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int releasedir(Pointer path, Pointer fi) { + return 0; + } + + @Override + @NotImplemented + public int fsyncdir(Pointer path, int datasync, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int access(Pointer path, int mask) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int create(Pointer path, short mode, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int ftruncate(Pointer path, long size, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int fgetattr(Pointer path, Pointer buf, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int lock(Pointer path, Pointer fi, int cmd, Pointer lock) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int utimens(Pointer path, Pointer<_timespec_h.timespec> tv) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int bmap(Pointer path, long blocksize, Pointer idx) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int ioctl(Pointer path, int cmd, Pointer arg, Pointer fi, int flags, Pointer data) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int poll(Pointer path, Pointer fi, Pointer ph, Pointer reventsp) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int writeBuf(Pointer path, Pointer buf, long offset, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int readBuf(Pointer path, Pointer> bufp, long size, long offset, Pointer fi) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int flock(Pointer path, Pointer fi, int op) { + return -Errno.ENOSYS; + } + + @Override + @NotImplemented + public int fallocate(Pointer path, int mode, long offset, long length, Pointer fi) { + return -Errno.ENOSYS; + } +} diff --git a/bindings/src/main/java/de/skymatic/fusepanama/examples/HelloFileSystem.java b/bindings/src/main/java/de/skymatic/fusepanama/examples/HelloFileSystem.java new file mode 100644 index 00000000..a3c6bc0f --- /dev/null +++ b/bindings/src/main/java/de/skymatic/fusepanama/examples/HelloFileSystem.java @@ -0,0 +1,94 @@ +package de.skymatic.fusepanama.examples; + +import java.foreign.Scope; +import java.foreign.memory.Callback; +import java.foreign.memory.Pointer; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import com.github.libfuse.fuse_common_h; +import com.github.libfuse.fuse_h; +import de.skymatic.fusepanama.Errno; +import de.skymatic.fusepanama.Fuse; +import de.skymatic.fusepanama.StubFuseFileSystem; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import usr.include.sys.stat_h; + +public class HelloFileSystem extends StubFuseFileSystem { + + private static final int S_IFDIR = 0040000; + private static final int S_IFREG = 0100000; + + private static final Logger LOG = LoggerFactory.getLogger(HelloFileSystem.class); + + public static final String HELLO_PATH = "/hello"; + public static final String HELLO_STR = "Hello World!"; + + public static void main(String[] args) throws IOException { + Path mountPoint = Files.createTempDirectory("mnt"); + LOG.info("mounting at {}. Unmount to terminate this process.", mountPoint); + Fuse.mount(new HelloFileSystem(), mountPoint.toString(), false); + } + + @Override + public int getattr(Pointer path, Pointer buf) { + String p = Pointer.toString(path); + LOG.debug("getattr() {}", p); + if ("/".equals(p)) { + buf.get().st_mode$set((short) (S_IFDIR | 0755)); + buf.get().st_nlink$set((short) 2); + return 0; + } else if (HELLO_PATH.equals(p)) { + buf.get().st_mode$set((short) (S_IFREG | 0444)); + buf.get().st_nlink$set((short) 1); + buf.get().st_size$set(HELLO_STR.getBytes().length); + return 0; + } else { + return -Errno.ENOENT; + } + } + + @Override + public int readdir(Pointer path, Pointer buf, Callback filler, long offset, Pointer fi) { + String p = Pointer.toString(path); + LOG.debug("readdir() {}", p); + try (Scope scope = fi.scope().fork()) { + filler.asFunction().fn(buf, scope.allocateCString("."), Pointer.ofNull(), 0); + filler.asFunction().fn(buf, scope.allocateCString(".."), Pointer.ofNull(), 0); + filler.asFunction().fn(buf, scope.allocateCString(HELLO_PATH.substring(1)), Pointer.ofNull(), 0); + return 0; + } + } + + @Override + public int open(Pointer path, Pointer fi) { + String p = Pointer.toString(path); + LOG.debug("open() {}", p); + if (!HELLO_PATH.equals(p)) { + return -Errno.ENOENT; + } + return 0; + } + + @Override + public int read(Pointer path, Pointer buf, long size, long offset, Pointer fi) { + String p = Pointer.toString(path); + LOG.debug("read() {}", p); + if (!HELLO_PATH.equals(p)) { + return -Errno.ENOENT; + } + + ByteBuffer content = StandardCharsets.UTF_8.encode(HELLO_STR); + content.position((int) Math.min(content.capacity(), offset)); + long numBytes = Math.min(size, content.remaining()); + + Pointer.copy(Pointer.fromByteBuffer(content), buf, numBytes); + + return (int) numBytes; + } + +} diff --git a/ffi/pom.xml b/ffi/pom.xml new file mode 100644 index 00000000..c8f9f6e1 --- /dev/null +++ b/ffi/pom.xml @@ -0,0 +1,103 @@ + + + + fuse-panama + de.skymatic + 0.1.0-SNAPSHOT + + 4.0.0 + fuse-panama-ffi + pom + + + + macos + + + mac + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + + exec + + process-resources + + ${java.home}/bin/jextract + ${project.basedir} + + -L + /usr/local/lib + -l + osxfuse + -o + fuse.jar + -t + com.github.libfuse + -C-D_FILE_OFFSET_BITS=64 + -C-DFUSE_USE_VERSION=29 + ${project.basedir}/../libfuse/include/fuse.h + + + + + + + + + + + linux + + + unix + Linux + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.6.0 + + + + exec + + process-resources + + ${java.home}/bin/jextract + ${project.basedir} + + -L + /usr/local/lib + -l + fuse + -o + fuse.jar + -t + com.github.libfuse + -C-D_FILE_OFFSET_BITS=64 + -C-DFUSE_USE_VERSION=29 + ${project.basedir}/../libfuse/include/fuse.h + + + + + + + + + + + \ No newline at end of file diff --git a/libfuse b/libfuse new file mode 160000 index 00000000..df499bf1 --- /dev/null +++ b/libfuse @@ -0,0 +1 @@ +Subproject commit df499bf1ce634f6e67d4d366c4475d32143f00f0 diff --git a/pom.xml b/pom.xml new file mode 100644 index 00000000..13450d94 --- /dev/null +++ b/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + de.skymatic + fuse-panama + pom + 0.1.0-SNAPSHOT + + + + generate-libfuse + + + ffi/fuse.jar + + + + ffi + + + + default + + + ffi/fuse.jar + + + + bindings + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.0 + + 14 + UTF-8 + true + + + + + + \ No newline at end of file