提交 24fbe952 编写于 作者: A alanb

6595866: File does work with symbolic links (win,vista)

Reviewed-by: sherman
上级 b345613e
......@@ -51,13 +51,25 @@ static struct {
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 @@ Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls)
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 @@ Java_java_io_WinNTFileSystem_getBooleanAttributes(JNIEnv *env, jobject this,
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 @@ JNICALL Java_java_io_WinNTFileSystem_checkAccess(JNIEnv *env, jobject this,
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 @@ Java_java_io_WinNTFileSystem_setPermission(JNIEnv *env, jobject this,
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 @@ Java_java_io_WinNTFileSystem_getLastModifiedTime(JNIEnv *env, jobject this,
/* 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 @@ Java_java_io_WinNTFileSystem_getLength(JNIEnv *env, jobject this, jobject file)
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 @@ Java_java_io_WinNTFileSystem_createFileExclusively(JNIEnv *env, jclass cls,
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 @@ removeFileOrDirectory(const jchar *path)
/* 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 @@ Java_java_io_WinNTFileSystem_setLastModifiedTime(JNIEnv *env, jobject this,
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 @@ Java_java_io_WinNTFileSystem_setReadOnly(JNIEnv *env, jobject this,
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;
......
/*
* 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();
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册