diff --git a/src/main/cpp/posix.cpp b/src/main/cpp/posix.cpp index 94e31c3..7e377f7 100755 --- a/src/main/cpp/posix.cpp +++ b/src/main/cpp/posix.cpp @@ -71,15 +71,37 @@ Java_net_rubygrapefruit_platform_internal_jni_PosixFileFunctions_stat(JNIEnv *en if (pathStr == NULL) { return; } - int retval = stat(pathStr, &fileInfo); + int retval = lstat(pathStr, &fileInfo); free(pathStr); - if (retval != 0) { + if (retval != 0 && errno != ENOENT) { mark_failed_with_errno(env, "could not stat file", result); return; } + jclass destClass = env->GetObjectClass(dest); jfieldID modeField = env->GetFieldID(destClass, "mode", "I"); - env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode); + jfieldID typeField = env->GetFieldID(destClass, "type", "I"); + + if (retval != 0) { + env->SetIntField(dest, typeField, 4); + } else { + env->SetIntField(dest, modeField, 0777 & fileInfo.st_mode); + int type; + switch (fileInfo.st_mode & S_IFMT) { + case S_IFREG: + type = 0; + break; + case S_IFDIR: + type = 1; + break; + case S_IFLNK: + type = 2; + break; + default: + type= 3; + } + env->SetIntField(dest, typeField, type); + } } JNIEXPORT void JNICALL diff --git a/src/main/java/net/rubygrapefruit/platform/PosixFile.java b/src/main/java/net/rubygrapefruit/platform/PosixFile.java new file mode 100644 index 0000000..0884949 --- /dev/null +++ b/src/main/java/net/rubygrapefruit/platform/PosixFile.java @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Adam Murdoch + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.rubygrapefruit.platform; + +/** + * Provides some information about a file. This is a snapshot and does not change. + */ +@ThreadSafe +public interface PosixFile { + enum Type {File, Directory, Symlink, Other, Missing} + + /** + * Returns the type of this file. + */ + Type getType(); + + /** + * Returns the mode of this file. + */ + int getMode(); +} diff --git a/src/main/java/net/rubygrapefruit/platform/PosixFiles.java b/src/main/java/net/rubygrapefruit/platform/PosixFiles.java index bde857d..39b1db1 100644 --- a/src/main/java/net/rubygrapefruit/platform/PosixFiles.java +++ b/src/main/java/net/rubygrapefruit/platform/PosixFiles.java @@ -40,7 +40,7 @@ public interface PosixFiles extends NativeIntegration { int getMode(File path) throws NativeException; /** - * Creates a symbolic link. + * Creates a symbolic link with given contents. * * @throws NativeException On failure. */ @@ -54,4 +54,12 @@ public interface PosixFiles extends NativeIntegration { */ @ThreadSafe String readLink(File link) throws NativeException; + + /** + * Returns basic information about the given file. + * + * @throws NativeException On failure. + */ + @ThreadSafe + PosixFile stat(File path) throws NativeException; } diff --git a/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java b/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java index 0f91951..2adfa25 100755 --- a/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/DefaultPosixFiles.java @@ -17,12 +17,23 @@ package net.rubygrapefruit.platform.internal; import net.rubygrapefruit.platform.NativeException; +import net.rubygrapefruit.platform.PosixFile; import net.rubygrapefruit.platform.PosixFiles; import net.rubygrapefruit.platform.internal.jni.PosixFileFunctions; import java.io.File; public class DefaultPosixFiles implements PosixFiles { + public PosixFile stat(File file) throws NativeException { + FunctionResult result = new FunctionResult(); + FileStat stat = new FileStat(); + PosixFileFunctions.stat(file.getPath(), stat, result); + if (result.isFailed()) { + throw new NativeException(String.format("Could not get posix file details of %s: %s", file, result.getMessage())); + } + return stat; + } + public void setMode(File file, int perms) { FunctionResult result = new FunctionResult(); PosixFileFunctions.chmod(file.getPath(), perms, result); @@ -32,13 +43,11 @@ public class DefaultPosixFiles implements PosixFiles { } public int getMode(File file) { - FunctionResult result = new FunctionResult(); - FileStat stat = new FileStat(); - PosixFileFunctions.stat(file.getPath(), stat, result); - if (result.isFailed()) { - throw new NativeException(String.format("Could not get UNIX mode on %s: %s", file, result.getMessage())); + PosixFile stat = stat(file); + if (stat.getType() == PosixFile.Type.Missing) { + throw new NativeException(String.format("Could not get UNIX mode on %s: file does not exist.", file)); } - return stat.mode; + return stat.getMode(); } public String readLink(File link) throws NativeException { diff --git a/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java b/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java index d51628e..5349dab 100644 --- a/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java +++ b/src/main/java/net/rubygrapefruit/platform/internal/FileStat.java @@ -16,6 +16,17 @@ package net.rubygrapefruit.platform.internal; -public class FileStat { +import net.rubygrapefruit.platform.PosixFile; + +public class FileStat implements PosixFile { public int mode; + public int type; + + public int getMode() { + return mode; + } + + public Type getType() { + return Type.values()[type]; + } } diff --git a/src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy b/src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy index 5a4ff8f..9d0fe94 100755 --- a/src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy +++ b/src/test/groovy/net/rubygrapefruit/platform/PosixFilesTest.groovy @@ -32,6 +32,48 @@ class PosixFilesTest extends Specification { Native.get(PosixFiles.class) == file } + def "can get details of a file"() { + def testFile = tmpDir.newFile(fileName) + + when: + def stat = file.stat(testFile) + + then: + stat.type == PosixFile.Type.File + stat.mode != 0 + + where: + fileName << ["test.txt", "test\u03b1\u2295.txt"] + } + + def "can get details of a directory"() { + def testFile = tmpDir.newFolder(fileName) + + when: + def stat = file.stat(testFile) + + then: + stat.type == PosixFile.Type.Directory + stat.mode != 0 + + where: + fileName << ["test-dir", "test\u03b1\u2295-dir"] + } + + def "can get details of a missing file"() { + def testFile = new File(tmpDir.root, fileName) + + when: + def stat = file.stat(testFile) + + then: + stat.type == PosixFile.Type.Missing + stat.mode == 0 + + where: + fileName << ["test-dir", "test\u03b1\u2295-dir"] + } + def "can set mode on a file"() { def testFile = tmpDir.newFile(fileName) @@ -40,11 +82,26 @@ class PosixFilesTest extends Specification { then: file.getMode(testFile) == 0740 + file.stat(testFile).mode == 0740 where: fileName << ["test.txt", "test\u03b1\u2295.txt"] } + def "can set mode on a directory"() { + def testFile = tmpDir.newFolder(fileName) + + when: + file.setMode(testFile, 0740) + + then: + file.getMode(testFile) == 0740 + file.stat(testFile).mode == 0740 + + where: + fileName << ["test-dir", "test\u03b1\u2295-dir"] + } + def "cannot set mode on file that does not exist"() { def testFile = new File(tmpDir.root, "unknown") @@ -64,7 +121,7 @@ class PosixFilesTest extends Specification { then: NativeException e = thrown() - e.message == "Could not get UNIX mode on $testFile: could not stat file (ENOENT errno 2)" + e.message == "Could not get UNIX mode on $testFile: file does not exist." } def "can create symbolic link"() { @@ -126,4 +183,21 @@ class PosixFilesTest extends Specification { symlinkFile.file symlinkFile.canonicalFile == testFile.canonicalFile } + + def "can get details of a symlink"() { + def testFile = new File(tmpDir.newFolder("parent"), fileName) + + given: + file.symlink(testFile, "target") + + when: + def stat = file.stat(testFile) + + then: + stat.type == PosixFile.Type.Symlink + stat.mode != 0 + + where: + fileName << ["test.txt", "test\u03b1\u2295.txt"] + } }