--- old/src/windows/native/java/io/WinNTFileSystem_md.c Fri Jul 31 18:02:14 2009 +++ new/src/windows/native/java/io/WinNTFileSystem_md.c Fri Jul 31 18:02:13 2009 @@ -51,13 +51,25 @@ jfieldID path; } ids; +/** + * GetFinalPathNameByHandle is available on Windows Vista and newer + */ +typedef BOOL (WINAPI* GetFinalPathNameByHandleProc) (HANDLE, LPWSTR, DWORD, DWORD); +static GetFinalPathNameByHandleProc GetFinalPathNameByHandle_func; + JNIEXPORT void JNICALL Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls) { + HANDLE handle; jclass fileClass = (*env)->FindClass(env, "java/io/File"); if (!fileClass) return; ids.path = (*env)->GetFieldID(env, fileClass, "path", "Ljava/lang/String;"); + handle = LoadLibrary("kernel32"); + if (handle != NULL) { + GetFinalPathNameByHandle_func = (GetFinalPathNameByHandleProc) + GetProcAddress(handle, "GetFinalPathNameByHandleW"); + } } /* -- Path operations -- */ @@ -65,6 +77,138 @@ extern int wcanonicalize(const WCHAR *path, WCHAR *out, int len); extern int wcanonicalizeWithPrefix(const WCHAR *canonicalPrefix, const WCHAR *pathWithCanonicalPrefix, WCHAR *out, int len); +/** + * Retrieves the fully resolved (final) path for the given path or NULL + * if the function fails. + */ +static WCHAR* getFinalPath(const WCHAR *path) +{ + HANDLE h; + WCHAR *result; + DWORD error; + + /* Need Windows Vista or newer to get the final path */ + if (GetFinalPathNameByHandle_func == NULL) + return NULL; + + h = CreateFileW(path, + FILE_READ_ATTRIBUTES, + FILE_SHARE_DELETE | + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (h == INVALID_HANDLE_VALUE) + return NULL; + + /** + * Allocate a buffer for the resolved path. For a long path we may need + * to allocate a larger buffer. + */ + result = (WCHAR*)malloc(MAX_PATH * sizeof(WCHAR)); + if (result != NULL) { + DWORD len = (*GetFinalPathNameByHandle_func)(h, result, MAX_PATH, 0); + if (len >= MAX_PATH) { + /* retry with a buffer of the right size */ + result = (WCHAR*)realloc(result, (len+1) * sizeof(WCHAR)); + if (result != NULL) { + len = (*GetFinalPathNameByHandle_func)(h, result, len, 0); + } else { + len = 0; + } + } + if (len > 0) { + /** + * Strip prefix (should be \\?\ or \\?\UNC */ + */ + if (result[0] == L'\\' && result[1] == L'\\' && + result[2] == L'?' && result[3] == L'\\') + { + int isUnc = (result[4] == L'U' && + result[5] == L'N' && + result[6] == L'C'); + int prefixLen = (isUnc) ? 7 : 4; + /* actual result length (includes terminator) */ + int resultLen = len - prefixLen + (isUnc ? 1 : 0) + 1; + + /* copy result without prefix into new buffer */ + WCHAR *tmp = (WCHAR*)malloc(resultLen * sizeof(WCHAR)); + if (tmp == NULL) { + len = 0; + } else { + WCHAR *p = result; + p += prefixLen; + if (isUnc) { + WCHAR *p2 = tmp; + p2[0] = L'\\'; + p2++; + wcscpy(p2, p); + } else { + wcscpy(tmp, p); + } + free(result); + result = tmp; + } + } + } + + /* unable to get final path */ + if (len == 0) { + free(result); + result = NULL; + } + } + + error = GetLastError(); + if (CloseHandle(h)) + SetLastError(error); + return result; +} + +/** + * Retrieves file information for the specified file. If the file is + * symbolic link then the information on fully resolved target is + * returned. + */ +static BOOL getFileInformation(const WCHAR *path, + BY_HANDLE_FILE_INFORMATION *finfo) +{ + BOOL result; + DWORD error; + HANDLE h = CreateFileW(path, + FILE_READ_ATTRIBUTES, + FILE_SHARE_DELETE | + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + NULL); + if (h == INVALID_HANDLE_VALUE) + return FALSE; + result = GetFileInformationByHandle(h, finfo); + error = GetLastError(); + if (CloseHandle(h)) + SetLastError(error); + return result; +} + +/** + * If the given attributes are the attributes of a reparse point, then + * read and return the attributes of the final target. + */ +DWORD getFinalAttributesIfReparsePoint(WCHAR *path, DWORD a) +{ + if ((a != INVALID_FILE_ATTRIBUTES) && + ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0)) + { + BY_HANDLE_FILE_INFORMATION finfo; + BOOL res = getFileInformation(path, &finfo); + a = (res) ? finfo.dwFileAttributes : INVALID_FILE_ATTRIBUTES; + } + return a; +} + JNIEXPORT jstring JNICALL Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this, jstring pathname) @@ -202,12 +346,15 @@ return rv; if (!isReservedDeviceNameW(pathbuf)) { if (GetFileAttributesExW(pathbuf, GetFileExInfoStandard, &wfad)) { - rv = (java_io_FileSystem_BA_EXISTS - | ((wfad.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - ? java_io_FileSystem_BA_DIRECTORY - : java_io_FileSystem_BA_REGULAR) - | ((wfad.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) - ? java_io_FileSystem_BA_HIDDEN : 0)); + DWORD a = getFinalAttributesIfReparsePoint(pathbuf, wfad.dwFileAttributes); + if (a != INVALID_FILE_ATTRIBUTES) { + rv = (java_io_FileSystem_BA_EXISTS + | ((a & FILE_ATTRIBUTE_DIRECTORY) + ? java_io_FileSystem_BA_DIRECTORY + : java_io_FileSystem_BA_REGULAR) + | ((a & FILE_ATTRIBUTE_HIDDEN) + ? java_io_FileSystem_BA_HIDDEN : 0)); + } } else { /* pagefile.sys is a special case */ if (GetLastError() == ERROR_SHARING_VIOLATION) { rv = java_io_FileSystem_BA_EXISTS; @@ -234,6 +381,7 @@ if (pathbuf == NULL) return JNI_FALSE; attr = GetFileAttributesW(pathbuf); + attr = getFinalAttributesIfReparsePoint(pathbuf, attr); free(pathbuf); if (attr == INVALID_FILE_ATTRIBUTES) return JNI_FALSE; @@ -272,6 +420,20 @@ if (pathbuf == NULL) return JNI_FALSE; a = GetFileAttributesW(pathbuf); + + /* if reparse point, get final target */ + if ((a != INVALID_FILE_ATTRIBUTES) && + ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0)) + { + WCHAR *fp = getFinalPath(pathbuf); + if (fp == NULL) { + a = INVALID_FILE_ATTRIBUTES; + } else { + free(pathbuf); + pathbuf = fp; + a = GetFileAttributesW(pathbuf); + } + } if (a != INVALID_FILE_ATTRIBUTES) { if (enable) a = a & ~FILE_ATTRIBUTE_READONLY; @@ -305,7 +467,7 @@ /* Open existing or fail */ OPEN_EXISTING, /* Backup semantics for directories */ - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_BACKUP_SEMANTICS, /* No template file */ NULL); if (h != INVALID_HANDLE_VALUE) { @@ -332,7 +494,16 @@ if (GetFileAttributesExW(pathbuf, GetFileExInfoStandard, &wfad)) { - rv = wfad.nFileSizeHigh * ((jlong)MAXDWORD + 1) + wfad.nFileSizeLow; + if ((wfad.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) == 0) { + rv = wfad.nFileSizeHigh * ((jlong)MAXDWORD + 1) + wfad.nFileSizeLow; + } else { + /* file is a reparse point so read attributes of final target */ + BY_HANDLE_FILE_INFORMATION finfo; + if (getFileInformation(pathbuf, &finfo)) { + rv = finfo.nFileSizeHigh * ((jlong)MAXDWORD + 1) + + finfo.nFileSizeLow; + } + } } else { if (GetLastError() == ERROR_SHARING_VIOLATION) { /* The error is "share violation", which means the file/dir @@ -360,24 +531,22 @@ if (pathbuf == NULL) return JNI_FALSE; h = CreateFileW( - pathbuf, /* Wide char path name */ - GENERIC_READ | GENERIC_WRITE, /* Read and write permission */ + pathbuf, /* Wide char path name */ + GENERIC_READ | GENERIC_WRITE, /* Read and write permission */ FILE_SHARE_READ | FILE_SHARE_WRITE, /* File sharing flags */ - NULL, /* Security attributes */ - CREATE_NEW, /* creation disposition */ - FILE_ATTRIBUTE_NORMAL, /* flags and attributes */ + NULL, /* Security attributes */ + CREATE_NEW, /* creation disposition */ + FILE_ATTRIBUTE_NORMAL | + FILE_FLAG_OPEN_REPARSE_POINT, /* flags and attributes */ NULL); if (h == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); if ((error != ERROR_FILE_EXISTS) && (error != ERROR_ALREADY_EXISTS)) { - - // If a directory by the named path already exists, - // return false (behavior of solaris and linux) instead of - // throwing an exception - DWORD fattr = GetFileAttributesW(pathbuf); - if ((fattr == INVALID_FILE_ATTRIBUTES) || - (fattr & ~FILE_ATTRIBUTE_DIRECTORY)) { + // return false rather than throwing an exception when there is + // an existing file. + DWORD a = GetFileAttributesW(pathbuf); + if (a == INVALID_FILE_ATTRIBUTES) { SetLastError(error); JNU_ThrowIOExceptionWithLastError(env, "Could not open file"); } @@ -384,7 +553,7 @@ } free(pathbuf); return JNI_FALSE; - } + } free(pathbuf); CloseHandle(h); return JNI_TRUE; @@ -396,9 +565,9 @@ /* Returns 0 on success */ DWORD a; - SetFileAttributesW(path, 0); + SetFileAttributesW(path, FILE_ATTRIBUTE_NORMAL); a = GetFileAttributesW(path); - if (a == ((DWORD)-1)) { + if (a == INVALID_FILE_ATTRIBUTES) { return 1; } else if (a & FILE_ATTRIBUTE_DIRECTORY) { return !RemoveDirectoryW(path); @@ -578,8 +747,13 @@ HANDLE h; if (pathbuf == NULL) return JNI_FALSE; - h = CreateFileW(pathbuf, GENERIC_WRITE, 0, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 0); + h = CreateFileW(pathbuf, + FILE_WRITE_ATTRIBUTES, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, + 0); if (h != INVALID_HANDLE_VALUE) { LARGE_INTEGER modTime; FILETIME t; @@ -607,6 +781,21 @@ if (pathbuf == NULL) return JNI_FALSE; a = GetFileAttributesW(pathbuf); + + /* if reparse point, get final target */ + if ((a != INVALID_FILE_ATTRIBUTES) && + ((a & FILE_ATTRIBUTE_REPARSE_POINT) != 0)) + { + WCHAR *fp = getFinalPath(pathbuf); + if (fp == NULL) { + a = INVALID_FILE_ATTRIBUTES; + } else { + free(pathbuf); + pathbuf = fp; + a = GetFileAttributesW(pathbuf); + } + } + if (a != INVALID_FILE_ATTRIBUTES) { if (SetFileAttributesW(pathbuf, a | FILE_ATTRIBUTE_READONLY)) rv = JNI_TRUE; --- /dev/null Fri Jul 31 18:02:17 2009 +++ new/test/java/io/File/SymLinks.java Fri Jul 31 18:02:16 2009 @@ -0,0 +1,380 @@ +/* + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* @test + * @bug 6595866 + * @summary Test java.io.File operations with sym links + */ + +import java.io.*; +import java.nio.file.Path; +import java.nio.file.attribute.*; +import static java.nio.file.LinkOption.*; + +public class SymLinks { + final static PrintStream out = System.out; + + final static File top = new File(System.getProperty("test.dir", ".")); + + // files used by the test + + final static File file = new File(top, "foofile"); + final static File link2file = new File(top, "link2file"); + final static File link2link2file = new File(top, "link2link2file"); + + final static File dir = new File(top, "foodir"); + final static File link2dir = new File(top, "link2dir"); + final static File link2link2dir = new File(top, "link2link2dir"); + + final static File link2nobody = new File(top, "link2nobody"); + final static File link2link2nobody = new File(top, "link2link2nobody"); + + /** + * Setup files, directories, and sym links used by test. + */ + static void setup() throws IOException { + // link2link2file -> link2file -> foofile + FileOutputStream fos = new FileOutputStream(file); + try { + fos.write(new byte[16*1024]); + } finally { + fos.close(); + } + mklink(link2file, file); + mklink(link2link2file, link2file); + + // link2link2dir -> link2dir -> dir + assertTrue(dir.mkdir()); + mklink(link2dir, dir); + mklink(link2link2dir, link2dir); + + // link2link2nobody -> link2nobody -> + mklink(link2nobody, new File(top, "DoesNotExist")); + mklink(link2link2nobody, link2nobody); + } + + /** + * Remove files, directories, and sym links used by test. + */ + static void cleanup() throws IOException { + if (file != null) + file.delete(); + if (link2file != null) + link2file.toPath().deleteIfExists(); + if (link2link2file != null) + link2link2file.toPath().deleteIfExists(); + if (dir != null) + dir.delete(); + if (link2dir != null) + link2dir.toPath().deleteIfExists(); + if (link2link2dir != null) + link2link2dir.toPath().deleteIfExists(); + if (link2nobody != null) + link2nobody.toPath().deleteIfExists(); + if (link2link2nobody != null) + link2link2nobody.toPath().deleteIfExists(); + } + + /** + * Creates a sym link source->target + */ + static void mklink(File source, File target) throws IOException { + source.toPath().createSymbolicLink(target.toPath()); + } + + /** + * Returns true if the "link" exists and is a sym link. + */ + static boolean isSymLink(File link) { + try { + BasicFileAttributes attrs = + Attributes.readBasicFileAttributes(link.toPath(), NOFOLLOW_LINKS); + return attrs.isSymbolicLink(); + } catch (IOException x) { + return false; + } + } + + /** + * Returns the last modified time of a sym link. + */ + static long lastModifiedOfSymLink(File link) throws IOException { + BasicFileAttributes attrs = + Attributes.readBasicFileAttributes(link.toPath(), NOFOLLOW_LINKS); + assertTrue(attrs.isSymbolicLink()); + return attrs.lastModifiedTime().toMillis(); + } + + /** + * Returns true if sym links are supported on the file system where + * "dir" exists. + */ + static boolean supportsSymLinks(File dir) { + Path link = dir.toPath().resolve("link"); + Path target = dir.toPath().resolve("target"); + try { + link.createSymbolicLink(target); + link.delete(); + return true; + } catch (UnsupportedOperationException x) { + return false; + } catch (IOException x) { + return false; + } + } + + static void assertTrue(boolean v) { + if (!v) throw new RuntimeException("Test failed"); + } + + static void assertFalse(boolean v) { + assertTrue(!v); + } + + static void header(String h) { + out.println(); + out.println(); + out.println("-- " + h + " --"); + } + + /** + * Tests go here. + */ + static void go() throws IOException { + + // check setup + assertTrue(file.isFile()); + assertTrue(isSymLink(link2file)); + assertTrue(isSymLink(link2link2file)); + assertTrue(dir.isDirectory()); + assertTrue(isSymLink(link2dir)); + assertTrue(isSymLink(link2link2dir)); + assertTrue(isSymLink(link2nobody)); + assertTrue(isSymLink(link2link2nobody)); + + header("createNewFile"); + + assertFalse(file.createNewFile()); + assertFalse(link2file.createNewFile()); + assertFalse(link2link2file.createNewFile()); + assertFalse(dir.createNewFile()); + assertFalse(link2dir.createNewFile()); + assertFalse(link2link2dir.createNewFile()); + assertFalse(link2nobody.createNewFile()); + assertFalse(link2link2nobody.createNewFile()); + + header("mkdir"); + + assertFalse(file.mkdir()); + assertFalse(link2file.mkdir()); + assertFalse(link2link2file.mkdir()); + assertFalse(dir.mkdir()); + assertFalse(link2dir.mkdir()); + assertFalse(link2link2dir.mkdir()); + assertFalse(link2nobody.mkdir()); + assertFalse(link2link2nobody.mkdir()); + + header("delete"); + + File link = new File(top, "mylink"); + try { + mklink(link, file); + assertTrue(link.delete()); + assertTrue(!isSymLink(link)); + assertTrue(file.exists()); + + mklink(link, link2file); + assertTrue(link.delete()); + assertTrue(!isSymLink(link)); + assertTrue(link2file.exists()); + + mklink(link, dir); + assertTrue(link.delete()); + assertTrue(!isSymLink(link)); + assertTrue(dir.exists()); + + mklink(link, link2dir); + assertTrue(link.delete()); + assertTrue(!isSymLink(link)); + assertTrue(link2dir.exists()); + + mklink(link, link2nobody); + assertTrue(link.delete()); + assertTrue(!isSymLink(link)); + assertTrue(isSymLink(link2nobody)); + + } finally { + link.toPath().deleteIfExists(); + } + + header("renameTo"); + + File newlink = new File(top, "newlink"); + assertTrue(link2file.renameTo(newlink)); + try { + assertTrue(file.exists()); + assertTrue(isSymLink(newlink)); + assertTrue(!isSymLink(link2file)); + } finally { + newlink.renameTo(link2file); // restore link + } + + assertTrue(link2dir.renameTo(newlink)); + try { + assertTrue(dir.exists()); + assertTrue(isSymLink(newlink)); + assertTrue(!isSymLink(link2dir)); + } finally { + newlink.renameTo(link2dir); // restore link + } + + header("list"); + + final String name = "entry"; + File entry = new File(dir, name); + try { + assertTrue(dir.list().length == 0); // directory should be empty + assertTrue(link2dir.list().length == 0); + assertTrue(link2link2dir.list().length == 0); + + assertTrue(entry.createNewFile()); + assertTrue(dir.list().length == 1); + assertTrue(dir.list()[0].equals(name)); + + // access directory by following links + assertTrue(link2dir.list().length == 1); + assertTrue(link2dir.list()[0].equals(name)); + assertTrue(link2link2dir.list().length == 1); + assertTrue(link2link2dir.list()[0].equals(name)); + + // files that are not directories + assertTrue(link2file.list() == null); + assertTrue(link2nobody.list() == null); + + } finally { + entry.delete(); + } + + header("isXXX"); + + assertTrue(file.isFile()); + assertTrue(link2file.isFile()); + assertTrue(link2link2file.isFile()); + + assertTrue(dir.isDirectory()); + assertTrue(link2dir.isDirectory()); + assertTrue(link2link2dir.isDirectory()); + + // on Windows we test with the DOS hidden attribute set + if (System.getProperty("os.name").startsWith("Windows")) { + DosFileAttributeView view = file.toPath() + .getFileAttributeView(DosFileAttributeView.class); + view.setHidden(true); + try { + assertTrue(file.isHidden()); + assertTrue(link2file.isHidden()); + assertTrue(link2link2file.isHidden()); + } finally { + view.setHidden(false); + } + assertFalse(file.isHidden()); + assertFalse(link2file.isHidden()); + assertFalse(link2link2file.isHidden()); + } + + header("length"); + + long len = file.length(); + assertTrue(len > 0L); + // these tests should follow links + assertTrue(link2file.length() == len); + assertTrue(link2link2file.length() == len); + assertTrue(link2nobody.length() == 0L); + + header("lastModified / setLastModified"); + + // need time to diff between link and file + long origLastModified = file.lastModified(); + assertTrue(origLastModified != 0L); + try { Thread.sleep(2000); } catch (InterruptedException x) { } + file.setLastModified(System.currentTimeMillis()); + + long lastModified = file.lastModified(); + assertTrue(lastModified != origLastModified); + assertTrue(lastModifiedOfSymLink(link2file) != lastModified); + assertTrue(lastModifiedOfSymLink(link2link2file) != lastModified); + assertTrue(link2file.lastModified() == lastModified); + assertTrue(link2link2file.lastModified() == lastModified); + assertTrue(link2nobody.lastModified() == 0L); + + origLastModified = dir.lastModified(); + assertTrue(origLastModified != 0L); + dir.setLastModified(0L); + assertTrue(dir.lastModified() == 0L); + assertTrue(link2dir.lastModified() == 0L); + assertTrue(link2link2dir.lastModified() == 0L); + dir.setLastModified(origLastModified); + + header("setXXX / canXXX"); + + assertTrue(file.canRead()); + assertTrue(file.canWrite()); + assertTrue(link2file.canRead()); + assertTrue(link2file.canWrite()); + assertTrue(link2link2file.canRead()); + assertTrue(link2link2file.canWrite()); + + if (file.setReadOnly()) { + assertFalse(file.canWrite()); + assertFalse(link2file.canWrite()); + assertFalse(link2link2file.canWrite()); + + assertTrue(file.setWritable(true)); // make writable + assertTrue(file.canWrite()); + assertTrue(link2file.canWrite()); + assertTrue(link2link2file.canWrite()); + + assertTrue(link2file.setReadOnly()); // make read only + assertFalse(file.canWrite()); + assertFalse(link2file.canWrite()); + assertFalse(link2link2file.canWrite()); + + assertTrue(link2link2file.setWritable(true)); // make writable + assertTrue(file.canWrite()); + assertTrue(link2file.canWrite()); + assertTrue(link2link2file.canWrite()); + } + } + + public static void main(String[] args) throws IOException { + if (supportsSymLinks(top)) { + try { + setup(); + go(); + } finally { + cleanup(); + } + } + } + +}