Co-authored-by: Alan Bateman <alan.bateman@oracle.com> Co-authored-by: Alex Buckley <alex.buckley@oracle.com> Co-authored-by: Erik Joelsson <erik.joelsson@oracle.com> Co-authored-by: Jonathan Gibbons <jonathan.gibbons@oracle.com> Co-authored-by: Karen Kinnear <karen.kinnear@oracle.com> Co-authored-by: Magnus Ihse Bursie <magnus.ihse.bursie@oracle.com> Co-authored-by: Mandy Chung <mandy.chung@oracle.com> Co-authored-by: Mark Reinhold <mark.reinhold@oracle.com> Co-authored-by: Paul Sandoz <paul.sandoz@oracle.com> Reviewed-by: alanb, chegar, ihse, mduigou
343 lines
13 KiB
Java
343 lines
13 KiB
Java
/*
|
|
* Copyright (c) 2008, 2011, Oracle and/or its affiliates. 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. Oracle designates this
|
|
* particular file as subject to the "Classpath" exception as provided
|
|
* by Oracle in the LICENSE file that accompanied this code.
|
|
*
|
|
* 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
package sun.nio.fs;
|
|
|
|
import java.nio.file.*;
|
|
import static java.nio.file.StandardOpenOption.*;
|
|
import java.nio.ByteBuffer;
|
|
import java.nio.channels.FileChannel;
|
|
import java.io.IOException;
|
|
import java.util.*;
|
|
import sun.misc.Unsafe;
|
|
|
|
import static sun.nio.fs.WindowsNativeDispatcher.*;
|
|
import static sun.nio.fs.WindowsConstants.*;
|
|
|
|
/**
|
|
* Windows emulation of NamedAttributeView using Alternative Data Streams
|
|
*/
|
|
|
|
class WindowsUserDefinedFileAttributeView
|
|
extends AbstractUserDefinedFileAttributeView
|
|
{
|
|
private static final Unsafe unsafe = Unsafe.getUnsafe();
|
|
|
|
// syntax to address named streams
|
|
private String join(String file, String name) {
|
|
if (name == null)
|
|
throw new NullPointerException("'name' is null");
|
|
return file + ":" + name;
|
|
}
|
|
private String join(WindowsPath file, String name) throws WindowsException {
|
|
return join(file.getPathForWin32Calls(), name);
|
|
}
|
|
|
|
private final WindowsPath file;
|
|
private final boolean followLinks;
|
|
|
|
WindowsUserDefinedFileAttributeView(WindowsPath file, boolean followLinks) {
|
|
this.file = file;
|
|
this.followLinks = followLinks;
|
|
}
|
|
|
|
// enumerates the file streams using FindFirstStream/FindNextStream APIs.
|
|
private List<String> listUsingStreamEnumeration() throws IOException {
|
|
List<String> list = new ArrayList<>();
|
|
try {
|
|
FirstStream first = FindFirstStream(file.getPathForWin32Calls());
|
|
if (first != null) {
|
|
long handle = first.handle();
|
|
try {
|
|
// first stream is always ::$DATA for files
|
|
String name = first.name();
|
|
if (!name.equals("::$DATA")) {
|
|
String[] segs = name.split(":");
|
|
list.add(segs[1]);
|
|
}
|
|
while ((name = FindNextStream(handle)) != null) {
|
|
String[] segs = name.split(":");
|
|
list.add(segs[1]);
|
|
}
|
|
} finally {
|
|
FindClose(handle);
|
|
}
|
|
}
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(file);
|
|
}
|
|
return Collections.unmodifiableList(list);
|
|
}
|
|
|
|
// enumerates the file streams by reading the stream headers using
|
|
// BackupRead
|
|
private List<String> listUsingBackupRead() throws IOException {
|
|
long handle = -1L;
|
|
try {
|
|
int flags = FILE_FLAG_BACKUP_SEMANTICS;
|
|
if (!followLinks && file.getFileSystem().supportsLinks())
|
|
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
|
|
|
handle = CreateFile(file.getPathForWin32Calls(),
|
|
GENERIC_READ,
|
|
FILE_SHARE_READ, // no write as we depend on file size
|
|
OPEN_EXISTING,
|
|
flags);
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(file);
|
|
}
|
|
|
|
// buffer to read stream header and stream name.
|
|
final int BUFFER_SIZE = 4096;
|
|
NativeBuffer buffer = null;
|
|
|
|
// result with names of alternative data streams
|
|
final List<String> list = new ArrayList<>();
|
|
|
|
try {
|
|
buffer = NativeBuffers.getNativeBuffer(BUFFER_SIZE);
|
|
long address = buffer.address();
|
|
|
|
/**
|
|
* typedef struct _WIN32_STREAM_ID {
|
|
* DWORD dwStreamId;
|
|
* DWORD dwStreamAttributes;
|
|
* LARGE_INTEGER Size;
|
|
* DWORD dwStreamNameSize;
|
|
* WCHAR cStreamName[ANYSIZE_ARRAY];
|
|
* } WIN32_STREAM_ID;
|
|
*/
|
|
final int SIZEOF_STREAM_HEADER = 20;
|
|
final int OFFSETOF_STREAM_ID = 0;
|
|
final int OFFSETOF_STREAM_SIZE = 8;
|
|
final int OFFSETOF_STREAM_NAME_SIZE = 16;
|
|
|
|
long context = 0L;
|
|
try {
|
|
for (;;) {
|
|
// read stream header
|
|
BackupResult result = BackupRead(handle, address,
|
|
SIZEOF_STREAM_HEADER, false, context);
|
|
context = result.context();
|
|
if (result.bytesTransferred() == 0)
|
|
break;
|
|
|
|
int streamId = unsafe.getInt(address + OFFSETOF_STREAM_ID);
|
|
long streamSize = unsafe.getLong(address + OFFSETOF_STREAM_SIZE);
|
|
int nameSize = unsafe.getInt(address + OFFSETOF_STREAM_NAME_SIZE);
|
|
|
|
// read stream name
|
|
if (nameSize > 0) {
|
|
result = BackupRead(handle, address, nameSize, false, context);
|
|
if (result.bytesTransferred() != nameSize)
|
|
break;
|
|
}
|
|
|
|
// check for alternative data stream
|
|
if (streamId == BACKUP_ALTERNATE_DATA) {
|
|
char[] nameAsArray = new char[nameSize/2];
|
|
unsafe.copyMemory(null, address, nameAsArray,
|
|
Unsafe.ARRAY_CHAR_BASE_OFFSET, nameSize);
|
|
|
|
String[] segs = new String(nameAsArray).split(":");
|
|
if (segs.length == 3)
|
|
list.add(segs[1]);
|
|
}
|
|
|
|
// sparse blocks not currently handled as documentation
|
|
// is not sufficient on how the spase block can be skipped.
|
|
if (streamId == BACKUP_SPARSE_BLOCK) {
|
|
throw new IOException("Spare blocks not handled");
|
|
}
|
|
|
|
// seek to end of stream
|
|
if (streamSize > 0L) {
|
|
BackupSeek(handle, streamSize, context);
|
|
}
|
|
}
|
|
} catch (WindowsException x) {
|
|
// failed to read or seek
|
|
throw new IOException(x.errorString());
|
|
} finally {
|
|
// release context
|
|
if (context != 0L) {
|
|
try {
|
|
BackupRead(handle, 0L, 0, true, context);
|
|
} catch (WindowsException ignore) { }
|
|
}
|
|
}
|
|
} finally {
|
|
if (buffer != null)
|
|
buffer.release();
|
|
CloseHandle(handle);
|
|
}
|
|
return Collections.unmodifiableList(list);
|
|
}
|
|
|
|
@Override
|
|
public List<String> list() throws IOException {
|
|
if (System.getSecurityManager() != null)
|
|
checkAccess(file.getPathForPermissionCheck(), true, false);
|
|
// use stream APIs on Windows Server 2003 and newer
|
|
if (file.getFileSystem().supportsStreamEnumeration()) {
|
|
return listUsingStreamEnumeration();
|
|
} else {
|
|
return listUsingBackupRead();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int size(String name) throws IOException {
|
|
if (System.getSecurityManager() != null)
|
|
checkAccess(file.getPathForPermissionCheck(), true, false);
|
|
|
|
// wrap with channel
|
|
FileChannel fc = null;
|
|
try {
|
|
Set<OpenOption> opts = new HashSet<>();
|
|
opts.add(READ);
|
|
if (!followLinks)
|
|
opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
|
|
fc = WindowsChannelFactory
|
|
.newFileChannel(join(file, name), null, opts, 0L);
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
|
|
}
|
|
try {
|
|
long size = fc.size();
|
|
if (size > Integer.MAX_VALUE)
|
|
throw new ArithmeticException("Stream too large");
|
|
return (int)size;
|
|
} finally {
|
|
fc.close();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int read(String name, ByteBuffer dst) throws IOException {
|
|
if (System.getSecurityManager() != null)
|
|
checkAccess(file.getPathForPermissionCheck(), true, false);
|
|
|
|
// wrap with channel
|
|
FileChannel fc = null;
|
|
try {
|
|
Set<OpenOption> opts = new HashSet<>();
|
|
opts.add(READ);
|
|
if (!followLinks)
|
|
opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
|
|
fc = WindowsChannelFactory
|
|
.newFileChannel(join(file, name), null, opts, 0L);
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
|
|
}
|
|
|
|
// read to EOF (nothing we can do if I/O error occurs)
|
|
try {
|
|
if (fc.size() > dst.remaining())
|
|
throw new IOException("Stream too large");
|
|
int total = 0;
|
|
while (dst.hasRemaining()) {
|
|
int n = fc.read(dst);
|
|
if (n < 0)
|
|
break;
|
|
total += n;
|
|
}
|
|
return total;
|
|
} finally {
|
|
fc.close();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public int write(String name, ByteBuffer src) throws IOException {
|
|
if (System.getSecurityManager() != null)
|
|
checkAccess(file.getPathForPermissionCheck(), false, true);
|
|
|
|
/**
|
|
* Creating a named stream will cause the unnamed stream to be created
|
|
* if it doesn't already exist. To avoid this we open the unnamed stream
|
|
* for reading and hope it isn't deleted/moved while we create or
|
|
* replace the named stream. Opening the file without sharing options
|
|
* may cause sharing violations with other programs that are accessing
|
|
* the unnamed stream.
|
|
*/
|
|
long handle = -1L;
|
|
try {
|
|
int flags = FILE_FLAG_BACKUP_SEMANTICS;
|
|
if (!followLinks)
|
|
flags |= FILE_FLAG_OPEN_REPARSE_POINT;
|
|
|
|
handle = CreateFile(file.getPathForWin32Calls(),
|
|
GENERIC_READ,
|
|
(FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
|
|
OPEN_EXISTING,
|
|
flags);
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(file);
|
|
}
|
|
try {
|
|
Set<OpenOption> opts = new HashSet<>();
|
|
if (!followLinks)
|
|
opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
|
|
opts.add(CREATE);
|
|
opts.add(WRITE);
|
|
opts.add(StandardOpenOption.TRUNCATE_EXISTING);
|
|
FileChannel named = null;
|
|
try {
|
|
named = WindowsChannelFactory
|
|
.newFileChannel(join(file, name), null, opts, 0L);
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
|
|
}
|
|
// write value (nothing we can do if I/O error occurs)
|
|
try {
|
|
int rem = src.remaining();
|
|
while (src.hasRemaining()) {
|
|
named.write(src);
|
|
}
|
|
return rem;
|
|
} finally {
|
|
named.close();
|
|
}
|
|
} finally {
|
|
CloseHandle(handle);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void delete(String name) throws IOException {
|
|
if (System.getSecurityManager() != null)
|
|
checkAccess(file.getPathForPermissionCheck(), false, true);
|
|
|
|
String path = WindowsLinkSupport.getFinalPath(file, followLinks);
|
|
String toDelete = join(path, name);
|
|
try {
|
|
DeleteFile(toDelete);
|
|
} catch (WindowsException x) {
|
|
x.rethrowAsIOException(toDelete);
|
|
}
|
|
}
|
|
}
|