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