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.FileTypeDetector;
31import java.nio.channels.*;
32import java.net.URI;
33import java.util.concurrent.ExecutorService;
34import java.io.IOException;
35import java.io.FilePermission;
36import java.util.*;
37import java.security.AccessController;
38
39import sun.nio.ch.ThreadPool;
40import sun.security.util.SecurityConstants;
41import static sun.nio.fs.UnixNativeDispatcher.*;
42import static sun.nio.fs.UnixConstants.*;
43
44/**
45 * Base implementation of FileSystemProvider
46 */
47
48public abstract class UnixFileSystemProvider
49    extends AbstractFileSystemProvider
50{
51    private static final String USER_DIR = "user.dir";
52    private final UnixFileSystem theFileSystem;
53
54    public UnixFileSystemProvider() {
55        String userDir = System.getProperty(USER_DIR);
56        theFileSystem = newFileSystem(userDir);
57    }
58
59    /**
60     * Constructs a new file system using the given default directory.
61     */
62    abstract UnixFileSystem newFileSystem(String dir);
63
64    @Override
65    public final String getScheme() {
66        return "file";
67    }
68
69    private void checkUri(URI uri) {
70        if (!uri.getScheme().equalsIgnoreCase(getScheme()))
71            throw new IllegalArgumentException("URI does not match this provider");
72        if (uri.getAuthority() != null)
73            throw new IllegalArgumentException("Authority component present");
74        if (uri.getPath() == null)
75            throw new IllegalArgumentException("Path component is undefined");
76        if (!uri.getPath().equals("/"))
77            throw new IllegalArgumentException("Path component should be '/'");
78        if (uri.getQuery() != null)
79            throw new IllegalArgumentException("Query component present");
80        if (uri.getFragment() != null)
81            throw new IllegalArgumentException("Fragment component present");
82    }
83
84    @Override
85    public final FileSystem newFileSystem(URI uri, Map<String,?> env) {
86        checkUri(uri);
87        throw new FileSystemAlreadyExistsException();
88    }
89
90    @Override
91    public final FileSystem getFileSystem(URI uri) {
92        checkUri(uri);
93        return theFileSystem;
94    }
95
96    @Override
97    public Path getPath(URI uri) {
98        return UnixUriUtils.fromUri(theFileSystem, uri);
99    }
100
101    UnixPath checkPath(Path obj) {
102        if (obj == null)
103            throw new NullPointerException();
104        if (!(obj instanceof UnixPath))
105            throw new ProviderMismatchException();
106        return (UnixPath)obj;
107    }
108
109    @Override
110    @SuppressWarnings("unchecked")
111    public <V extends FileAttributeView> V getFileAttributeView(Path obj,
112                                                                Class<V> type,
113                                                                LinkOption... options)
114    {
115        UnixPath file = UnixPath.toUnixPath(obj);
116        boolean followLinks = Util.followLinks(options);
117        if (type == BasicFileAttributeView.class)
118            return (V) UnixFileAttributeViews.createBasicView(file, followLinks);
119        if (type == PosixFileAttributeView.class)
120            return (V) UnixFileAttributeViews.createPosixView(file, followLinks);
121        if (type == FileOwnerAttributeView.class)
122            return (V) UnixFileAttributeViews.createOwnerView(file, followLinks);
123        if (type == null)
124            throw new NullPointerException();
125        return (V) null;
126    }
127
128    @Override
129    @SuppressWarnings("unchecked")
130    public <A extends BasicFileAttributes> A readAttributes(Path file,
131                                                               Class<A> type,
132                                                               LinkOption... options)
133        throws IOException
134    {
135        Class<? extends BasicFileAttributeView> view;
136        if (type == BasicFileAttributes.class)
137            view = BasicFileAttributeView.class;
138        else if (type == PosixFileAttributes.class)
139            view = PosixFileAttributeView.class;
140        else if (type == null)
141            throw new NullPointerException();
142        else
143            throw new UnsupportedOperationException();
144        return (A) getFileAttributeView(file, view, options).readAttributes();
145    }
146
147    @Override
148    protected DynamicFileAttributeView getFileAttributeView(Path obj,
149                                                            String name,
150                                                            LinkOption... options)
151    {
152        UnixPath file = UnixPath.toUnixPath(obj);
153        boolean followLinks = Util.followLinks(options);
154        if (name.equals("basic"))
155            return UnixFileAttributeViews.createBasicView(file, followLinks);
156        if (name.equals("posix"))
157            return UnixFileAttributeViews.createPosixView(file, followLinks);
158        if (name.equals("unix"))
159            return UnixFileAttributeViews.createUnixView(file, followLinks);
160        if (name.equals("owner"))
161            return UnixFileAttributeViews.createOwnerView(file, followLinks);
162        return null;
163    }
164
165    @Override
166    public FileChannel newFileChannel(Path obj,
167                                      Set<? extends OpenOption> options,
168                                      FileAttribute<?>... attrs)
169        throws IOException
170    {
171        UnixPath file = checkPath(obj);
172        int mode = UnixFileModeAttribute
173            .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
174        try {
175            return UnixChannelFactory.newFileChannel(file, options, mode);
176        } catch (UnixException x) {
177            x.rethrowAsIOException(file);
178            return null;
179        }
180    }
181
182    @Override
183    public AsynchronousFileChannel newAsynchronousFileChannel(Path obj,
184                                                              Set<? extends OpenOption> options,
185                                                              ExecutorService executor,
186                                                              FileAttribute<?>... attrs) throws IOException
187    {
188        UnixPath file = checkPath(obj);
189        int mode = UnixFileModeAttribute
190            .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
191        ThreadPool pool = (executor == null) ? null : ThreadPool.wrap(executor, 0);
192        try {
193            return UnixChannelFactory
194                .newAsynchronousFileChannel(file, options, mode, pool);
195        } catch (UnixException x) {
196            x.rethrowAsIOException(file);
197            return null;
198        }
199    }
200
201
202    @Override
203    public SeekableByteChannel newByteChannel(Path obj,
204                                              Set<? extends OpenOption> options,
205                                              FileAttribute<?>... attrs)
206         throws IOException
207    {
208        UnixPath file = UnixPath.toUnixPath(obj);
209        int mode = UnixFileModeAttribute
210            .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
211        try {
212            return UnixChannelFactory.newFileChannel(file, options, mode);
213        } catch (UnixException x) {
214            x.rethrowAsIOException(file);
215            return null;  // keep compiler happy
216        }
217    }
218
219    @Override
220    boolean implDelete(Path obj, boolean failIfNotExists) throws IOException {
221        UnixPath file = UnixPath.toUnixPath(obj);
222        file.checkDelete();
223
224        // need file attributes to know if file is directory
225        UnixFileAttributes attrs = null;
226        try {
227            attrs = UnixFileAttributes.get(file, false);
228            if (attrs.isDirectory()) {
229                rmdir(file);
230            } else {
231                unlink(file);
232            }
233            return true;
234        } catch (UnixException x) {
235            // no-op if file does not exist
236            if (!failIfNotExists && x.errno() == ENOENT)
237                return false;
238
239            // DirectoryNotEmptyException if not empty
240            if (attrs != null && attrs.isDirectory() &&
241                (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
242                throw new DirectoryNotEmptyException(file.getPathForExceptionMessage());
243
244            x.rethrowAsIOException(file);
245            return false;
246        }
247    }
248
249    @Override
250    public void copy(Path source, Path target, CopyOption... options)
251        throws IOException
252    {
253        UnixCopyFile.copy(UnixPath.toUnixPath(source),
254                          UnixPath.toUnixPath(target),
255                          options);
256    }
257
258    @Override
259    public void move(Path source, Path target, CopyOption... options)
260        throws IOException
261    {
262        UnixCopyFile.move(UnixPath.toUnixPath(source),
263                          UnixPath.toUnixPath(target),
264                          options);
265    }
266
267    @Override
268    public void checkAccess(Path obj, AccessMode... modes) throws IOException {
269        UnixPath file = UnixPath.toUnixPath(obj);
270        boolean e = false;
271        boolean r = false;
272        boolean w = false;
273        boolean x = false;
274
275        if (modes.length == 0) {
276            e = true;
277        } else {
278            for (AccessMode mode: modes) {
279                switch (mode) {
280                    case READ : r = true; break;
281                    case WRITE : w = true; break;
282                    case EXECUTE : x = true; break;
283                    default: throw new AssertionError("Should not get here");
284                }
285            }
286        }
287
288        int mode = 0;
289        if (e || r) {
290            file.checkRead();
291            mode |= (r) ? R_OK : F_OK;
292        }
293        if (w) {
294            file.checkWrite();
295            mode |= W_OK;
296        }
297        if (x) {
298            SecurityManager sm = System.getSecurityManager();
299            if (sm != null) {
300                // not cached
301                sm.checkExec(file.getPathForPermissionCheck());
302            }
303            mode |= X_OK;
304        }
305        try {
306            access(file, mode);
307        } catch (UnixException exc) {
308            exc.rethrowAsIOException(file);
309        }
310    }
311
312    @Override
313    public boolean isSameFile(Path obj1, Path obj2) throws IOException {
314        UnixPath file1 = UnixPath.toUnixPath(obj1);
315        if (file1.equals(obj2))
316            return true;
317        if (obj2 == null)
318            throw new NullPointerException();
319        if (!(obj2 instanceof UnixPath))
320            return false;
321        UnixPath file2 = (UnixPath)obj2;
322
323        // check security manager access to both files
324        file1.checkRead();
325        file2.checkRead();
326
327        UnixFileAttributes attrs1;
328        UnixFileAttributes attrs2;
329        try {
330             attrs1 = UnixFileAttributes.get(file1, true);
331        } catch (UnixException x) {
332            x.rethrowAsIOException(file1);
333            return false;    // keep compiler happy
334        }
335        try {
336            attrs2 = UnixFileAttributes.get(file2, true);
337        } catch (UnixException x) {
338            x.rethrowAsIOException(file2);
339            return false;    // keep compiler happy
340        }
341        return attrs1.isSameFile(attrs2);
342    }
343
344    @Override
345    public boolean isHidden(Path obj) {
346        UnixPath file = UnixPath.toUnixPath(obj);
347        file.checkRead();
348        UnixPath name = file.getFileName();
349        if (name == null)
350            return false;
351        return (name.asByteArray()[0] == '.');
352    }
353
354    /**
355     * Returns a FileStore to represent the file system where the given file
356     * reside.
357     */
358    abstract FileStore getFileStore(UnixPath path) throws IOException;
359
360    @Override
361    public FileStore getFileStore(Path obj) throws IOException {
362        // BEGIN Android-changed: getFileStore(Path) always throws SecurityException.
363        // Complete information about file systems is neither available to regular apps nor the
364        // system server due to SELinux policies.
365        /*
366        UnixPath file = UnixPath.toUnixPath(obj);
367        SecurityManager sm = System.getSecurityManager();
368        if (sm != null) {
369            sm.checkPermission(new RuntimePermission("getFileStoreAttributes"));
370            file.checkRead();
371        }
372        return getFileStore(file);
373        */
374        throw new SecurityException("getFileStore");
375        // END Android-changed: getFileStore(Path) always throws SecurityException.
376    }
377
378    @Override
379    public void createDirectory(Path obj, FileAttribute<?>... attrs)
380        throws IOException
381    {
382        UnixPath dir = UnixPath.toUnixPath(obj);
383        dir.checkWrite();
384
385        int mode = UnixFileModeAttribute.toUnixMode(UnixFileModeAttribute.ALL_PERMISSIONS, attrs);
386        try {
387            mkdir(dir, mode);
388        } catch (UnixException x) {
389            if (x.errno() == EISDIR)
390                throw new FileAlreadyExistsException(dir.toString());
391            x.rethrowAsIOException(dir);
392        }
393    }
394
395
396    @Override
397    public DirectoryStream<Path> newDirectoryStream(Path obj, DirectoryStream.Filter<? super Path> filter)
398        throws IOException
399    {
400        UnixPath dir = UnixPath.toUnixPath(obj);
401        dir.checkRead();
402        if (filter == null)
403            throw new NullPointerException();
404
405        // can't return SecureDirectoryStream on kernels that don't support openat
406        // or O_NOFOLLOW
407        if (!openatSupported() || O_NOFOLLOW == 0) {
408            try {
409                long ptr = opendir(dir);
410                return new UnixDirectoryStream(dir, ptr, filter);
411            } catch (UnixException x) {
412                if (x.errno() == ENOTDIR)
413                    throw new NotDirectoryException(dir.getPathForExceptionMessage());
414                x.rethrowAsIOException(dir);
415            }
416        }
417
418        // open directory and dup file descriptor for use by
419        // opendir/readdir/closedir
420        int dfd1 = -1;
421        int dfd2 = -1;
422        long dp = 0L;
423        try {
424            dfd1 = open(dir, O_RDONLY, 0);
425            dfd2 = dup(dfd1);
426            dp = fdopendir(dfd1);
427        } catch (UnixException x) {
428            if (dfd1 != -1)
429                UnixNativeDispatcher.close(dfd1);
430            if (dfd2 != -1)
431                UnixNativeDispatcher.close(dfd2);
432            if (x.errno() == UnixConstants.ENOTDIR)
433                throw new NotDirectoryException(dir.getPathForExceptionMessage());
434            x.rethrowAsIOException(dir);
435        }
436        return new UnixSecureDirectoryStream(dir, dp, dfd2, filter);
437    }
438
439    @Override
440    public void createSymbolicLink(Path obj1, Path obj2, FileAttribute<?>... attrs)
441        throws IOException
442    {
443        UnixPath link = UnixPath.toUnixPath(obj1);
444        UnixPath target = UnixPath.toUnixPath(obj2);
445
446        // no attributes supported when creating links
447        if (attrs.length > 0) {
448            UnixFileModeAttribute.toUnixMode(0, attrs);  // may throw NPE or UOE
449            throw new UnsupportedOperationException("Initial file attributes" +
450                "not supported when creating symbolic link");
451        }
452
453        // permission check
454        SecurityManager sm = System.getSecurityManager();
455        if (sm != null) {
456            sm.checkPermission(new LinkPermission("symbolic"));
457            link.checkWrite();
458        }
459
460        // create link
461        try {
462            symlink(target.asByteArray(), link);
463        } catch (UnixException x) {
464            x.rethrowAsIOException(link);
465        }
466    }
467
468    @Override
469    public void createLink(Path obj1, Path obj2) throws IOException {
470        UnixPath link = UnixPath.toUnixPath(obj1);
471        UnixPath existing = UnixPath.toUnixPath(obj2);
472
473        // permission check
474        SecurityManager sm = System.getSecurityManager();
475        if (sm != null) {
476            sm.checkPermission(new LinkPermission("hard"));
477            link.checkWrite();
478            existing.checkWrite();
479        }
480        try {
481            link(existing, link);
482        } catch (UnixException x) {
483            x.rethrowAsIOException(link, existing);
484        }
485    }
486
487    @Override
488    public Path readSymbolicLink(Path obj1) throws IOException {
489        UnixPath link = UnixPath.toUnixPath(obj1);
490        // permission check
491        SecurityManager sm = System.getSecurityManager();
492        if (sm != null) {
493            FilePermission perm = new FilePermission(link.getPathForPermissionCheck(),
494                SecurityConstants.FILE_READLINK_ACTION);
495            sm.checkPermission(perm);
496        }
497        try {
498            byte[] target = readlink(link);
499            return new UnixPath(link.getFileSystem(), target);
500        } catch (UnixException x) {
501           if (x.errno() == UnixConstants.EINVAL)
502                throw new NotLinkException(link.getPathForExceptionMessage());
503            x.rethrowAsIOException(link);
504            return null;    // keep compiler happy
505        }
506    }
507
508    /**
509     * Returns a {@code FileTypeDetector} for this platform.
510     */
511    FileTypeDetector getFileTypeDetector() {
512        return new AbstractFileTypeDetector() {
513            @Override
514            public String implProbeContentType(Path file) {
515                return null;
516            }
517        };
518    }
519
520    /**
521     * Returns a {@code FileTypeDetector} that chains the given array of file
522     * type detectors. When the {@code implProbeContentType} method is invoked
523     * then each of the detectors is invoked in turn, the result from the
524     * first to detect the file type is returned.
525     */
526    final FileTypeDetector chain(final AbstractFileTypeDetector... detectors) {
527        return new AbstractFileTypeDetector() {
528            @Override
529            protected String implProbeContentType(Path file) throws IOException {
530                for (AbstractFileTypeDetector detector : detectors) {
531                    String result = detector.implProbeContentType(file);
532                    if (result != null && !result.isEmpty()) {
533                        return result;
534                    }
535                }
536                return null;
537            }
538        };
539    }
540}
541