1 /*
   2  * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.nio.fs;
  27 
  28 import java.nio.file.*;
  29 import static java.nio.file.StandardOpenOption.*;
  30 import java.nio.ByteBuffer;
  31 import java.nio.channels.FileChannel;
  32 import java.io.IOException;
  33 import java.util.*;
  34 import jdk.internal.misc.Unsafe;
  35 
  36 import static sun.nio.fs.WindowsNativeDispatcher.*;
  37 import static sun.nio.fs.WindowsConstants.*;
  38 
  39 /**
  40  * Windows emulation of NamedAttributeView using Alternative Data Streams
  41  */
  42 
  43 class WindowsUserDefinedFileAttributeView
  44     extends AbstractUserDefinedFileAttributeView
  45 {
  46     private static final Unsafe unsafe = Unsafe.getUnsafe();
  47 
  48     // syntax to address named streams
  49     private String join(String file, String name) {
  50         if (name == null)
  51             throw new NullPointerException("'name' is null");
  52         return file + ":" + name;
  53     }
  54     private String join(WindowsPath file, String name) throws WindowsException {
  55         return join(file.getPathForWin32Calls(), name);
  56     }
  57 
  58     private final WindowsPath file;
  59     private final boolean followLinks;
  60 
  61     WindowsUserDefinedFileAttributeView(WindowsPath file, boolean followLinks) {
  62         this.file = file;
  63         this.followLinks = followLinks;
  64     }
  65 
  66     // enumerates the file streams using FindFirstStream/FindNextStream APIs.
  67     private List<String> listUsingStreamEnumeration() throws IOException {
  68         List<String> list = new ArrayList<>();
  69         try {
  70             FirstStream first = FindFirstStream(file.getPathForWin32Calls());
  71             if (first != null) {
  72                 long handle = first.handle();
  73                 try {
  74                     // first stream is always ::$DATA for files
  75                     String name = first.name();
  76                     if (!name.equals("::$DATA")) {
  77                         String[] segs = name.split(":");
  78                         list.add(segs[1]);
  79                     }
  80                     while ((name = FindNextStream(handle)) != null) {
  81                         String[] segs = name.split(":");
  82                         list.add(segs[1]);
  83                     }
  84                 } finally {
  85                     FindClose(handle);
  86                 }
  87             }
  88         } catch (WindowsException x) {
  89             x.rethrowAsIOException(file);
  90         }
  91         return Collections.unmodifiableList(list);
  92     }
  93 
  94     // enumerates the file streams by reading the stream headers using
  95     // BackupRead
  96     private List<String> listUsingBackupRead() throws IOException {
  97         long handle = -1L;
  98         try {
  99             int flags = FILE_FLAG_BACKUP_SEMANTICS;
 100             if (!followLinks && file.getFileSystem().supportsLinks())
 101                 flags |= FILE_FLAG_OPEN_REPARSE_POINT;
 102 
 103             handle = CreateFile(file.getPathForWin32Calls(),
 104                                 GENERIC_READ,
 105                                 FILE_SHARE_READ, // no write as we depend on file size
 106                                 OPEN_EXISTING,
 107                                 flags);
 108         } catch (WindowsException x) {
 109             x.rethrowAsIOException(file);
 110         }
 111 
 112         // buffer to read stream header and stream name.
 113         final int BUFFER_SIZE = 4096;
 114         NativeBuffer buffer = null;
 115 
 116         // result with names of alternative data streams
 117         final List<String> list = new ArrayList<>();
 118 
 119         try {
 120             buffer = NativeBuffers.getNativeBuffer(BUFFER_SIZE);
 121             long address = buffer.address();
 122 
 123             /**
 124              * typedef struct _WIN32_STREAM_ID {
 125              *     DWORD dwStreamId;
 126              *     DWORD dwStreamAttributes;
 127              *     LARGE_INTEGER Size;
 128              *     DWORD dwStreamNameSize;
 129              *     WCHAR cStreamName[ANYSIZE_ARRAY];
 130              * } WIN32_STREAM_ID;
 131              */
 132             final int SIZEOF_STREAM_HEADER      = 20;
 133             final int OFFSETOF_STREAM_ID        = 0;
 134             final int OFFSETOF_STREAM_SIZE      = 8;
 135             final int OFFSETOF_STREAM_NAME_SIZE = 16;
 136 
 137             long context = 0L;
 138             try {
 139                 for (;;) {
 140                     // read stream header
 141                     BackupResult result = BackupRead(handle, address,
 142                        SIZEOF_STREAM_HEADER, false, context);
 143                     context = result.context();
 144                     if (result.bytesTransferred() == 0)
 145                         break;
 146 
 147                     int streamId = unsafe.getInt(address + OFFSETOF_STREAM_ID);
 148                     long streamSize = unsafe.getLong(address + OFFSETOF_STREAM_SIZE);
 149                     int nameSize = unsafe.getInt(address + OFFSETOF_STREAM_NAME_SIZE);
 150 
 151                     // read stream name
 152                     if (nameSize > 0) {
 153                         result = BackupRead(handle, address, nameSize, false, context);
 154                         if (result.bytesTransferred() != nameSize)
 155                             break;
 156                     }
 157 
 158                     // check for alternative data stream
 159                     if (streamId == BACKUP_ALTERNATE_DATA) {
 160                         char[] nameAsArray = new char[nameSize/2];
 161                         unsafe.copyMemory(null, address, nameAsArray,
 162                             Unsafe.ARRAY_CHAR_BASE_OFFSET, nameSize);
 163 
 164                         String[] segs = new String(nameAsArray).split(":");
 165                         if (segs.length == 3)
 166                             list.add(segs[1]);
 167                     }
 168 
 169                     // sparse blocks not currently handled as documentation
 170                     // is not sufficient on how the spase block can be skipped.
 171                     if (streamId == BACKUP_SPARSE_BLOCK) {
 172                         throw new IOException("Spare blocks not handled");
 173                     }
 174 
 175                     // seek to end of stream
 176                     if (streamSize > 0L) {
 177                         BackupSeek(handle, streamSize, context);
 178                     }
 179                 }
 180             } catch (WindowsException x) {
 181                 // failed to read or seek
 182                 throw new IOException(x.errorString());
 183             } finally {
 184                 // release context
 185                 if (context != 0L) {
 186                    try {
 187                        BackupRead(handle, 0L, 0, true, context);
 188                    } catch (WindowsException ignore) { }
 189                 }
 190             }
 191         } finally {
 192             if (buffer != null)
 193                 buffer.release();
 194             CloseHandle(handle);
 195         }
 196         return Collections.unmodifiableList(list);
 197     }
 198 
 199     @Override
 200     public List<String> list() throws IOException  {
 201         if (System.getSecurityManager() != null)
 202             checkAccess(file.getPathForPermissionCheck(), true, false);
 203         // use stream APIs on Windows Server 2003 and newer
 204         if (file.getFileSystem().supportsStreamEnumeration()) {
 205             return listUsingStreamEnumeration();
 206         } else {
 207             return listUsingBackupRead();
 208         }
 209     }
 210 
 211     @Override
 212     public int size(String name) throws IOException  {
 213         if (System.getSecurityManager() != null)
 214             checkAccess(file.getPathForPermissionCheck(), true, false);
 215 
 216         // wrap with channel
 217         FileChannel fc = null;
 218         try {
 219             Set<OpenOption> opts = new HashSet<>();
 220             opts.add(READ);
 221             if (!followLinks)
 222                 opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
 223             fc = WindowsChannelFactory
 224                 .newFileChannel(join(file, name), null, opts, 0L);
 225         } catch (WindowsException x) {
 226             x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
 227         }
 228         try {
 229             long size = fc.size();
 230             if (size > Integer.MAX_VALUE)
 231                 throw new ArithmeticException("Stream too large");
 232             return (int)size;
 233         } finally {
 234             fc.close();
 235         }
 236     }
 237 
 238     @Override
 239     public int read(String name, ByteBuffer dst) throws IOException {
 240         if (System.getSecurityManager() != null)
 241             checkAccess(file.getPathForPermissionCheck(), true, false);
 242 
 243         // wrap with channel
 244         FileChannel fc = null;
 245         try {
 246             Set<OpenOption> opts = new HashSet<>();
 247             opts.add(READ);
 248             if (!followLinks)
 249                 opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
 250             fc = WindowsChannelFactory
 251                 .newFileChannel(join(file, name), null, opts, 0L);
 252         } catch (WindowsException x) {
 253             x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
 254         }
 255 
 256         // read to EOF (nothing we can do if I/O error occurs)
 257         try {
 258             if (fc.size() > dst.remaining())
 259                 throw new IOException("Stream too large");
 260             int total = 0;
 261             while (dst.hasRemaining()) {
 262                 int n = fc.read(dst);
 263                 if (n < 0)
 264                     break;
 265                 total += n;
 266             }
 267             return total;
 268         } finally {
 269             fc.close();
 270         }
 271     }
 272 
 273     @Override
 274     public int write(String name, ByteBuffer src) throws IOException {
 275         if (System.getSecurityManager() != null)
 276             checkAccess(file.getPathForPermissionCheck(), false, true);
 277 
 278         /**
 279          * Creating a named stream will cause the unnamed stream to be created
 280          * if it doesn't already exist. To avoid this we open the unnamed stream
 281          * for reading and hope it isn't deleted/moved while we create or
 282          * replace the named stream. Opening the file without sharing options
 283          * may cause sharing violations with other programs that are accessing
 284          * the unnamed stream.
 285          */
 286         long handle = -1L;
 287         try {
 288             int flags = FILE_FLAG_BACKUP_SEMANTICS;
 289             if (!followLinks)
 290                 flags |= FILE_FLAG_OPEN_REPARSE_POINT;
 291 
 292             handle = CreateFile(file.getPathForWin32Calls(),
 293                                 GENERIC_READ,
 294                                 (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
 295                                 OPEN_EXISTING,
 296                                 flags);
 297         } catch (WindowsException x) {
 298             x.rethrowAsIOException(file);
 299         }
 300         try {
 301             Set<OpenOption> opts = new HashSet<>();
 302             if (!followLinks)
 303                 opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
 304             opts.add(CREATE);
 305             opts.add(WRITE);
 306             opts.add(StandardOpenOption.TRUNCATE_EXISTING);
 307             FileChannel named = null;
 308             try {
 309                 named = WindowsChannelFactory
 310                     .newFileChannel(join(file, name), null, opts, 0L);
 311             } catch (WindowsException x) {
 312                 x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
 313             }
 314             // write value (nothing we can do if I/O error occurs)
 315             try {
 316                 int rem = src.remaining();
 317                 while (src.hasRemaining()) {
 318                     named.write(src);
 319                 }
 320                 return rem;
 321             } finally {
 322                 named.close();
 323             }
 324         } finally {
 325             CloseHandle(handle);
 326         }
 327     }
 328 
 329     @Override
 330     public void delete(String name) throws IOException {
 331         if (System.getSecurityManager() != null)
 332             checkAccess(file.getPathForPermissionCheck(), false, true);
 333 
 334         String path = WindowsLinkSupport.getFinalPath(file, followLinks);
 335         String toDelete = join(path, name);
 336         try {
 337             DeleteFile(toDelete);
 338         } catch (WindowsException x) {
 339             x.rethrowAsIOException(toDelete);
 340         }
 341     }
 342 }