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.*;
29import java.nio.file.attribute.*;
30import java.nio.file.spi.*;
31import java.io.IOException;
32import java.util.*;
33import java.util.regex.Pattern;
34import java.security.AccessController;
35import sun.security.action.GetPropertyAction;
36
37/**
38 * Base implementation of FileSystem for Unix-like implementations.
39 */
40
41abstract class UnixFileSystem
42    extends FileSystem
43{
44    private final UnixFileSystemProvider provider;
45    private final byte[] defaultDirectory;
46    private final boolean needToResolveAgainstDefaultDirectory;
47    private final UnixPath rootDirectory;
48
49    // package-private
50    UnixFileSystem(UnixFileSystemProvider provider, String dir) {
51        this.provider = provider;
52        this.defaultDirectory = Util.toBytes(UnixPath.normalizeAndCheck(dir));
53        if (this.defaultDirectory[0] != '/') {
54            throw new RuntimeException("default directory must be absolute");
55        }
56
57        // if process-wide chdir is allowed or default directory is not the
58        // process working directory then paths must be resolved against the
59        // default directory.
60        String propValue = AccessController.doPrivileged(
61            new GetPropertyAction("sun.nio.fs.chdirAllowed", "false"));
62        boolean chdirAllowed = (propValue.length() == 0) ?
63            true : Boolean.valueOf(propValue);
64        if (chdirAllowed) {
65            this.needToResolveAgainstDefaultDirectory = true;
66        } else {
67            byte[] cwd = UnixNativeDispatcher.getcwd();
68            boolean defaultIsCwd = (cwd.length == defaultDirectory.length);
69            if (defaultIsCwd) {
70                for (int i=0; i<cwd.length; i++) {
71                    if (cwd[i] != defaultDirectory[i]) {
72                        defaultIsCwd = false;
73                        break;
74                    }
75                }
76            }
77            this.needToResolveAgainstDefaultDirectory = !defaultIsCwd;
78        }
79
80        // the root directory
81        this.rootDirectory = new UnixPath(this, "/");
82    }
83
84    // package-private
85    byte[] defaultDirectory() {
86        return defaultDirectory;
87    }
88
89    boolean needToResolveAgainstDefaultDirectory() {
90        return needToResolveAgainstDefaultDirectory;
91    }
92
93    UnixPath rootDirectory() {
94        return rootDirectory;
95    }
96
97    boolean isSolaris() {
98        return false;
99    }
100
101    static List<String> standardFileAttributeViews() {
102        return Arrays.asList("basic", "posix", "unix", "owner");
103    }
104
105    @Override
106    public final FileSystemProvider provider() {
107        return provider;
108    }
109
110    @Override
111    public final String getSeparator() {
112        return "/";
113    }
114
115    @Override
116    public final boolean isOpen() {
117        return true;
118    }
119
120    @Override
121    public final boolean isReadOnly() {
122        return false;
123    }
124
125    @Override
126    public final void close() throws IOException {
127        throw new UnsupportedOperationException();
128    }
129
130    /**
131     * Copies non-POSIX attributes from the source to target file.
132     *
133     * Copying a file preserving attributes, or moving a file, will preserve
134     * the file owner/group/permissions/timestamps but it does not preserve
135     * other non-POSIX attributes. This method is invoked by the
136     * copy or move operation to preserve these attributes. It should copy
137     * extended attributes, ACLs, or other attributes.
138     *
139     * @param   sfd
140     *          Open file descriptor to source file
141     * @param   tfd
142     *          Open file descriptor to target file
143     */
144    void copyNonPosixAttributes(int sfd, int tfd) {
145        // no-op by default
146    }
147
148    /**
149     * Unix systems only have a single root directory (/)
150     */
151    @Override
152    public final Iterable<Path> getRootDirectories() {
153        final List<Path> allowedList =
154           Collections.unmodifiableList(Arrays.asList((Path)rootDirectory));
155        return new Iterable<Path>() {
156            public Iterator<Path> iterator() {
157                try {
158                    SecurityManager sm = System.getSecurityManager();
159                    if (sm != null)
160                        sm.checkRead(rootDirectory.toString());
161                    return allowedList.iterator();
162                } catch (SecurityException x) {
163                    List<Path> disallowed = Collections.emptyList();
164                    return disallowed.iterator();
165                }
166            }
167        };
168    }
169
170    /**
171     * Returns object to iterate over entries in mounttab or equivalent
172     */
173    abstract Iterable<UnixMountEntry> getMountEntries();
174
175    /**
176     * Returns a FileStore to represent the file system for the given mount
177     * mount.
178     */
179    abstract FileStore getFileStore(UnixMountEntry entry) throws IOException;
180
181    /**
182     * Iterator returned by getFileStores method.
183     */
184    private class FileStoreIterator implements Iterator<FileStore> {
185        private final Iterator<UnixMountEntry> entries;
186        private FileStore next;
187
188        FileStoreIterator() {
189            this.entries = getMountEntries().iterator();
190        }
191
192        private FileStore readNext() {
193            assert Thread.holdsLock(this);
194            for (;;) {
195                if (!entries.hasNext())
196                    return null;
197                UnixMountEntry entry = entries.next();
198
199                // skip entries with the "ignore" option
200                if (entry.isIgnored())
201                    continue;
202
203                // check permission to read mount point
204                SecurityManager sm = System.getSecurityManager();
205                if (sm != null) {
206                    try {
207                        sm.checkRead(Util.toString(entry.dir()));
208                    } catch (SecurityException x) {
209                        continue;
210                    }
211                }
212                try {
213                    return getFileStore(entry);
214                } catch (IOException ignore) {
215                    // ignore as per spec
216                }
217            }
218        }
219
220        @Override
221        public synchronized boolean hasNext() {
222            if (next != null)
223                return true;
224            next = readNext();
225            return next != null;
226        }
227
228        @Override
229        public synchronized FileStore next() {
230            if (next == null)
231                next = readNext();
232            if (next == null) {
233                throw new NoSuchElementException();
234            } else {
235                FileStore result = next;
236                next = null;
237                return result;
238            }
239        }
240
241        @Override
242        public void remove() {
243            throw new UnsupportedOperationException();
244        }
245    }
246
247    @Override
248    public final Iterable<FileStore> getFileStores() {
249        SecurityManager sm = System.getSecurityManager();
250        if (sm != null) {
251            try {
252                sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
253            } catch (SecurityException se) {
254                return Collections.emptyList();
255            }
256        }
257        return new Iterable<FileStore>() {
258            public Iterator<FileStore> iterator() {
259                return new FileStoreIterator();
260            }
261        };
262    }
263
264    @Override
265    public final Path getPath(String first, String... more) {
266        String path;
267        if (more.length == 0) {
268            path = first;
269        } else {
270            StringBuilder sb = new StringBuilder();
271            sb.append(first);
272            for (String segment: more) {
273                if (segment.length() > 0) {
274                    if (sb.length() > 0)
275                        sb.append('/');
276                    sb.append(segment);
277                }
278            }
279            path = sb.toString();
280        }
281        return new UnixPath(this, path);
282    }
283
284    @Override
285    public PathMatcher getPathMatcher(String syntaxAndInput) {
286        int pos = syntaxAndInput.indexOf(':');
287        if (pos <= 0 || pos == syntaxAndInput.length())
288            throw new IllegalArgumentException();
289        String syntax = syntaxAndInput.substring(0, pos);
290        String input = syntaxAndInput.substring(pos+1);
291
292        String expr;
293        if (syntax.equals(GLOB_SYNTAX)) {
294            expr = Globs.toUnixRegexPattern(input);
295        } else {
296            if (syntax.equals(REGEX_SYNTAX)) {
297                expr = input;
298            } else {
299                throw new UnsupportedOperationException("Syntax '" + syntax +
300                    "' not recognized");
301            }
302        }
303
304        // return matcher
305        final Pattern pattern = compilePathMatchPattern(expr);
306
307        return new PathMatcher() {
308            @Override
309            public boolean matches(Path path) {
310                return pattern.matcher(path.toString()).matches();
311            }
312        };
313    }
314
315    private static final String GLOB_SYNTAX = "glob";
316    private static final String REGEX_SYNTAX = "regex";
317
318    @Override
319    public final UserPrincipalLookupService getUserPrincipalLookupService() {
320        return LookupService.instance;
321    }
322
323    private static class LookupService {
324        static final UserPrincipalLookupService instance =
325            new UserPrincipalLookupService() {
326                @Override
327                public UserPrincipal lookupPrincipalByName(String name)
328                    throws IOException
329                {
330                    return UnixUserPrincipals.lookupUser(name);
331                }
332
333                @Override
334                public GroupPrincipal lookupPrincipalByGroupName(String group)
335                    throws IOException
336                {
337                    return UnixUserPrincipals.lookupGroup(group);
338                }
339            };
340    }
341
342    // Override if the platform has different path match requrement, such as
343    // case insensitive or Unicode canonical equal on MacOSX
344    Pattern compilePathMatchPattern(String expr) {
345        return Pattern.compile(expr);
346    }
347
348    // Override if the platform uses different Unicode normalization form
349    // for native file path. For example on MacOSX, the native path is stored
350    // in Unicode NFD form.
351    char[] normalizeNativePath(char[] path) {
352        return path;
353    }
354
355    // Override if the native file path use non-NFC form. For example on MacOSX,
356    // the native path is stored in Unicode NFD form, the path need to be
357    // normalized back to NFC before passed back to Java level.
358    String normalizeJavaPath(String path) {
359        return path;
360    }
361}
362