--- a/jdk/src/windows/native/java/io/WinNTFileSystem_md.c Thu Aug 20 11:24:42 2009 +0800
+++ b/jdk/src/windows/native/java/io/WinNTFileSystem_md.c Thu Aug 20 08:39:18 2009 +0100
@@ -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 && result != NULL) {
+ 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,31 +531,29 @@
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");
}
}
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 Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/io/File/SymLinks.java Thu Aug 20 08:39:18 2009 +0100
@@ -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 -> <does-not-exist>
+ 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();
+ }
+ }
+ }
+
+}