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