diff options
-rw-r--r-- | NativeCode.mk | 4 | ||||
-rw-r--r-- | luni/src/main/java/java/io/File.java | 208 | ||||
-rw-r--r-- | luni/src/main/native/java_io_File.cpp | 60 | ||||
-rw-r--r-- | luni/src/main/native/readlink.cpp | 42 | ||||
-rw-r--r-- | luni/src/main/native/readlink.h | 25 | ||||
-rw-r--r-- | luni/src/main/native/realpath.cpp | 125 | ||||
-rw-r--r-- | luni/src/main/native/sub.mk | 2 | ||||
-rw-r--r-- | luni/src/test/java/libcore/java/io/FileTest.java | 14 |
8 files changed, 272 insertions, 208 deletions
diff --git a/NativeCode.mk b/NativeCode.mk index 4c6ffa5..57c2457 100644 --- a/NativeCode.mk +++ b/NativeCode.mk @@ -94,8 +94,8 @@ endif # Define the rules. LOCAL_SRC_FILES := $(core_src_files) -LOCAL_C_INCLUDES := $(core_c_includes) -LOCAL_SHARED_LIBRARIES := $(core_shared_libraries) +LOCAL_C_INCLUDES := $(core_c_includes) bionic/ bionic/libstdc++/include external/stlport/stlport +LOCAL_SHARED_LIBRARIES := $(core_shared_libraries) libstlport LOCAL_STATIC_LIBRARIES := $(core_static_libraries) LOCAL_MODULE_TAGS := optional LOCAL_MODULE := libjavacore diff --git a/luni/src/main/java/java/io/File.java b/luni/src/main/java/java/io/File.java index ffc20aa..1e1d625 100644 --- a/luni/src/main/java/java/io/File.java +++ b/luni/src/main/java/java/io/File.java @@ -105,7 +105,7 @@ public class File implements Serializable, Comparable<File> { /** * The path we return from getAbsolutePath, and pass down to native code. */ - private String absolutePath; + private transient String absolutePath; static { // The default protection domain grants access to these properties. @@ -262,8 +262,8 @@ public class File implements Serializable, Comparable<File> { /** * Lists the file system roots. The Java platform may support zero or more * file systems, each with its own platform-dependent root. Further, the - * canonical pathname of any file on the system will always begin with one - * of the returned file system roots. + * {@link #getCanonicalPath canonical} path of any file on the system will + * always begin with one of the returned file system roots. * * @return the array of file system roots. */ @@ -436,9 +436,12 @@ public class File implements Serializable, Comparable<File> { private static native boolean existsImpl(String path); /** - * Returns the absolute path of this file. + * Returns the absolute path of this file. An absolute path is a path that starts at a root + * of the file system. On Android, there is only one root: {@code /}. * - * @return the absolute file path. + * <p>A common use for absolute paths is when passing paths to a {@code Process} as + * command-line arguments, to remove the requirement implied by relative paths, that the + * child must have the same working directory as its parent. */ public String getAbsolutePath() { return absolutePath; @@ -446,191 +449,46 @@ public class File implements Serializable, Comparable<File> { /** * Returns a new file constructed using the absolute path of this file. - * - * @return a new file from this file's absolute path. - * @see java.lang.SecurityManager#checkPropertyAccess + * Equivalent to {@code new File(this.getAbsolutePath())}. */ public File getAbsoluteFile() { return new File(this.getAbsolutePath()); } /** - * Returns the absolute path of this file with all references resolved. An - * <em>absolute</em> path is one that begins at the root of the file - * system. The canonical path is one in which all references have been - * resolved. For the cases of '..' and '.', where the file system supports - * parent and working directory respectively, these are removed and replaced - * with a direct directory reference. If the file does not exist, - * getCanonicalPath() may not resolve any references and simply returns an - * absolute path name or throws an IOException. + * Returns the canonical path of this file. + * An <i>absolute</i> path is one that begins at the root of the file system. + * A <i>canonical</i> path is an absolute path with symbolic links + * and references to "." or ".." resolved. If a path element does not exist (or + * is not searchable), there is a conflict between interpreting canonicalization + * as a textual operation (where "a/../b" is "b" even if "a" does not exist) . + * + * <p>Most callers should use {@link #getAbsolutePath} instead. A canonical path is + * significantly more expensive to compute, and not generally useful. The primary + * use for canonical paths is determining whether two paths point to the same file by + * comparing the canonicalized paths. + * + * <p>It can be actively harmful to use a canonical path, specifically because + * canonicalization removes symbolic links. It's wise to assume that a symbolic link + * is present for a reason, and that that reason is because the link may need to change. + * Canonicalization removes this layer of indirection. Good code should generally avoid + * caching canonical paths. * * @return the canonical path of this file. * @throws IOException * if an I/O error occurs. */ public String getCanonicalPath() throws IOException { - // BEGIN android-removed - // Caching the canonical path is bogus. Users facing specific - // performance problems can perform their own caching, with - // eviction strategies that are appropriate for their application. - // A VM-wide cache with no mechanism to evict stale elements is a - // disservice to applications that need up-to-date data. - // String canonPath = FileCanonPathCache.get(absPath); - // if (canonPath != null) { - // return canonPath; - // } - // END android-removed - - // TODO: rewrite getCanonicalPath, resolve, and resolveLink. - - String result = absolutePath; - if (separatorChar == '/') { - // resolve the full path first - result = resolveLink(result, result.length(), false); - // resolve the parent directories - result = resolve(result); - } - int numSeparators = 1; - for (int i = 0; i < result.length(); ++i) { - if (result.charAt(i) == separatorChar) { - numSeparators++; - } - } - int[] sepLocations = new int[numSeparators]; - int rootLoc = 0; - if (separatorChar != '/') { - if (result.charAt(0) == '\\') { - rootLoc = (result.length() > 1 && result.charAt(1) == '\\') ? 1 : 0; - } else { - rootLoc = 2; // skip drive i.e. c: - } - } - - char[] newResult = new char[result.length() + 1]; - int newLength = 0, lastSlash = 0, foundDots = 0; - sepLocations[lastSlash] = rootLoc; - for (int i = 0; i <= result.length(); ++i) { - if (i < rootLoc) { - newResult[newLength++] = result.charAt(i); - } else { - if (i == result.length() || result.charAt(i) == separatorChar) { - if (i == result.length() && foundDots == 0) { - break; - } - if (foundDots == 1) { - /* Don't write anything, just reset and continue */ - foundDots = 0; - continue; - } - if (foundDots > 1) { - /* Go back N levels */ - lastSlash = lastSlash > (foundDots - 1) ? lastSlash - (foundDots - 1) : 0; - newLength = sepLocations[lastSlash] + 1; - foundDots = 0; - continue; - } - sepLocations[++lastSlash] = newLength; - newResult[newLength++] = separatorChar; - continue; - } - if (result.charAt(i) == '.') { - foundDots++; - continue; - } - /* Found some dots within text, write them out */ - if (foundDots > 0) { - for (int j = 0; j < foundDots; j++) { - newResult[newLength++] = '.'; - } - } - newResult[newLength++] = result.charAt(i); - foundDots = 0; - } - } - // remove trailing slash - if (newLength > (rootLoc + 1) && newResult[newLength - 1] == separatorChar) { - newLength--; - } - return new String(newResult, 0, newLength); - } - - /* - * Resolve symbolic links in the parent directories. - */ - private static String resolve(String path) throws IOException { - int last = 1; - String linkPath = path; - String bytes; - boolean done; - for (int i = 1; i <= path.length(); i++) { - if (i == path.length() || path.charAt(i) == separatorChar) { - done = i >= path.length() - 1; - // if there is only one segment, do nothing - if (done && linkPath.length() == 1) { - return path; - } - boolean inPlace = false; - if (linkPath.equals(path)) { - bytes = path; - // if there are no symbolic links, truncate the path instead of copying - if (!done) { - inPlace = true; - path = path.substring(0, i); - } - } else { - int nextSize = i - last + 1; - int linkSize = linkPath.length(); - if (linkPath.charAt(linkSize - 1) == separatorChar) { - linkSize--; - } - bytes = linkPath.substring(0, linkSize) + - path.substring(last - 1, last - 1 + nextSize); - // the full path has already been resolved - } - if (done) { - return bytes; - } - linkPath = resolveLink(bytes, inPlace ? i : bytes.length(), true); - if (inPlace) { - // path[i] = '/'; - path = path.substring(0, i) + '/' + (i + 1 < path.length() ? path.substring(i + 1) : ""); - } - last = i + 1; - } - } - throw new InternalError(); + return realpath(absolutePath); } - /* - * Resolve a symbolic link. While the path resolves to an existing path, - * keep resolving. If an absolute link is found, resolve the parent - * directories if resolveAbsolute is true. + /** + * TODO: move this stuff to libcore.os. + * @hide */ - private static String resolveLink(String path, int length, boolean resolveAbsolute) throws IOException { - boolean restart = false; - do { - String fragment = path.substring(0, length); - String target = readlink(fragment); - if (target.equals(fragment)) { - break; - } - if (target.charAt(0) == separatorChar) { - // The link target was an absolute path, so we may need to start again. - restart = resolveAbsolute; - path = target + path.substring(length); - } else { - path = path.substring(0, path.lastIndexOf(separatorChar, length - 1) + 1) + target; - } - length = path.length(); - } while (existsImpl(path)); - // resolve the parent directories - if (restart) { - return resolve(path); - } - return path; - } - - private static native String readlink(String filePath); + public static native void symlink(String oldPath, String newPath); + private static native String realpath(String path); + private static native String readlink(String path); /** * Returns a new file created using the canonical path of this file. diff --git a/luni/src/main/native/java_io_File.cpp b/luni/src/main/native/java_io_File.cpp index ea8d12b..2419e2e 100644 --- a/luni/src/main/native/java_io_File.cpp +++ b/luni/src/main/native/java_io_File.cpp @@ -25,6 +25,9 @@ #include "ScopedPrimitiveArray.h" #include "ScopedUtfChars.h" #include "StaticAssert.h" +#include "readlink.h" + +#include <string> #include <dirent.h> #include <errno.h> @@ -32,8 +35,8 @@ #include <stdlib.h> #include <string.h> #include <sys/stat.h> -#include <sys/vfs.h> #include <sys/types.h> +#include <sys/vfs.h> #include <time.h> #include <unistd.h> #include <utime.h> @@ -126,24 +129,27 @@ static jstring File_readlink(JNIEnv* env, jclass, jstring javaPath) { return NULL; } - // We can't know how big a buffer readlink(2) will need, so we need to - // loop until it says "that fit". - size_t bufSize = 512; - while (true) { - LocalArray<512> buf(bufSize); - ssize_t len = readlink(path.c_str(), &buf[0], buf.size() - 1); - if (len == -1) { - // An error occurred. - return javaPath; - } - if (static_cast<size_t>(len) < buf.size() - 1) { - // The buffer was big enough. - buf[len] = '\0'; // readlink(2) doesn't NUL-terminate. - return env->NewStringUTF(&buf[0]); - } - // Try again with a bigger buffer. - bufSize *= 2; + std::string result; + if (!readlink(path.c_str(), result)) { + jniThrowIOException(env, errno); + return NULL; + } + return env->NewStringUTF(result.c_str()); +} + +static jstring File_realpath(JNIEnv* env, jclass, jstring javaPath) { + ScopedUtfChars path(env, javaPath); + if (path.c_str() == NULL) { + return NULL; + } + + extern bool realpath(const char* path, std::string& resolved); + std::string result; + if (!realpath(path.c_str(), result)) { + jniThrowIOException(env, errno); + return NULL; } + return env->NewStringUTF(result.c_str()); } static jboolean File_setLastModifiedImpl(JNIEnv* env, jclass, jstring javaPath, jlong ms) { @@ -421,6 +427,22 @@ static jboolean File_renameToImpl(JNIEnv* env, jclass, jstring javaOldPath, jstr return (rename(oldPath.c_str(), newPath.c_str()) == 0); } +static void File_symlink(JNIEnv* env, jclass, jstring javaOldPath, jstring javaNewPath) { + ScopedUtfChars oldPath(env, javaOldPath); + if (oldPath.c_str() == NULL) { + return; + } + + ScopedUtfChars newPath(env, javaNewPath); + if (newPath.c_str() == NULL) { + return; + } + + if (symlink(oldPath.c_str(), newPath.c_str()) == -1) { + jniThrowIOException(env, errno); + } +} + static JNINativeMethod gMethods[] = { NATIVE_METHOD(File, canExecuteImpl, "(Ljava/lang/String;)Z"), NATIVE_METHOD(File, canReadImpl, "(Ljava/lang/String;)Z"), @@ -438,11 +460,13 @@ static JNINativeMethod gMethods[] = { NATIVE_METHOD(File, listImpl, "(Ljava/lang/String;)[Ljava/lang/String;"), NATIVE_METHOD(File, mkdirImpl, "(Ljava/lang/String;)Z"), NATIVE_METHOD(File, readlink, "(Ljava/lang/String;)Ljava/lang/String;"), + NATIVE_METHOD(File, realpath, "(Ljava/lang/String;)Ljava/lang/String;"), NATIVE_METHOD(File, renameToImpl, "(Ljava/lang/String;Ljava/lang/String;)Z"), NATIVE_METHOD(File, setExecutableImpl, "(Ljava/lang/String;ZZ)Z"), NATIVE_METHOD(File, setLastModifiedImpl, "(Ljava/lang/String;J)Z"), NATIVE_METHOD(File, setReadableImpl, "(Ljava/lang/String;ZZ)Z"), NATIVE_METHOD(File, setWritableImpl, "(Ljava/lang/String;ZZ)Z"), + NATIVE_METHOD(File, symlink, "(Ljava/lang/String;Ljava/lang/String;)V"), }; int register_java_io_File(JNIEnv* env) { return jniRegisterNativeMethods(env, "java/io/File", gMethods, NELEM(gMethods)); diff --git a/luni/src/main/native/readlink.cpp b/luni/src/main/native/readlink.cpp new file mode 100644 index 0000000..555d515 --- /dev/null +++ b/luni/src/main/native/readlink.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +#include "LocalArray.h" +#include "readlink.h" + +#include <string> +#include <unistd.h> + +bool readlink(const char* path, std::string& result) { + // We can't know how big a buffer readlink(2) will need, so we need to + // loop until it says "that fit". + size_t bufSize = 512; + while (true) { + LocalArray<512> buf(bufSize); + ssize_t len = readlink(path, &buf[0], buf.size()); + if (len == -1) { + // An error occurred. + return false; + } + if (static_cast<size_t>(len) < buf.size()) { + // The buffer was big enough. + result.assign(&buf[0], len); + return true; + } + // Try again with a bigger buffer. + bufSize *= 2; + } +} diff --git a/luni/src/main/native/readlink.h b/luni/src/main/native/readlink.h new file mode 100644 index 0000000..14031dc --- /dev/null +++ b/luni/src/main/native/readlink.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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. + */ + +#include <string> + +/** + * Fills 'result' with the contents of the symbolic link 'path'. Sets errno and returns false on + * failure, returns true on success. The contents of 'result' on failure are undefined. Possible + * errors are those defined for readlink(2), except that this function takes care of sizing the + * buffer appropriately. + */ +bool readlink(const char* path, std::string& result); diff --git a/luni/src/main/native/realpath.cpp b/luni/src/main/native/realpath.cpp new file mode 100644 index 0000000..d1960a4 --- /dev/null +++ b/luni/src/main/native/realpath.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2003 Constantin S. Svintsoff <kostik@iclub.nsu.ru> + * + * 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. + * 3. The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 THE AUTHOR 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. + */ + +#include "readlink.h" + +#include <string> + +#include <errno.h> +#include <sys/param.h> +#include <sys/stat.h> +#include <unistd.h> + +/** + * This differs from realpath(3) mainly in its behavior when a path element does not exist or can + * not be searched. realpath(3) treats that as an error and gives up, but we have Java-compatible + * behavior where we just assume the path element was not a symbolic link. This leads to a textual + * treatment of ".." from that point in the path, which may actually lead us back to a path we + * can resolve (as in "/tmp/does-not-exist/../blah.txt" which would be an error for realpath(3) + * but "/tmp/blah.txt" under the traditional Java interpretation). + * + * This implementation also removes all the fixed-length buffers of the C original. + */ +bool realpath(const char* path, std::string& resolved) { + // 'path' must be an absolute path. + if (path[0] != '/') { + errno = EINVAL; + return false; + } + + resolved = "/"; + if (path[1] == '\0') { + return true; + } + + // Iterate over path components in 'left'. + int symlinkCount = 0; + std::string left(path + 1); + while (!left.empty()) { + // Extract the next path component. + size_t nextSlash = left.find('/'); + std::string nextPathComponent = left.substr(0, nextSlash); + if (nextSlash != std::string::npos) { + left.erase(0, nextSlash + 1); + } else { + left.clear(); + } + if (nextPathComponent.empty()) { + continue; + } else if (nextPathComponent == ".") { + continue; + } else if (nextPathComponent == "..") { + // Strip the last path component except when we have single "/". + if (resolved.size() > 1) { + resolved.erase(resolved.rfind('/')); + } + continue; + } + + // Append the next path component. + if (resolved[resolved.size() - 1] != '/') { + resolved += '/'; + } + resolved += nextPathComponent; + + // See if we've got a symbolic link, and resolve it if so. + struct stat sb; + if (lstat(resolved.c_str(), &sb) == 0 && S_ISLNK(sb.st_mode)) { + if (symlinkCount++ > MAXSYMLINKS) { + errno = ELOOP; + return false; + } + + std::string symlink; + if (!readlink(resolved.c_str(), symlink)) { + return false; + } + if (symlink[0] == '/') { + // The symbolic link is absolute, so we need to start from scratch. + resolved = "/"; + } else if (resolved.size() > 1) { + // The symbolic link is relative, so we just lose the last path component (which + // was the link). + resolved.erase(resolved.rfind('/')); + } + + if (!left.empty()) { + const char* maybeSlash = (symlink[symlink.size() - 1] != '/') ? "/" : ""; + left = symlink + maybeSlash + left; + } else { + left = symlink; + } + } + } + + // Remove trailing slash except when the resolved pathname is a single "/". + if (resolved.size() > 1 && resolved[resolved.size() - 1] == '/') { + resolved.erase(resolved.size() - 1, 1); + } + return true; +} diff --git a/luni/src/main/native/sub.mk b/luni/src/main/native/sub.mk index 774e8cb..cb6a68c 100644 --- a/luni/src/main/native/sub.mk +++ b/luni/src/main/native/sub.mk @@ -52,6 +52,8 @@ LOCAL_SRC_FILES := \ org_apache_harmony_luni_platform_OSNetworkSystem.cpp \ org_apache_harmony_luni_util_FloatingPointParser.cpp \ org_apache_harmony_xml_ExpatParser.cpp \ + readlink.cpp \ + realpath.cpp \ valueOf.cpp LOCAL_C_INCLUDES += \ diff --git a/luni/src/test/java/libcore/java/io/FileTest.java b/luni/src/test/java/libcore/java/io/FileTest.java index cd9b877..535975d 100644 --- a/luni/src/test/java/libcore/java/io/FileTest.java +++ b/luni/src/test/java/libcore/java/io/FileTest.java @@ -186,18 +186,6 @@ public class FileTest extends junit.framework.TestCase { } private static void ln_s(String target, String linkName) throws Exception { - String[] args = new String[] { "ln", "-s", target, linkName }; - // System.err.println("ln -s " + target + " " + linkName); - Process p = Runtime.getRuntime().exec(args); - int result = p.waitFor(); - if (result != 0) { - BufferedReader r = new BufferedReader(new InputStreamReader(p.getErrorStream())); - String line; - while ((line = r.readLine()) != null) { - System.err.println(line); - } - fail("ln -s " + target + " " + linkName + " failed. " + - "Does that file system support symlinks?"); - } + File.symlink(target, linkName); } } |