From 158438c7b8906e663895fbb929886c30485dfba8 Mon Sep 17 00:00:00 2001 From: pron Date: Sat, 5 Sep 2015 18:13:33 +0300 Subject: [PATCH] Initial commit --- .gitignore | 16 + .project | 17 + LICENSE | 99 + README.md | 85 + build.gradle | 138 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52266 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++ gradlew.bat | 90 + run.sh | 6 + .../fuse/AbstractFuseFilesystem.java | 239 ++ .../fuse/AccessConstants.java | 19 + .../fuse/DirectoryFiller.java | 14 + .../fuse/DirectoryFillerImpl.java | 64 + .../co/paralleluniverse/fuse/ErrorCodes.java | 2501 +++++++++++++++++ .../co/paralleluniverse/fuse/Filesystem.java | 277 ++ .../java/co/paralleluniverse/fuse/Fuse.java | 210 ++ .../paralleluniverse/fuse/FuseBufFlags.java | 56 + .../paralleluniverse/fuse/FuseException.java | 11 + .../paralleluniverse/fuse/FuseFilesystem.java | 767 +++++ .../co/paralleluniverse/fuse/IoctlFlags.java | 39 + .../co/paralleluniverse/fuse/JNRUtil.java | 38 + .../java/co/paralleluniverse/fuse/LibDl.java | 9 + .../co/paralleluniverse/fuse/LibFuse.java | 44 + .../fuse/LoggedFuseFilesystem.java | 538 ++++ .../co/paralleluniverse/fuse/Platform.java | 99 + .../paralleluniverse/fuse/ProcessGobbler.java | 83 + .../co/paralleluniverse/fuse/StructFlock.java | 128 + .../paralleluniverse/fuse/StructFuseBuf.java | 73 + .../fuse/StructFuseBufvec.java | 66 + .../fuse/StructFuseConnInfo.java | 33 + .../fuse/StructFuseContext.java | 16 + .../fuse/StructFuseFileInfo.java | 218 ++ .../fuse/StructFuseOperations.java | 143 + .../fuse/StructFuseOperationsIfaces.java | 67 + .../fuse/StructFusePollHandle.java | 44 + .../co/paralleluniverse/fuse/StructStat.java | 301 ++ .../paralleluniverse/fuse/StructStatvfs.java | 187 ++ .../fuse/StructTimeBuffer.java | 107 + .../paralleluniverse/fuse/StructTimespec.java | 64 + .../co/paralleluniverse/fuse/TypeMode.java | 56 + .../paralleluniverse/fuse/XAttrConstants.java | 16 + .../co/paralleluniverse/fuse/XattrFiller.java | 36 + .../fuse/XattrListFiller.java | 68 + .../javafs/FuseFileSystemProvider.java | 803 ++++++ .../co/paralleluniverse/javafs/JavaFS.java | 50 + .../javafs/ReadOnlyFileSystem.java | 90 + .../javafs/ReadOnlyFileSystemProvider.java | 195 ++ .../jnr/ffi/provider/jffi/ClosureHelper.java | 81 + .../co/paralleluniverse/javafs/JFSTest.java | 77 + .../java/co/paralleluniverse/javafs/Main.java | 34 + .../co/paralleluniverse/javafs/ZipFS.java | 73 + 52 files changed, 8655 insertions(+) create mode 100644 .gitignore create mode 100644 .project create mode 100644 LICENSE create mode 100644 README.md create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100755 run.sh create mode 100644 src/main/java/co/paralleluniverse/fuse/AbstractFuseFilesystem.java create mode 100644 src/main/java/co/paralleluniverse/fuse/AccessConstants.java create mode 100644 src/main/java/co/paralleluniverse/fuse/DirectoryFiller.java create mode 100644 src/main/java/co/paralleluniverse/fuse/DirectoryFillerImpl.java create mode 100644 src/main/java/co/paralleluniverse/fuse/ErrorCodes.java create mode 100644 src/main/java/co/paralleluniverse/fuse/Filesystem.java create mode 100644 src/main/java/co/paralleluniverse/fuse/Fuse.java create mode 100644 src/main/java/co/paralleluniverse/fuse/FuseBufFlags.java create mode 100644 src/main/java/co/paralleluniverse/fuse/FuseException.java create mode 100644 src/main/java/co/paralleluniverse/fuse/FuseFilesystem.java create mode 100644 src/main/java/co/paralleluniverse/fuse/IoctlFlags.java create mode 100644 src/main/java/co/paralleluniverse/fuse/JNRUtil.java create mode 100644 src/main/java/co/paralleluniverse/fuse/LibDl.java create mode 100644 src/main/java/co/paralleluniverse/fuse/LibFuse.java create mode 100644 src/main/java/co/paralleluniverse/fuse/LoggedFuseFilesystem.java create mode 100644 src/main/java/co/paralleluniverse/fuse/Platform.java create mode 100644 src/main/java/co/paralleluniverse/fuse/ProcessGobbler.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFlock.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseBuf.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseBufvec.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseConnInfo.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseContext.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseFileInfo.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseOperations.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFuseOperationsIfaces.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructFusePollHandle.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructStat.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructStatvfs.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructTimeBuffer.java create mode 100644 src/main/java/co/paralleluniverse/fuse/StructTimespec.java create mode 100644 src/main/java/co/paralleluniverse/fuse/TypeMode.java create mode 100644 src/main/java/co/paralleluniverse/fuse/XAttrConstants.java create mode 100644 src/main/java/co/paralleluniverse/fuse/XattrFiller.java create mode 100644 src/main/java/co/paralleluniverse/fuse/XattrListFiller.java create mode 100644 src/main/java/co/paralleluniverse/javafs/FuseFileSystemProvider.java create mode 100644 src/main/java/co/paralleluniverse/javafs/JavaFS.java create mode 100644 src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystem.java create mode 100644 src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystemProvider.java create mode 100644 src/main/java/jnr/ffi/provider/jffi/ClosureHelper.java create mode 100644 src/test/java/co/paralleluniverse/javafs/JFSTest.java create mode 100644 src/test/java/co/paralleluniverse/javafs/Main.java create mode 100644 src/test/java/co/paralleluniverse/javafs/ZipFS.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..445ac6a --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +/dist/ +/out/ +_site/ +/docs/javadoc/ +.idea/ +*.iml +/artifacts +/nbproject/private/ +/.nb-gradle +/build +/*/build/ +.gradle/ +.nb-gradle-properties +Gemfile.lock +/push_docs.sh diff --git a/.project b/.project new file mode 100644 index 0000000..9466604 --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + fuse-jna + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..42d6125 --- /dev/null +++ b/LICENSE @@ -0,0 +1,99 @@ +Copyright (c) 2015 Parallel Universe +Copyright (c) 2012-2015 Etienne Perot. All rights reserved. +Copyright (c) 2015 Sergey Tselovalnikov + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--- + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY ETIENNE PEROT ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Etienne Perot. + +Copyright 2012 Etienne Perot. All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY ETIENNE PEROT ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those of the +authors and should not be interpreted as representing official policies, either expressed +or implied, of Etienne Perot. + +--- + +The MIT License (MIT) + +Copyright (c) 2015 Sergey Tselovalnikov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..2df9c73 --- /dev/null +++ b/README.md @@ -0,0 +1,85 @@ +# JavaFS +## Java filesystems as FUSE + +## Requirements + +Java 7 and up. + +Your OS must support FUSE or have it installed. + +## Usage + +The API consists of a single class with two methods: + +JavaFS.mount, which mounts a Java `FileSystem` as FUSE filesystem, and + +JavaFS.unmount, which unmounts a FUSE filesystem + +## Test + +``` +./run.sh [-r] [] +``` + +#### Compatibility + +* OS X with [MacFUSE]/[fuse4x]/[OSXFUSE] on Intel architectures +* Linux with [FUSE][Linux-Fuse] on Intel, PowerPC and ARM architectures +* FreeBSD with [FUSE][FreeBSD-Fuse] on Intel architectures + +## Project Information + +This is essentially a port of [fuse-jna], by Etienne Perot, from [JNA] to [JNR], +with some code copied from [jnr-fuse], by Sergey Tselovalnikov, made to work with the standard JDK [FileSystem] API. + +* Differences from [fuse-jna]: this project uses [JNR] rather than [JNA]. +* Differences from [jnr-fuse]: this project support Java 7 (jnr-fuse supports only Java 8), and more platforms (like Mac). +* Differences from both: rather than exposing a new, specific, Java FUSE API, this project uses the standard [FileSystem] API. + +## License + +``` +Copyright (c) 2015 Parallel Universe +Copyright (c) 2012-2015 Etienne Perot. All rights reserved. +Copyright (c) 2015 Sergey Tselovalnikov + +Redistribution and use in source and binary forms, with or without modification, are +permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list + of conditions and the following disclaimer in the documentation and/or other materials + provided with the distribution. + +THIS SOFTWARE IS PROVIDED THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +``` + +This is the 2-clause BSD license. + +[fuse-jna] is licensed under the [BSD 2-Clause License]. + +[jnr-fuse] is licensed under the [MIT License]. + + +[fuse-jna]: https://github.com/EtiennePerot/fuse-jna +[jnr-fuse]: https://github.com/SerCeMan/jnr-fuse +[FileSystem]: http://docs.oracle.com/javase/7/docs/api/java/nio/file/FileSystems.html +[JNA]: https://github.com/java-native-access/jna +[JNR]: https://github.com/jnr +[MacFUSE]: http://code.google.com/p/macfuse/ +[fuse4x]: http://fuse4x.org/ +[OSXFUSE]: http://osxfuse.github.com/ +[Linux-FUSE]: http://fuse.sourceforge.net/ +[FreeBSD-FUSE]: http://wiki.freebsd.org/FuseFilesystem +[BSD 2-Clause License]: http://www.opensource.org/licenses/bsd-license +[MIT License]: http://opensource.org/licenses/MIT \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5ec9ee9 --- /dev/null +++ b/build.gradle @@ -0,0 +1,138 @@ +plugins { + id 'java' + id 'maven' + id 'signing' +} + +sourceCompatibility = '1.7' +targetCompatibility = '1.7' + +[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' + +group = "co.paralleluniverse" +version = "0.1.0-SNAPSHOT" +status = "integration" +description = "Java FileSystem as FUSE" +ext.url = "http://puniverse.github.com/jfuse" +ext.vendor = "Parallel Universe Software Co." +ext.licenseName = "GNU General Public License, version 2, with the Classpath Exception" +ext.licenseUrl = "http://openjdk.java.net/legal/gplv2+ce.html" +ext.scmUrl = "https://github.com/puniverse/jfuse" +ext.scmConnection = "https://github.com/puniverse/jfuse.git" + +ext.distDir = "$buildDir/dist" +ext.isReleaseVersion = !version.endsWith("SNAPSHOT") + +repositories { + mavenCentral() +} + +dependencies { + compile 'com.github.jnr:jnr-ffi:2.0.3' + compile 'com.github.jnr:jnr-posix:3.0.17' + + testCompile 'junit:junit:4.12' + testCompile 'org.truth0:truth:0.23' + testCompile 'com.google.jimfs:jimfs:1.0' +} + +tasks.withType(JavaExec) { + classpath += sourceSets.test.runtimeClasspath +} + +javadoc { + options { + links = [ "http://docs.oracle.com/javase/7/docs/api/" ] + noDeprecated = true + addStringOption('public', '-quiet') + } + excludes = [ + "co/paralleluniverse/fuse/**", + "jnr/**", + ] +} + +task sourcesJar(type: Jar, dependsOn: classes) { + classifier = 'sources' + from sourceSets.main.allSource +} + +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} + +artifacts { + archives jar + archives sourcesJar + archives javadocJar +} + +signing { + required { isReleaseVersion && gradle.taskGraph.hasTask("uploadArchives") } + sign configurations.archives +} + +if (!project.hasProperty("sonatypeUsername") || !project.hasProperty("sonatypePassword")) { + println "sonatype username or password not set" + ext.sonatypeUsername = "" + ext.sonatypePassword = "" +} + +uploadArchives { + repositories { + mavenDeployer { + beforeDeployment { deployment -> signing.signPom(deployment) } + + repository( + url: (isReleaseVersion ? + "https://oss.sonatype.org/service/local/staging/deploy/maven2" : + "https://oss.sonatype.org/content/repositories/snapshots")) { + // User and Password are taken from ~/.gradle/gradle.properties + authentication(userName: project.sonatypeUsername, password: project.sonatypePassword) + } + pom.project { + name project.name + packaging 'jar' + description project.description + url project.url + scm { + url project.scmUrl + connection project.scmConnection + developerConnection project.scmConnection + } + licenses { + license { + name project.licenseName + url project.licenseUrl + distribution 'repo' + } + } + developers { + developer { + id 'pron' + name 'Ron Pressler' + } + } + } + } + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '2.4' +} + + +task('run', type: JavaExec, dependsOn:[testClasses]) { + classpath = sourceSets.main.runtimeClasspath + sourceSets.test.runtimeClasspath + + main = 'co.paralleluniverse.javafs.Main' + + if(project.hasProperty('args')){ + args project.args.split('\\s+') + } + + // systemProperty 'jnr.ffi.compile.dump', 'true' +} + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b5166dad4d90021f6a0b45268c0755719f1d5cd4 GIT binary patch literal 52266 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 z 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2ec1e35 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Sep 04 17:18:36 IDT 2015 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.4-bin.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..aec9973 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..764140a --- /dev/null +++ b/run.sh @@ -0,0 +1,6 @@ +#!/bin/sh +if [ "$#" == "0" ]; then + ./gradlew run +else + ./gradlew run -Pargs="$*" +fi diff --git a/src/main/java/co/paralleluniverse/fuse/AbstractFuseFilesystem.java b/src/main/java/co/paralleluniverse/fuse/AbstractFuseFilesystem.java new file mode 100644 index 0000000..7974987 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/AbstractFuseFilesystem.java @@ -0,0 +1,239 @@ +package co.paralleluniverse.fuse; + +import java.nio.ByteBuffer; +import java.nio.file.Path; +import jnr.constants.platform.Errno; +import jnr.ffi.Pointer; + +/** + * An adapter that tries to put sane defaults for default return values. Will silently pretend that most non-critical operations + * have succeeded, but return ENOSYS on non-implemented important operations. + */ +public abstract class AbstractFuseFilesystem extends FuseFilesystem { + @Override + protected String getName() { + return null; + } + + @Override + protected String[] getOptions() { + return null; + } + + @Override + protected void afterUnmount(Path mountPoint) { + } + + @Override + protected void beforeMount(Path mountPoint) { + } + + @Override + protected int getattr(String path, StructStat stat) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int readlink(String path, ByteBuffer buffer, long size) { + return 0; + } + + @Override + protected int mknod(String path, long mode, long dev) { + return create(path, mode, null); + } + + @Override + protected int mkdir(String path, long mode) { + return 0; + } + + @Override + protected int unlink(String path) { + return 0; + } + + @Override + protected int rmdir(String path) { + return 0; + } + + @Override + protected int symlink(String path, String target) { + return 0; + } + + @Override + protected int rename(String path, String newName) { + return 0; + } + + @Override + protected int link(String path, String target) { + return 0; + } + + @Override + protected int chmod(String path, long mode) { + return 0; + } + + @Override + protected int chown(String path, long uid, long gid) { + return 0; + } + + @Override + protected int truncate(String path, long offset) { + return 0; + } + + @Override + protected int open(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int read(String path, ByteBuffer buffer, long size, long offset, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int write(String path, ByteBuffer buf, long bufSize, long writeOffset, StructFuseFileInfo wrapper) { + return 0; + } + + @Override + protected int statfs(String path, StructStatvfs stratvfs) { + return 0; + } + + @Override + protected int flush(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int release(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int fsync(String path, int datasync, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int setxattr(String path, String xattr, ByteBuffer buf, long size, int flags, int position) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int getxattr(String path, String xattr, XattrFiller filler, long size, long position) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int listxattr(String path, XattrListFiller filler) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int removexattr(String path, String xattr) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int opendir(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int readdir(String path, StructFuseFileInfo info, DirectoryFiller filler) { + return 0; + } + + @Override + protected int releasedir(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + protected int fsyncdir(String path, int datasync, StructFuseFileInfo info) { + return 0; + } + + @Override + protected void init() { + } + + @Override + protected void destroy() { + } + + @Override + protected int access(String path, int access) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int create(String path, long mode, StructFuseFileInfo info) { + return -Errno.ENOSYS.intValue(); + } + + @Override + public int ftruncate(String path, long offset, StructFuseFileInfo info) { + return truncate(path, offset); + } + + @Override + protected int fgetattr(String path, StructStat stat, StructFuseFileInfo info) { + return getattr(path, stat); + } + + @Override + protected int lock(String path, StructFuseFileInfo info, int command, StructFlock flock) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int utimens(String path, StructTimeBuffer timeBuffer) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int bmap(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + public int ioctl(String path, int cmd, Pointer arg, StructFuseFileInfo fi, long flags, Pointer data) { + return -ErrorCodes.ENOSYS(); + } + + @Override + public int poll(String path, StructFuseFileInfo fi, StructFusePollHandle ph, Pointer reventsp) { + return -ErrorCodes.ENOSYS(); + } + + @Override + protected int write_buf(String path, StructFuseBufvec buf, long off, StructFuseFileInfo fi) { + return -ErrorCodes.ENOSYS(); + } + + @Override + protected int read_buf(String path, Pointer bufp, long size, long off, StructFuseFileInfo fi) { + return -ErrorCodes.ENOSYS(); + } + + @Override + public int flock(String path, StructFuseFileInfo fi, int op) { + return -ErrorCodes.ENOSYS(); + } + + @Override + public int fallocate(String path, int mode, long off, long length, StructFuseFileInfo fi) { + return -ErrorCodes.ENOSYS(); + } + +} diff --git a/src/main/java/co/paralleluniverse/fuse/AccessConstants.java b/src/main/java/co/paralleluniverse/fuse/AccessConstants.java new file mode 100644 index 0000000..fba9f39 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/AccessConstants.java @@ -0,0 +1,19 @@ +package co.paralleluniverse.fuse; + +import jnr.constants.platform.Access; + +/** + * Values for the second argument to access. These may be OR'd together. + *

+ * + * @author Sergey Tselovalnikov + * @since 05.06.15 + */ +public final class AccessConstants { + public static final int R_OK = Access.R_OK.intValue(); /* Test for read permission. */ + public static final int W_OK = Access.W_OK.intValue(); /* Test for write permission. */ + public static final int X_OK = Access.X_OK.intValue(); /* Test for execute permission. */ + public static final int F_OK = Access.F_OK.intValue(); /* Test for existence. */ + + private AccessConstants() {} +} diff --git a/src/main/java/co/paralleluniverse/fuse/DirectoryFiller.java b/src/main/java/co/paralleluniverse/fuse/DirectoryFiller.java new file mode 100644 index 0000000..b864580 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/DirectoryFiller.java @@ -0,0 +1,14 @@ +package co.paralleluniverse.fuse; + +/** + * Base interface for the transfer-object for the readdir() call. + */ +public interface DirectoryFiller { + /** + * Pass the given files to the FUSE interfaces. + * + * @param files A list of filenames without directory. + * @return true if the operation succeeds, false if a problem happens when passing any of the files to FUSE. + */ + public boolean add(Iterable files); +} diff --git a/src/main/java/co/paralleluniverse/fuse/DirectoryFillerImpl.java b/src/main/java/co/paralleluniverse/fuse/DirectoryFillerImpl.java new file mode 100644 index 0000000..05d5af4 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/DirectoryFillerImpl.java @@ -0,0 +1,64 @@ +package co.paralleluniverse.fuse; + +import java.nio.ByteBuffer; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import jnr.ffi.Pointer; +import jnr.ffi.annotations.Delegate; +import jnr.ffi.types.off_t; + +/** + * A class which provides functionality to pass filenames back to FUSE as part of a readdir() call. + */ +final class DirectoryFillerImpl implements DirectoryFiller { + private static final String currentDirectory = "."; + private static final String parentDirectory = ".."; + private final Pointer buf; + private final fuse_fill_dir_t nativeFunction; + private final Set addedFiles = new HashSet(); + + DirectoryFillerImpl(Pointer buf, fuse_fill_dir_t nativeFunction) { + this.buf = buf; + this.nativeFunction = nativeFunction; + + add(Arrays.asList(currentDirectory, parentDirectory)); + } + + public static interface fuse_fill_dir_t { + @Delegate + int invoke(Pointer buf, ByteBuffer name, Pointer stat, @off_t long off); + } + + @Override + public final boolean add(Iterable files) { + int result; + for (String file : files) { + if (file == null) + continue; + + file = Paths.get(file).getFileName().toString(); // Keep only the name component + + if (addedFiles.add(file)) { + result = nativeFunction.invoke(buf, ByteBuffer.wrap(file.getBytes()), null, 0); + if (result != 0) + return false; + } + } + return true; + } + + @Override + public String toString() { + final StringBuilder output = new StringBuilder(); + int count = 0; + for (final String file : addedFiles) { + output.append(file); + if (count < addedFiles.size() - 1) + output.append(", "); + count++; + } + return output.toString(); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/ErrorCodes.java b/src/main/java/co/paralleluniverse/fuse/ErrorCodes.java new file mode 100644 index 0000000..bf522f7 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/ErrorCodes.java @@ -0,0 +1,2501 @@ +package co.paralleluniverse.fuse; + +final class ErrorCodes { + private static final class ErrorCodesBSD implements IErrorCodes { + @Override public int E2BIG() { return 7; } + @Override public int EACCES() { return 13; } + @Override public int EADDRINUSE() { return 48; } + @Override public int EADDRNOTAVAIL() { return 49; } + @Override public int EAFNOSUPPORT() { return 47; } + @Override public int EAGAIN() { return 35; } + @Override public int EALREADY() { return 37; } + @Override public int EBADF() { return 9; } + @Override public int EBADMSG() { return 89; } + @Override public int EBUSY() { return 16; } + @Override public int ECANCELED() { return 85; } + @Override public int ECHILD() { return 10; } + @Override public int ECONNABORTED() { return 53; } + @Override public int ECONNREFUSED() { return 61; } + @Override public int ECONNRESET() { return 54; } + @Override public int EDEADLK() { return 11; } + @Override public int EDESTADDRREQ() { return 39; } + @Override public int EDOM() { return 33; } + @Override public int EDQUOT() { return 69; } + @Override public int EEXIST() { return 17; } + @Override public int EFAULT() { return 14; } + @Override public int EFBIG() { return 27; } + @Override public int EHOSTDOWN() { return 64; } + @Override public int EHOSTUNREACH() { return 65; } + @Override public int EIDRM() { return 82; } + @Override public int EILSEQ() { return 86; } + @Override public int EINPROGRESS() { return 36; } + @Override public int EINTR() { return 4; } + @Override public int EINVAL() { return 22; } + @Override public int EIO() { return 5; } + @Override public int EISCONN() { return 56; } + @Override public int EISDIR() { return 21; } + @Override public int ELOOP() { return 62; } + @Override public int EMFILE() { return 24; } + @Override public int EMLINK() { return 31; } + @Override public int EMSGSIZE() { return 40; } + @Override public int EMULTIHOP() { return 90; } + @Override public int ENAMETOOLONG() { return 63; } + @Override public int ENETDOWN() { return 50; } + @Override public int ENETRESET() { return 52; } + @Override public int ENETUNREACH() { return 51; } + @Override public int ENFILE() { return 23; } + @Override public int ENOBUFS() { return 55; } + @Override public int ENODEV() { return 19; } + @Override public int ENOENT() { return 2; } + @Override public int ENOEXEC() { return 8; } + @Override public int ENOLCK() { return 77; } + @Override public int ENOLINK() { return 91; } + @Override public int ENOMEM() { return 12; } + @Override public int ENOMSG() { return 83; } + @Override public int ENOPROTOOPT() { return 42; } + @Override public int ENOSPC() { return 28; } + @Override public int ENOSYS() { return 78; } + @Override public int ENOTBLK() { return 15; } + @Override public int ENOTCONN() { return 57; } + + @Override public Integer EADV() { return null; } + @Override public Integer EAUTH() { return 80; } + @Override public Integer EBADE() { return null; } + @Override public Integer EBADFD() { return null; } + @Override public Integer EBADR() { return null; } + @Override public Integer EBADRPC() { return 72; } + @Override public Integer EBADRQC() { return null; } + @Override public Integer EBADSLT() { return null; } + @Override public Integer EBFONT() { return null; } + @Override public Integer ECHRNG() { return null; } + @Override public Integer ECOMM() { return null; } + @Override public Integer EDEADLOCK() { return null; } + @Override public Integer EDOOFUS() { return 88; } + @Override public Integer EDOTDOT() { return null; } + @Override public Integer EFTYPE() { return 79; } + @Override public Integer EISNAM() { return null; } + @Override public Integer EKEYEXPIRED() { return null; } + @Override public Integer EKEYREJECTED() { return null; } + @Override public Integer EKEYREVOKED() { return null; } + @Override public Integer EL2HLT() { return null; } + @Override public Integer EL2NSYNC() { return null; } + @Override public Integer EL3HLT() { return null; } + @Override public Integer EL3RST() { return null; } + @Override public Integer ELAST() { return 93; } + @Override public Integer ELIBACC() { return null; } + @Override public Integer ELIBBAD() { return null; } + @Override public Integer ELIBEXEC() { return null; } + @Override public Integer ELIBMAX() { return null; } + @Override public Integer ELIBSCN() { return null; } + @Override public Integer ELNRNG() { return null; } + @Override public Integer EMEDIUMTYPE() { return null; } + @Override public Integer ENAVAIL() { return null; } + @Override public Integer ENEEDAUTH() { return 81; } + @Override public Integer ENOANO() { return null; } + @Override public Integer ENOATTR() { return 87; } + @Override public Integer ENOCSI() { return null; } + @Override public Integer ENODATA() { return null; } + @Override public Integer ENOKEY() { return null; } + @Override public Integer ENOMEDIUM() { return null; } + @Override public Integer ENONET() { return null; } + @Override public Integer ENOPKG() { return null; } + @Override public Integer ENOSR() { return null; } + @Override public Integer ENOSTR() { return null; } + @Override public Integer ENOTCAPABLE() { return 93; } + + + + @Override + public int ENOTDIR() { + return 20; + } + + @Override + public int ENOTEMPTY() { + return 66; + } + + @Override + public Integer ENOTNAM() { + return null; + } + + @Override + public Integer ENOTRECOVERABLE() { + return null; + } + + @Override + public int ENOTSOCK() { + return 38; + } + + @Override + public Integer ENOTSUP() { + return EOPNOTSUPP(); + } + + @Override + public int ENOTTY() { + return 25; + } + + @Override + public Integer ENOTUNIQ() { + return null; + } + + @Override + public int ENXIO() { + return 6; + } + + @Override + public int EOPNOTSUPP() { + return 45; + } + + @Override + public int EOVERFLOW() { + return 84; + } + + @Override + public Integer EOWNERDEAD() { + return null; + } + + @Override + public int EPERM() { + return 1; + } + + @Override + public int EPFNOSUPPORT() { + return 46; + } + + @Override + public int EPIPE() { + return 32; + } + + @Override + public Integer EPROCLIM() { + return 67; + } + + @Override + public Integer EPROCUNAVAIL() { + return 76; + } + + @Override + public Integer EPROGMISMATCH() { + return 75; + } + + @Override + public Integer EPROGUNAVAIL() { + return 74; + } + + @Override + public int EPROTO() { + return 92; + } + + @Override + public int EPROTONOSUPPORT() { + return 43; + } + + @Override + public int EPROTOTYPE() { + return 41; + } + + @Override + public int ERANGE() { + return 34; + } + + @Override + public Integer EREMCHG() { + return null; + } + + @Override + public int EREMOTE() { + return 71; + } + + @Override + public Integer EREMOTEIO() { + return null; + } + + @Override + public Integer ERESTART() { + return null; + } + + @Override + public int EROFS() { + return 30; + } + + @Override + public Integer ERPCMISMATCH() { + return 73; + } + + @Override + public int ESHUTDOWN() { + return 58; + } + + @Override + public int ESOCKTNOSUPPORT() { + return 44; + } + + @Override + public int ESPIPE() { + return 29; + } + + @Override + public int ESRCH() { + return 3; + } + + @Override + public Integer ESRMNT() { + return null; + } + + @Override + public int ESTALE() { + return 70; + } + + @Override + public Integer ESTRPIPE() { + return null; + } + + @Override + public Integer ETIME() { + return null; + } + + @Override + public int ETIMEDOUT() { + return 60; + } + + @Override + public int ETOOMANYREFS() { + return 59; + } + + @Override + public int ETXTBSY() { + return 26; + } + + @Override public Integer EUCLEAN() { return null; } + @Override public Integer EUNATCH() { return null; } + @Override public int EUSERS() { return 68; } + @Override public int EWOULDBLOCK() { return EAGAIN(); } + @Override public int EXDEV() { return 18; } + @Override public Integer EXFULL() { return null; } + } + + private static final class ErrorCodesLinux implements IErrorCodes { + @Override + public int E2BIG() { + return 7; + } + + @Override + public int EACCES() { + return 13; + } + + @Override + public int EADDRINUSE() { + return 98; + } + + @Override + public int EADDRNOTAVAIL() { + return 99; + } + + @Override + public Integer EADV() { + return 68; + } + + @Override + public int EAFNOSUPPORT() { + return 97; + } + + @Override + public int EAGAIN() { + return 11; + } + + @Override + public int EALREADY() { + return 114; + } + + @Override + public Integer EAUTH() { + return null; + } + + @Override + public Integer EBADE() { + return 52; + } + + @Override + public int EBADF() { + return 9; + } + + @Override + public Integer EBADFD() { + return 77; + } + + @Override + public int EBADMSG() { + return 74; + } + + @Override + public Integer EBADR() { + return 53; + } + + @Override + public Integer EBADRPC() { + return null; + } + + @Override + public Integer EBADRQC() { + return 56; + } + + @Override + public Integer EBADSLT() { + return 57; + } + + @Override + public Integer EBFONT() { + return 59; + } + + @Override + public int EBUSY() { + return 16; + } + + @Override + public int ECANCELED() { + return 125; + } + + @Override + public int ECHILD() { + return 10; + } + + @Override + public Integer ECHRNG() { + return 44; + } + + @Override + public Integer ECOMM() { + return 70; + } + + @Override + public int ECONNABORTED() { + return 103; + } + + @Override + public int ECONNREFUSED() { + return 111; + } + + @Override + public int ECONNRESET() { + return 104; + } + + @Override + public int EDEADLK() { + return 35; + } + + @Override + public Integer EDEADLOCK() { + return EDEADLK(); + } + + @Override + public int EDESTADDRREQ() { + return 89; + } + + @Override + public int EDOM() { + return 33; + } + + @Override + public Integer EDOOFUS() { + return null; + } + + @Override + public Integer EDOTDOT() { + return 73; + } + + @Override + public int EDQUOT() { + return 122; + } + + @Override + public int EEXIST() { + return 17; + } + + @Override + public int EFAULT() { + return 14; + } + + @Override + public int EFBIG() { + return 27; + } + + @Override + public Integer EFTYPE() { + return null; + } + + @Override + public int EHOSTDOWN() { + return 112; + } + + @Override + public int EHOSTUNREACH() { + return 113; + } + + @Override + public int EIDRM() { + return 43; + } + + @Override + public int EILSEQ() { + return 84; + } + + @Override + public int EINPROGRESS() { + return 115; + } + + @Override + public int EINTR() { + return 4; + } + + @Override + public int EINVAL() { + return 22; + } + + @Override + public int EIO() { + return 5; + } + + @Override + public int EISCONN() { + return 106; + } + + @Override + public int EISDIR() { + return 21; + } + + @Override + public Integer EISNAM() { + return 120; + } + + @Override + public Integer EKEYEXPIRED() { + return 127; + } + + @Override + public Integer EKEYREJECTED() { + return 129; + } + + @Override + public Integer EKEYREVOKED() { + return 128; + } + + @Override + public Integer EL2HLT() { + return 51; + } + + @Override + public Integer EL2NSYNC() { + return 45; + } + + @Override + public Integer EL3HLT() { + return 46; + } + + @Override + public Integer EL3RST() { + return 47; + } + + @Override + public Integer ELAST() { + return null; + } + + @Override + public Integer ELIBACC() { + return 79; + } + + @Override + public Integer ELIBBAD() { + return 80; + } + + @Override + public Integer ELIBEXEC() { + return 83; + } + + @Override + public Integer ELIBMAX() { + return 82; + } + + @Override + public Integer ELIBSCN() { + return 81; + } + + @Override + public Integer ELNRNG() { + return 48; + } + + @Override + public int ELOOP() { + return 40; + } + + @Override + public Integer EMEDIUMTYPE() { + return 124; + } + + @Override + public int EMFILE() { + return 24; + } + + @Override + public int EMLINK() { + return 31; + } + + @Override + public int EMSGSIZE() { + return 90; + } + + @Override + public int EMULTIHOP() { + return 72; + } + + @Override + public int ENAMETOOLONG() { + return 36; + } + + @Override + public Integer ENAVAIL() { + return 119; + } + + @Override + public Integer ENEEDAUTH() { + return null; + } + + @Override + public int ENETDOWN() { + return 100; + } + + @Override + public int ENETRESET() { + return 102; + } + + @Override + public int ENETUNREACH() { + return 101; + } + + @Override + public int ENFILE() { + return 23; + } + + @Override + public Integer ENOANO() { + return 55; + } + + @Override + public Integer ENOATTR() { + return null; + } + + @Override + public int ENOBUFS() { + return 105; + } + + @Override + public Integer ENOCSI() { + return 50; + } + + @Override + public Integer ENODATA() { + return 61; + } + + @Override + public int ENODEV() { + return 19; + } + + @Override + public int ENOENT() { + return 2; + } + + @Override + public int ENOEXEC() { + return 8; + } + + @Override + public Integer ENOKEY() { + return 126; + } + + @Override + public int ENOLCK() { + return 37; + } + + @Override + public int ENOLINK() { + return 67; + } + + @Override + public Integer ENOMEDIUM() { + return 123; + } + + @Override + public int ENOMEM() { + return 12; + } + + @Override + public int ENOMSG() { + return 42; + } + + @Override + public Integer ENONET() { + return 64; + } + + @Override + public Integer ENOPKG() { + return 65; + } + + @Override + public int ENOPROTOOPT() { + return 92; + } + + @Override + public int ENOSPC() { + return 28; + } + + @Override + public Integer ENOSR() { + return 63; + } + + @Override + public Integer ENOSTR() { + return 60; + } + + @Override + public int ENOSYS() { + return 38; + } + + @Override + public int ENOTBLK() { + return 15; + } + + @Override + public Integer ENOTCAPABLE() { + return null; + } + + @Override + public int ENOTCONN() { + return 107; + } + + @Override + public int ENOTDIR() { + return 20; + } + + @Override + public int ENOTEMPTY() { + return 39; + } + + @Override + public Integer ENOTNAM() { + return 118; + } + + @Override + public Integer ENOTRECOVERABLE() { + return 131; + } + + @Override + public int ENOTSOCK() { + return 88; + } + + @Override + public Integer ENOTSUP() { + return null; + } + + @Override + public int ENOTTY() { + return 25; + } + + @Override + public Integer ENOTUNIQ() { + return 76; + } + + @Override + public int ENXIO() { + return 6; + } + + @Override + public int EOPNOTSUPP() { + return 95; + } + + @Override + public int EOVERFLOW() { + return 75; + } + + @Override + public Integer EOWNERDEAD() { + return 130; + } + + @Override + public int EPERM() { + return 1; + } + + @Override + public int EPFNOSUPPORT() { + return 96; + } + + @Override + public int EPIPE() { + return 32; + } + + @Override + public Integer EPROCLIM() { + return null; + } + + @Override + public Integer EPROCUNAVAIL() { + return null; + } + + @Override + public Integer EPROGMISMATCH() { + return null; + } + + @Override + public Integer EPROGUNAVAIL() { + return null; + } + + @Override + public int EPROTO() { + return 71; + } + + @Override + public int EPROTONOSUPPORT() { + return 93; + } + + @Override + public int EPROTOTYPE() { + return 91; + } + + @Override + public int ERANGE() { + return 34; + } + + @Override + public Integer EREMCHG() { + return 78; + } + + @Override + public int EREMOTE() { + return 66; + } + + @Override + public Integer EREMOTEIO() { + return 121; + } + + @Override + public Integer ERESTART() { + return 85; + } + + @Override + public int EROFS() { + return 30; + } + + @Override + public Integer ERPCMISMATCH() { + return null; + } + + @Override + public int ESHUTDOWN() { + return 108; + } + + @Override + public int ESOCKTNOSUPPORT() { + return 94; + } + + @Override + public int ESPIPE() { + return 29; + } + + @Override + public int ESRCH() { + return 3; + } + + @Override + public Integer ESRMNT() { + return 69; + } + + @Override + public int ESTALE() { + return 116; + } + + @Override + public Integer ESTRPIPE() { + return 86; + } + + @Override + public Integer ETIME() { + return 62; + } + + @Override + public int ETIMEDOUT() { + return 110; + } + + @Override + public int ETOOMANYREFS() { + return 109; + } + + @Override + public int ETXTBSY() { + return 26; + } + + @Override + public Integer EUCLEAN() { + return 117; + } + + @Override + public Integer EUNATCH() { + return 49; + } + + @Override + public int EUSERS() { + return 87; + } + + @Override + public int EWOULDBLOCK() { + return EAGAIN(); + } + + @Override + public int EXDEV() { + return 18; + } + + @Override + public Integer EXFULL() { + return 54; + } + } + + private static interface IErrorCodes { + int E2BIG(); + + int EACCES(); + + int EADDRINUSE(); + + int EADDRNOTAVAIL(); + + Integer EADV(); + + int EAFNOSUPPORT(); + + int EAGAIN(); + + int EALREADY(); + + Integer EAUTH(); + + Integer EBADE(); + + int EBADF(); + + Integer EBADFD(); + + int EBADMSG(); + + Integer EBADR(); + + Integer EBADRPC(); + + Integer EBADRQC(); + + Integer EBADSLT(); + + Integer EBFONT(); + + int EBUSY(); + + int ECANCELED(); + + int ECHILD(); + + Integer ECHRNG(); + + Integer ECOMM(); + + int ECONNABORTED(); + + int ECONNREFUSED(); + + int ECONNRESET(); + + int EDEADLK(); + + Integer EDEADLOCK(); + + int EDESTADDRREQ(); + + int EDOM(); + + Integer EDOOFUS(); + + Integer EDOTDOT(); + + int EDQUOT(); + + int EEXIST(); + + int EFAULT(); + + int EFBIG(); + + Integer EFTYPE(); + + int EHOSTDOWN(); + + int EHOSTUNREACH(); + + int EIDRM(); + + int EILSEQ(); + + int EINPROGRESS(); + + int EINTR(); + + int EINVAL(); + + int EIO(); + + int EISCONN(); + + int EISDIR(); + + Integer EISNAM(); + + Integer EKEYEXPIRED(); + + Integer EKEYREJECTED(); + + Integer EKEYREVOKED(); + + Integer EL2HLT(); + + Integer EL2NSYNC(); + + Integer EL3HLT(); + + Integer EL3RST(); + + Integer ELAST(); + + Integer ELIBACC(); + + Integer ELIBBAD(); + + Integer ELIBEXEC(); + + Integer ELIBMAX(); + + Integer ELIBSCN(); + + Integer ELNRNG(); + + int ELOOP(); + + Integer EMEDIUMTYPE(); + + int EMFILE(); + + int EMLINK(); + + int EMSGSIZE(); + + int EMULTIHOP(); + + int ENAMETOOLONG(); + + Integer ENAVAIL(); + + Integer ENEEDAUTH(); + + int ENETDOWN(); + + int ENETRESET(); + + int ENETUNREACH(); + + int ENFILE(); + + Integer ENOANO(); + + Integer ENOATTR(); + + int ENOBUFS(); + + Integer ENOCSI(); + + Integer ENODATA(); + + int ENODEV(); + + int ENOENT(); + + int ENOEXEC(); + + Integer ENOKEY(); + + int ENOLCK(); + + int ENOLINK(); + + Integer ENOMEDIUM(); + + int ENOMEM(); + + int ENOMSG(); + + Integer ENONET(); + + Integer ENOPKG(); + + int ENOPROTOOPT(); + + int ENOSPC(); + + Integer ENOSR(); + + Integer ENOSTR(); + + int ENOSYS(); + + int ENOTBLK(); + + Integer ENOTCAPABLE(); + + int ENOTCONN(); + + int ENOTDIR(); + + int ENOTEMPTY(); + + Integer ENOTNAM(); + + Integer ENOTRECOVERABLE(); + + int ENOTSOCK(); + + Integer ENOTSUP(); + + int ENOTTY(); + + Integer ENOTUNIQ(); + + int ENXIO(); + + int EOPNOTSUPP(); + + int EOVERFLOW(); + + Integer EOWNERDEAD(); + + int EPERM(); + + int EPFNOSUPPORT(); + + int EPIPE(); + + Integer EPROCLIM(); + + Integer EPROCUNAVAIL(); + + Integer EPROGMISMATCH(); + + Integer EPROGUNAVAIL(); + + int EPROTO(); + + int EPROTONOSUPPORT(); + + int EPROTOTYPE(); + + int ERANGE(); + + Integer EREMCHG(); + + int EREMOTE(); + + Integer EREMOTEIO(); + + Integer ERESTART(); + + int EROFS(); + + Integer ERPCMISMATCH(); + + int ESHUTDOWN(); + + int ESOCKTNOSUPPORT(); + + int ESPIPE(); + + int ESRCH(); + + Integer ESRMNT(); + + int ESTALE(); + + Integer ESTRPIPE(); + + Integer ETIME(); + + int ETIMEDOUT(); + + int ETOOMANYREFS(); + + int ETXTBSY(); + + Integer EUCLEAN(); + + Integer EUNATCH(); + + int EUSERS(); + + int EWOULDBLOCK(); + + int EXDEV(); + + Integer EXFULL(); + } + + private static IErrorCodes platformErrorCodes = null; + + /** + * Argument list too long + */ + public static int E2BIG() { + return getPlatformErrorCodes().E2BIG(); + } + + /** + * Permission denied + */ + public static int EACCES() { + return getPlatformErrorCodes().EACCES(); + } + + /** + * Address already in use + */ + public static int EADDRINUSE() { + return getPlatformErrorCodes().EADDRINUSE(); + } + + /** + * Can't assign requested address + */ + public static int EADDRNOTAVAIL() { + return getPlatformErrorCodes().EADDRNOTAVAIL(); + } + + /** + * Advertise error + * + * @return null on BSD (not defined) + */ + public static Integer EADV() { + return getPlatformErrorCodes().EADV(); + } + + /** + * Address family not supported by protocol family + */ + public static int EAFNOSUPPORT() { + return getPlatformErrorCodes().EAFNOSUPPORT(); + } + + /** + * Resource temporarily unavailable + */ + public static int EAGAIN() { + return getPlatformErrorCodes().EAGAIN(); + } + + /** + * Operation already in progress + */ + public static int EALREADY() { + return getPlatformErrorCodes().EALREADY(); + } + + /** + * Authentication error + * + * @return null on Linux (not defined) + */ + public static Integer EAUTH() { + return getPlatformErrorCodes().EAUTH(); + } + + /** + * Invalid exchange + * + * @return null on BSD (not defined) + */ + public static Integer EBADE() { + return getPlatformErrorCodes().EBADE(); + } + + /** + * Bad file descriptor + */ + public static int EBADF() { + return getPlatformErrorCodes().EBADF(); + } + + /** + * File descriptor in bad state + * + * @return null on BSD (not defined) + */ + public static Integer EBADFD() { + return getPlatformErrorCodes().EBADFD(); + } + + /** + * Bad message + */ + public static int EBADMSG() { + return getPlatformErrorCodes().EBADMSG(); + } + + /** + * Invalid request descriptor + * + * @return null on BSD (not defined) + */ + public static Integer EBADR() { + return getPlatformErrorCodes().EBADR(); + } + + /** + * RPC struct is bad + * + * @return null on Linux (not defined) + */ + public static Integer EBADRPC() { + return getPlatformErrorCodes().EBADRPC(); + } + + /** + * Invalid request code + * + * @return null on BSD (not defined) + */ + public static Integer EBADRQC() { + return getPlatformErrorCodes().EBADRQC(); + } + + /** + * Invalid slot + * + * @return null on BSD (not defined) + */ + public static Integer EBADSLT() { + return getPlatformErrorCodes().EBADSLT(); + } + + /** + * Bad font file format + * + * @return null on BSD (not defined) + */ + public static Integer EBFONT() { + return getPlatformErrorCodes().EBFONT(); + } + + /** + * Device busy + */ + public static int EBUSY() { + return getPlatformErrorCodes().EBUSY(); + } + + /** + * Operation canceled + */ + public static int ECANCELED() { + return getPlatformErrorCodes().ECANCELED(); + } + + /** + * No child processes + */ + public static int ECHILD() { + return getPlatformErrorCodes().ECHILD(); + } + + /** + * Channel number out of range + * + * @return null on BSD (not defined) + */ + public static Integer ECHRNG() { + return getPlatformErrorCodes().ECHRNG(); + } + + /** + * Communication error on send + * + * @return null on BSD (not defined) + */ + public static Integer ECOMM() { + return getPlatformErrorCodes().ECOMM(); + } + + /** + * Software caused connection abort + */ + public static int ECONNABORTED() { + return getPlatformErrorCodes().ECONNABORTED(); + } + + /** + * Connection refused + */ + public static int ECONNREFUSED() { + return getPlatformErrorCodes().ECONNREFUSED(); + } + + /** + * Connection reset by peer + */ + public static int ECONNRESET() { + return getPlatformErrorCodes().ECONNRESET(); + } + + /** + * Resource deadlock avoided + */ + public static int EDEADLK() { + return getPlatformErrorCodes().EDEADLK(); + } + + /** + * Resource deadlock avoided + * + * @return null on BSD (not defined) + */ + public static Integer EDEADLOCK() { + return getPlatformErrorCodes().EDEADLOCK(); + } + + /** + * Destination address required + */ + public static int EDESTADDRREQ() { + return getPlatformErrorCodes().EDESTADDRREQ(); + } + + /** + * Numerical argument out of domain + */ + public static int EDOM() { + return getPlatformErrorCodes().EDOM(); + } + + /** + * Programming error + * + * @return null on Linux (not defined) + */ + public static Integer EDOOFUS() { + return getPlatformErrorCodes().EDOOFUS(); + } + + /** + * RFS specific error + * + * @return null on BSD (not defined) + */ + public static Integer EDOTDOT() { + return getPlatformErrorCodes().EDOTDOT(); + } + + /** + * Disc quota exceeded + */ + public static int EDQUOT() { + return getPlatformErrorCodes().EDQUOT(); + } + + /** + * File exists + */ + public static int EEXIST() { + return getPlatformErrorCodes().EEXIST(); + } + + /** + * Bad address + */ + public static int EFAULT() { + return getPlatformErrorCodes().EFAULT(); + } + + /** + * File too large + */ + public static int EFBIG() { + return getPlatformErrorCodes().EFBIG(); + } + + /** + * Inappropriate file type or format + * + * @return null on Linux (not defined) + */ + public static Integer EFTYPE() { + return getPlatformErrorCodes().EFTYPE(); + } + + /** + * Host is down + */ + public static int EHOSTDOWN() { + return getPlatformErrorCodes().EHOSTDOWN(); + } + + /** + * No route to host + */ + public static int EHOSTUNREACH() { + return getPlatformErrorCodes().EHOSTUNREACH(); + } + + /** + * Identifier removed + */ + public static int EIDRM() { + return getPlatformErrorCodes().EIDRM(); + } + + /** + * Illegal byte sequence + */ + public static int EILSEQ() { + return getPlatformErrorCodes().EILSEQ(); + } + + /** + * Operation now in progress + */ + public static int EINPROGRESS() { + return getPlatformErrorCodes().EINPROGRESS(); + } + + /** + * Interrupted system call + */ + public static int EINTR() { + return getPlatformErrorCodes().EINTR(); + } + + /** + * Invalid argument + */ + public static int EINVAL() { + return getPlatformErrorCodes().EINVAL(); + } + + /** + * Input/output error + */ + public static int EIO() { + return getPlatformErrorCodes().EIO(); + } + + /** + * Socket is already connected + */ + public static int EISCONN() { + return getPlatformErrorCodes().EISCONN(); + } + + /** + * Is a directory + */ + public static int EISDIR() { + return getPlatformErrorCodes().EISDIR(); + } + + /** + * Is a named type file + * + * @return null on BSD (not defined) + */ + public static Integer EISNAM() { + return getPlatformErrorCodes().EISNAM(); + } + + /** + * Key has expired + * + * @return null on BSD (not defined) + */ + public static Integer EKEYEXPIRED() { + return getPlatformErrorCodes().EKEYEXPIRED(); + } + + /** + * Key was rejected by service + * + * @return null on BSD (not defined) + */ + public static Integer EKEYREJECTED() { + return getPlatformErrorCodes().EKEYREJECTED(); + } + + /** + * Key has been revoked + * + * @return null on BSD (not defined) + */ + public static Integer EKEYREVOKED() { + return getPlatformErrorCodes().EKEYREVOKED(); + } + + /** + * Level 2 halted + * + * @return null on BSD (not defined) + */ + public static Integer EL2HLT() { + return getPlatformErrorCodes().EL2HLT(); + } + + /** + * Level 2 not synchronized + * + * @return null on BSD (not defined) + */ + public static Integer EL2NSYNC() { + return getPlatformErrorCodes().EL2NSYNC(); + } + + /** + * Level 3 halted + * + * @return null on BSD (not defined) + */ + public static Integer EL3HLT() { + return getPlatformErrorCodes().EL3HLT(); + } + + /** + * Level 3 reset + * + * @return null on BSD (not defined) + */ + public static Integer EL3RST() { + return getPlatformErrorCodes().EL3RST(); + } + + /** + * Must be equal largest errno + * + * @return null on Linux (not defined) + */ + public static Integer ELAST() { + return getPlatformErrorCodes().ELAST(); + } + + /** + * Can not access a needed shared library + * + * @return null on BSD (not defined) + */ + public static Integer ELIBACC() { + return getPlatformErrorCodes().ELIBACC(); + } + + /** + * Accessing a corrupted shared library + * + * @return null on BSD (not defined) + */ + public static Integer ELIBBAD() { + return getPlatformErrorCodes().ELIBBAD(); + } + + /** + * Cannot exec a shared library directly + * + * @return null on BSD (not defined) + */ + public static Integer ELIBEXEC() { + return getPlatformErrorCodes().ELIBEXEC(); + } + + /** + * Attempting to link in too many shared libraries + * + * @return null on BSD (not defined) + */ + public static Integer ELIBMAX() { + return getPlatformErrorCodes().ELIBMAX(); + } + + /** + * .lib section in a.out corrupted + * + * @return null on BSD (not defined) + */ + public static Integer ELIBSCN() { + return getPlatformErrorCodes().ELIBSCN(); + } + + /** + * Link number out of range + * + * @return null on BSD (not defined) + */ + public static Integer ELNRNG() { + return getPlatformErrorCodes().ELNRNG(); + } + + /** + * Too many levels of symbolic links + */ + public static int ELOOP() { + return getPlatformErrorCodes().ELOOP(); + } + + /** + * Wrong medium type + * + * @return null on BSD (not defined) + */ + public static Integer EMEDIUMTYPE() { + return getPlatformErrorCodes().EMEDIUMTYPE(); + } + + /** + * Too many open files + */ + public static int EMFILE() { + return getPlatformErrorCodes().EMFILE(); + } + + /** + * Too many links + */ + public static int EMLINK() { + return getPlatformErrorCodes().EMLINK(); + } + + /** + * Message too long + */ + public static int EMSGSIZE() { + return getPlatformErrorCodes().EMSGSIZE(); + } + + /** + * Multihop attempted + */ + public static int EMULTIHOP() { + return getPlatformErrorCodes().EMULTIHOP(); + } + + /** + * File name too long + */ + public static int ENAMETOOLONG() { + return getPlatformErrorCodes().ENAMETOOLONG(); + } + + /** + * No XENIX semaphores available + * + * @return null on BSD (not defined) + */ + public static Integer ENAVAIL() { + return getPlatformErrorCodes().ENAVAIL(); + } + + /** + * Need authenticator + * + * @return null on Linux (not defined) + */ + public static Integer ENEEDAUTH() { + return getPlatformErrorCodes().ENEEDAUTH(); + } + + /** + * Network is down + */ + public static int ENETDOWN() { + return getPlatformErrorCodes().ENETDOWN(); + } + + /** + * Network dropped connection on reset + */ + public static int ENETRESET() { + return getPlatformErrorCodes().ENETRESET(); + } + + /** + * Network is unreachable + */ + public static int ENETUNREACH() { + return getPlatformErrorCodes().ENETUNREACH(); + } + + /** + * Too many open files in system + */ + public static int ENFILE() { + return getPlatformErrorCodes().ENFILE(); + } + + /** + * No anode + * + * @return null on BSD (not defined) + */ + public static Integer ENOANO() { + return getPlatformErrorCodes().ENOANO(); + } + + /** + * Attribute not found + * + * @return null on Linux (not defined) + */ + public static Integer ENOATTR() { + return getPlatformErrorCodes().ENOATTR(); + } + + /** + * No buffer space available + */ + public static int ENOBUFS() { + return getPlatformErrorCodes().ENOBUFS(); + } + + /** + * No CSI structure available + * + * @return null on BSD (not defined) + */ + public static Integer ENOCSI() { + return getPlatformErrorCodes().ENOCSI(); + } + + /** + * No data available + * + * @return null on BSD (not defined) + */ + public static Integer ENODATA() { + return getPlatformErrorCodes().ENODATA(); + } + + /** + * Operation not supported by device + */ + public static int ENODEV() { + return getPlatformErrorCodes().ENODEV(); + } + + /** + * No such file or directory + */ + public static int ENOENT() { + return getPlatformErrorCodes().ENOENT(); + } + + /** + * Exec format error + */ + public static int ENOEXEC() { + return getPlatformErrorCodes().ENOEXEC(); + } + + /** + * Required key not available + * + * @return null on BSD (not defined) + */ + public static Integer ENOKEY() { + return getPlatformErrorCodes().ENOKEY(); + } + + /** + * No locks available + */ + public static int ENOLCK() { + return getPlatformErrorCodes().ENOLCK(); + } + + /** + * Link has been severed + */ + public static int ENOLINK() { + return getPlatformErrorCodes().ENOLINK(); + } + + /** + * No medium found + * + * @return null on BSD (not defined) + */ + public static Integer ENOMEDIUM() { + return getPlatformErrorCodes().ENOMEDIUM(); + } + + /** + * Cannot allocate memory + */ + public static int ENOMEM() { + return getPlatformErrorCodes().ENOMEM(); + } + + /** + * No message of desired type + */ + public static int ENOMSG() { + return getPlatformErrorCodes().ENOMSG(); + } + + /** + * Machine is not on the network + * + * @return null on BSD (not defined) + */ + public static Integer ENONET() { + return getPlatformErrorCodes().ENONET(); + } + + /** + * Package not installed + * + * @return null on BSD (not defined) + */ + public static Integer ENOPKG() { + return getPlatformErrorCodes().ENOPKG(); + } + + /** + * Protocol not available + */ + public static int ENOPROTOOPT() { + return getPlatformErrorCodes().ENOPROTOOPT(); + } + + /** + * No space left on device + */ + public static int ENOSPC() { + return getPlatformErrorCodes().ENOSPC(); + } + + /** + * Out of streams resources + * + * @return null on BSD (not defined) + */ + public static Integer ENOSR() { + return getPlatformErrorCodes().ENOSR(); + } + + /** + * Device not a stream + * + * @return null on BSD (not defined) + */ + public static Integer ENOSTR() { + return getPlatformErrorCodes().ENOSTR(); + } + + /** + * Function not implemented + */ + public static int ENOSYS() { + return getPlatformErrorCodes().ENOSYS(); + } + + /** + * Block device required + */ + public static int ENOTBLK() { + return getPlatformErrorCodes().ENOTBLK(); + } + + /** + * Capabilities insufficient + * + * @return null on Linux (not defined) + */ + public static Integer ENOTCAPABLE() { + return getPlatformErrorCodes().ENOTCAPABLE(); + } + + /** + * Socket is not connected + */ + public static int ENOTCONN() { + return getPlatformErrorCodes().ENOTCONN(); + } + + /** + * Not a directory + */ + public static int ENOTDIR() { + return getPlatformErrorCodes().ENOTDIR(); + } + + /** + * Directory not empty + */ + public static int ENOTEMPTY() { + return getPlatformErrorCodes().ENOTEMPTY(); + } + + /** + * Not a XENIX named type file + * + * @return null on BSD (not defined) + */ + public static Integer ENOTNAM() { + return getPlatformErrorCodes().ENOTNAM(); + } + + /** + * State not recoverable + * + * @return null on BSD (not defined) + */ + public static Integer ENOTRECOVERABLE() { + return getPlatformErrorCodes().ENOTRECOVERABLE(); + } + + /** + * Socket operation on non-socket + */ + public static int ENOTSOCK() { + return getPlatformErrorCodes().ENOTSOCK(); + } + + /** + * Operation not supported + * + * @return null on Linux (not defined) + */ + public static Integer ENOTSUP() { + return getPlatformErrorCodes().ENOTSUP(); + } + + /** + * Inappropriate ioctl for device + */ + public static int ENOTTY() { + return getPlatformErrorCodes().ENOTTY(); + } + + /** + * Name not unique on network + * + * @return null on BSD (not defined) + */ + public static Integer ENOTUNIQ() { + return getPlatformErrorCodes().ENOTUNIQ(); + } + + /** + * Device not configured + */ + public static int ENXIO() { + return getPlatformErrorCodes().ENXIO(); + } + + /** + * Operation not supported + */ + public static int EOPNOTSUPP() { + return getPlatformErrorCodes().EOPNOTSUPP(); + } + + /** + * Value too large to be stored in data type + */ + public static int EOVERFLOW() { + return getPlatformErrorCodes().EOVERFLOW(); + } + + /** + * Owner died + * + * @return null on BSD (not defined) + */ + public static Integer EOWNERDEAD() { + return getPlatformErrorCodes().EOWNERDEAD(); + } + + /** + * Operation not permitted + */ + public static int EPERM() { + return getPlatformErrorCodes().EPERM(); + } + + /** + * Protocol family not supported + */ + public static int EPFNOSUPPORT() { + return getPlatformErrorCodes().EPFNOSUPPORT(); + } + + /** + * Broken pipe + */ + public static int EPIPE() { + return getPlatformErrorCodes().EPIPE(); + } + + /** + * Too many processes + * + * @return null on Linux (not defined) + */ + public static Integer EPROCLIM() { + return getPlatformErrorCodes().EPROCLIM(); + } + + /** + * Bad procedure for program + * + * @return null on Linux (not defined) + */ + public static Integer EPROCUNAVAIL() { + return getPlatformErrorCodes().EPROCUNAVAIL(); + } + + /** + * Program version wrong + * + * @return null on Linux (not defined) + */ + public static Integer EPROGMISMATCH() { + return getPlatformErrorCodes().EPROGMISMATCH(); + } + + /** + * RPC prog. not avail + * + * @return null on Linux (not defined) + */ + public static Integer EPROGUNAVAIL() { + return getPlatformErrorCodes().EPROGUNAVAIL(); + } + + /** + * Protocol error + */ + public static int EPROTO() { + return getPlatformErrorCodes().EPROTO(); + } + + /** + * Protocol not supported + */ + public static int EPROTONOSUPPORT() { + return getPlatformErrorCodes().EPROTONOSUPPORT(); + } + + /** + * Protocol wrong type for socket + */ + public static int EPROTOTYPE() { + return getPlatformErrorCodes().EPROTOTYPE(); + } + + /** + * Result too large + */ + public static int ERANGE() { + return getPlatformErrorCodes().ERANGE(); + } + + /** + * Remote address changed + * + * @return null on BSD (not defined) + */ + public static Integer EREMCHG() { + return getPlatformErrorCodes().EREMCHG(); + } + + /** + * Too many levels of remote in path + */ + public static int EREMOTE() { + return getPlatformErrorCodes().EREMOTE(); + } + + /** + * Remote I/O error + * + * @return null on BSD (not defined) + */ + public static Integer EREMOTEIO() { + return getPlatformErrorCodes().EREMOTEIO(); + } + + /** + * Interrupted system call should be restarted + * + * @return null on BSD (not defined) + */ + public static Integer ERESTART() { + return getPlatformErrorCodes().ERESTART(); + } + + /** + * Read-only file system + */ + public static int EROFS() { + return getPlatformErrorCodes().EROFS(); + } + + /** + * RPC version wrong + * + * @return null on Linux (not defined) + */ + public static Integer ERPCMISMATCH() { + return getPlatformErrorCodes().ERPCMISMATCH(); + } + + /** + * Can't send after socket shutdown + */ + public static int ESHUTDOWN() { + return getPlatformErrorCodes().ESHUTDOWN(); + } + + /** + * Socket type not supported + */ + public static int ESOCKTNOSUPPORT() { + return getPlatformErrorCodes().ESOCKTNOSUPPORT(); + } + + /** + * Illegal seek + */ + public static int ESPIPE() { + return getPlatformErrorCodes().ESPIPE(); + } + + /** + * No such process + */ + public static int ESRCH() { + return getPlatformErrorCodes().ESRCH(); + } + + /** + * Srmount error + * + * @return null on BSD (not defined) + */ + public static Integer ESRMNT() { + return getPlatformErrorCodes().ESRMNT(); + } + + /** + * Stale NFS file handle + */ + public static int ESTALE() { + return getPlatformErrorCodes().ESTALE(); + } + + /** + * Streams pipe error + * + * @return null on BSD (not defined) + */ + public static Integer ESTRPIPE() { + return getPlatformErrorCodes().ESTRPIPE(); + } + + /** + * Timer expired + * + * @return null on BSD (not defined) + */ + public static Integer ETIME() { + return getPlatformErrorCodes().ETIME(); + } + + /** + * Operation timed out + */ + public static int ETIMEDOUT() { + return getPlatformErrorCodes().ETIMEDOUT(); + } + + /** + * Too many references: can't splice + */ + public static int ETOOMANYREFS() { + return getPlatformErrorCodes().ETOOMANYREFS(); + } + + /** + * Text file busy + */ + public static int ETXTBSY() { + return getPlatformErrorCodes().ETXTBSY(); + } + + /** + * Structure needs cleaning + * + * @return null on BSD (not defined) + */ + public static Integer EUCLEAN() { + return getPlatformErrorCodes().EUCLEAN(); + } + + /** + * Protocol driver not attached + * + * @return null on BSD (not defined) + */ + public static Integer EUNATCH() { + return getPlatformErrorCodes().EUNATCH(); + } + + /** + * Too many users + */ + public static int EUSERS() { + return getPlatformErrorCodes().EUSERS(); + } + + /** + * Operation would block + */ + public static int EWOULDBLOCK() { + return getPlatformErrorCodes().EWOULDBLOCK(); + } + + /** + * Cross-device link + */ + public static int EXDEV() { + return getPlatformErrorCodes().EXDEV(); + } + + /** + * Exchange full + * + * @return null on BSD (not defined) + */ + public static Integer EXFULL() { + return getPlatformErrorCodes().EXFULL(); + } + + public static Integer firstNonNull(Integer... errorCodes) { + for (Integer i : errorCodes) { + if (i != null) { + return i; + } + } + return null; + } + + private static IErrorCodes getPlatformErrorCodes() { + if (platformErrorCodes == null) { + switch (Platform.platform()) { + case FREEBSD: + case MAC: + case MAC_MACFUSE: + platformErrorCodes = new ErrorCodes.ErrorCodesBSD(); + break; + default: + platformErrorCodes = new ErrorCodes.ErrorCodesLinux(); + } + } + return platformErrorCodes; + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/Filesystem.java b/src/main/java/co/paralleluniverse/fuse/Filesystem.java new file mode 100644 index 0000000..5212a7e --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/Filesystem.java @@ -0,0 +1,277 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.provider.jffi.ClosureHelper; +import java.nio.BufferOverflowException; +import java.nio.ByteBuffer; +import jnr.ffi.Pointer; +import jnr.ffi.annotations.In; +import jnr.ffi.annotations.Out; +import jnr.ffi.types.dev_t; +import jnr.ffi.types.gid_t; +import jnr.ffi.types.mode_t; +import jnr.ffi.types.off_t; +import jnr.ffi.types.size_t; +import jnr.ffi.types.u_int32_t; +import jnr.ffi.types.uid_t; +import static co.paralleluniverse.fuse.JNRUtil.toByteBuffer; +import co.paralleluniverse.fuse.StructFuseOperationsIfaces.*; + +class Filesystem implements + _readlink, _mknod, _mkdir, _unlink, _rmdir, _symlink, _rename, _link, _chmod, _chown, _truncate, + _open, _read, _write, _flush, _release, _fsync, _statfs, _lock, _getattr, _fgetattr, + _setxattr_MAC, _setxattr_NOT_MAC, + _getxattr_MAC, _getxattr_NOT_MAC, + _listxattr, _removexattr, + _opendir, _readdir, _releasedir, _fsyncdir, + _init, _destroy, _access, _create, _ftruncate, _utimens, _bmap, + _ioctl, _poll, _write_buf, _read_buf, _flock, _fallocate { + private final FuseFilesystem fs; + + public Filesystem(FuseFilesystem fs) { + this.fs = fs; + } + + @Override + public final int _getattr(String path, Pointer stat) { + return fs.getattr(path, fs.defaultStat(new StructStat(stat, path))); + } + + @Override + public final int _readlink(String path, Pointer buffer, @size_t long size) { + final ByteBuffer buf = toByteBuffer(buffer, size); + final int result = fs.readlink(path, buf, size); + if (result == 0) { + try { + buf.put((byte) 0); + } catch (final BufferOverflowException e) { + ((ByteBuffer) buf.position(buf.limit() - 1)).put((byte) 0); + } + } + return result; + } + + @Override + public final int _mknod(String path, @mode_t long mode, @dev_t long dev) { + return fs.mknod(path, mode, dev); + } + + @Override + public final int _mkdir(String path, @mode_t long mode) { + return fs.mkdir(path, mode); + } + + @Override + public final int _unlink(String path) { + return fs.unlink(path); + } + + @Override + public final int _rmdir(String path) { + return fs.rmdir(path); + } + + @Override + public final int _symlink(String path, String target) { + return fs.symlink(path, target); + } + + @Override + public final int _rename(String path, String newName) { + return fs.rename(path, newName); + } + + @Override + public final int _link(String path, String target) { + return fs.link(path, target); + } + + @Override + public final int _chmod(String path, @mode_t long mode) { + return fs.chmod(path, mode); + } + + @Override + public final int _chown(String path, @uid_t long uid, @gid_t long gid) { + return fs.chown(path, uid, gid); + } + + @Override + public final int _truncate(String path, @off_t long offset) { + return fs.truncate(path, offset); + } + + @Override + public final int _open(String path, Pointer info) { + return fs.open(path, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _read(String path, @Out Pointer buffer, @size_t long size, @off_t long offset, Pointer info) { + final ByteBuffer buf = toByteBuffer(buffer, size); + return fs.read(path, buf, size, offset, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _write(String path, @In Pointer buffer, @size_t long size, @off_t long offset, Pointer info) { + final ByteBuffer buf = toByteBuffer(buffer, size); + return fs.write(path, buf, size, offset, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _statfs(String path, Pointer statsvfs) { + return fs.statfs(path, new StructStatvfs(statsvfs, path)); + } + + @Override + public final int _flush(String path, Pointer info) { + return fs.flush(path, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _release(String path, Pointer info) { + return fs.release(path, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _fsync(String path, int datasync, @In Pointer info) { + return fs.fsync(path, datasync, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _setxattr(String path, String xattr, Pointer value, @size_t long size, int flags) { + return _setxattr(path, xattr, value, size, flags, 0); + } + + @Override + public final int _setxattr(String path, String xattr, Pointer value, @size_t long size, int flags, int position) { + final ByteBuffer val = toByteBuffer(value, size); + return fs.setxattr(path, xattr, val, size, flags, position); + } + + @Override + public final int _getxattr(String path, String xattr, Pointer buffer, @size_t long size) { + return _getxattr(path, xattr, buffer, size, 0); + } + + @Override + public final int _getxattr(String path, String xattr, Pointer buffer, @size_t long size, @u_int32_t long position) { + final XattrFiller filler = new XattrFiller(buffer == null ? null : toByteBuffer(buffer, size), size, (int) position); + final int result = fs.getxattr(path, xattr, filler, size, position); + return result < 0 ? result : (int) filler.getSize(); + } + + @Override + public final int _listxattr(String path, Pointer buffer, @size_t long size) { + final XattrListFiller filler = new XattrListFiller(buffer == null ? null : toByteBuffer(buffer, size), size); + final int result = fs.listxattr(path, filler); + return result < 0 ? result : (int) filler.requiredSize(); + } + + @Override + public final int _removexattr(String path, String xattr) { + return fs.removexattr(path, xattr); + } + + @Override + public final int _opendir(String path, Pointer info) { + return fs.opendir(path, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _readdir(String path, Pointer buf, Pointer fillFunction, @off_t long offset, @In Pointer info) { + return fs.readdir(path, + new StructFuseFileInfo(info, path), + new DirectoryFillerImpl(buf, ClosureHelper.getInstance().fromNative(fillFunction, DirectoryFillerImpl.fuse_fill_dir_t.class))); + } + + @Override + public final int _releasedir(String path, Pointer info) { + return fs.releasedir(path, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _fsyncdir(String path, int datasync, @In Pointer info) { + return fs.fsyncdir(path, datasync, new StructFuseFileInfo(info, path)); + } + + @Override + public final void _init(Pointer conn) { + fs.init(); + } + + @Override + public final void _destroy() { + fs.destroy(); + fs._destroy(); + } + + @Override + public final int _access(String path, int access) { + return fs.access(path, access); + } + + @Override + public final int _create(String path, @mode_t long mode, Pointer info) { + return fs.create(path, mode, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _ftruncate(String path, @off_t long offset, @In Pointer info) { + return fs.ftruncate(path, offset, new StructFuseFileInfo(info, path)); + } + + @Override + public final int _fgetattr(String path, Pointer stat, Pointer info) { + return fs.fgetattr(path, + fs.defaultStat(new StructStat(stat, path)), + new StructFuseFileInfo(info, path)); + } + + @Override + public final int _lock(String path, Pointer info, int cmd, Pointer flock) { + final StructFuseFileInfo fileWrapper = new StructFuseFileInfo(info, path); + final StructFlock flockWrapper = new StructFlock(flock, path); + final int result = fs.lock(path, fileWrapper, cmd, flockWrapper); + return result; + } + + @Override + public final int _utimens(String path, Pointer timebuffer) { + return fs.utimens(path, new StructTimeBuffer(timebuffer)); + } + + @Override + public final int _bmap(String path, Pointer info) { + return fs.bmap(path, new StructFuseFileInfo(info, path)); + } + + @Override + public void _ioctl(String path, int cmd, Pointer arg, Pointer fi, long flags, Pointer data) { + fs.ioctl(path, cmd, arg, new StructFuseFileInfo(fi, path), flags, data); + } + + @Override + public void _poll(String path, Pointer fi, Pointer ph, Pointer reventsp) { + fs.poll(path, new StructFuseFileInfo(fi, path), new StructFusePollHandle(ph), reventsp); + } + + @Override + public void _write_buf(String path, Pointer buf, long off, Pointer fi) { + fs.write_buf(path, new StructFuseBufvec(buf), off, new StructFuseFileInfo(fi, path)); + } + + @Override + public void _read_buf(String path, Pointer bufp, long size, long off, Pointer fi) { + fs.read_buf(path, bufp, size, off, new StructFuseFileInfo(fi, path)); + } + + @Override + public void _flock(String path, Pointer fi, int op) { + fs.flock(path, new StructFuseFileInfo(fi, path), op); + } + + @Override + public void _fallocate(String path, int mode, long off, long length, Pointer fi) { + fs.fallocate(path, mode, off, length, new StructFuseFileInfo(fi, path)); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/Fuse.java b/src/main/java/co/paralleluniverse/fuse/Fuse.java new file mode 100644 index 0000000..0954d11 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/Fuse.java @@ -0,0 +1,210 @@ +package co.paralleluniverse.fuse; + +import java.io.IOException; +import java.nio.file.AccessDeniedException; +import java.nio.file.Files; +import java.nio.file.NotDirectoryException; +import java.nio.file.Path; +import java.util.Random; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; +import jnr.ffi.Struct; + +public final class Fuse { + private static final class MountThread extends Thread { + private Integer result = null; + private final String[] args; + private final StructFuseOperations operations; + private final LibFuse fuse; + private final Path mountPoint; + + private MountThread(String filesystemName, LibFuse fuse, String[] args, Path mountPoint, StructFuseOperations operations) { + super(filesystemName + "-fuse"); + this.fuse = fuse; + this.args = args; + this.mountPoint = mountPoint; + this.operations = operations; + } + + private Integer getResult() { + return result; + } + + @Override + public void run() { + result = fuse.fuse_main_real(args.length, args, operations, Struct.size(operations), null); + } + } + + private static LibFuse libFuse = null; + private static long initTime; + private static final Lock initLock = new ReentrantLock(); + private static final Random defaultFilesystemRandom = new Random(); + private static final long errorSleepDuration = 750; + private static String fusermount = "fusermount"; + private static String umount = "umount"; + private static int currentUid = 0; + private static int currentGid = 0; + private static final ConcurrentMap mountedFs = new ConcurrentHashMap<>(); + + static void destroyed(FuseFilesystem fuseFilesystem) { + if (handleShutdownHooks()) { + try { + Runtime.getRuntime().removeShutdownHook(fuseFilesystem.getUnmountHook()); + } catch (IllegalStateException e) { + // Already shutting down; this is fine and expected, ignore the exception. + } + } + } + + static StructFuseContext getFuseContext() { + return init().fuse_get_context(); + } + + static int getGid() { + return currentGid; + } + + static long getInitTime() { + init(); + return initTime; + } + + static int getUid() { + return currentUid; + } + + private static boolean handleShutdownHooks() { + final SecurityManager security = System.getSecurityManager(); + if (security == null) { + return true; + } + try { + security.checkPermission(new RuntimePermission("shutdownHooks")); + return true; + } catch (SecurityException e) { + return false; + } + } + + static LibFuse init() throws UnsatisfiedLinkError { + if (libFuse != null) + return libFuse; // No need to lock if everything is fine already + + initLock.lock(); + try { + if (libFuse == null) { + libFuse = Platform.fuse(); + initTime = System.currentTimeMillis(); + } + try { + currentUid = Integer.parseInt(new ProcessGobbler("id", "-u").getStdout()); + currentGid = Integer.parseInt(new ProcessGobbler("id", "-g").getStdout()); + } catch (Exception e) { + // Oh well, keep default values + } + return libFuse; + } finally { + initLock.unlock(); + } + } + + public static void mount(FuseFilesystem filesystem, Path mountPoint, boolean blocking, boolean debug) throws IOException { + mountPoint = mountPoint.toAbsolutePath().normalize().toRealPath(); + if (!Files.isDirectory(mountPoint)) + throw new NotDirectoryException(mountPoint.toString()); + + if (!Files.isReadable(mountPoint) || !Files.isWritable(mountPoint) || !Files.isExecutable(mountPoint)) + throw new AccessDeniedException(mountPoint.toString()); + + final Logger logger = filesystem.getLogger(); + if (logger != null) + filesystem = new LoggedFuseFilesystem(filesystem, logger); + + filesystem.mount(mountPoint, blocking); + + final String filesystemName = filesystem.getFuseName(); + final String[] options = filesystem.getOptions(); + final String[] argv; + if (options == null) + argv = new String[debug ? 4 : 3]; + else { + argv = new String[3 + options.length]; + System.arraycopy(options, 0, argv, 2, options.length); + } + argv[0] = filesystemName; + argv[1] = "-f"; + if (debug) + argv[2] = "-d"; + argv[argv.length - 1] = mountPoint.toString(); + final LibFuse fuse = init(); + final StructFuseOperations operations = new StructFuseOperations(jnr.ffi.Runtime.getRuntime(fuse), filesystem); + + if (handleShutdownHooks()) + Runtime.getRuntime().addShutdownHook(filesystem.getUnmountHook()); + + mountedFs.put(mountPoint, filesystem); + + final Integer result; + if (blocking) + result = fuse.fuse_main_real(argv.length, argv, operations, Struct.size(operations), null); + else { + final MountThread mountThread = new MountThread(filesystemName, fuse, argv, mountPoint, operations); + mountThread.start(); + try { + mountThread.join(errorSleepDuration); + } catch (final InterruptedException e) { + } + result = mountThread.getResult(); + } + if (result != null && result != 0) + throw new FuseException(result); + } + + static void unmount(FuseFilesystem fuseFilesystem) throws IOException { + fuseFilesystem.unmount(); + final Path mountPoint = fuseFilesystem.getMountPoint(); + + final FuseFilesystem fs = mountedFs.remove(mountPoint); + assert fs == null || fs == fuseFilesystem; + + unmount(mountPoint); + } + + public static void setFusermount(String fusermount) { + Fuse.fusermount = fusermount; + } + + public static void setUmount(String umount) { + Fuse.umount = umount; + } + + /** + * Try to unmount an existing FUSE mountpoint. NOTE: You should use {@link FuseFilesystem#unmount FuseFilesystem.unmount()} + * for unmounting the FuseFilesystem (or let the shutdown hook take care unmounting during shutdown of the application). + * This method is available for special cases, e.g. where mountpoints were left over from previous invocations and need to + * be unmounted before the filesystem can be mounted again. + * + * @param mountPoint The location where the filesystem is mounted. + * @throws IOException thrown if an error occurs while starting the external process. + */ + public static void unmount(Path mountPoint) throws IOException { + final FuseFilesystem fs = mountedFs.get(mountPoint); + if (fs != null) { + unmount(fs); + return; + } + ProcessGobbler process; + try { + process = new ProcessGobbler(Fuse.fusermount, "-z", "-u", mountPoint.toString()); + } catch (IOException e) { + process = new ProcessGobbler(Fuse.umount, mountPoint.toString()); + } + final int res = process.getReturnCode(); + if (res != 0) + throw new FuseException(res); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/FuseBufFlags.java b/src/main/java/co/paralleluniverse/fuse/FuseBufFlags.java new file mode 100644 index 0000000..62359b1 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/FuseBufFlags.java @@ -0,0 +1,56 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.util.EnumMapper; + +/** + * Buffer flags + * + * @author Sergey Tselovalnikov + * @since 02.06.15 + */ +enum FuseBufFlags implements EnumMapper.IntegerEnum { + /** + * Buffer contains a file descriptor + *

+ * If this flag is set, the .fd field is valid, otherwise the + * .mem fields is valid. + */ + FUSE_BUF_IS_FD(1 << 1), + + /** + * Seek on the file descriptor + *

+ * If this flag is set then the .pos field is valid and is + * used to seek to the given offset before performing + * operation on file descriptor. + */ + FUSE_BUF_FD_SEEK(1 << 2), + + /** + * Retry operation on file descriptor + *

+ * If this flag is set then retry operation on file descriptor + * until .size bytes have been copied or an error or EOF is + * detected. + */ + FUSE_BUF_FD_RETRY(1 << 3), + + /** + * JNR does not work without null value enum + */ + NULL_VALUE(0); + + private final int value; + + FuseBufFlags(int value) { + this.value = value; + } + + /** + * Special JNR method, see jnr.ffi.util.EnumMapper#getNumberValueMethod(java.lang.Class, java.lang.Class) + */ + @Override + public int intValue() { + return value; + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/FuseException.java b/src/main/java/co/paralleluniverse/fuse/FuseException.java new file mode 100644 index 0000000..1f3c0a6 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/FuseException.java @@ -0,0 +1,11 @@ +package co.paralleluniverse.fuse; + +import java.io.IOException; + +final class FuseException extends IOException { + private static long serialVersionUID = -3323428017667312747L; + + FuseException(int returnCode) { + super("FUSE returned error code " + returnCode); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/FuseFilesystem.java b/src/main/java/co/paralleluniverse/fuse/FuseFilesystem.java new file mode 100644 index 0000000..17bd728 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/FuseFilesystem.java @@ -0,0 +1,767 @@ +package co.paralleluniverse.fuse; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import jnr.ffi.Pointer; +import jnr.ffi.types.gid_t; +import jnr.ffi.types.pid_t; +import jnr.ffi.types.uid_t; + +/** + * Fuse file system. + * All documentation from "fuse.h" + * + * @see http://fuse.sourceforge.net/doxygen/index.html + * @see http://fuse.sourceforge.net/wiki/ + */ +public abstract class FuseFilesystem { + private static final String defaultFilesystemName = "userfs-"; + private static final Pattern regexNormalizeFilesystemName = Pattern.compile("[^a-zA-Z]"); + + /** + * Perform destroy-time cleanup. Takes two {@link FuseFilesystem}s arguments which should be equal in most cases, but may + * not in the case of a wrapped filesystem object for logging ({@link LoggedFuseFilesystem}). + * + * @param mountedFilesystem The {@link FuseFilesystem} object that is actually mounted (the one receiving the destroy call) + * @param userFilesystem The {@link FuseFilesystem} that the user believes is mounted (the one that the user called .mount on) + */ + final static void _destroy(FuseFilesystem mountedFilesystem, FuseFilesystem userFilesystem) { + final Path oldMountPoint; + mountedFilesystem.mountLock.lock(); + userFilesystem.mountLock.lock(); + try { + if (!mountedFilesystem.isMounted()) + throw new IllegalStateException("destroy called on a non-mounted filesystem"); + + oldMountPoint = mountedFilesystem.mountPoint; + Fuse.destroyed(mountedFilesystem); + userFilesystem.mountPoint = null; + mountedFilesystem.mountPoint = null; + } finally { + userFilesystem.mountLock.unlock(); + mountedFilesystem.mountLock.unlock(); + } + mountedFilesystem.afterUnmount(oldMountPoint); + } + + private final ReentrantLock mountLock = new ReentrantLock(); + private final AutoUnmountHook unmountHook = new AutoUnmountHook(); + private volatile Path mountPoint = null; + private Logger logger = null; + + protected abstract void afterUnmount(Path mountPoint); + + protected abstract void beforeMount(Path mountPoint); + + public final boolean isMounted() { + return getMountPoint() != null; + } + + void _destroy() { + _destroy(this, this); + } + + /** + * Returns the raw fuse_context structure. Only valid when called while a filesystem operation is taking place. + * + * @return The fuse_context structure by reference. + */ + protected final StructFuseContext getFuseContext() { + if (!isMounted()) + throw new IllegalStateException("Cannot get FUSE context if the filesystem is not mounted."); + + return Fuse.getFuseContext(); + } + + /** + * Returns the gid field of the fuse context. Only valid when called while a filesystem operation is taking place. + * + * @return The group ID of the process executing an operation on this filesystem. + */ + protected @gid_t long getFuseContextGid() { + return getFuseContext().gid.get(); + } + + /** + * Returns the pid field of the fuse context. Only valid when called while a filesystem operation is taking place. + * + * @return The process ID of the process executing an operation on this filesystem. + */ + protected @pid_t long getFuseContextPid() { + return getFuseContext().pid.get(); + } + + /** + * Returns the uid field of the fuse context. Only valid when called while a filesystem operation is taking place. + * + * @return The user ID of the user running the process executing an operation of this filesystem. + */ + protected @uid_t long getFuseContextUid() { + return getFuseContext().uid.get(); + } + + final String getFuseName() { + String name = getName(); + if (name == null) + return defaultFilesystemName; + + name = regexNormalizeFilesystemName.matcher(name).replaceAll(""); + if (name.isEmpty()) + return defaultFilesystemName; + + return name.toLowerCase(); + } + + protected final Logger getLogger() { + return logger; + } + + public final Path getMountPoint() { + return this.mountPoint; + } + + protected abstract String getName(); + + protected abstract String[] getOptions(); + + final AutoUnmountHook getUnmountHook() { + return unmountHook; + } + + /** + * Populates a {@link StatWrapper} with somewhat-sane, usually-better-than-zero values. Subclasses may override this to + * customize the default parameters applied to the stat structure, or to prevent such behavior in the first place (by + * overriding this method with an empty one). + * + * @param stat The StructStat object to write to. + * @param uid The UID under which the JVM is running. + * @param gid The GID under which the JVM is running. + */ + protected StructStat defaultStat(StructStat stat) { + // Set some sensible defaults + stat.mode(0).setAllTimesMillis(Fuse.getInitTime()).nlink(1).uid(Fuse.getUid()).gid(Fuse.getGid()); + return stat; + } + + public final FuseFilesystem log(boolean logging) { + return log(logging ? Logger.getLogger(getClass().getCanonicalName()) : null); + } + + public final FuseFilesystem log(Logger logger) { + mountLock.lock(); + try { + if (mountPoint != null) + throw new IllegalStateException("Cannot turn logging on/orr when filesystem is already mounted."); + this.logger = logger; + } finally { + mountLock.unlock(); + } + return this; + } + + final void mount(Path mountPoint, boolean blocking) throws IOException { + mountLock.lock(); + try { + if (isMounted()) { + mountLock.unlock(); + throw new IllegalStateException(getFuseName() + " is already mounted at " + this.mountPoint); + } + this.mountPoint = mountPoint; + } finally { + mountLock.unlock(); + } + beforeMount(mountPoint); + } + + final void unmount() throws IOException { + if (!isMounted()) + throw new IllegalStateException("Tried to unmount a filesystem which is not mounted"); + } + + final class AutoUnmountHook extends Thread { + @Override + public final void run() { + try { + Fuse.unmount(FuseFilesystem.this); + } catch (final Exception e) { + // Can't do much here in a shutdown hook. Silently ignore. + } + } + } + + //////////////////////////////// + /** + * 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. + * + * @param path + * @param stat + * @return + */ + protected abstract int getattr(String path, StructStat stat); + + /** + * 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. + * + * @param path + * @param buffer + * @param size + * @return + */ + protected abstract int readlink(String path, ByteBuffer buffer, long size); + + /** + * 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. + * + * @param path + * @param mode + * @param dev + * @return + */ + protected abstract int mknod(String path, long mode, long dev); + + /** + * 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 + * + * @param path + * @param mode + * @return + */ + protected abstract int mkdir(String path, long mode); + + /** + * Remove a file. + * + * @param path + * @return + */ + protected abstract int unlink(String path); + + /** + * Remove a directory. + * + * @param path + * @return + */ + protected abstract int rmdir(String path); + + /** + * Create a symbolic link. + * + * @param path + * @param target + * @return + */ + protected abstract int symlink(String path, String target); + + /** + * Rename a file. + * + * @param path + * @param newName + * @return + */ + protected abstract int rename(String path, String newName); + + /** + * Create a hard link to a file. + * + * @param path + * @param target + * @return + */ + protected abstract int link(String path, String target); + + /** + * Change the permission bits of a file. + * + * @param path + * @param mode + * @return + */ + protected abstract int chmod(String path, long mode); + + /** + * Change the owner and group of a file. + * + * @param path + * @param uid + * @param gid + * @return + */ + protected abstract int chown(String path, long uid, long gid); + + /** + * Change the size of a file. + * + * @param path + * @param offset + * @return + */ + protected abstract int truncate(String path, long offset); + + /** + * 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 + * + * @param path + * @param info + * @return + */ + protected abstract int open(String path, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param buffer + * @param size + * @param offset + * @param info + * @return + */ + protected abstract int read(String path, ByteBuffer buffer, long size, long offset, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param buf + * @param bufSize + * @param writeOffset + * @param info + * @return + */ + protected abstract int write(String path, ByteBuffer buf, long bufSize, long writeOffset, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param statvfs + * @return + */ + protected abstract int statfs(String path, StructStatvfs statvfs); + + /** + * 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 + * + * @param path + * @param info + * @return + */ + protected abstract int flush(String path, StructFuseFileInfo 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 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 + * + * @param path + * @param info + * @return + */ + protected abstract int release(String path, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param datasync + * @param info + * @return + */ + protected abstract int fsync(String path, int datasync, StructFuseFileInfo info); + + /** + * Set extended attribute. + * + * Set the attribute NAME of the file pointed to by PATH to VALUE (which + * is SIZE bytes long). + * + * @param flags see {@link XAttrConstants} + * @return Return 0 on success, -1 for errors. + */ + protected abstract int setxattr(String path, String xattr, ByteBuffer value, long size, int flags, int position); + + /** + * Get extended attribute. + * + * Get the attribute NAME of the file pointed to by PATH to VALUE (which is + * SIZE bytes long). + * + * @return Return 0 on success, -1 for errors. + */ + protected abstract int getxattr(String path, String xattr, XattrFiller filler, long size, long position); + + /** + * List extended attributes. + *

+ * The retrieved list is placed + * in list, a caller-allocated buffer whose size (in bytes) is specified + * in the argument size. The list is the set of (null-terminated) + * names, one after the other. Names of extended attributes to which + * the calling process does not have access may be omitted from the + * list. The length of the attribute name list is returned + * + * @param path + * @param filler + * @return + */ + protected abstract int listxattr(String path, XattrListFiller filler); + + /** + * Remove extended attributes. + * Remove the attribute NAME from the file pointed to by PATH. + * + * @param path + * @param xattr + * @return 0 on success, -1 for errors. + */ + protected abstract int removexattr(String path, String xattr); + + /** + * 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 + * + * @param path + * @param info + * @return + */ + protected abstract int opendir(String path, StructFuseFileInfo info); + + /** + * 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. + * + *
  3. 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'.
  4. + *
+ * Introduced in version 2.3 + * + * @param path + * @param filler + * @return + */ + protected abstract int readdir(String path, StructFuseFileInfo info, DirectoryFiller filler); + + /** + * Release directory. + *

+ * Introduced in version 2.3 + * + * @param path + * @param info + * @return + */ + protected abstract int releasedir(String path, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param datasync + * @param info + * @return + */ + protected abstract int fsyncdir(String path, int datasync, StructFuseFileInfo info); + + /** + * 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 + */ + protected abstract void init(); + + /** + * Clean up filesystem + *

+ * Called on filesystem exit. + *

+ * Introduced in version 2.3 + */ + protected abstract void destroy(); + + /** + * 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 + * + * @param mask see {@link AccessConstants} + * @return -ENOENT if the path doesn't exist, -EACCESS if the requested permission isn't available, or 0 for success + * @return + */ + protected abstract int access(String path, int access); + + /** + * 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 + * + * @param path + * @param mode + * @param info + * @return + */ + protected abstract int create(String path, long mode, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param offset + * @param info + * @return + */ + protected abstract int ftruncate(String path, long offset, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param stat + * @param info + * @return + */ + protected abstract int fgetattr(String path, StructStat stat, StructFuseFileInfo info); + + /** + * 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 + * + * @param path + * @param info + * @param command + * @param flock + * @return + */ + protected abstract int lock(String path, StructFuseFileInfo info, int command, StructFlock flock); + + /** + * 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 + * + * @param path + * @param timeBuffer + * @return + */ + protected abstract int utimens(String path, StructTimeBuffer timeBuffer); + + /** + * 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 + * + * @param path + * @param info + * @return + */ + protected abstract int bmap(String path, StructFuseFileInfo info); + + /** + * 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. + * + * @param flags See { + * @IoctlFlags} + */ + protected abstract int ioctl(String path, int cmd, Pointer arg, StructFuseFileInfo fi, long 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. + * + * @param reventsp A pointer to a bitmask of the returned events satisfied. + */ + protected abstract int poll(String path, StructFuseFileInfo fi, StructFusePollHandle 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. + */ + protected abstract int write_buf(String path, StructFuseBufvec buf, long off, StructFuseFileInfo 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. + */ + protected abstract int read_buf(String path, Pointer bufp, long size, long off, StructFuseFileInfo 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. + * + * @param op see {@link ru.serce.jnrfuse.struct.Flock} + */ + protected abstract int flock(String path, StructFuseFileInfo 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. + */ + protected abstract int fallocate(String path, int mode, long off, long length, StructFuseFileInfo fi); +} diff --git a/src/main/java/co/paralleluniverse/fuse/IoctlFlags.java b/src/main/java/co/paralleluniverse/fuse/IoctlFlags.java new file mode 100644 index 0000000..12a1d5d --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/IoctlFlags.java @@ -0,0 +1,39 @@ +package co.paralleluniverse.fuse; + +/** + * @author Sergey Tselovalnikov + * @since 05.06.15 + */ +final class IoctlFlags { + + // flags + public static final int _IOC_NONE = 0; + public static final int _IOC_READ = 2; + public static final int _IOC_WRITE = 1; + + + // macros + public static final int _IOC_SIZEBITS = 14; + public static final int _IOC_TYPEBITS = 8; + public static final int _IOC_NRBITS = 8; + public static final int _IOC_NRSHIFT = 0; + + public static int _IOC_TYPESHIFT() { + return (_IOC_NRSHIFT + _IOC_NRBITS); + } + + public static int _IOC_SIZESHIFT() { + return (_IOC_TYPESHIFT() + _IOC_TYPEBITS); + } + + public static int _IOC_SIZEMASK() { + return ((1 << _IOC_SIZEBITS) - 1); + } + + public static int _IOC_SIZE(int nr) { + return (((nr) >> _IOC_SIZESHIFT()) & _IOC_SIZEMASK()); + } + + private IoctlFlags() { + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/JNRUtil.java b/src/main/java/co/paralleluniverse/fuse/JNRUtil.java new file mode 100644 index 0000000..9af5a38 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/JNRUtil.java @@ -0,0 +1,38 @@ +package co.paralleluniverse.fuse; + +import com.kenai.jffi.MemoryIO; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import jnr.ffi.Pointer; +import jnr.ffi.StructLayout; + +final class JNRUtil { + public static ByteBuffer toByteBuffer(Pointer pointer, long capacity) { + if (capacity > Integer.MAX_VALUE) + throw new IllegalArgumentException("Capacity too big: " + capacity); + return MemoryIO.getInstance().newDirectByteBuffer(pointer.address(), (int) capacity); + } + + public static String toString(StructLayout layout, Pointer p) { + StringBuilder sb = new StringBuilder(); + Field[] fields = layout.getClass().getDeclaredFields(); + sb.append(layout.getClass().getSimpleName()).append(" { \n"); + final String fieldPrefix = " "; + for (Field field : fields) { + try { + sb.append(fieldPrefix) + .append(field.getType().getSimpleName()).append(' ').append(field.getName()).append(": ") + .append(field.getType().getMethod("toString", Pointer.class).invoke(p)) + .append('\n'); + } catch (Throwable ex) { + throw new RuntimeException(ex); + } + } + sb.append("}\n"); + + return sb.toString(); + } + + private JNRUtil() { + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/LibDl.java b/src/main/java/co/paralleluniverse/fuse/LibDl.java new file mode 100644 index 0000000..3b5719b --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/LibDl.java @@ -0,0 +1,9 @@ +package co.paralleluniverse.fuse; + +public interface LibDl { + public static final int RTLD_LAZY = 0x1; + public static final int RTLD_NOW = 0x2; + public static final int RTLD_GLOBAL = 0x100; + + public void dlopen(String file, int mode); +} diff --git a/src/main/java/co/paralleluniverse/fuse/LibFuse.java b/src/main/java/co/paralleluniverse/fuse/LibFuse.java new file mode 100644 index 0000000..55f356b --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/LibFuse.java @@ -0,0 +1,44 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.types.size_t; +import jnr.ffi.types.ssize_t; + +public interface LibFuse { + static interface LibFuseProbe { + } + + static interface LibMacFuseProbe extends LibFuseProbe { + String macfuse_version(); + } + + StructFuseContext fuse_get_context(); + + /** + * Main function of FUSE. + *

+ * This function does the following: + * - parses command line options (-d -s and -h) + * - passes relevant mount options to the fuse_mount() + * - installs signal handlers for INT, HUP, TERM and PIPE + * - registers an exit handler to unmount the filesystem on program exit + * - creates a fuse handle + * - registers the operations + * - calls either the single-threaded or the multi-threaded event loop + * + * @param argc the argument counter passed to the main() function + * @param argv the argument vector passed to the main() function + * @param op the file system operation + * @param user_data user data supplied in the context during the init() method + * @return 0 on success, nonzero on failure + */ + int fuse_main_real(int argc, String[] argv, StructFuseOperations op, @size_t int size, Pointer user_data); + + @size_t long fuse_buf_size(Pointer bufv); // StructFuseBufvec + + @ssize_t long fuse_buf_copy(Pointer dstv, Pointer srcv, int flags); // StructFuseBufvec + + void fuse_pollhandle_destroy(Pointer ph); // StructFusePollhandle + + int fuse_notify_poll(Pointer ph); // StructFusePollhandle +} diff --git a/src/main/java/co/paralleluniverse/fuse/LoggedFuseFilesystem.java b/src/main/java/co/paralleluniverse/fuse/LoggedFuseFilesystem.java new file mode 100644 index 0000000..85c604c --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/LoggedFuseFilesystem.java @@ -0,0 +1,538 @@ +package co.paralleluniverse.fuse; + +import java.nio.ByteBuffer; +import java.nio.file.Path; +import java.util.logging.Level; +import java.util.logging.Logger; +import jnr.ffi.Pointer; + +final class LoggedFuseFilesystem extends FuseFilesystem { + private static interface LoggedMethod { + public T invoke(); + } + + private static interface LoggedVoidMethod { + public void invoke(); + } + + private static final String methodSuccess = "Method succeeded."; + private static final String methodFailure = "Exception thrown: "; + private static final String methodResult = " Result: "; + private final String className; + private final Logger logger; + private final FuseFilesystem filesystem; + + LoggedFuseFilesystem(FuseFilesystem filesystem, Logger logger) { + this.filesystem = filesystem; + this.logger = logger; + className = filesystem.getClass().getName(); + } + + private void log(final String methodName, final LoggedVoidMethod method) { + log(methodName, method, null, (Object[]) null); + } + + private void log(final String methodName, final LoggedVoidMethod method, final String path, final Object... args) { + try { + logger.entering(className, methodName, args); + method.invoke(); + logger.logp(Level.INFO, className, methodName, (path == null ? "" : "[" + path + "] ") + methodSuccess, args); + logger.exiting(className, methodName, args); + } catch (Throwable e) { + logException(e, methodName, null, args); + } + } + + private T log(String methodName, T defaultValue, LoggedMethod method) { + return log(methodName, defaultValue, method, null, (Object[]) null); + } + + private T log(String methodName, T defaultValue, LoggedMethod method, String path, Object... args) { + try { + logger.entering(className, methodName, args); + final T result = method.invoke(); + logger.logp(Level.INFO, className, methodName, (path == null ? "" : "[" + path + "] ") + methodSuccess + methodResult + result, args); + logger.exiting(className, methodName, args); + return result; + } catch (Throwable e) { + return logException(e, methodName, defaultValue, args); + } + } + + private T logException(Throwable e, String methodName, T defaultValue, Object... args) { + final StackTraceElement[] stack = e.getStackTrace(); + final StringBuilder builder = new StringBuilder(); + for (StackTraceElement element : stack) + builder.append('\n').append(element); + + logger.logp(Level.SEVERE, className, methodName, methodFailure + e + builder.toString(), args); + return defaultValue; + } + + @Override + public final void _destroy() { + destroy(); + _destroy(this, filesystem); + } + + @Override + public int access(final String path, final int access) { + return log("access", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.access(path, access); + } + }, path, access); + } + + @Override + public void afterUnmount(final Path mountPoint) { + log("afterUnmount", new LoggedVoidMethod() { + @Override + public void invoke() { + filesystem.afterUnmount(mountPoint); + } + }, mountPoint.toString()); + } + + @Override + public void beforeMount(final Path mountPoint) { + log("beforeMount", new LoggedVoidMethod() { + @Override + public void invoke() { + filesystem.beforeMount(mountPoint); + } + }, mountPoint.toString()); + } + + @Override + public int bmap(final String path, final StructFuseFileInfo info) { + return log("bmap", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.bmap(path, info); + } + }, path, info); + } + + @Override + public int chmod(final String path, final long mode) { + return log("chmod", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.chmod(path, mode); + } + }, path, mode); + } + + @Override + public int chown(final String path, final long uid, final long gid) { + return log("chown", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.chown(path, uid, gid); + } + }, path, uid, gid); + } + + @Override + public int create(final String path, final long mode, final StructFuseFileInfo info) { + return log("create", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.create(path, mode, info); + } + }, path, mode, info); + } + + @Override + public void destroy() { + log("destroy", new LoggedVoidMethod() { + @Override + public void invoke() { + filesystem.destroy(); + } + }); + } + + @Override + public int fgetattr(final String path, final StructStat stat, final StructFuseFileInfo info) { + return log("fgetattr", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.fgetattr(path, stat, info); + } + }, path, stat); + } + + @Override + public int flush(final String path, final StructFuseFileInfo info) { + return log("flush", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.flush(path, info); + } + }, path, info); + } + + @Override + public int fsync(final String path, final int datasync, final StructFuseFileInfo info) { + return log("fsync", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.fsync(path, datasync, info); + } + }, path, info); + } + + @Override + public int fsyncdir(final String path, final int datasync, final StructFuseFileInfo info) { + return log("fsyncdir", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.fsyncdir(path, datasync, info); + } + }, path, info); + } + + @Override + public int ftruncate(final String path, final long offset, final StructFuseFileInfo info) { + return log("ftruncate", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.ftruncate(path, offset, info); + } + }, path, offset, info); + } + + @Override + public int getattr(final String path, final StructStat stat) { + return log("getattr", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.getattr(path, stat); + } + }, path, stat); + } + + @Override + protected String getName() { + return log("getName", null, new LoggedMethod() { + @Override + public String invoke() { + return filesystem.getName(); + } + }); + } + + @Override + protected String[] getOptions() { + return log("getOptions", null, new LoggedMethod() { + @Override + public String[] invoke() { + return filesystem.getOptions(); + } + }); + } + + @Override + public int getxattr(final String path, final String xattr, final XattrFiller filler, final long size, final long position) { + return log("getxattr", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.getxattr(path, xattr, filler, size, position); + } + }, path, xattr, filler, size, position); + } + + @Override + public void init() { + log("init", new LoggedVoidMethod() { + @Override + public void invoke() { + filesystem.init(); + } + }); + } + + @Override + public int link(final String path, final String target) { + return log("link", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.link(path, target); + } + }, path, target); + } + + @Override + public int listxattr(final String path, final XattrListFiller filler) { + return log("listxattr", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.listxattr(path, filler); + } + }, path, filler); + } + + @Override + public int lock(final String path, final StructFuseFileInfo info, final int command, final StructFlock flock) { + return log("lock", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.lock(path, info, command, flock); + } + }, path, info, command, flock); + } + + @Override + public int mkdir(final String path, final long mode) { + return log("mkdir", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.mkdir(path, mode); + } + }, path, mode); + } + + @Override + public int mknod(final String path, final long mode, final long dev) { + return log("mknod", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.mknod(path, mode, dev); + } + }, path, mode, dev); + } + + @Override + public int open(final String path, final StructFuseFileInfo info) { + return log("open", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.open(path, info); + } + }, path, info); + } + + @Override + public int opendir(final String path, final StructFuseFileInfo info) { + return log("opendir", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.opendir(path, info); + } + }, path, info); + } + + @Override + public int read(final String path, final ByteBuffer buffer, final long size, final long offset, final StructFuseFileInfo info) { + return log("read", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.read(path, buffer, size, offset, info); + } + }, path, buffer, size, offset, info); + } + + @Override + public int readdir(final String path, final StructFuseFileInfo info, final DirectoryFiller filler) { + return log("readdir", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.readdir(path, info, filler); + } + }, path, filler); + } + + @Override + public int readlink(final String path, final ByteBuffer buffer, final long size) { + return log("readlink", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.readlink(path, buffer, size); + } + }, path, buffer, size); + } + + @Override + public int release(final String path, final StructFuseFileInfo info) { + return log("release", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.release(path, info); + } + }, path, info); + } + + @Override + public int releasedir(final String path, final StructFuseFileInfo info) { + return log("releasedir", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.releasedir(path, info); + } + }, path, info); + } + + @Override + public int removexattr(final String path, final String xattr) { + return log("removexattr", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.removexattr(path, xattr); + } + }, path, xattr); + } + + @Override + public int rename(final String path, final String newName) { + return log("rename", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.rename(path, newName); + } + }); + } + + @Override + public int rmdir(final String path) { + return log("rmdir", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.rmdir(path); + } + }, path); + } + + @Override + public int setxattr(final String path, final String name, final ByteBuffer buf, final long size, final int flags, final int position) { + return log("setxattr", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.setxattr(path, name, buf, size, flags, position); + } + }, path, name, buf, size, flags, position); + } + + @Override + public int statfs(final String path, final StructStatvfs statvfs) { + return log("statfs", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.statfs(path, statvfs); + } + }, path, statvfs); + } + + @Override + public int symlink(final String path, final String target) { + return log("symlink", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.symlink(path, target); + } + }, path, target); + } + + @Override + public int truncate(final String path, final long offset) { + return log("truncate", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.truncate(path, offset); + } + }, path, offset); + } + + @Override + public int unlink(final String path) { + return log("unlink", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.unlink(path); + } + }, path); + } + + @Override + public int utimens(final String path, final StructTimeBuffer timeBuffer) { + return log("utimens", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.utimens(path, timeBuffer); + } + }, path, timeBuffer); + } + + @Override + public int write(final String path, final ByteBuffer buf, final long bufSize, final long writeOffset, final StructFuseFileInfo wrapper) { + return log("write", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.write(path, buf, bufSize, writeOffset, wrapper); + } + }, path, buf, bufSize, writeOffset, wrapper); + } + + @Override + protected int ioctl(final String path, final int cmd, final Pointer arg, final StructFuseFileInfo fi, final long flags, final Pointer data) { + return log("ioctl", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.ioctl(path, cmd, arg, fi, flags, data); + } + }, path, cmd, arg, fi, flags, data); + } + + @Override + protected int poll(final String path, final StructFuseFileInfo fi, final StructFusePollHandle ph, final Pointer reventsp) { + return log("poll", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.poll(path, fi, ph, reventsp); + } + }, path, fi, ph, reventsp); + } + + @Override + protected int write_buf(final String path, final StructFuseBufvec buf, final long off, final StructFuseFileInfo fi) { + return log("write_buf", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.write_buf(path, buf, off, fi); + } + }, path, buf, off, fi); + } + + @Override + protected int read_buf(final String path, final Pointer bufp, final long size, final long off, final StructFuseFileInfo fi) { + return log("read_buf", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.read_buf(path, bufp, size, off, fi); + } + }, path, bufp, size, off, fi); + } + + @Override + protected int flock(final String path, final StructFuseFileInfo fi, final int op) { + return log("flock", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.flock(path, fi, op); + } + }, path, fi, op); + } + + @Override + protected int fallocate(final String path, final int mode, final long off, final long length, final StructFuseFileInfo fi) { + return log("fallocate", 0, new LoggedMethod() { + @Override + public Integer invoke() { + return filesystem.fallocate(path, mode, off, length, fi); + } + }, path, mode, off, length, fi); + } + +} diff --git a/src/main/java/co/paralleluniverse/fuse/Platform.java b/src/main/java/co/paralleluniverse/fuse/Platform.java new file mode 100644 index 0000000..01b6b2d --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/Platform.java @@ -0,0 +1,99 @@ +package co.paralleluniverse.fuse; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import jnr.ffi.LibraryLoader; + +import co.paralleluniverse.fuse.LibFuse.LibFuseProbe; +import co.paralleluniverse.fuse.LibFuse.LibMacFuseProbe; + +import jnr.ffi.Platform.CPU; + +final class Platform { + public static enum PlatformEnum { + LINUX_X86_64, LINUX_I686, LINUX_PPC, MAC, MAC_MACFUSE, FREEBSD, LINUX_ARM + } + + private static final String[] osxFuseLibraries = {"fuse4x", "osxfuse", "macfuse", "fuse"}; + private static PlatformEnum platform = null; + private static LibFuse libFuse = null; + private static final Lock initLock = new ReentrantLock(); + + static final LibFuse fuse() { + if (libFuse == null) + init(); + return libFuse; + } + + private static void init() { + if (libFuse != null) + return; + initLock.lock(); + try { + final jnr.ffi.Platform p = jnr.ffi.Platform.getNativePlatform(); + // Need to recheck + if (libFuse == null) { + switch (p.getOS()) { + case FREEBSD: + platform = PlatformEnum.FREEBSD; + libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("fuse"); + break; + case DARWIN: + // First, need to load iconv + final LibDl dl = LibraryLoader.create(LibDl.class).failImmediately().load("iconv"); + dl.dlopen("iconv", LibDl.RTLD_LAZY | LibDl.RTLD_GLOBAL); + libFuse = null; + LibFuseProbe probe; + for (String library : osxFuseLibraries) { + try { + // Regular FUSE-compatible fuse library + platform = PlatformEnum.MAC; + libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load(library); + break; + } catch (Throwable e) { + // Carry on + } + try { + probe = LibraryLoader.create(LibMacFuseProbe.class).failImmediately().load(library); + ((LibMacFuseProbe) probe).macfuse_version(); + // MacFUSE-compatible fuse library + platform = PlatformEnum.MAC_MACFUSE; + libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("fuse"); + break; + } catch (Throwable e) { + // Carry on + } + } + if (libFuse == null) { + // Everything failed. Do a last-ditch attempt. + // Worst-case scenario, this causes an exception + // which will be more meaningful to the user than a NullPointerException on libFuse. + libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("fuse"); + } + break; + default: + if (p.getCPU() == CPU.X86_64) + platform = PlatformEnum.LINUX_X86_64; + else if(p.getCPU() == CPU.I386) + platform = PlatformEnum.LINUX_I686; + else if(p.getCPU() == CPU.ARM) + platform = PlatformEnum.LINUX_ARM; + else + platform = PlatformEnum.LINUX_PPC; + + libFuse = LibraryLoader.create(LibFuse.class).failImmediately().load("fuse"); + break; + } + } + } finally { + initLock.unlock(); + } + } + + public static final PlatformEnum platform() { + if (platform == null) + init(); + + return platform; + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/ProcessGobbler.java b/src/main/java/co/paralleluniverse/fuse/ProcessGobbler.java new file mode 100644 index 0000000..7114b48 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/ProcessGobbler.java @@ -0,0 +1,83 @@ +package co.paralleluniverse.fuse; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +/** + * Run a process, grab all from stdout, return that. + */ +final class ProcessGobbler { + private static final class Gobbler extends Thread { + private final InputStream stream; + private String contents = null; + private boolean failed = false; + + Gobbler(InputStream stream) { + this.stream = stream; + start(); + } + + private String getContents() { + if (failed) + return null; + + return contents; + } + + @Override + public final void run() { + final BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); + final StringBuilder contents = new StringBuilder(); + String line; + try { + while ((line = reader.readLine()) != null) { + contents.append(line); + } + } catch (IOException e) { + failed = true; + return; + } + this.contents = contents.toString(); + } + } + + private final Process process; + private final Gobbler stdout; + private final Gobbler stderr; + private Integer returnCode = null; + + ProcessGobbler(String... args) throws IOException { + process = new ProcessBuilder(args).start(); + stdout = new Gobbler(process.getInputStream()); + stderr = new Gobbler(process.getErrorStream()); + } + + int getReturnCode() { + try { + returnCode = process.waitFor(); + } catch (InterruptedException e) { + // Too bad + } + return returnCode; + } + + String getStderr() { + try { + stderr.join(); + } catch (InterruptedException e) { + return null; + } + return stderr.getContents(); + } + + String getStdout() { + try { + stdout.join(); + } catch (InterruptedException e) { + return null; + } + return stdout.getContents(); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFlock.java b/src/main/java/co/paralleluniverse/fuse/StructFlock.java new file mode 100644 index 0000000..15d6c8b --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFlock.java @@ -0,0 +1,128 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.StructLayout; + +public class StructFlock { + public static final int CMD_GETLK = 5; + public static final int CMD_SETLK = 6; + public static final int CMD_SETLKW = 7; // set lock write + + public static final int LOCK_SH = 1; // Shared lock. + public static final int LOCK_EX = 2; // Exclusive lock. + public static final int LOCK_UN = 8; // Unlock. + + // lock types + public static final int F_RDLCK = 0; // Read lock. + public static final int F_WRLCK = 1; // Write lock + public static final int F_UNLCK = 2; // Remove lock + + public static final int SEEK_SET = 0; // Offset is calculated from the start of the file. + public static final int SEEK_CUR = 1; // Offset is calculated from the current position in the file. + public static final int SEEK_END = 2; // Offset is calculated from the end of the file. + + private static final class Layout extends StructLayout { + public final int16_t l_type; // Type of lock: F_RDLCK, F_WRLCK, or F_UNLCK. + public final int16_t l_whence; // Where `l_start' is relative to (like `lseek') -- SEEK_SET/CUR/END + public final off_t l_start; // Offset where the lock begins. + public final off_t l_len; // Size of the locked area; zero means until EOF. + public final pid_t l_pid; // Process holding the lock + public final int32_t l_sysid; + + private Layout(Runtime runtime) { + super(runtime); + + switch (Platform.platform()) { + case FREEBSD: { + this.l_start = new off_t(); + this.l_len = new off_t(); + this.l_pid = new pid_t(); + this.l_type = new int16_t(); + this.l_whence = new int16_t(); + this.l_sysid = new int32_t(); + break; + } + default: { + this.l_type = new int16_t(); + this.l_whence = new int16_t(); + this.l_start = new off_t(); + this.l_len = new off_t(); + this.l_pid = new pid_t(); + this.l_sysid = null; + } + } + } + } + + private static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + private final String path; + + StructFlock(Pointer p, String path) { + this.p = p; + this.path = path; + } + + public final int type() { + return (int) layout.l_type.get(p); + } + + public final StructFlock type(int l_type) { + layout.l_type.set(p, l_type); + return this; + } + + public final long len() { + return layout.l_len.get(p); + } + + public final StructFlock len(long l_len) { + layout.l_len.set(p, l_len); + return this; + } + + public final long pid() { + return layout.l_pid.get(p); + } + + public final StructFlock pid(long l_pid) { + layout.l_pid.set(p, l_pid); + return this; + } + + public final long start() { + return layout.l_start.get(p); + } + + public final StructFlock start(long l_start) { + layout.l_start.set(p, l_start); + return this; + } + + public final long sysid() { + return layout.l_sysid != null ? layout.l_sysid.get(p) : -1L; + } + + public final StructFlock sysid(long l_sysid) { + layout.l_sysid.set(p, l_sysid); + return this; + } + + public final long whence() { + return layout.l_whence.get(p); + } + + public final StructFlock whence(long l_whence) { + layout.l_whence.set(p, l_whence); + return this; + } + + @Override + public final java.lang.String toString() { + if (path != null) + return path + "\n" + JNRUtil.toString(layout, p); + return JNRUtil.toString(layout, p); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseBuf.java b/src/main/java/co/paralleluniverse/fuse/StructFuseBuf.java new file mode 100644 index 0000000..dd05bfc --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseBuf.java @@ -0,0 +1,73 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.StructLayout; +import jnr.ffi.Runtime; + +/** + * Single data buffer + *

+ * Generic data buffer for I/O, extended attributes, etc... Data may + * be supplied as a memory pointer or as a file descriptor + */ +class StructFuseBuf { + static final class Layout extends StructLayout { + /** + * Size of data in bytes + */ + public final size_t size = new size_t(); + + /** + * Buffer flags + */ + public final Enum flags = new Enum<>(FuseBufFlags.class); + + /** + * Memory pointer + *

+ * Used unless FUSE_BUF_IS_FD flag is set. + */ + public final Pointer mem = new Pointer(); + + /** + * File descriptor + *

+ * Used if FUSE_BUF_IS_FD flag is set. + */ + public final Signed32 fd = new Signed32(); + + /** + * File position + *

+ * Used if FUSE_BUF_FD_SEEK flag is set. + */ + public final off_t pos = new off_t(); + + private Layout(Runtime runtime) { + super(runtime); + } + } + static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + + public StructFuseBuf(Pointer p) { + this.p = p; + } + + public final long size() { + return layout.size.get(p); + } + + public final FuseBufFlags flags() { + return layout.flags.get(p); + } + + public final int fd() { + return layout.fd.get(p); + } + + public final long pos() { + return layout.pos.get(p); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseBufvec.java b/src/main/java/co/paralleluniverse/fuse/StructFuseBufvec.java new file mode 100644 index 0000000..01230cb --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseBufvec.java @@ -0,0 +1,66 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.*; +import jnr.ffi.Runtime; + +/** + * Data buffer vector + *

+ * An array of data buffers, each containing a memory pointer or a + * file descriptor. + *

+ * Allocate dynamically to add more than one buffer. + * + * @author Sergey Tselovalnikov + * @since 02.06.15 + */ +public class StructFuseBufvec { + private static final class Layout extends StructLayout { + public final size_t count = new size_t(); // Number of buffers in the array + public final size_t idx = new size_t(); // Index of current buffer within the array + public final size_t off = new size_t(); // Current offset within the current buffer + public final StructFuseBuf.Layout buf = inner(StructFuseBuf.layout); // Array of buffers + + private Layout(Runtime runtime) { + super(runtime); + } + } + + static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + + public StructFuseBufvec(Pointer p) { + this.p = p; + } + + public final long cout() { + return layout.count.get(p); + } + + public final long idx() { + return layout.idx.get(p); + } + + public final long off() { + return layout.off.get(p); + } + + public final StructFuseBuf buf() { + return new StructFuseBuf(p.getPointer(layout.buf.offset())); + } + + /** + * Similar to FUSE_BUFVEC_INIT macros + */ + public static void init(StructFuseBufvec buf, long size) { + layout.count.set(buf.p, 1); + layout.idx.set(buf.p, 0); + layout.off.set(buf.p, 0); + layout.buf.size.set(buf.p, size); + layout.buf.flags.set(buf.p, 0); + layout.buf.mem.set(buf.p, 0); + layout.buf.fd.set(buf.p, -1); + layout.buf.pos.set(buf.p, 0); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseConnInfo.java b/src/main/java/co/paralleluniverse/fuse/StructFuseConnInfo.java new file mode 100644 index 0000000..f94ba8d --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseConnInfo.java @@ -0,0 +1,33 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.StructLayout; +import jnr.ffi.TypeAlias; + +class StructFuseConnInfo { + static final class Layout extends StructLayout { + Layout(Runtime runtime) { + super(runtime); + } + public final int32_t proto_major = new int32_t(); + public final int32_t proto_minor = new int32_t(); + public final int32_t async_read = new int32_t(); + public final int32_t max_write = new int32_t(); + public final int32_t max_readahead = new int32_t(); + public final int32_t enable = new int32_t(); + public final int32_t want = new int32_t(); + private final Padding reserved = new Padding(getRuntime().findType(TypeAlias.int32_t), 25); + } + + static final Layout layout = new Layout(Runtime.getSystemRuntime()); + private final Pointer p; + + public StructFuseConnInfo(Pointer p) { + this.p = p; + } + + public final void setOptions(final boolean setVolumeName, final boolean caseInsensitive) { + layout.want.set(p, (setVolumeName ? 0x2 : 0x0) | (caseInsensitive ? 0x1 : 0x0)); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseContext.java b/src/main/java/co/paralleluniverse/fuse/StructFuseContext.java new file mode 100644 index 0000000..8c7cec3 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseContext.java @@ -0,0 +1,16 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Runtime; +import jnr.ffi.Struct; + +abstract class StructFuseContext extends Struct { + public final Pointer fuse = new Pointer(); + public final uid_t uid = new uid_t(); + public final gid_t gid = new gid_t(); + public final pid_t pid = new pid_t(); + public final Pointer private_data = new Pointer(); + + public StructFuseContext(Runtime runtime) { + super(runtime); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseFileInfo.java b/src/main/java/co/paralleluniverse/fuse/StructFuseFileInfo.java new file mode 100644 index 0000000..4fde651 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseFileInfo.java @@ -0,0 +1,218 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.StructLayout; + +public class StructFuseFileInfo { + private static final class Layout extends StructLayout { + + private Layout(Runtime runtime) { + super(runtime); + } + + public final Signed32 flags = new Signed32(); + public final SignedLong fh_old = new SignedLong(); + public final Signed32 writepage = new Signed32(); + public final Signed32 flags_bitfield = new Signed32(); + public final Unsigned64 fh = new Unsigned64(); + public final Unsigned64 lock_owner = new Unsigned64(); + } + private static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + private final String path; + + StructFuseFileInfo(Pointer p, String path) { + this.p = p; + this.path = path; + } + + @Override + public final java.lang.String toString() { + if (path != null) + return path + "\n" + JNRUtil.toString(layout, p); + return JNRUtil.toString(layout, p); + } + +// FuseFileInfo(final FuseFileInfo fileinfo) { +// this(null, fileinfo); +// } + public final boolean append() { + return (layout.flags.intValue(p) & O_APPEND) != 0; + } + + public final StructFuseFileInfo append(boolean append) { + layout.flags.set(p, (layout.flags.get(p) & ~O_APPEND) | (append ? O_APPEND : 0)); + return this; + } + + public final boolean create() { + return (layout.flags.get(p) & O_CREAT) != 0; + } + + public final StructFuseFileInfo create(boolean create) { + layout.flags.set(p, (layout.flags.get(p) & ~O_CREAT) | (create ? O_CREAT : 0)); + return this; + } + + public final boolean noblock() { + return (layout.flags.get(p) & O_NONBLOCK) != 0; + } + + public final StructFuseFileInfo noblock(boolean value) { + layout.flags.set(p, (layout.flags.get(p) & ~O_NONBLOCK) | (value ? O_NONBLOCK : 0)); + return this; + } + + public final boolean direct_io() { + return (layout.flags_bitfield.get(p) & BIT_DIRECT_IO) != 0; + } + + public final StructFuseFileInfo direct_io(boolean direct_io) { + if (direct_io) + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) | BIT_DIRECT_IO); + else + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) & ~BIT_DIRECT_IO); + return this; + } + + public final long fh() { + return layout.fh.get(p); + } + + public final StructFuseFileInfo fh(long fh) { + layout.fh.set(p, fh); + return this; + } + + public final long fh_old() { + return layout.fh_old.get(p); + } + + public final StructFuseFileInfo fh_old(long fh_old) { + layout.fh_old.set(p, fh_old); + return this; + } + + public final int flags() { + return layout.flags.intValue(p); + } + + public final StructFuseFileInfo flags(int flags) { + layout.flags.set(p, flags); + return this; + } + + public final boolean flockrelease() { + return (layout.flags_bitfield.get(p) & BIT_FLOCKRELEASE) != 0; + } + + public final StructFuseFileInfo flockrelease(boolean flockrelease) { + if (flockrelease) + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) | BIT_FLOCKRELEASE); + else + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) & ~BIT_FLOCKRELEASE); + return this; + } + + public final boolean flush() { + return (layout.flags_bitfield.get(p) & BIT_FLUSH) != 0; + } + + public final StructFuseFileInfo flush(boolean flush) { + if (flush) + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) | BIT_FLUSH); + else + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) & ~BIT_FLUSH); + + return this; + } + + public final boolean keep_cache() { + return (layout.flags_bitfield.get(p) & BIT_KEEP_CACHE) != 0; + } + + public final StructFuseFileInfo keep_cache(boolean keep_cache) { + if (keep_cache) + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) | BIT_KEEP_CACHE); + else + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) & ~BIT_KEEP_CACHE); + + return this; + } + + public final long lock_owner() { + return layout.lock_owner.longValue(p); + } + + public final StructFuseFileInfo lock_owner(long lock_owner) { + layout.lock_owner.set(p, lock_owner); + return this; + } + + public final boolean nonseekable() { + return (layout.flags_bitfield.get(p) & BIT_NONSEEKABLE) != 0; + } + + public final StructFuseFileInfo nonseekable(boolean nonseekable) { + if (nonseekable) + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) | BIT_NONSEEKABLE); + else + layout.flags_bitfield.set(p, layout.flags_bitfield.get(p) & ~BIT_NONSEEKABLE); + + return this; + } + + public final int openMode() { + return layout.flags.intValue(p) & openMask; + } + + public final StructFuseFileInfo openMode(int openMode) { + layout.flags.set(p, (layout.flags.get(p) & ~openMask) | openMode); + return this; + } + + public final boolean truncate() { + return (layout.flags.get(p) & O_TRUNC) != 0; + } + + public final StructFuseFileInfo truncate(boolean truncate) { + layout.flags.set(p, (layout.flags.get(p) & ~O_TRUNC) | (truncate ? O_TRUNC : 0)); + return this; + } + + public final boolean writepage() { + return layout.writepage.get(p) != 0; + } + + public final StructFuseFileInfo writepage(boolean writepage) { + layout.writepage.set(p, writepage ? 1 : 0); + return this; + } + + public static final int openMask = 03; + public static final int O_RDONLY = 00; + public static final int O_WRONLY = 01; + public static final int O_RDWR = 02; + public static final int O_CREAT = 0100; + public static final int O_EXCL = 0200; + public static final int O_NOCTTY = 0400; + public static final int O_TRUNC = 01000; + public static final int O_APPEND = 02000; + public static final int O_NONBLOCK = 04000; + public static final int O_NDELAY = O_NONBLOCK; + public static final int O_SYNC = 010000; + public static final int O_ASYNC = 020000; + public static final int O_DIRECT = 040000; + public static final int O_DIRECTORY = 0200000; + public static final int O_NOFOLLOW = 0400000; + public static final int O_NOATIME = 01000000; + public static final int O_CLOEXEC = 02000000; + + private static final int BIT_DIRECT_IO = 1 << 0; + private static final int BIT_KEEP_CACHE = 1 << 1; + private static final int BIT_FLUSH = 1 << 2; + private static final int BIT_NONSEEKABLE = 1 << 3; + private static final int BIT_FLOCKRELEASE = 1 << 4; +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseOperations.java b/src/main/java/co/paralleluniverse/fuse/StructFuseOperations.java new file mode 100644 index 0000000..c1a015c --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseOperations.java @@ -0,0 +1,143 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.NativeType; +import jnr.ffi.Struct; +import co.paralleluniverse.fuse.StructFuseOperationsIfaces.*; +import static jnr.ffi.provider.jffi.ClosureHelper.toNative; + +class StructFuseOperations extends Struct { + private final Function<_getattr> getattr = function(_getattr.class); + private final Function<_readlink> readlink = function(_readlink.class); + private final Pointer getdir = new Pointer(); + private final Function<_mknod> mknod = function(_mknod.class); + private final Function<_mkdir> mkdir = function(_mkdir.class); + private final Function<_unlink> unlink = function(_unlink.class); + private final Function<_rmdir> rmdir = function(_rmdir.class); + private final Function<_symlink> symlink = function(_symlink.class); + private final Function<_rename> rename = function(_rename.class); + private final Function<_link> link = function(_link.class); + private final Function<_chmod> chmod = function(_chmod.class); + private final Function<_chown> chown = function(_chown.class); + private final Function<_truncate> truncate = function(_truncate.class); + private final Pointer utime = new Pointer(); + private final Function<_open> open = function(_open.class); + private final Function<_read> read = function(_read.class); + private final Function<_write> write = function(_write.class); + private final Function<_statfs> statfs = function(_statfs.class); + private final Function<_flush> flush = function(_flush.class); + private final Function<_release> release = function(_release.class); + private final Function<_fsync> fsync = function(_fsync.class); + private final Pointer setxattr = new Pointer(); + private final Pointer getxattr = new Pointer(); + private final Function<_listxattr> listxattr = function(_listxattr.class); + private final Function<_removexattr> removexattr = function(_removexattr.class); + private final Function<_opendir> opendir = function(_opendir.class); + private final Function<_readdir> readdir = function(_readdir.class); + private final Function<_releasedir> releasedir = function(_releasedir.class); + private final Function<_fsyncdir> fsyncdir = function(_fsyncdir.class); + private final Function<_init> init = function(_init.class); + private final Function<_destroy> destroy = function(_destroy.class); + private final Function<_access> access = function(_access.class); + private final Function<_create> create = function(_create.class); + private final Function<_ftruncate> ftruncate = function(_ftruncate.class); + private final Function<_fgetattr> fgetattr = function(_fgetattr.class); + private final Function<_lock> lock = function(_lock.class); + private final Function<_utimens> utimens = function(_utimens.class); + private final Function<_bmap> bmap = function(_bmap.class); + /** + * Flag indicating that the filesystem can accept a NULL path + * as the first argument for the following operations: + * + * read, write, flush, release, fsync, readdir, releasedir, + * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll + * + * If this flag is set these operations continue to work on + * unlinked files even if "-ohard_remove" option was specified. + */ + private final Padding flag_nullpath_ok = new Padding(NativeType.UCHAR, 1); + /** + * Flag indicating that the path need not be calculated for + * the following operations: + * + * read, write, flush, release, fsync, readdir, releasedir, + * fsyncdir, ftruncate, fgetattr, lock, ioctl and poll + * + * Closely related to flag_nullpath_ok, but if this flag is + * set then the path will not be calculaged even if the file + * wasn't unlinked. However the path can still be non-NULL if + * it needs to be calculated for some other reason. + */ + private final Padding flag_nopath = new Padding(NativeType.UCHAR, 1); + /** + * Flag indicating that the filesystem accepts special + * UTIME_NOW and UTIME_OMIT values in its utimens operation. + */ + private final Padding flag_utime_omit_ok = new Padding(NativeType.UCHAR, 1); + /** + * Reserved flags, don't set + */ + private final Padding flag_reserved = new Padding(NativeType.UCHAR, 1); + private final Function<_ioctl> ioctl = function(_ioctl.class); + private final Function<_poll> poll = function(_poll.class); + private final Function<_write_buf> write_buf = function(_write_buf.class); + private final Function<_read_buf> read_buf = function(_read_buf.class); + private final Function<_flock> flock = function(_flock.class); + private final Function<_fallocate> fallocate = function(_fallocate.class); + + @SuppressWarnings("unused") + public StructFuseOperations(jnr.ffi.Runtime runtime, FuseFilesystem fs) { + super(runtime); + final Filesystem filesystem = new Filesystem(fs); + getattr.set(filesystem); + readlink.set(filesystem); + mknod.set(filesystem); + mkdir.set(filesystem); + unlink.set(filesystem); + rmdir.set(filesystem); + symlink.set(filesystem); + rename.set(filesystem); + link.set(filesystem); + chmod.set(filesystem); + chown.set(filesystem); + truncate.set(filesystem); + utime.set((jnr.ffi.Pointer) null); + open.set(filesystem); + read.set(filesystem); + write.set(filesystem); + statfs.set(filesystem); + flush.set(filesystem); + release.set(filesystem); + fsync.set(filesystem); + switch (Platform.platform()) { + case MAC: + case MAC_MACFUSE: + setxattr.set(toNative(_setxattr_MAC.class, filesystem)); + getxattr.set(toNative(_getxattr_MAC.class, filesystem)); + break; + default: + setxattr.set(toNative(_setxattr_NOT_MAC.class, filesystem)); + getxattr.set(toNative(_getxattr_NOT_MAC.class, filesystem)); + } + listxattr.set(filesystem); + removexattr.set(filesystem); + opendir.set(filesystem); + readdir.set(filesystem); + releasedir.set(filesystem); + fsyncdir.set(filesystem); + init.set(filesystem); + destroy.set(filesystem); + access.set(filesystem); + create.set(filesystem); + ftruncate.set(filesystem); + fgetattr.set(filesystem); + lock.set(filesystem); + utimens.set(filesystem); + bmap.set(filesystem); + ioctl.set(filesystem); + poll.set(filesystem); + write_buf.set(null); // TODO: implement + read_buf.set(null); // TODO: implement + flock.set(filesystem); + fallocate.set(filesystem); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFuseOperationsIfaces.java b/src/main/java/co/paralleluniverse/fuse/StructFuseOperationsIfaces.java new file mode 100644 index 0000000..869275c --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFuseOperationsIfaces.java @@ -0,0 +1,67 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.annotations.Delegate; +import jnr.ffi.annotations.In; +import jnr.ffi.annotations.Out; +import jnr.ffi.types.dev_t; +import jnr.ffi.types.gid_t; +import jnr.ffi.types.mode_t; +import jnr.ffi.types.off_t; +import jnr.ffi.types.size_t; +import jnr.ffi.types.u_int32_t; +import jnr.ffi.types.uid_t; + +final class StructFuseOperationsIfaces { + public static interface _init { @Delegate void _init(Pointer conn); } + public static interface _destroy { @Delegate void _destroy(); } + public static interface _readlink { @Delegate int _readlink(String path, Pointer buffer, @size_t long size); } + public static interface _mknod { @Delegate int _mknod(String path, @mode_t long mode, @dev_t long dev); } + public static interface _mkdir { @Delegate int _mkdir(String path, @mode_t long mode); } + public static interface _unlink { @Delegate int _unlink(String path); } + public static interface _rmdir { @Delegate int _rmdir(String path); } + public static interface _symlink { @Delegate int _symlink(String path, String target); } + public static interface _rename { @Delegate int _rename(String path, String newName); } + public static interface _link { @Delegate int _link(String path, String target); } + public static interface _chmod { @Delegate int _chmod(String path, @mode_t long mode); } + public static interface _chown { @Delegate int _chown(String path, @uid_t long uid, @gid_t long gid); } + public static interface _truncate { @Delegate int _truncate(String path, @off_t long offset); } + public static interface _open { @Delegate int _open(String path, Pointer info); } + public static interface _read { @Delegate int _read(String path, @Out Pointer buffer, @size_t long size, @off_t long offset, Pointer info); } + public static interface _write { @Delegate int _write(String path, @In Pointer buffer, @size_t long size, @off_t long offset, Pointer info); } + public static interface _flush { @Delegate int _flush(String path, Pointer info); } + public static interface _fsync { @Delegate int _fsync(String path, int datasync, @In Pointer info); } + public static interface _release { @Delegate int _release(String path, Pointer info); } + public static interface _opendir { @Delegate int _opendir(String path, Pointer info); } + public static interface _readdir { @Delegate int _readdir(String path, Pointer buf, Pointer fillFunction, @off_t long offset, @In Pointer info); } + public static interface _releasedir { @Delegate int _releasedir(String path, Pointer info); } + public static interface _fsyncdir { @Delegate int _fsyncdir(String path, int datasync, @In Pointer info); } + public static interface _access { @Delegate int _access(String path, int access); } + public static interface _create { @Delegate int _create(String path, @mode_t long mode, Pointer info); } + public static interface _ftruncate { @Delegate int _ftruncate(String path, @off_t long offset, @In Pointer info); } + public static interface _utimens { @Delegate int _utimens(String path, Pointer timebuffer); } + public static interface _bmap { @Delegate int _bmap(String path, Pointer info); } + public static interface _statfs { @Delegate int _statfs(String path, @Out Pointer statsvfs); } + public static interface _lock { @Delegate int _lock(String path, Pointer info, int cmd, Pointer flock); } + public static interface _getattr { @Delegate int _getattr(String path, Pointer stat); } + public static interface _fgetattr { @Delegate int _fgetattr(String path, Pointer stat, Pointer info); } + + public static interface _listxattr { @Delegate int _listxattr(String path, Pointer buffer, @size_t long size); } + public static interface _removexattr { @Delegate int _removexattr(String path, String xattr); } + + public static interface _getxattr_MAC { @Delegate int _getxattr(String path, String xattr, Pointer buffer, @size_t long size, @u_int32_t long position); } + public static interface _setxattr_MAC { @Delegate int _setxattr(String path, String xattr, Pointer value, @size_t long size, int flags, int position); } + + public static interface _getxattr_NOT_MAC { @Delegate int _getxattr(String path, String xattr, Pointer buffer, @size_t long size); } + public static interface _setxattr_NOT_MAC { @Delegate int _setxattr(String path, String xattr, Pointer value, @size_t long size, int flags); } + + public static interface _ioctl { @Delegate void _ioctl(String path, int cmd, Pointer arg, Pointer fi, @u_int32_t long flags, Pointer data); } + public static interface _poll { @Delegate void _poll(String path, Pointer fi, Pointer ph, Pointer reventsp); } + public static interface _write_buf { @Delegate void _write_buf(String path, Pointer buf, @off_t long off, Pointer fi); } + public static interface _read_buf { @Delegate void _read_buf(String path, Pointer bufp, @size_t long size, @off_t long off, Pointer fi); } + public static interface _flock { @Delegate void _flock(String path, Pointer fi, int op); } + public static interface _fallocate { @Delegate void _fallocate(String path, int mode, @off_t long off, @off_t long length, Pointer fi); } + + private StructFuseOperationsIfaces() { + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructFusePollHandle.java b/src/main/java/co/paralleluniverse/fuse/StructFusePollHandle.java new file mode 100644 index 0000000..5595b32 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructFusePollHandle.java @@ -0,0 +1,44 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.StructLayout; +import jnr.ffi.Runtime; + +/** + * @see "fuse_lowlevel.c" + * + *

+ * struct fuse_pollhandle {
+ *   uint64_t kh;
+ *   struct fuse_chan *ch;
+ *   struct fuse_ll *f;
+ * };
+ * 
+ * + * @author Sergey Tselovalnikov + * @since 02.06.15 + */ +public class StructFusePollHandle { + private static final class Layout extends StructLayout { + public final Unsigned64 kh = new Unsigned64(); + // TODO struct fuse_chan *ch; + public final Pointer ch = new Pointer(); + // TODO struct fuse_ll *f; + public final Pointer f = new Pointer(); + + protected Layout(Runtime runtime) { + super(runtime); + } + } + + private static final Layout layout = new Layout(Runtime.getSystemRuntime()); + private final Pointer p; + + public StructFusePollHandle(Pointer p) { + this.p = p; + } + + public long kh() { + return layout.kh.get(p); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructStat.java b/src/main/java/co/paralleluniverse/fuse/StructStat.java new file mode 100644 index 0000000..98ae54d --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructStat.java @@ -0,0 +1,301 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.StructLayout; + +public class StructStat { + private static final class Layout extends StructLayout { + public final dev_t st_dev; // Device. + public final ino_t st_ino; // File serial number. + public final mode_t st_mode; // File mode. + public final nlink_t st_nlink; // Link count. + public final uid_t st_uid; // User ID of the file's owner. + public final gid_t st_gid; // Group ID of the file's group. + public final dev_t st_rdev; // + public final StructTimespec.Layout st_atime; // Time of last access. + public final StructTimespec.Layout st_mtime; // Time of last modification. + public final StructTimespec.Layout st_ctime; // Time of last status change. + public final StructTimespec.Layout st_birthtime; + public final off_t st_size; // Size of file, in bytes. + public final blkcnt_t st_blocks; // Number 512-byte blocks allocated. + public final blksize_t st_blksize; // Optimal block size for I/O. + public final int32_t st_gen; + public final int32_t st_lspare; + public final int64_t st_qspare; + + private Layout(Runtime runtime) { + super(runtime); + + switch (Platform.platform()) { + case LINUX_X86_64: { + this.st_dev = new dev_t(); + this.st_ino = new ino_t(); + this.st_nlink = new nlink_t(); + this.st_mode = new mode_t(); + this.st_uid = new uid_t(); + this.st_gid = new gid_t(); + int32_t __pad0 = new int32_t(); + this.st_rdev = new dev_t(); + this.st_size = new off_t(); + this.st_blksize = new blksize_t(); + this.st_blocks = new blkcnt_t(); + this.st_atime = inner(new StructTimespec.Layout(getRuntime())); + this.st_mtime = inner(new StructTimespec.Layout(getRuntime())); + this.st_ctime = inner(new StructTimespec.Layout(getRuntime())); + this.st_birthtime = null; + this.st_gen = null; + this.st_lspare = null; + this.st_qspare = null; + break; + } + case LINUX_ARM: + case LINUX_I686: { + this.st_dev = new dev_t(); + int16_t __pad1 = new int16_t(); + u_int32_t __st_ino = new u_int32_t(); + this.st_mode = new mode_t(); + this.st_nlink = new nlink_t(); + this.st_uid = new uid_t(); + this.st_gid = new gid_t(); + this.st_rdev = new dev_t(); + int16_t __pad2 = new int16_t(); + this.st_size = new off_t(); + this.st_blksize = new blksize_t(); + this.st_blocks = new blkcnt_t(); + this.st_atime = inner(new StructTimespec.Layout(getRuntime())); + this.st_mtime = inner(new StructTimespec.Layout(getRuntime())); + this.st_ctime = inner(new StructTimespec.Layout(getRuntime())); + this.st_ino = new ino_t(); + this.st_birthtime = null; + this.st_gen = null; + this.st_lspare = null; + this.st_qspare = null; + break; + } + case LINUX_PPC: { + this.st_dev = new dev_t(); + this.st_ino = new ino_t(); + this.st_mode = new mode_t(); + this.st_nlink = new nlink_t(); + this.st_uid = new uid_t(); + this.st_gid = new gid_t(); + this.st_rdev = new dev_t(); + int16_t __pad0 = new int16_t(); + this.st_size = new off_t(); + this.st_blksize = new blksize_t(); + this.st_blocks = new blkcnt_t(); + this.st_atime = inner(new StructTimespec.Layout(getRuntime())); + this.st_mtime = inner(new StructTimespec.Layout(getRuntime())); + this.st_ctime = inner(new StructTimespec.Layout(getRuntime())); + this.st_birthtime = null; + this.st_gen = null; + this.st_lspare = null; + this.st_qspare = null; + break; + } + case MAC: { + this.st_dev = new dev_t(); + this.st_mode = new mode_t(); + this.st_nlink = new nlink_t(); + this.st_ino = new ino_t(); + this.st_uid = new uid_t(); + this.st_gid = new gid_t(); + this.st_rdev = new dev_t(); + this.st_atime = inner(new StructTimespec.Layout(getRuntime())); + this.st_mtime = inner(new StructTimespec.Layout(getRuntime())); + this.st_ctime = inner(new StructTimespec.Layout(getRuntime())); + this.st_birthtime = inner(new StructTimespec.Layout(getRuntime())); + this.st_size = new off_t(); + this.st_blocks = new blkcnt_t(); + this.st_blksize = new blksize_t(); + this.st_gen = new int32_t(); + this.st_lspare = new int32_t(); + this.st_qspare = new int64_t(); + break; + } + case FREEBSD: + case MAC_MACFUSE: { + this.st_dev = new dev_t(); + this.st_ino = new ino_t(); + this.st_mode = new mode_t(); + this.st_nlink = new nlink_t(); + this.st_uid = new uid_t(); + this.st_gid = new gid_t(); + this.st_rdev = new dev_t(); + this.st_atime = inner(new StructTimespec.Layout(getRuntime())); + this.st_mtime = inner(new StructTimespec.Layout(getRuntime())); + this.st_ctime = inner(new StructTimespec.Layout(getRuntime())); + this.st_size = new off_t(); + this.st_blocks = new blkcnt_t(); + this.st_blksize = new blksize_t(); + this.st_birthtime = null; + this.st_gen = null; + this.st_lspare = null; + this.st_qspare = null; + break; + } + default: + throw new AssertionError(); + } + } + + } + + private static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + final String path; + + public StructStat(Pointer p, String path) { + this.p = p; + this.path = path; + } + + private void setTime(StructTimespec.Layout layout, long sec, long nsec) { + StructTimespec.set(layout, p, sec, nsec); + } + + public StructStat atime(long sec) { + return atime(sec, 0); + } + + public StructStat atime(long sec, long nsec) { + setTime(layout.st_atime, sec, nsec); + return this; + } + + public StructStat blksize(long blksize) { + layout.st_blksize.set(p, blksize); + return this; + } + + public StructStat blocks(long blocks) { + layout.st_blocks.set(p, blocks); + return this; + } + + public StructStat ctime(long sec) { + return ctime(sec, 0); + } + + public StructStat ctime(long sec, long nsec) { + setTime(layout.st_ctime, sec, nsec); + return this; + } + + public StructStat dev(long dev) { + layout.st_dev.set(p, dev); + return this; + } + + public StructStat gen(long gen) { + if (layout.st_gen != null) + layout.st_gen.set(p, gen); + return this; + } + + public StructStat gid(long gid) { + layout.st_gid.set(p, gid); + return this; + } + + public StructStat ino(long ino) { + layout.st_ino.set(p, ino); + return this; + } + + public StructStat lspare(long lspare) { + if (layout.st_lspare != null) + layout.st_lspare.set(p, lspare); + return this; + } + + public long mode() { + return layout.st_mode.get(p); + } + + public StructStat mode(long bits) { + layout.st_mode.set(p, bits); + return this; + } + + public StructStat mtime(long sec) { + return mtime(sec, 0); + } + + public StructStat mtime(long sec, long nsec) { + setTime(layout.st_mtime, sec, nsec); + return this; + } + + public StructStat nlink(long nlink) { + layout.st_nlink.set(p, nlink); + return this; + } + + public StructStat qspare(long qspare) { + if (layout.st_qspare != null) + layout.st_qspare.set(p, qspare); + return this; + } + + public StructStat rdev(long rdev) { + layout.st_rdev.set(p, rdev); + return this; + } + + public StructStat setAllTimes(long sec, long nsec) { + return setTimes(sec, nsec, sec, nsec, sec, nsec); + } + + public StructStat setAllTimesMillis(long millis) { + final long sec = millis / 1000L; + final long nsec = (millis % 1000L) * 1000000L; + return setAllTimes(sec, nsec); + } + + public StructStat setAllTimesSec(long sec) { + return setAllTimesSec(sec, sec, sec); + } + + public StructStat setAllTimesSec(long atime, long mtime, long ctime) { + return setAllTimesSec(atime, mtime, ctime, ctime); + } + + public StructStat setAllTimesSec(long atime, long mtime, long ctime, long birthtime) { + return setTimes(atime, 0, mtime, 0, ctime, 0); + } + + public StructStat setTimes(long atime_sec, long atime_nsec, long mtime_sec, + long mtime_nsec, long ctime_sec, long ctime_nsec) { + return setTimes(atime_sec, atime_nsec, mtime_sec, mtime_nsec, ctime_sec, ctime_nsec, ctime_sec, ctime_nsec); + } + + public StructStat setTimes(long atime_sec, long atime_nsec, long mtime_sec, long mtime_nsec, + long ctime_sec, long ctime_nsec, long birthtime_sec, long birthtime_nsec) { + setTime(layout.st_atime, atime_sec, atime_nsec); + setTime(layout.st_mtime, mtime_sec, mtime_nsec); + setTime(layout.st_ctime, ctime_sec, ctime_nsec); + if (layout.st_birthtime != null) + setTime(layout.st_birthtime, birthtime_sec, birthtime_nsec); + return this; + } + + public StructStat size(long size) { + layout.st_size.set(p, size); + return this; + } + + public StructStat uid(long uid) { + layout.st_uid.set(p, uid); + return this; + } + + @Override + public java.lang.String toString() { + if (path != null) + return path + "\n" + JNRUtil.toString(layout, p); + return JNRUtil.toString(layout, p); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructStatvfs.java b/src/main/java/co/paralleluniverse/fuse/StructStatvfs.java new file mode 100644 index 0000000..8af5e42 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructStatvfs.java @@ -0,0 +1,187 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.StructLayout; +import jnr.ffi.Platform.CPU; + +public class StructStatvfs { + /* Definitions for the flag in `f_flag'.*/ + public static final int ST_RDONLY = 1; // Mount read-only. + public static final int ST_NOSUID = 2; // Ignore suid and sgid bits. + public static final int ST_NODEV = 4; // Disallow access to device special files. + public static final int ST_NOEXEC = 8; // Disallow program execution. + public static final int ST_SYNCHRONOUS = 16; // Writes are synced at once. + public static final int ST_MANDLOCK = 64; // Allow mandatory locks on an FS. + public static final int ST_WRITE = 128; // Write on file/directory/symlink. + public static final int ST_APPEND = 256; // Append-only file. + public static final int ST_IMMUTABLE = 512; // Immutable file. + public static final int ST_NOATIME = 1024; // Do not update access times. + public static final int ST_NODIRATIME = 2048;// Do not update directory access times. + public static final int ST_RELATIME = 4096; // Update atime relative to mtime/ctime. + + private static final class Layout extends StructLayout { + public final SignedLong f_bsize; // file system block size + public final SignedLong f_frsize; // fragment size + public final blkcnt_t f_blocks; // size of fs in f_frsize units + public final blkcnt_t f_bfree; // # free blocks + public final blkcnt_t f_bavail; // # free blocks for non-root + public final fsfilcnt_t f_files; // # inodes + public final fsfilcnt_t f_ffree; // # free inodes + public final fsfilcnt_t f_favail; // # free inodes for non-root + public final Signed32 f_unused; + public final UnsignedLong f_flag; // mount flags + public final UnsignedLong f_namemax;// maximum filename length + public final Signed32[] __f_spare; + + private Layout(Runtime runtime) { + super(runtime); + + switch (Platform.platform()) { + case FREEBSD: { + this.f_bavail = new blkcnt_t(); + this.f_bfree = new blkcnt_t(); + this.f_blocks = new blkcnt_t(); + this.f_ffree = new fsfilcnt_t(); + this.f_favail = new fsfilcnt_t(); + this.f_files = new fsfilcnt_t(); + this.f_bsize = new SignedLong(); + SignedLong __pad0 = new SignedLong(); + this.f_frsize = new SignedLong(); + break; + } + default: { + this.f_bsize = new SignedLong(); + this.f_frsize = new SignedLong(); + this.f_blocks = new blkcnt_t(); + this.f_bfree = new blkcnt_t(); + this.f_bavail = new blkcnt_t(); + this.f_files = new fsfilcnt_t(); + this.f_ffree = new fsfilcnt_t(); + this.f_favail = new fsfilcnt_t(); + } + + } + final boolean is32bit = (jnr.ffi.Platform.getNativePlatform().getCPU() == CPU.I386 || jnr.ffi.Platform.getNativePlatform().getCPU() == CPU.ARM); + this.f_unused = is32bit ? new Signed32() : null; + this.f_flag = new UnsignedLong(); + this.f_namemax = new UnsignedLong(); + this.__f_spare = array(new Signed32[6]); + } + } + + private static final Layout layout = new Layout(Runtime.getSystemRuntime()); + private final Pointer p; + private final String path; + + public StructStatvfs(Pointer p, String path) { + this.p = p; + this.path = path; + } + + public final long bavail() { + return layout.f_bavail.get(p); + } + + public final StructStatvfs bavail(long f_bavail) { + layout.f_bavail.set(p, f_bavail); + return this; + } + + public final long bfree() { + return layout.f_bfree.get(p); + } + + public final StructStatvfs bfree(long f_bfree) { + layout.f_bfree.set(p, f_bfree); + return this; + } + + public final long blocks() { + return layout.f_blocks.get(p); + } + + public final StructStatvfs blocks(long f_blocks) { + layout.f_blocks.set(p, f_blocks); + return this; + } + + public final long bsize() { + return layout.f_bsize.get(p); + } + + public final StructStatvfs bsize(long f_bsize) { + layout.f_bsize.set(p, f_bsize); + return this; + } + + public final long favail() { + return layout.f_favail.get(p); + } + + public final StructStatvfs favail(long f_favail) { + layout.f_favail.set(p, f_favail); + return this; + } + + public final long ffree() { + return layout.f_ffree.get(p); + } + + public final StructStatvfs ffree(long f_ffree) { + layout.f_ffree.set(p, f_ffree); + return this; + } + + public final long files() { + return layout.f_files.get(p); + } + + public final StructStatvfs files(long f_files) { + layout.f_files.set(p, f_files); + return this; + } + + public final long frsize() { + return layout.f_frsize.get(p); + } + + public final StructStatvfs frsize(long f_frsize) { + layout.f_frsize.set(p, f_frsize); + return this; + } + + public final long flags() { + return layout.f_flag.get(p); + } + + public final StructStatvfs flags(long f_flags) { + layout.f_flag.set(p, f_flags); + return this; + } + + public final StructStatvfs set(long blockSize, long fragmentSize, long freeBlocks, long availBlocks, long totalBlocks, + long freeFiles, long availFiles, long totalFiles) { + return setSizes(blockSize, fragmentSize).setBlockInfo(freeBlocks, availBlocks, totalBlocks).setFileInfo(freeFiles, + availFiles, totalFiles); + } + + public final StructStatvfs setBlockInfo(long freeBlocks, long availBlocks, long totalBlocks) { + return bfree(freeBlocks).bavail(availBlocks).blocks(totalBlocks); + } + + public final StructStatvfs setFileInfo(long freeFiles, long availFiles, long totalFiles) { + return ffree(freeFiles).favail(availFiles).files(totalFiles); + } + + public final StructStatvfs setSizes(long blockSize, long fragmentSize) { + return bsize(blockSize).frsize(fragmentSize); + } + + @Override + public final java.lang.String toString() { + if (path != null) + return path + "\n" + JNRUtil.toString(layout, p); + return JNRUtil.toString(layout, p); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructTimeBuffer.java b/src/main/java/co/paralleluniverse/fuse/StructTimeBuffer.java new file mode 100644 index 0000000..a9e735a --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructTimeBuffer.java @@ -0,0 +1,107 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.StructLayout; + +public final class StructTimeBuffer { + static final class Layout extends StructLayout { + Layout(Runtime runtime) { + super(runtime); + } + public final StructTimespec.Layout actime = inner(new StructTimespec.Layout(getRuntime())); + public final StructTimespec.Layout modtime = inner(new StructTimespec.Layout(getRuntime())); + } + static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + + StructTimeBuffer(Pointer p) { + this.p = p; + } + + public long ac_nsec() { + return layout.actime.tv_nsec.get(p); + } + + public long ac_sec() { + return layout.actime.tv_sec.get(p); + } + + public StructTimeBuffer ac_set(double time) { + StructTimespec.set(layout.actime, p, time); + return this; + } + + public StructTimeBuffer ac_set(long sec, long nsec) { + StructTimespec.set(layout.actime, p, sec, nsec); + return this; + } + + public StructTimeBuffer ac_setMillis(long millis) { + StructTimespec.setMillis(layout.actime, p, millis); + return this; + } + + public StructTimeBuffer ac_setSeconds(long seconds) { + StructTimespec.setSeconds(layout.actime, p, seconds); + return this; + } + + public long mod_nsec() { + return layout.modtime.tv_nsec.get(p); + } + + public long mod_sec() { + return layout.modtime.tv_sec.get(p); + } + + public final StructTimeBuffer mod_set(double time) { + StructTimespec.set(layout.modtime, p, time); + return this; + } + + public StructTimeBuffer mod_set(long sec, final long nsec) { + StructTimespec.set(layout.modtime, p, sec, nsec); + return this; + } + + public StructTimeBuffer mod_setMillis(long millis) { + StructTimespec.setMillis(layout.modtime, p, millis); + return this; + } + + public StructTimeBuffer mod_setSeconds(long seconds) { + StructTimespec.setSeconds(layout.modtime, p, seconds); + return this; + } + + public StructTimeBuffer both_set(double time) { + ac_set(time); + mod_set(time); + return this; + } + + public StructTimeBuffer both_set(long sec, long nsec) { + ac_set(sec, nsec); + mod_set(sec, nsec); + return this; + } + + public StructTimeBuffer both_setMillis(long millis) { + ac_setMillis(millis); + mod_setMillis(millis); + return this; + } + + public StructTimeBuffer both_setSeconds(long seconds) { + ac_setSeconds(seconds); + mod_setSeconds(seconds); + return this; + } + + @Override + public java.lang.String toString() { + return layout.toString(); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/StructTimespec.java b/src/main/java/co/paralleluniverse/fuse/StructTimespec.java new file mode 100644 index 0000000..8d23d8e --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/StructTimespec.java @@ -0,0 +1,64 @@ +package co.paralleluniverse.fuse; + +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.Struct; +import jnr.ffi.StructLayout; + +final class StructTimespec { + static final class Layout extends StructLayout { + Layout(Runtime runtime) { + super(runtime); + } + public final SignedLong tv_sec = new SignedLong(); + public final SignedLong tv_nsec = new SignedLong(); + } + static final Layout layout = new Layout(Runtime.getSystemRuntime()); + + private final Pointer p; + + public StructTimespec(Pointer p) { + this.p = p; + } + + public long nsec() { + return layout.tv_nsec.longValue(p); + } + + public long sec() { + return layout.tv_sec.longValue(p); + } + + public void set(double time) { + set(layout, p, time); + } + + public void set(long sec, long nsec) { + set(layout, p, sec, nsec); + } + + public void setMillis(long millis) { + set(millis / 1000L, (millis % 1000L) * 1000000L); + } + + public void setSeconds(long seconds) { + set(seconds); + } + + static void set(StructTimespec.Layout layout, Pointer p, double time) { + set(layout, p, (long) time, (long) (time * 1000000000d)); + } + + static void set(StructTimespec.Layout layout, Pointer p, long sec, long nsec) { + layout.tv_sec.set(p, sec); + layout.tv_nsec.set(p, nsec); + } + + static void setMillis(StructTimespec.Layout layout, Pointer p, long millis) { + set(layout, p, millis / 1000L, (millis % 1000L) * 1000000L); + } + + static void setSeconds(StructTimespec.Layout layout, Pointer p, long seconds) { + set(layout, p, seconds); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/TypeMode.java b/src/main/java/co/paralleluniverse/fuse/TypeMode.java new file mode 100644 index 0000000..bc0b80c --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/TypeMode.java @@ -0,0 +1,56 @@ +package co.paralleluniverse.fuse; + +public class TypeMode { + public static final int S_IFIFO = 0010000; // named pipe (fifo) + public static final int S_IFCHR = 0020000; // character special + public static final int S_IFDIR = 0040000; // directory + public static final int S_IFBLK = 0060000; // block special + public static final int S_IFREG = 0100000; // regular + public static final int S_IFLNK = 0120000; // symbolic link + public static final int S_IFSOCK = 0140000; // socket + public static final int S_IFMT = 0170000; // file mask for type checks + public static final int S_ISUID = 0004000; // set user id on execution + public static final int S_ISGID = 0002000; // set group id on execution + public static final int S_ISVTX = 0001000; // save swapped text even after use + public static final int S_IRUSR = 0000400; // read permission, owner + public static final int S_IWUSR = 0000200; // write permission, owner + public static final int S_IXUSR = 0000100; // execute/search permission, owner + public static final int S_IRGRP = 0000040; // read permission, group + public static final int S_IWGRP = 0000020; // write permission, group + public static final int S_IXGRP = 0000010; // execute/search permission, group + public static final int S_IROTH = 0000004; // read permission, other + public static final int S_IWOTH = 0000002; // write permission, other + public static final int S_IXOTH = 0000001; // execute permission, other + + public static final int ALL_READ = S_IRUSR | S_IRGRP | S_IROTH; + public static final int ALL_WRITE = S_IWUSR | S_IWGRP | S_IWOTH; + public static final int S_IXUGO = S_IXUSR | S_IXGRP | S_IXOTH; + + public static boolean S_ISTYPE(int mode, int mask) { + return (mode & S_IFMT) == mask; + } + + public static boolean S_ISDIR(int mode) { + return S_ISTYPE(mode, S_IFDIR); + } + + public static boolean S_ISCHR(int mode) { + return S_ISTYPE(mode, S_IFCHR); + } + + public static boolean S_ISBLK(int mode) { + return S_ISTYPE(mode, S_IFBLK); + } + + public static boolean S_ISREG(int mode) { + return S_ISTYPE(mode, S_IFREG); + } + + public static boolean S_ISFIFO(int mode) { + return S_ISTYPE(mode, S_IFIFO); + } + + public static boolean S_ISLNK(int mode) { + return S_ISTYPE(mode, S_IFLNK); + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/XAttrConstants.java b/src/main/java/co/paralleluniverse/fuse/XAttrConstants.java new file mode 100644 index 0000000..410a5b2 --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/XAttrConstants.java @@ -0,0 +1,16 @@ +package co.paralleluniverse.fuse; + +/** + * The following constants should be used for the fifth parameter of + * `*setxattr'. + * + * @author Sergey Tselovalnikov + * @since 05.06.15 + */ +final class XAttrConstants { + public static final int XATTR_CREATE = 1; /* set value, fail if attr already exists. */ + public static final int XATTR_REPLACE = 2; /* set value, fail if attr does not exist. */ + + private XAttrConstants() { + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/XattrFiller.java b/src/main/java/co/paralleluniverse/fuse/XattrFiller.java new file mode 100644 index 0000000..84f6e5e --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/XattrFiller.java @@ -0,0 +1,36 @@ +package co.paralleluniverse.fuse; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +public final class XattrFiller { + private final ByteBuffer buffer; + private final long maxSize; + private final int position; + private byte[] value = null; + private boolean isSet = false; + + XattrFiller(ByteBuffer buffer, long size, int position) { + this.buffer = buffer; + maxSize = size; + this.position = position; + } + + long getSize() { + return value == null ? 0 : value.length; + } + + public final void set(byte[] value) { + if (buffer != null && value != null) { + if (isSet) + throw new IllegalStateException("Cannot set the xattr twice."); + + isSet = true; + if (value.length > position + maxSize) + value = Arrays.copyOf(value, position + (int) maxSize); + + buffer.put(value, position, value.length); + } + this.value = value; + } +} diff --git a/src/main/java/co/paralleluniverse/fuse/XattrListFiller.java b/src/main/java/co/paralleluniverse/fuse/XattrListFiller.java new file mode 100644 index 0000000..001386a --- /dev/null +++ b/src/main/java/co/paralleluniverse/fuse/XattrListFiller.java @@ -0,0 +1,68 @@ +package co.paralleluniverse.fuse; + +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; + +public final class XattrListFiller { + private final ByteBuffer buffer; + private final long maxSize; + private long currentSize = 0; + private final Set addedXattrs = new HashSet(); + + XattrListFiller(ByteBuffer buffer, long size) { + this.buffer = buffer; + maxSize = size; + } + + public final boolean add(Iterable xattrs) { + byte[] bytes; + int size; + boolean hasNullByte; + for (final String xattr : xattrs) { + if (addedXattrs.contains(xattr)) + continue; + + if (currentSize >= maxSize && buffer != null) + return false; + + bytes = xattr.getBytes(); + hasNullByte = bytes[bytes.length - 1] == 0; + size = bytes.length + (hasNullByte ? 0 : 1); + if (currentSize + size > maxSize && buffer != null) + return false; + + addedXattrs.add(xattr); + if (buffer != null) { + buffer.put(bytes); + if (!hasNullByte) + buffer.put((byte) 0); + } + currentSize += size; + } + return true; + } + + public final boolean add(String... xattrs) { + return add(Arrays.asList(xattrs)); + } + + public final long requiredSize() { + return currentSize; + } + + @Override + public String toString() { + final StringBuilder output = new StringBuilder(); + int count = 0; + for (final String xattr : addedXattrs) { + output.append(xattr); + if (count < addedXattrs.size() - 1) { + output.append(", "); + } + count++; + } + return output.toString(); + } +} diff --git a/src/main/java/co/paralleluniverse/javafs/FuseFileSystemProvider.java b/src/main/java/co/paralleluniverse/javafs/FuseFileSystemProvider.java new file mode 100644 index 0000000..1145dad --- /dev/null +++ b/src/main/java/co/paralleluniverse/javafs/FuseFileSystemProvider.java @@ -0,0 +1,803 @@ +package co.paralleluniverse.javafs; + +import java.io.FileNotFoundException; +import java.io.IOError; +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.Channel; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; +import java.nio.file.AccessDeniedException; +import java.nio.file.AccessMode; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.attribute.FileTime; +import java.nio.file.NotDirectoryException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.ReadOnlyFileSystemException; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.nio.file.spi.FileSystemProvider; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicLong; +import jnr.constants.platform.Errno; +import jnr.ffi.Pointer; +import co.paralleluniverse.fuse.AccessConstants; +import co.paralleluniverse.fuse.DirectoryFiller; +import co.paralleluniverse.fuse.StructFuseFileInfo; +import co.paralleluniverse.fuse.FuseFilesystem; +import co.paralleluniverse.fuse.StructFlock; +import co.paralleluniverse.fuse.StructFuseBufvec; +import co.paralleluniverse.fuse.StructFusePollHandle; +import co.paralleluniverse.fuse.StructStat; +import co.paralleluniverse.fuse.StructStatvfs; +import co.paralleluniverse.fuse.StructTimeBuffer; +import co.paralleluniverse.fuse.TypeMode; +import co.paralleluniverse.fuse.XattrFiller; +import co.paralleluniverse.fuse.XattrListFiller; +import java.util.Arrays; +import java.util.logging.Level; + +class FuseFileSystemProvider extends FuseFilesystem { + private final FileSystemProvider fsp; + private final FileSystem fs; + private final ConcurrentMap openFiles = new ConcurrentHashMap<>(); + private final AtomicLong fileHandle = new AtomicLong(0); + private final boolean debug; + + public FuseFileSystemProvider(FileSystemProvider fsp, URI uri, boolean debug) { + this.fsp = fsp; + this.fs = fsp.getFileSystem(uri); + this.debug = debug; + } + + public FuseFileSystemProvider(FileSystem fs, boolean debug) { + this.fsp = fs.provider(); + this.fs = fs; + this.debug = debug; + } + + private Path path(String p) { + return fs.getPath(p); + } + + @Override + protected void afterUnmount(Path mountPoint) { + } + + @Override + protected void beforeMount(Path mountPoint) { + } + + @Override + protected String getName() { + return fs.toString(); + } + + @Override + protected String[] getOptions() { + return null; + } + + @Override + protected int getattr(String path, StructStat stat) { + try { + Path p = path(path); + BasicFileAttributes attributes = fsp.readAttributes(p, BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + + // time + stat.atime(sec(attributes.lastAccessTime()), nsec(attributes.lastAccessTime())); + stat.mtime(sec(attributes.lastModifiedTime()), nsec(attributes.lastModifiedTime())); + stat.ctime(sec(attributes.creationTime()), nsec(attributes.creationTime())); + + stat.size(attributes.size()); + + // mode + long mode = 0L; + if (attributes.isRegularFile()) + mode = TypeMode.S_IFREG; + else if (attributes.isDirectory()) + mode = TypeMode.S_IFDIR; + else if (attributes.isSymbolicLink()) + mode = TypeMode.S_IFLNK; + + PosixFileAttributes pas = attributes instanceof PosixFileAttributes ? (PosixFileAttributes) attributes : null; + if (pas == null) { + try { + pas = fsp.readAttributes(p, PosixFileAttributes.class, LinkOption.NOFOLLOW_LINKS); + } catch (UnsupportedOperationException e) { + } + } + if (pas != null) + mode |= permissionsToMode(pas.permissions()); + stat.mode(mode); + + try { + final Map uattrs = fsp.readAttributes(p, "unix:*", LinkOption.NOFOLLOW_LINKS); + int uid = (int) uattrs.get("uid"); + stat.uid(uid); + + int gid = (int) uattrs.get("gid"); + stat.gid(gid); + } catch (Exception e) { + } + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int readlink(String path, ByteBuffer buffer, long size) { + try { + fillBufferWithString(fsp.readSymbolicLink(path(path)).toString(), buffer, size); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int mknod(String path, long mode, long dev) { + return create(path, mode, null); + } + + @Override + protected int mkdir(String path, long mode) { + try { + fsp.createDirectory(path(path), PosixFilePermissions.asFileAttribute(modeToPermissions(mode))); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int unlink(String path) { + try { + Path p = path(path); + if (Files.isDirectory(p)) + throw new IOException(p + " is a directory"); + fsp.delete(path(path)); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int rmdir(String path) { + try { + Path p = path(path); + if (!Files.isDirectory(p)) + throw new IOException(p + " is not a directory"); + fsp.delete(p); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int symlink(String path, String target) { + try { + fsp.createSymbolicLink(path(path), path(target)); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int rename(String path, String newName) { + try { + fsp.move(path(path), path(newName)); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int link(String path, String target) { + try { + fsp.createLink(path(target), path(path)); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int chmod(String path, long mode) { + try { + final PosixFileAttributeView attrs = fsp.getFileAttributeView(path(path), PosixFileAttributeView.class); + attrs.setPermissions(modeToPermissions(mode)); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int chown(String path, long uid, long gid) { + try { + final PosixFileAttributeView attrs = fsp.getFileAttributeView(path(path), PosixFileAttributeView.class); + attrs.setOwner(fs.getUserPrincipalLookupService().lookupPrincipalByName(Long.toString(uid))); + attrs.setGroup(fs.getUserPrincipalLookupService().lookupPrincipalByGroupName(Long.toString(gid))); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int truncate(String path, long offset) { + try { + final FileChannel ch = fsp.newFileChannel(path(path), EnumSet.of(StandardOpenOption.WRITE)); + ch.truncate(offset); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int open(String path, StructFuseFileInfo info) { + try { + final FileChannel channel = fsp.newFileChannel(path(path), fileInfoToOpenOptions(info)); + final long fh = fileHandle.incrementAndGet(); + openFiles.put(fh, channel); + info.fh(fh); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int read(String path, ByteBuffer buffer, long size, long offset, StructFuseFileInfo info) { + try { + final Channel channel = toChannel(info); + if (channel instanceof FileChannel) { + final FileChannel ch = ((FileChannel) channel); + if (info.nonseekable()) + assert offset == ch.position(); + else + ch.position(offset); + int n = ch.read(buffer); + if (n > 0) { + if (!info.noblock()) + assert n <= 0 || n == size; + else { + int c; + while (n < size) { + if ((c = ch.read(buffer)) <= 0) + break; + n += c; + } + } + } + return n; + } else { + final AsynchronousFileChannel ch = ((AsynchronousFileChannel) channel); + int n = ch.read(buffer, offset).get(); + assert n == size; + return n; + } + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int write(String path, ByteBuffer buffer, long size, long offset, StructFuseFileInfo info) { + try { + final Channel channel = toChannel(info); + if (channel instanceof FileChannel) { + final FileChannel ch = ((FileChannel) channel); + if (!info.append() && !info.nonseekable()) + ch.position(offset); + int n = ch.write(buffer); + if (n > 0) { + if (!info.noblock()) + assert n <= 0 || n == size; + else { + int c; + while (n < size) { + if ((c = ch.write(buffer)) <= 0) + break; + n += c; + } + } + } + return n; + } else { + final AsynchronousFileChannel ch = ((AsynchronousFileChannel) channel); + int n = ch.write(buffer, offset).get(); + return n; + } + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int statfs(String path, StructStatvfs statvfs) { + try { + boolean hasStore = false; + for (FileStore store : fs.getFileStores()) { + if (hasStore) + throw new IOException("Multiple FileStores not supported"); + hasStore = true; + +// store.getUsableSpace(); +// store.getUnallocatedSpace(); +// store.getTotalSpace(); + long mountFlags = 0; + if (fs.isReadOnly()) + mountFlags |= StructStatvfs.ST_RDONLY; + statvfs.flags(mountFlags); + } + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int flush(String path, StructFuseFileInfo info) { + return 0; + } + + @Override + public int release(String path, StructFuseFileInfo info) { + try { + final Channel ch = toChannel(info); + ch.close(); + openFiles.remove(info.fh()); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int fsync(String path, int datasync, StructFuseFileInfo info) { + try { + final Channel channel = toChannel(info); + if (channel instanceof FileChannel) { + final FileChannel ch = ((FileChannel) channel); + ch.force(datasync == 0); + } else { + final AsynchronousFileChannel ch = ((AsynchronousFileChannel) channel); + ch.force(true); + ch.force(datasync == 0); + } + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int setxattr(String path, String xattr, ByteBuffer value, long size, int flags, int position) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int getxattr(String path, String xattr, XattrFiller filler, long size, long position) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int listxattr(String path, XattrListFiller filler) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int removexattr(String path, String xattr) { + return -Errno.ENOSYS.intValue(); + } + + @Override + protected int opendir(String path, StructFuseFileInfo info) { + try { + final DirectoryStream ds = fsp.newDirectoryStream(path(path), new DirectoryStream.Filter() { + @Override + public boolean accept(Path entry) throws IOException { + return true; + } + }); + final long fh = fileHandle.incrementAndGet(); + openFiles.put(fh, ds); + info.fh(fh); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int readdir(String path, StructFuseFileInfo info, DirectoryFiller filler) { + final DirectoryStream ds = (DirectoryStream) openFiles.get(info.fh()); + filler.add(toStringIterable(ds)); + return 0; + } + + @Override + protected int releasedir(String path, StructFuseFileInfo info) { + try { + final DirectoryStream ds = (DirectoryStream) openFiles.get(info.fh()); + ds.close(); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int fsyncdir(String path, int datasync, StructFuseFileInfo info) { + return 0; + } + + @Override + protected void init() { + } + + @Override + protected void destroy() { + try { + fs.close(); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + } + + @Override + protected int access(String path, int access) { + try { + final Path p = path(path); + fsp.checkAccess(p, toAccessMode(access, Files.isDirectory(p))); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int create(String path, long mode, StructFuseFileInfo info) { + try { + final Set options = fileInfoToOpenOptions(info); + options.add(StandardOpenOption.CREATE); + final FileChannel channel = fsp.newFileChannel(path(path), options); + final long fh = fileHandle.incrementAndGet(); + openFiles.put(fh, channel); + info.fh(fh); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int ftruncate(String path, long offset, StructFuseFileInfo info) { + try { + final Channel channel = toChannel(info); + if (channel instanceof FileChannel) + ((FileChannel) channel).truncate(offset); + else if (channel instanceof AsynchronousFileChannel) + ((AsynchronousFileChannel) channel).truncate(offset); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int fgetattr(String path, StructStat stat, StructFuseFileInfo info) { + return getattr(path, stat); + } + + @Override + protected int lock(String path, StructFuseFileInfo info, int command, StructFlock flock) { + try { + throw new UnsupportedOperationException(); +// if (command == StructFlock.CMD_GETLK) +// throw new UnsupportedOperationException(); +// +// final Channel channel = toChannel(info); +// if (channel instanceof FileChannel) { +// FileChannel ch = (FileChannel) channel; +// switch (command) { +// case StructFlock.CMD_SETLK: +// FileLock lock = ch.lock(flock.start(), flock.len(), false); +// lock. +// } +// } else if (channel instanceof AsynchronousFileChannel) { +// AsynchronousFileChannel ch = (AsynchronousFileChannel) channel; +// } +// return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int utimens(String path, StructTimeBuffer timeBuffer) { + try { + fsp.getFileAttributeView(path(path), BasicFileAttributeView.class).setTimes( + FileTime.from(toNanos(timeBuffer.mod_sec(), timeBuffer.mod_nsec()), TimeUnit.NANOSECONDS), + FileTime.from(toNanos(timeBuffer.ac_sec(), timeBuffer.ac_nsec()), TimeUnit.NANOSECONDS), + null); + return 0; + } catch (Exception e) { + return -errno(e); + } + } + + @Override + protected int bmap(String path, StructFuseFileInfo info) { + try { + throw new UnsupportedOperationException(); + } catch (Exception e) { + return -errno(e); + } + } + + @Override + public int ioctl(String path, int cmd, Pointer arg, StructFuseFileInfo fi, long flags, Pointer data) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int poll(String path, StructFuseFileInfo fi, StructFusePollHandle ph, Pointer reventsp) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + protected int write_buf(String path, StructFuseBufvec buf, long off, StructFuseFileInfo fi) { + throw new UnsupportedOperationException("Not supported yet."); // TODO: implement + } + + @Override + protected int read_buf(String path, Pointer bufp, long size, long off, StructFuseFileInfo fi) { + throw new UnsupportedOperationException("Not supported yet."); // TODO: implement + } + + @Override + public int flock(String path, StructFuseFileInfo fi, int op) { + throw new UnsupportedOperationException("Not supported yet."); + } + + @Override + public int fallocate(String path, int mode, long off, long length, StructFuseFileInfo fi) { + throw new UnsupportedOperationException("Not supported yet."); + } + //////////// + + private Channel toChannel(StructFuseFileInfo info) { + return (Channel) openFiles.get(info.fh()); + } + + private static Set fileInfoToOpenOptions(StructFuseFileInfo info) { + final Set options = new HashSet<>(); + if (info != null) { + if (info.create()) + options.add(StandardOpenOption.CREATE); + if (info.append()) + options.add(StandardOpenOption.APPEND); + if (info.truncate()) + options.add(StandardOpenOption.TRUNCATE_EXISTING); + switch (info.openMode()) { + case StructFuseFileInfo.O_RDONLY: + options.add(StandardOpenOption.READ); + break; + case StructFuseFileInfo.O_WRONLY: + options.add(StandardOpenOption.WRITE); + break; + case StructFuseFileInfo.O_RDWR: + options.add(StandardOpenOption.READ); + options.add(StandardOpenOption.WRITE); + break; + } + } + return options; + } + + private static Set modeToPermissions(long mode) { + final EnumSet permissions = EnumSet.noneOf(PosixFilePermission.class); + if ((mode & TypeMode.S_IRUSR) != 0) + permissions.add(PosixFilePermission.OWNER_READ); + if ((mode & TypeMode.S_IWUSR) != 0) + permissions.add(PosixFilePermission.OWNER_WRITE); + if ((mode & TypeMode.S_IXUSR) != 0) + permissions.add(PosixFilePermission.OWNER_EXECUTE); + if ((mode & TypeMode.S_IRGRP) != 0) + permissions.add(PosixFilePermission.GROUP_READ); + if ((mode & TypeMode.S_IWGRP) != 0) + permissions.add(PosixFilePermission.GROUP_WRITE); + if ((mode & TypeMode.S_IXGRP) != 0) + permissions.add(PosixFilePermission.GROUP_EXECUTE); + if ((mode & TypeMode.S_IROTH) != 0) + permissions.add(PosixFilePermission.OTHERS_READ); + if ((mode & TypeMode.S_IWOTH) != 0) + permissions.add(PosixFilePermission.OTHERS_WRITE); + if ((mode & TypeMode.S_IXOTH) != 0) + permissions.add(PosixFilePermission.OTHERS_EXECUTE); + return permissions; + } + + private static long permissionsToMode(Set permissions) { + long mode = 0; + for (PosixFilePermission px : permissions) { + switch (px) { + case OWNER_READ: + mode |= TypeMode.S_IRUSR; + break; + case OWNER_WRITE: + mode |= TypeMode.S_IWUSR; + break; + case OWNER_EXECUTE: + mode |= TypeMode.S_IXUSR; + break; + case GROUP_READ: + mode |= TypeMode.S_IRGRP; + break; + case GROUP_WRITE: + mode |= TypeMode.S_IWGRP; + break; + case GROUP_EXECUTE: + mode |= TypeMode.S_IXGRP; + break; + case OTHERS_READ: + mode |= TypeMode.S_IROTH; + break; + case OTHERS_WRITE: + mode |= TypeMode.S_IWOTH; + break; + case OTHERS_EXECUTE: + mode |= TypeMode.S_IXOTH; + break; + } + } + return mode; + } + + private static AccessMode[] toAccessMode(int access, boolean dir) { + List modes = new ArrayList<>(3); + if ((access & AccessConstants.R_OK) != 0 || (dir && (access & AccessConstants.X_OK) != 0)) + modes.add(AccessMode.READ); + if ((access & AccessConstants.W_OK) != 0) + modes.add(AccessMode.WRITE); + if (!dir && (access & AccessConstants.X_OK) != 0) + modes.add(AccessMode.EXECUTE); + return modes.toArray(new AccessMode[modes.size()]); + } + + private static Iterable toStringIterable(final Iterable iterable) { + return new Iterable() { + + @Override + public Iterator iterator() { + final Iterator it = iterable.iterator(); + return new Iterator() { + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public String next() { + return it.next().toString(); + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + + private int errno(Throwable e) { + final Errno en = errno0(e); + if (en == null) + return 0; + if (debug) + getLogger().log(Level.WARNING, "Exception", e); + return en.intValue(); + } + + private static Errno errno0(Throwable t) { + if (t == null) + return null; + + if (t instanceof IllegalArgumentException) + return Errno.EINVAL; + if (t instanceof UnsupportedOperationException) + return Errno.ENOSYS; // Errno.EOPNOTSUPP + if (t instanceof InterruptedException) + return Errno.EINTR; + if (t instanceof NullPointerException || t instanceof ClassCastException) + return Errno.EFAULT; + + if (t instanceof SecurityException) + return Errno.EPERM; + + if (t instanceof TimeoutException) + return Errno.ETIMEDOUT; + + if (t instanceof AccessDeniedException) + return Errno.EACCES; + if (t instanceof FileAlreadyExistsException) + return Errno.EEXIST; + if (t instanceof FileNotFoundException || t instanceof NoSuchFileException) + return Errno.ENOENT; + if (t instanceof NotDirectoryException) + return Errno.ENOTDIR; + if (t instanceof DirectoryNotEmptyException) + return Errno.ENOTEMPTY; + if (t instanceof ReadOnlyFileSystemException) + return Errno.EROFS; // Errno.EACCES + if (t instanceof IOException || t instanceof IOError) + return Errno.EIO; + return Errno.EFAULT; + } + + private static void fillBufferWithString(String str, ByteBuffer buffer, long size) { + final byte[] bytes = str.getBytes(); + final int s = (int) Math.min((long) Integer.MAX_VALUE, size - 1); + buffer.put(bytes, 0, Math.min(bytes.length, s)); + buffer.put((byte) 0); + buffer.flip(); + } + + private static long toNanos(long sec, long nanos) { + return sec * 1_000_000_000 + nanos; + } + + private static long sec(FileTime ft) { + return ft != null ? sec(ft.to(TimeUnit.NANOSECONDS)) : 0; + } + + private static long nsec(FileTime ft) { + return ft != null ? nsec(ft.to(TimeUnit.NANOSECONDS)) : 0; + } + + private static long sec(long nanos) { + return nanos / 1_000_000_000; + } + + private static long nsec(long nanos) { + return nanos % 1_000_000_000; + } +} diff --git a/src/main/java/co/paralleluniverse/javafs/JavaFS.java b/src/main/java/co/paralleluniverse/javafs/JavaFS.java new file mode 100644 index 0000000..7b288ee --- /dev/null +++ b/src/main/java/co/paralleluniverse/javafs/JavaFS.java @@ -0,0 +1,50 @@ +package co.paralleluniverse.javafs; + +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import co.paralleluniverse.fuse.Fuse; + +/** + * Mounts Java {@link FileSystem}s as a FUSE filesystems. + * + * @author pron + */ +public final class JavaFS { + /** + * Mounts a filesystem. + * + * @param fs the filesystem + * @param mountPoint the path of the mount point + * @param readonly if {@code true}, mounts the filesystem as read-only + * @param log if {@code true}, all filesystem calls will be logged with juc logging. + */ + public static void mount(FileSystem fs, Path mountPoint, boolean readonly, boolean log) throws IOException { + if (readonly) + fs = new ReadOnlyFileSystem(fs); + Fuse.mount(new FuseFileSystemProvider(fs, log).log(log), mountPoint, false, log); + } + + /** + * Mounts a filesystem. + * + * @param fs the filesystem + * @param mountPoint the path of the mount point + */ + public static void mount(FileSystem fs, Path mountPoint) throws IOException { + mount(fs, mountPoint, false, false); + } + + /** + * Try to unmount an existing mountpoint. + * + * @param mountPoint The location where the filesystem is mounted. + * @throws IOException thrown if an error occurs while starting the external process. + */ + public static void unmount(Path mountPoint) throws IOException { + Fuse.unmount(mountPoint); + } + + private JavaFS() { + } +} diff --git a/src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystem.java b/src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystem.java new file mode 100644 index 0000000..4123afb --- /dev/null +++ b/src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystem.java @@ -0,0 +1,90 @@ +package co.paralleluniverse.javafs; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.WatchService; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.Set; + +/** + * + * @author pron + */ +class ReadOnlyFileSystem extends FileSystem { + private final FileSystem fs; + private final FileSystemProvider provider; + + public ReadOnlyFileSystem(FileSystem fs) { + this.fs = fs; + this.provider = new ReadOnlyFileSystemProvider(fs.provider()); + } + + @Override + public boolean isReadOnly() { + return true; + } + + @Override + public String toString() { + return super.toString(); + } + + @Override + public FileSystemProvider provider() { + return provider; + } + + @Override + public void close() throws IOException { + fs.close(); + } + + @Override + public boolean isOpen() { + return fs.isOpen(); + } + + @Override + public String getSeparator() { + return fs.getSeparator(); + } + + @Override + public Iterable getRootDirectories() { + return fs.getRootDirectories(); + } + + @Override + public Iterable getFileStores() { + return fs.getFileStores(); + } + + @Override + public Set supportedFileAttributeViews() { + return fs.supportedFileAttributeViews(); + } + + @Override + public Path getPath(String first, String... more) { + return fs.getPath(first, more); + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + return fs.getPathMatcher(syntaxAndPattern); + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + return fs.getUserPrincipalLookupService(); + } + + @Override + public WatchService newWatchService() throws IOException { + return fs.newWatchService(); + } +} diff --git a/src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystemProvider.java b/src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystemProvider.java new file mode 100644 index 0000000..0a41d2b --- /dev/null +++ b/src/main/java/co/paralleluniverse/javafs/ReadOnlyFileSystemProvider.java @@ -0,0 +1,195 @@ +package co.paralleluniverse.javafs; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.nio.channels.AsynchronousFileChannel; +import java.nio.channels.FileChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessDeniedException; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.ReadOnlyFileSystemException; +import java.nio.file.StandardOpenOption; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.spi.FileSystemProvider; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ExecutorService; + +/** + * + * @author pron + */ +class ReadOnlyFileSystemProvider extends FileSystemProvider { + private final FileSystemProvider fsp; + + public ReadOnlyFileSystemProvider(FileSystemProvider fsp) { + this.fsp = fsp; + } + + @Override + public FileSystem newFileSystem(Path path, Map env) throws IOException { + return new ReadOnlyFileSystem(fsp.newFileSystem(path, env)); + } + + @Override + public OutputStream newOutputStream(Path path, OpenOption... options) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public FileChannel newFileChannel(Path path, Set options, FileAttribute... attrs) throws IOException { + checkOpenOptions(options); + return fsp.newFileChannel(path, options, attrs); + } + + @Override + public AsynchronousFileChannel newAsynchronousFileChannel(Path path, Set options, ExecutorService executor, FileAttribute... attrs) throws IOException { + checkOpenOptions(options); + return fsp.newAsynchronousFileChannel(path, options, executor, attrs); + } + + @Override + public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) throws IOException { + checkOpenOptions(options); + return fsp.newByteChannel(path, options, attrs); + } + + @Override + public void checkAccess(Path path, AccessMode... modes) throws IOException { + for(AccessMode mode : modes) { + if(mode == AccessMode.WRITE) + throw new AccessDeniedException(path.toString()); + } + fsp.checkAccess(path, modes); + } + + private void checkOpenOptions(Set options) { + if (options.contains(StandardOpenOption.CREATE) + || options.contains(StandardOpenOption.CREATE_NEW) + || options.contains(StandardOpenOption.APPEND) + || options.contains(StandardOpenOption.WRITE) + || options.contains(StandardOpenOption.DELETE_ON_CLOSE)) + throw new ReadOnlyFileSystemException(); + } + + @Override + public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public void createSymbolicLink(Path link, Path target, FileAttribute... attrs) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public void createLink(Path link, Path existing) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public void delete(Path path) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public boolean deleteIfExists(Path path) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + throw new ReadOnlyFileSystemException(); + } + + @Override + public String toString() { + return fsp.toString(); + } + + @Override + public String getScheme() { + return fsp.getScheme(); + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { + return fsp.newFileSystem(uri, env); + } + + @Override + public FileSystem getFileSystem(URI uri) { + return fsp.getFileSystem(uri); + } + + @Override + public Path getPath(URI uri) { + return fsp.getPath(uri); + } + + @Override + public InputStream newInputStream(Path path, OpenOption... options) throws IOException { + return fsp.newInputStream(path, options); + } + + @Override + public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException { + return fsp.newDirectoryStream(dir, filter); + } + + @Override + public Path readSymbolicLink(Path link) throws IOException { + return fsp.readSymbolicLink(link); + } + + @Override + public boolean isSameFile(Path path, Path path2) throws IOException { + return fsp.isSameFile(path, path2); + } + + @Override + public boolean isHidden(Path path) throws IOException { + return fsp.isHidden(path); + } + + @Override + public FileStore getFileStore(Path path) throws IOException { + return fsp.getFileStore(path); + } + + @Override + public V getFileAttributeView(Path path, Class type, LinkOption... options) { + return fsp.getFileAttributeView(path, type, options); + } + + @Override + public
A readAttributes(Path path, Class type, LinkOption... options) throws IOException { + return fsp.readAttributes(path, type, options); + } + + @Override + public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { + return fsp.readAttributes(path, attributes, options); + } +} diff --git a/src/main/java/jnr/ffi/provider/jffi/ClosureHelper.java b/src/main/java/jnr/ffi/provider/jffi/ClosureHelper.java new file mode 100644 index 0000000..e819a77 --- /dev/null +++ b/src/main/java/jnr/ffi/provider/jffi/ClosureHelper.java @@ -0,0 +1,81 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Sergey Tselovalnikov + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package jnr.ffi.provider.jffi; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AccessibleObject; +import java.util.Collection; +import jnr.ffi.Pointer; +import jnr.ffi.Runtime; +import jnr.ffi.mapper.CompositeTypeMapper; +import jnr.ffi.mapper.DefaultSignatureType; +import jnr.ffi.mapper.FromNativeContext; +import jnr.ffi.mapper.FromNativeConverter; +import jnr.ffi.provider.ClosureManager; +import java.util.Collections; + +public class ClosureHelper { + public static ClosureHelper getInstance() { + return SingletonHolder.INSTANCE; + } + + private static class SingletonHolder { + private static final ClosureHelper INSTANCE = new ClosureHelper(); + } + + private final SimpleNativeContext ctx; + private final ClassValue> cache; + + private ClosureHelper() { + try { + final ClosureManager closureManager = Runtime.getSystemRuntime().getClosureManager(); + + final AsmClassLoader cl = (AsmClassLoader) accessible(NativeClosureManager.class.getDeclaredField("classLoader")).get(closureManager); + final CompositeTypeMapper ctm = (CompositeTypeMapper) accessible(NativeClosureManager.class.getDeclaredField("typeMapper")).get(closureManager); + this.ctx = new SimpleNativeContext(Runtime.getSystemRuntime(), (Collection) Collections.EMPTY_LIST); + this.cache = new ClassValue>() { + @Override + protected FromNativeConverter computeValue(Class closureClass) { + return ClosureFromNativeConverter. + getInstance(Runtime.getSystemRuntime(), DefaultSignatureType.create(closureClass, (FromNativeContext) ctx), cl, ctm); + } + }; + } catch (Exception e) { + throw new RuntimeException("Unable to create helper", e); + } + } + + public T fromNative(Pointer nativeValue, Class closureClass) { + return (T) cache.get(closureClass).fromNative(nativeValue, ctx); + } + + public static Pointer toNative(Class closureClass, T instance) { + return Runtime.getSystemRuntime().getClosureManager().getClosurePointer(closureClass, instance); + } + + private static T accessible(T obj) { + obj.setAccessible(true); + return obj; + } +} diff --git a/src/test/java/co/paralleluniverse/javafs/JFSTest.java b/src/test/java/co/paralleluniverse/javafs/JFSTest.java new file mode 100644 index 0000000..2f0e299 --- /dev/null +++ b/src/test/java/co/paralleluniverse/javafs/JFSTest.java @@ -0,0 +1,77 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package co.paralleluniverse.javafs; + +import co.paralleluniverse.javafs.JavaFS; +import com.google.common.jimfs.Jimfs; +import static com.google.common.truth.Truth.assert_; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import static org.junit.Assert.*; + +/** + * + * @author pron + */ +public class JFSTest { + + public JFSTest() { + } + + @Test + public void test() throws Exception { + FileSystem fs = Jimfs.newFileSystem(); + try (DataOutputStream os = new DataOutputStream(Files.newOutputStream(fs.getPath("/jimfs.txt")))) { + os.writeUTF("JIMFS"); + } + + final Path mnt = Files.createTempDirectory("jfsmnt"); + try { + JavaFS.mount(fs, mnt, false, false); + + // From this point on we use the old file IO + File root = mnt.toFile(); + + // verify that we are, in fact, in Jimfs + try (DataInputStream is = new DataInputStream(new FileInputStream(new File(root, "jimfs.txt")))) { + assertEquals("JIMFS", is.readUTF()); + } + + try (DataOutputStream os = new DataOutputStream(new FileOutputStream(new File(root, "a.txt")))) { + os.writeUTF("hello!"); + } + try (DataOutputStream os = new DataOutputStream(new FileOutputStream(new File(root, "b.txt")))) { + os.writeUTF("wha?"); + } + try (DataOutputStream os = new DataOutputStream(new FileOutputStream(new File(root, "c.txt")))) { + os.writeUTF("goodbye!"); + } + + assert_().that(root.list()).asList().has().allOf("a.txt", "b.txt", "c.txt", "jimfs.txt"); + + try (DataInputStream is = new DataInputStream(new FileInputStream(new File(root, "a.txt")))) { + assertEquals("hello!", is.readUTF()); + } + try (DataInputStream is = new DataInputStream(new FileInputStream(new File(root, "b.txt")))) { + assertEquals("wha?", is.readUTF()); + } + try (DataInputStream is = new DataInputStream(new FileInputStream(new File(root, "c.txt")))) { + assertEquals("goodbye!", is.readUTF()); + } + } finally { + JavaFS.unmount(mnt); + Files.delete(mnt); + } + + } +} diff --git a/src/test/java/co/paralleluniverse/javafs/Main.java b/src/test/java/co/paralleluniverse/javafs/Main.java new file mode 100644 index 0000000..7ea616b --- /dev/null +++ b/src/test/java/co/paralleluniverse/javafs/Main.java @@ -0,0 +1,34 @@ +package co.paralleluniverse.javafs; + +import co.paralleluniverse.javafs.JavaFS; +import com.google.common.jimfs.Jimfs; +import java.nio.file.FileSystem; +import java.nio.file.Paths; + +public class Main { + public static void main(final String... args) throws Exception { + try { + if (args.length < 1 || args.length > 3) + throw new IllegalArgumentException(); + + int i = 0; + boolean readonly = false; + if ("-r".equals(args[i])) { + readonly = true; + i++; + } + final String mountPoint = args[i++]; + final FileSystem fs = i >= args.length ? Jimfs.newFileSystem() : ZipFS.newZipFileSystem(Paths.get(args[i++])); + + System.out.println("========================"); + System.out.println("Mounting filesystem " + fs + " at " + mountPoint + (readonly ? " READONLY" : "")); + System.out.println("========================"); + + JavaFS.mount(fs, Paths.get(mountPoint), readonly, true); + Thread.sleep(Long.MAX_VALUE); + } catch (IllegalArgumentException e) { + System.err.println("Usage: JavaFS [-r] []"); + System.exit(1); + } + } +} diff --git a/src/test/java/co/paralleluniverse/javafs/ZipFS.java b/src/test/java/co/paralleluniverse/javafs/ZipFS.java new file mode 100644 index 0000000..a382107 --- /dev/null +++ b/src/test/java/co/paralleluniverse/javafs/ZipFS.java @@ -0,0 +1,73 @@ +/* + * Capsule + * Copyright (c) 2014-2015, Parallel Universe Software Co. All rights reserved. + * + * This program and the accompanying materials are licensed under the terms + * of the Eclipse Public License v1.0, available at + * http://www.eclipse.org/legal/epl-v10.html + */ +package co.paralleluniverse.javafs; + +import com.sun.nio.zipfs.ZipFileSystem; +import com.sun.nio.zipfs.ZipFileSystemProvider; +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.spi.FileSystemProvider; +import java.util.Collections; +import java.util.Map; + +/** + * Bypasses the check in ZipFileSystemProvider.newFileSystem that verifies that the given path is in the default FileSystem. + * This is a JDK bug, fixed in JDK 8. See https://bugs.openjdk.java.net/browse/JDK-8004789 + */ +public final class ZipFS { + private static final ZipFileSystemProvider ZIP_FILE_SYSTEM_PROVIDER = getZipFileSystemProvider(); + private static final Constructor ZIP_FILE_SYSTEM_CONSTRUCTOR; + + static { + try { + Constructor c = ZipFileSystem.class.getDeclaredConstructor(ZipFileSystemProvider.class, Path.class, Map.class); + c.setAccessible(true); + ZIP_FILE_SYSTEM_CONSTRUCTOR = c; + } catch (NoSuchMethodException e) { + throw new AssertionError(e); + } + } + + private static ZipFileSystemProvider getZipFileSystemProvider() { + for (FileSystemProvider fsr : FileSystemProvider.installedProviders()) { + if (fsr instanceof ZipFileSystemProvider) + return (ZipFileSystemProvider) fsr; + } + throw new AssertionError("Zip file system not installed!"); + } + + public static FileSystem newZipFileSystem(Path path) throws IOException { + // return FileSystems.newFileSystem(path, null); + if (path.getFileSystem() instanceof ZipFileSystem) + throw new IllegalArgumentException("Can't create a ZIP file system nested in a ZIP file system. (" + path + " is nested in " + path.getFileSystem() + ")"); + try { + return (ZipFileSystem) ZIP_FILE_SYSTEM_CONSTRUCTOR.newInstance(ZIP_FILE_SYSTEM_PROVIDER, path, Collections.emptyMap()); + } catch (ReflectiveOperationException e) { + throw new AssertionError(e); + } catch (Exception e) { + throw rethrow(e); + } + } + + public static RuntimeException rethrow(Throwable t) { + while (t instanceof InvocationTargetException) + t = ((InvocationTargetException) t).getTargetException(); + if (t instanceof RuntimeException) + throw (RuntimeException) t; + if (t instanceof Error) + throw (Error) t; + throw new RuntimeException(t); + } + + private ZipFS() { + } +}