1/*
2 * Copyright (c) 2008, 2013, 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 sun.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 = file.openForAttributeAccess(followLinks);
121        try {
122             final UnixFileAttributes attrs = UnixFileAttributes.get(fd);
123             final int dosAttribute = getDosAttribute(fd);
124
125             return new DosFileAttributes() {
126                @Override
127                public FileTime lastModifiedTime() {
128                    return attrs.lastModifiedTime();
129                }
130                @Override
131                public FileTime lastAccessTime() {
132                    return attrs.lastAccessTime();
133                }
134                @Override
135                public FileTime creationTime() {
136                    return attrs.creationTime();
137                }
138                @Override
139                public boolean isRegularFile() {
140                    return attrs.isRegularFile();
141                }
142                @Override
143                public boolean isDirectory() {
144                    return attrs.isDirectory();
145                }
146                @Override
147                public boolean isSymbolicLink() {
148                    return attrs.isSymbolicLink();
149                }
150                @Override
151                public boolean isOther() {
152                    return attrs.isOther();
153                }
154                @Override
155                public long size() {
156                    return attrs.size();
157                }
158                @Override
159                public Object fileKey() {
160                    return attrs.fileKey();
161                }
162                @Override
163                public boolean isReadOnly() {
164                    return (dosAttribute & DOS_XATTR_READONLY) != 0;
165                }
166                @Override
167                public boolean isHidden() {
168                    return (dosAttribute & DOS_XATTR_HIDDEN) != 0;
169                }
170                @Override
171                public boolean isArchive() {
172                    return (dosAttribute & DOS_XATTR_ARCHIVE) != 0;
173                }
174                @Override
175                public boolean isSystem() {
176                    return (dosAttribute & DOS_XATTR_SYSTEM) != 0;
177                }
178             };
179
180        } catch (UnixException x) {
181            x.rethrowAsIOException(file);
182            return null;    // keep compiler happy
183        } finally {
184            close(fd);
185        }
186    }
187
188    @Override
189    public void setReadOnly(boolean value) throws IOException {
190        updateDosAttribute(DOS_XATTR_READONLY, value);
191    }
192
193    @Override
194    public void setHidden(boolean value) throws IOException {
195        updateDosAttribute(DOS_XATTR_HIDDEN, value);
196    }
197
198    @Override
199    public void setArchive(boolean value) throws IOException {
200        updateDosAttribute(DOS_XATTR_ARCHIVE, value);
201    }
202
203    @Override
204    public void setSystem(boolean value) throws IOException {
205        updateDosAttribute(DOS_XATTR_SYSTEM, value);
206    }
207
208    /**
209     * Reads the value of the user.DOSATTRIB extended attribute
210     */
211    private int getDosAttribute(int fd) throws UnixException {
212        final int size = 24;
213
214        NativeBuffer buffer = NativeBuffers.getNativeBuffer(size);
215        try {
216            int len = LinuxNativeDispatcher
217                .fgetxattr(fd, DOS_XATTR_NAME_AS_BYTES, buffer.address(), size);
218
219            if (len > 0) {
220                // ignore null terminator
221                if (unsafe.getByte(buffer.address()+len-1) == 0)
222                    len--;
223
224                // convert to String and parse
225                byte[] buf = new byte[len];
226                // Android-changed: We don't have Unsafe.copyMemory yet, so we use putByte.
227                // unsafe.copyMemory(null, buffer.address(), buf,
228                //     Unsafe.ARRAY_BYTE_BASE_OFFSET, len);
229                for (int i = 0; i < len; i++) {
230                    buf[i] = unsafe.getByte(buffer.address() + i);
231                }
232                String value = Util.toString(buf);
233
234                // should be something like 0x20
235                if (value.length() >= 3 && value.startsWith("0x")) {
236                    try {
237                        return Integer.parseInt(value.substring(2), 16);
238                    } catch (NumberFormatException x) {
239                        // ignore
240                    }
241                }
242            }
243            throw new UnixException("Value of " + DOS_XATTR_NAME + " attribute is invalid");
244        } catch (UnixException x) {
245            // default value when attribute does not exist
246            if (x.errno() == ENODATA)
247                return 0;
248            throw x;
249        } finally {
250            buffer.release();
251        }
252    }
253
254    /**
255     * Updates the value of the user.DOSATTRIB extended attribute
256     */
257    private void updateDosAttribute(int flag, boolean enable) throws IOException {
258        file.checkWrite();
259
260        int fd = file.openForAttributeAccess(followLinks);
261        try {
262            int oldValue = getDosAttribute(fd);
263            int newValue = oldValue;
264            if (enable) {
265                newValue |= flag;
266            } else {
267                newValue &= ~flag;
268            }
269            if (newValue != oldValue) {
270                byte[] value = Util.toBytes("0x" + Integer.toHexString(newValue));
271                NativeBuffer buffer = NativeBuffers.asNativeBuffer(value);
272                try {
273                    LinuxNativeDispatcher.fsetxattr(fd, DOS_XATTR_NAME_AS_BYTES,
274                        buffer.address(), value.length+1);
275                } finally {
276                    buffer.release();
277                }
278            }
279        } catch (UnixException x) {
280            x.rethrowAsIOException(file);
281        } finally {
282            close(fd);
283        }
284    }
285}
286