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 sun.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
join(String file, String name)49     private String join(String file, String name) {
50         if (name == null)
51             throw new NullPointerException("'name' is null");
52         return file + ":" + name;
53     }
join(WindowsPath file, String name)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 
WindowsUserDefinedFileAttributeView(WindowsPath file, boolean followLinks)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.
listUsingStreamEnumeration()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
listUsingBackupRead()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
list()200     public List<String> list() throws IOException  {
201         if (System.getSecurityManager() != null)
202             checkAccess(file.getPathForPermissionCheck(), true, false);
203         // use stream APIs on Windwos Server 2003 and newer
204         if (file.getFileSystem().supportsStreamEnumeration()) {
205             return listUsingStreamEnumeration();
206         } else {
207             return listUsingBackupRead();
208         }
209     }
210 
211     @Override
size(String name)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
read(String name, ByteBuffer dst)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
write(String name, ByteBuffer src)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
delete(String name)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 }
343