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
26package sun.nio.fs;
27
28import java.nio.file.*;
29import static java.nio.file.StandardOpenOption.*;
30import java.nio.ByteBuffer;
31import java.nio.channels.FileChannel;
32import java.io.IOException;
33import java.util.*;
34import jdk.internal.misc.Unsafe;
35
36import static sun.nio.fs.WindowsNativeDispatcher.*;
37import static sun.nio.fs.WindowsConstants.*;
38
39/**
40 * Windows emulation of NamedAttributeView using Alternative Data Streams
41 */
42
43class 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    @Override
95    public List<String> list() throws IOException  {
96        if (System.getSecurityManager() != null)
97            checkAccess(file.getPathForPermissionCheck(), true, false);
98        return listUsingStreamEnumeration();
99    }
100
101    @Override
102    public int size(String name) throws IOException  {
103        if (System.getSecurityManager() != null)
104            checkAccess(file.getPathForPermissionCheck(), true, false);
105
106        // wrap with channel
107        FileChannel fc = null;
108        try {
109            Set<OpenOption> opts = new HashSet<>();
110            opts.add(READ);
111            if (!followLinks)
112                opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
113            fc = WindowsChannelFactory
114                .newFileChannel(join(file, name), null, opts, 0L);
115        } catch (WindowsException x) {
116            x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
117        }
118        try {
119            long size = fc.size();
120            if (size > Integer.MAX_VALUE)
121                throw new ArithmeticException("Stream too large");
122            return (int)size;
123        } finally {
124            fc.close();
125        }
126    }
127
128    @Override
129    public int read(String name, ByteBuffer dst) throws IOException {
130        if (System.getSecurityManager() != null)
131            checkAccess(file.getPathForPermissionCheck(), true, false);
132
133        // wrap with channel
134        FileChannel fc = null;
135        try {
136            Set<OpenOption> opts = new HashSet<>();
137            opts.add(READ);
138            if (!followLinks)
139                opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
140            fc = WindowsChannelFactory
141                .newFileChannel(join(file, name), null, opts, 0L);
142        } catch (WindowsException x) {
143            x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
144        }
145
146        // read to EOF (nothing we can do if I/O error occurs)
147        try {
148            if (fc.size() > dst.remaining())
149                throw new IOException("Stream too large");
150            int total = 0;
151            while (dst.hasRemaining()) {
152                int n = fc.read(dst);
153                if (n < 0)
154                    break;
155                total += n;
156            }
157            return total;
158        } finally {
159            fc.close();
160        }
161    }
162
163    @Override
164    public int write(String name, ByteBuffer src) throws IOException {
165        if (System.getSecurityManager() != null)
166            checkAccess(file.getPathForPermissionCheck(), false, true);
167
168        /**
169         * Creating a named stream will cause the unnamed stream to be created
170         * if it doesn't already exist. To avoid this we open the unnamed stream
171         * for reading and hope it isn't deleted/moved while we create or
172         * replace the named stream. Opening the file without sharing options
173         * may cause sharing violations with other programs that are accessing
174         * the unnamed stream.
175         */
176        long handle = -1L;
177        try {
178            int flags = FILE_FLAG_BACKUP_SEMANTICS;
179            if (!followLinks)
180                flags |= FILE_FLAG_OPEN_REPARSE_POINT;
181
182            handle = CreateFile(file.getPathForWin32Calls(),
183                                GENERIC_READ,
184                                (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE),
185                                OPEN_EXISTING,
186                                flags);
187        } catch (WindowsException x) {
188            x.rethrowAsIOException(file);
189        }
190        try {
191            Set<OpenOption> opts = new HashSet<>();
192            if (!followLinks)
193                opts.add(WindowsChannelFactory.OPEN_REPARSE_POINT);
194            opts.add(CREATE);
195            opts.add(WRITE);
196            opts.add(StandardOpenOption.TRUNCATE_EXISTING);
197            FileChannel named = null;
198            try {
199                named = WindowsChannelFactory
200                    .newFileChannel(join(file, name), null, opts, 0L);
201            } catch (WindowsException x) {
202                x.rethrowAsIOException(join(file.getPathForPermissionCheck(), name));
203            }
204            // write value (nothing we can do if I/O error occurs)
205            try {
206                int rem = src.remaining();
207                while (src.hasRemaining()) {
208                    named.write(src);
209                }
210                return rem;
211            } finally {
212                named.close();
213            }
214        } finally {
215            CloseHandle(handle);
216        }
217    }
218
219    @Override
220    public void delete(String name) throws IOException {
221        if (System.getSecurityManager() != null)
222            checkAccess(file.getPathForPermissionCheck(), false, true);
223
224        String path = WindowsLinkSupport.getFinalPath(file, followLinks);
225        String toDelete = join(path, name);
226        try {
227            DeleteFile(toDelete);
228        } catch (WindowsException x) {
229            x.rethrowAsIOException(toDelete);
230        }
231    }
232}
233