1 /*
2  * Copyright (c) 2008, 2011, 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.channels.SeekableByteChannel;
31 import java.util.*;
32 import java.util.concurrent.TimeUnit;
33 import java.io.IOException;
34 
35 import dalvik.system.CloseGuard;
36 
37 import static sun.nio.fs.UnixNativeDispatcher.*;
38 import static sun.nio.fs.UnixConstants.*;
39 
40 /**
41  * Unix implementation of SecureDirectoryStream.
42  */
43 
44 class UnixSecureDirectoryStream
45     implements SecureDirectoryStream<Path>
46 {
47     private final UnixDirectoryStream ds;
48     private final int dfd;
49 
50     // Android-changed: Add CloseGuard support.
51     private final CloseGuard guard = CloseGuard.get();
52 
UnixSecureDirectoryStream(UnixPath dir, long dp, int dfd, DirectoryStream.Filter<? super Path> filter)53     UnixSecureDirectoryStream(UnixPath dir,
54                               long dp,
55                               int dfd,
56                               DirectoryStream.Filter<? super Path> filter)
57     {
58         this.ds = new UnixDirectoryStream(dir, dp, filter);
59         this.dfd = dfd;
60         // Android-changed: Add CloseGuard support.
61         if (dfd != -1) {
62             guard.open("close");
63         }
64     }
65 
66     @Override
close()67     public void close()
68         throws IOException
69     {
70         ds.writeLock().lock();
71         try {
72             if (ds.closeImpl()) {
73                 UnixNativeDispatcher.close(dfd);
74             }
75         } finally {
76             ds.writeLock().unlock();
77         }
78         // Android-changed: Add CloseGuard support.
79         guard.close();
80     }
81 
82     @Override
iterator()83     public Iterator<Path> iterator() {
84         return ds.iterator(this);
85     }
86 
getName(Path obj)87     private UnixPath getName(Path obj) {
88         if (obj == null)
89             throw new NullPointerException();
90         if (!(obj instanceof UnixPath))
91             throw new ProviderMismatchException();
92         return (UnixPath)obj;
93     }
94 
95     /**
96      * Opens sub-directory in this directory
97      */
98     @Override
newDirectoryStream(Path obj, LinkOption... options)99     public SecureDirectoryStream<Path> newDirectoryStream(Path obj,
100                                                           LinkOption... options)
101         throws IOException
102     {
103         UnixPath file = getName(obj);
104         UnixPath child = ds.directory().resolve(file);
105         boolean followLinks = Util.followLinks(options);
106 
107         // permission check using name resolved against original path of directory
108         SecurityManager sm = System.getSecurityManager();
109         if (sm != null) {
110             child.checkRead();
111         }
112 
113         ds.readLock().lock();
114         try {
115             if (!ds.isOpen())
116                 throw new ClosedDirectoryStreamException();
117 
118             // open directory and create new secure directory stream
119             int newdfd1 = -1;
120             int newdfd2 = -1;
121             long ptr = 0L;
122             try {
123                 int flags = O_RDONLY;
124                 if (!followLinks)
125                     flags |= O_NOFOLLOW;
126                 newdfd1 = openat(dfd, file.asByteArray(), flags , 0);
127                 newdfd2 = dup(newdfd1);
128                 ptr = fdopendir(newdfd1);
129             } catch (UnixException x) {
130                 if (newdfd1 != -1)
131                     UnixNativeDispatcher.close(newdfd1);
132                 if (newdfd2 != -1)
133                     UnixNativeDispatcher.close(newdfd2);
134                 if (x.errno() == UnixConstants.ENOTDIR)
135                     throw new NotDirectoryException(file.toString());
136                 x.rethrowAsIOException(file);
137             }
138             return new UnixSecureDirectoryStream(child, ptr, newdfd2, null);
139         } finally {
140             ds.readLock().unlock();
141         }
142     }
143 
144     /**
145      * Opens file in this directory
146      */
147     @Override
newByteChannel(Path obj, Set<? extends OpenOption> options, FileAttribute<?>... attrs)148     public SeekableByteChannel newByteChannel(Path obj,
149                                               Set<? extends OpenOption> options,
150                                               FileAttribute<?>... attrs)
151         throws IOException
152     {
153         UnixPath file = getName(obj);
154 
155         int mode = UnixFileModeAttribute
156             .toUnixMode(UnixFileModeAttribute.ALL_READWRITE, attrs);
157 
158         // path for permission check
159         String pathToCheck = ds.directory().resolve(file).getPathForPermissionCheck();
160 
161         ds.readLock().lock();
162         try {
163             if (!ds.isOpen())
164                 throw new ClosedDirectoryStreamException();
165             try {
166                 return UnixChannelFactory.newFileChannel(dfd, file, pathToCheck, options, mode);
167             } catch (UnixException x) {
168                 x.rethrowAsIOException(file);
169                 return null; // keep compiler happy
170             }
171         } finally {
172             ds.readLock().unlock();
173         }
174     }
175 
176     /**
177      * Deletes file/directory in this directory. Works in a race-free manner
178      * when invoked with flags.
179      */
implDelete(Path obj, boolean haveFlags, int flags)180     private void implDelete(Path obj, boolean haveFlags, int flags)
181         throws IOException
182     {
183         UnixPath file = getName(obj);
184 
185         // permission check using name resolved against original path of directory
186         SecurityManager sm = System.getSecurityManager();
187         if (sm != null) {
188             ds.directory().resolve(file).checkDelete();
189         }
190 
191         ds.readLock().lock();
192         try {
193             if (!ds.isOpen())
194                 throw new ClosedDirectoryStreamException();
195 
196             if (!haveFlags) {
197                 // need file attribute to know if file is directory. This creates
198                 // a race in that the file may be replaced by a directory or a
199                 // directory replaced by a file between the time we query the
200                 // file type and unlink it.
201                 UnixFileAttributes attrs = null;
202                 try {
203                     attrs = UnixFileAttributes.get(dfd, file, false);
204                 } catch (UnixException x) {
205                     x.rethrowAsIOException(file);
206                 }
207                 flags = (attrs.isDirectory()) ? AT_REMOVEDIR : 0;
208             }
209 
210             try {
211                 unlinkat(dfd, file.asByteArray(), flags);
212             } catch (UnixException x) {
213                 if ((flags & AT_REMOVEDIR) != 0) {
214                     if (x.errno() == EEXIST || x.errno() == ENOTEMPTY) {
215                         throw new DirectoryNotEmptyException(null);
216                     }
217                 }
218                 x.rethrowAsIOException(file);
219             }
220         } finally {
221             ds.readLock().unlock();
222         }
223     }
224 
225     @Override
deleteFile(Path file)226     public void deleteFile(Path file) throws IOException {
227         implDelete(file, true, 0);
228     }
229 
230     @Override
deleteDirectory(Path dir)231     public void deleteDirectory(Path dir) throws IOException {
232         implDelete(dir, true, AT_REMOVEDIR);
233     }
234 
235     /**
236      * Rename/move file in this directory to another (open) directory
237      */
238     @Override
move(Path fromObj, SecureDirectoryStream<Path> dir, Path toObj)239     public void move(Path fromObj, SecureDirectoryStream<Path> dir, Path toObj)
240         throws IOException
241     {
242         UnixPath from = getName(fromObj);
243         UnixPath to = getName(toObj);
244         if (dir == null)
245             throw new NullPointerException();
246         if (!(dir instanceof UnixSecureDirectoryStream))
247             throw new ProviderMismatchException();
248         UnixSecureDirectoryStream that = (UnixSecureDirectoryStream)dir;
249 
250         // permission check
251         SecurityManager sm = System.getSecurityManager();
252         if (sm != null) {
253             this.ds.directory().resolve(from).checkWrite();
254             that.ds.directory().resolve(to).checkWrite();
255         }
256 
257         // lock ordering doesn't matter
258         this.ds.readLock().lock();
259         try {
260             that.ds.readLock().lock();
261             try {
262                 if (!this.ds.isOpen() || !that.ds.isOpen())
263                     throw new ClosedDirectoryStreamException();
264                 try {
265                     renameat(this.dfd, from.asByteArray(), that.dfd, to.asByteArray());
266                 } catch (UnixException x) {
267                     if (x.errno() == EXDEV) {
268                         throw new AtomicMoveNotSupportedException(
269                             from.toString(), to.toString(), x.errorString());
270                     }
271                     x.rethrowAsIOException(from, to);
272                 }
273             } finally {
274                 that.ds.readLock().unlock();
275             }
276         } finally {
277             this.ds.readLock().unlock();
278         }
279     }
280 
281     @SuppressWarnings("unchecked")
getFileAttributeViewImpl(UnixPath file, Class<V> type, boolean followLinks)282     private <V extends FileAttributeView> V getFileAttributeViewImpl(UnixPath file,
283                                                                      Class<V> type,
284                                                                      boolean followLinks)
285     {
286         if (type == null)
287             throw new NullPointerException();
288         Class<?> c = type;
289         if (c == BasicFileAttributeView.class) {
290             return (V) new BasicFileAttributeViewImpl(file, followLinks);
291         }
292         if (c == PosixFileAttributeView.class || c == FileOwnerAttributeView.class) {
293             return (V) new PosixFileAttributeViewImpl(file, followLinks);
294         }
295         // TBD - should also support AclFileAttributeView
296         return (V) null;
297     }
298 
299     /**
300      * Returns file attribute view bound to this directory
301      */
302     @Override
getFileAttributeView(Class<V> type)303     public <V extends FileAttributeView> V getFileAttributeView(Class<V> type) {
304         return getFileAttributeViewImpl(null, type, false);
305     }
306 
307     /**
308      * Returns file attribute view bound to dfd/filename.
309      */
310     @Override
getFileAttributeView(Path obj, Class<V> type, LinkOption... options)311     public <V extends FileAttributeView> V getFileAttributeView(Path obj,
312                                                                 Class<V> type,
313                                                                 LinkOption... options)
314     {
315         UnixPath file = getName(obj);
316         boolean followLinks = Util.followLinks(options);
317         return getFileAttributeViewImpl(file, type, followLinks);
318     }
319 
320     /**
321      * A BasicFileAttributeView implementation that using a dfd/name pair.
322      */
323     private class BasicFileAttributeViewImpl
324         implements BasicFileAttributeView
325     {
326         final UnixPath file;
327         final boolean followLinks;
328 
BasicFileAttributeViewImpl(UnixPath file, boolean followLinks)329         BasicFileAttributeViewImpl(UnixPath file, boolean followLinks)
330         {
331             this.file = file;
332             this.followLinks = followLinks;
333         }
334 
open()335         int open() throws IOException {
336             int oflags = O_RDONLY;
337             if (!followLinks)
338                 oflags |= O_NOFOLLOW;
339             try {
340                 return openat(dfd, file.asByteArray(), oflags, 0);
341             } catch (UnixException x) {
342                 x.rethrowAsIOException(file);
343                 return -1; // keep compiler happy
344             }
345         }
346 
checkWriteAccess()347         private void checkWriteAccess() {
348             SecurityManager sm = System.getSecurityManager();
349             if (sm != null) {
350                 if (file == null) {
351                     ds.directory().checkWrite();
352                 } else {
353                     ds.directory().resolve(file).checkWrite();
354                 }
355             }
356         }
357 
358         @Override
name()359         public String name() {
360             return "basic";
361         }
362 
363         @Override
readAttributes()364         public BasicFileAttributes readAttributes() throws IOException {
365             ds.readLock().lock();
366             try {
367                 if (!ds.isOpen())
368                     throw new ClosedDirectoryStreamException();
369 
370                 SecurityManager sm = System.getSecurityManager();
371                 if (sm != null) {
372                     if (file == null) {
373                         ds.directory().checkRead();
374                     } else {
375                         ds.directory().resolve(file).checkRead();
376                     }
377                 }
378                 try {
379                      UnixFileAttributes attrs = (file == null) ?
380                          UnixFileAttributes.get(dfd) :
381                          UnixFileAttributes.get(dfd, file, followLinks);
382 
383                      // SECURITY: must return as BasicFileAttribute
384                      return attrs.asBasicFileAttributes();
385                 } catch (UnixException x) {
386                     x.rethrowAsIOException(file);
387                     return null;    // keep compiler happy
388                 }
389             } finally {
390                 ds.readLock().unlock();
391             }
392         }
393 
394         @Override
setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime)395         public void setTimes(FileTime lastModifiedTime,
396                              FileTime lastAccessTime,
397                              FileTime createTime) // ignore
398             throws IOException
399         {
400             checkWriteAccess();
401 
402             ds.readLock().lock();
403             try {
404                 if (!ds.isOpen())
405                     throw new ClosedDirectoryStreamException();
406 
407                 int fd = (file == null) ? dfd : open();
408                 try {
409                     // if not changing both attributes then need existing attributes
410                     if (lastModifiedTime == null || lastAccessTime == null) {
411                         try {
412                             UnixFileAttributes attrs = UnixFileAttributes.get(fd);
413                             if (lastModifiedTime == null)
414                                 lastModifiedTime = attrs.lastModifiedTime();
415                             if (lastAccessTime == null)
416                                 lastAccessTime = attrs.lastAccessTime();
417                         } catch (UnixException x) {
418                             x.rethrowAsIOException(file);
419                         }
420                     }
421                     // update times
422                     try {
423                         futimes(fd,
424                                 lastAccessTime.to(TimeUnit.MICROSECONDS),
425                                 lastModifiedTime.to(TimeUnit.MICROSECONDS));
426                     } catch (UnixException x) {
427                         x.rethrowAsIOException(file);
428                     }
429                 } finally {
430                     if (file != null)
431                         UnixNativeDispatcher.close(fd);
432                 }
433             } finally {
434                 ds.readLock().unlock();
435             }
436         }
437     }
438 
439     /**
440      * A PosixFileAttributeView implementation that using a dfd/name pair.
441      */
442     private class PosixFileAttributeViewImpl
443         extends BasicFileAttributeViewImpl implements PosixFileAttributeView
444     {
PosixFileAttributeViewImpl(UnixPath file, boolean followLinks)445         PosixFileAttributeViewImpl(UnixPath file, boolean followLinks) {
446             super(file, followLinks);
447         }
448 
checkWriteAndUserAccess()449         private void checkWriteAndUserAccess() {
450             SecurityManager sm = System.getSecurityManager();
451             if (sm != null) {
452                 super.checkWriteAccess();
453                 sm.checkPermission(new RuntimePermission("accessUserInformation"));
454             }
455         }
456 
457         @Override
name()458         public String name() {
459             return "posix";
460         }
461 
462         @Override
readAttributes()463         public PosixFileAttributes readAttributes() throws IOException {
464             SecurityManager sm = System.getSecurityManager();
465             if (sm != null) {
466                 if (file == null)
467                     ds.directory().checkRead();
468                 else
469                     ds.directory().resolve(file).checkRead();
470                 sm.checkPermission(new RuntimePermission("accessUserInformation"));
471             }
472 
473             ds.readLock().lock();
474             try {
475                 if (!ds.isOpen())
476                     throw new ClosedDirectoryStreamException();
477 
478                 try {
479                      UnixFileAttributes attrs = (file == null) ?
480                          UnixFileAttributes.get(dfd) :
481                          UnixFileAttributes.get(dfd, file, followLinks);
482                      return attrs;
483                 } catch (UnixException x) {
484                     x.rethrowAsIOException(file);
485                     return null;    // keep compiler happy
486                 }
487             } finally {
488                 ds.readLock().unlock();
489             }
490         }
491 
492         @Override
setPermissions(Set<PosixFilePermission> perms)493         public void setPermissions(Set<PosixFilePermission> perms)
494             throws IOException
495         {
496             // permission check
497             checkWriteAndUserAccess();
498 
499             ds.readLock().lock();
500             try {
501                 if (!ds.isOpen())
502                     throw new ClosedDirectoryStreamException();
503 
504                 int fd = (file == null) ? dfd : open();
505                 try {
506                     fchmod(fd, UnixFileModeAttribute.toUnixMode(perms));
507                 } catch (UnixException x) {
508                     x.rethrowAsIOException(file);
509                 } finally {
510                     if (file != null && fd >= 0)
511                         UnixNativeDispatcher.close(fd);
512                 }
513             } finally {
514                 ds.readLock().unlock();
515             }
516         }
517 
setOwners(int uid, int gid)518         private void setOwners(int uid, int gid) throws IOException {
519             // permission check
520             checkWriteAndUserAccess();
521 
522             ds.readLock().lock();
523             try {
524                 if (!ds.isOpen())
525                     throw new ClosedDirectoryStreamException();
526 
527                 int fd = (file == null) ? dfd : open();
528                 try {
529                     fchown(fd, uid, gid);
530                 } catch (UnixException x) {
531                     x.rethrowAsIOException(file);
532                 } finally {
533                     if (file != null && fd >= 0)
534                         UnixNativeDispatcher.close(fd);
535                 }
536             } finally {
537                 ds.readLock().unlock();
538             }
539         }
540 
541         @Override
getOwner()542         public UserPrincipal getOwner() throws IOException {
543             return readAttributes().owner();
544         }
545 
546         @Override
setOwner(UserPrincipal owner)547         public void setOwner(UserPrincipal owner)
548             throws IOException
549         {
550             if (!(owner instanceof UnixUserPrincipals.User))
551                 throw new ProviderMismatchException();
552             if (owner instanceof UnixUserPrincipals.Group)
553                 throw new IOException("'owner' parameter can't be a group");
554             int uid = ((UnixUserPrincipals.User)owner).uid();
555             setOwners(uid, -1);
556         }
557 
558         @Override
setGroup(GroupPrincipal group)559         public void setGroup(GroupPrincipal group)
560             throws IOException
561         {
562             if (!(group instanceof UnixUserPrincipals.Group))
563                 throw new ProviderMismatchException();
564             int gid = ((UnixUserPrincipals.Group)group).gid();
565             setOwners(-1, gid);
566         }
567     }
568 
569     /**
570      * Cleans up if the user forgets to close it.
571      */
572     // Android-changed: Add CloseGuard support.
finalize()573     protected void finalize() throws IOException {
574         if (guard != null) {
575             guard.warnIfOpen();
576         }
577 
578         close();
579     }
580 }
581