1/*
2 * Copyright (c) 2008, 2015, 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.attribute.*;
29import java.util.Map;
30import java.util.Set;
31import java.io.IOException;
32import jdk.internal.misc.Unsafe;
33
34import static sun.nio.fs.UnixNativeDispatcher.*;
35import static sun.nio.fs.UnixConstants.*;
36
37/**
38 * Linux implementation of DosFileAttributeView for use on file systems such
39 * as ext3 that have extended attributes enabled and SAMBA configured to store
40 * DOS attributes.
41 */
42
43class LinuxDosFileAttributeView
44    extends UnixFileAttributeViews.Basic implements DosFileAttributeView
45{
46    private static final Unsafe unsafe = Unsafe.getUnsafe();
47
48    private static final String READONLY_NAME = "readonly";
49    private static final String ARCHIVE_NAME = "archive";
50    private static final String SYSTEM_NAME = "system";
51    private static final String HIDDEN_NAME = "hidden";
52
53    private static final String DOS_XATTR_NAME = "user.DOSATTRIB";
54    private static final byte[] DOS_XATTR_NAME_AS_BYTES = Util.toBytes(DOS_XATTR_NAME);
55
56    private static final int DOS_XATTR_READONLY = 0x01;
57    private static final int DOS_XATTR_HIDDEN   = 0x02;
58    private static final int DOS_XATTR_SYSTEM   = 0x04;
59    private static final int DOS_XATTR_ARCHIVE  = 0x20;
60
61    // the names of the DOS attributes (includes basic)
62    private static final Set<String> dosAttributeNames =
63        Util.newSet(basicAttributeNames, READONLY_NAME, ARCHIVE_NAME, SYSTEM_NAME, HIDDEN_NAME);
64
65    LinuxDosFileAttributeView(UnixPath file, boolean followLinks) {
66        super(file, followLinks);
67    }
68
69    @Override
70    public String name() {
71        return "dos";
72    }
73
74    @Override
75    public void setAttribute(String attribute, Object value)
76        throws IOException
77    {
78        if (attribute.equals(READONLY_NAME)) {
79            setReadOnly((Boolean)value);
80            return;
81        }
82        if (attribute.equals(ARCHIVE_NAME)) {
83            setArchive((Boolean)value);
84            return;
85        }
86        if (attribute.equals(SYSTEM_NAME)) {
87            setSystem((Boolean)value);
88            return;
89        }
90        if (attribute.equals(HIDDEN_NAME)) {
91            setHidden((Boolean)value);
92            return;
93        }
94        super.setAttribute(attribute, value);
95    }
96
97    @Override
98    public Map<String,Object> readAttributes(String[] attributes)
99        throws IOException
100    {
101        AttributesBuilder builder =
102            AttributesBuilder.create(dosAttributeNames, attributes);
103        DosFileAttributes attrs = readAttributes();
104        addRequestedBasicAttributes(attrs, builder);
105        if (builder.match(READONLY_NAME))
106            builder.add(READONLY_NAME, attrs.isReadOnly());
107        if (builder.match(ARCHIVE_NAME))
108            builder.add(ARCHIVE_NAME, attrs.isArchive());
109        if (builder.match(SYSTEM_NAME))
110            builder.add(SYSTEM_NAME, attrs.isSystem());
111        if (builder.match(HIDDEN_NAME))
112            builder.add(HIDDEN_NAME, attrs.isHidden());
113        return builder.unmodifiableMap();
114    }
115
116    @Override
117    public DosFileAttributes readAttributes() throws IOException {
118        file.checkRead();
119
120        int fd = -1;
121        try {
122             fd = file.openForAttributeAccess(followLinks);
123             final UnixFileAttributes attrs = UnixFileAttributes.get(fd);
124             final int dosAttribute = getDosAttribute(fd);
125
126             return new DosFileAttributes() {
127                @Override
128                public FileTime lastModifiedTime() {
129                    return attrs.lastModifiedTime();
130                }
131                @Override
132                public FileTime lastAccessTime() {
133                    return attrs.lastAccessTime();
134                }
135                @Override
136                public FileTime creationTime() {
137                    return attrs.creationTime();
138                }
139                @Override
140                public boolean isRegularFile() {
141                    return attrs.isRegularFile();
142                }
143                @Override
144                public boolean isDirectory() {
145                    return attrs.isDirectory();
146                }
147                @Override
148                public boolean isSymbolicLink() {
149                    return attrs.isSymbolicLink();
150                }
151                @Override
152                public boolean isOther() {
153                    return attrs.isOther();
154                }
155                @Override
156                public long size() {
157                    return attrs.size();
158                }
159                @Override
160                public Object fileKey() {
161                    return attrs.fileKey();
162                }
163                @Override
164                public boolean isReadOnly() {
165                    return (dosAttribute & DOS_XATTR_READONLY) != 0;
166                }
167                @Override
168                public boolean isHidden() {
169                    return (dosAttribute & DOS_XATTR_HIDDEN) != 0;
170                }
171                @Override
172                public boolean isArchive() {
173                    return (dosAttribute & DOS_XATTR_ARCHIVE) != 0;
174                }
175                @Override
176                public boolean isSystem() {
177                    return (dosAttribute & DOS_XATTR_SYSTEM) != 0;
178                }
179             };
180
181        } catch (UnixException x) {
182            x.rethrowAsIOException(file);
183            return null;    // keep compiler happy
184        } finally {
185            close(fd);
186        }
187    }
188
189    @Override
190    public void setReadOnly(boolean value) throws IOException {
191        updateDosAttribute(DOS_XATTR_READONLY, value);
192    }
193
194    @Override
195    public void setHidden(boolean value) throws IOException {
196        updateDosAttribute(DOS_XATTR_HIDDEN, value);
197    }
198
199    @Override
200    public void setArchive(boolean value) throws IOException {
201        updateDosAttribute(DOS_XATTR_ARCHIVE, value);
202    }
203
204    @Override
205    public void setSystem(boolean value) throws IOException {
206        updateDosAttribute(DOS_XATTR_SYSTEM, value);
207    }
208
209    /**
210     * Reads the value of the user.DOSATTRIB extended attribute
211     */
212    private int getDosAttribute(int fd) throws UnixException {
213        final int size = 24;
214
215        NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
216        try {
217            int len = LinuxNativeDispatcher
218                .fgetxattr(fd, DOS_XATTR_NAME_AS_BYTES, buffer.address(), size);
219
220            if (len > 0) {
221                // ignore null terminator
222                if (unsafe.getByte(buffer.address()+len-1) == 0)
223                    len--;
224
225                // convert to String and parse
226                byte[] buf = new byte[len];
227                unsafe.copyMemory(null, buffer.address(), buf,
228                    Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
229                String value = Util.toString(buf);
230
231                // should be something like 0x20
232                if (value.length() >= 3 && value.startsWith("0x")) {
233                    try {
234                        return Integer.parseInt(value.substring(2), 16);
235                    } catch (NumberFormatException x) {
236                        // ignore
237                    }
238                }
239            }
240            throw new UnixException("Value of " + DOS_XATTR_NAME + " attribute is invalid");
241        } catch (UnixException x) {
242            // default value when attribute does not exist
243            if (x.errno() == ENODATA)
244                return 0;
245            throw x;
246        } finally {
247            buffer.release();
248        }
249    }
250
251    /**
252     * Updates the value of the user.DOSATTRIB extended attribute
253     */
254    private void updateDosAttribute(int flag, boolean enable) throws IOException {
255        file.checkWrite();
256
257        int fd = -1;
258        try {
259            fd = file.openForAttributeAccess(followLinks);
260            int oldValue = getDosAttribute(fd);
261            int newValue = oldValue;
262            if (enable) {
263                newValue |= flag;
264            } else {
265                newValue &= ~flag;
266            }
267            if (newValue != oldValue) {
268                byte[] value = Util.toBytes("0x" + Integer.toHexString(newValue));
269                NativeBuffer buffer = NativeBuffers.asNativeBuffer(value);
270                try {
271                    LinuxNativeDispatcher.fsetxattr(fd, DOS_XATTR_NAME_AS_BYTES,
272                        buffer.address(), value.length+1);
273                } finally {
274                    buffer.release();
275                }
276            }
277        } catch (UnixException x) {
278            x.rethrowAsIOException(file);
279        } finally {
280            close(fd);
281        }
282    }
283}
284