Home | History | Annotate | Download | only in fs
      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 
     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
     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
     83     public Iterator<Path> iterator() {
     84         return ds.iterator(this);
     85     }
     86 
     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
     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
    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      */
    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
    226     public void deleteFile(Path file) throws IOException {
    227         implDelete(file, true, 0);
    228     }
    229 
    230     @Override
    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
    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")
    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
    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
    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 
    329         BasicFileAttributeViewImpl(UnixPath file, boolean followLinks)
    330         {
    331             this.file = file;
    332             this.followLinks = followLinks;
    333         }
    334 
    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 
    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
    359         public String name() {
    360             return "basic";
    361         }
    362 
    363         @Override
    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
    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     {
    445         PosixFileAttributeViewImpl(UnixPath file, boolean followLinks) {
    446             super(file, followLinks);
    447         }
    448 
    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
    458         public String name() {
    459             return "posix";
    460         }
    461 
    462         @Override
    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
    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 
    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
    542         public UserPrincipal getOwner() throws IOException {
    543             return readAttributes().owner();
    544         }
    545 
    546         @Override
    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
    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.
    573     protected void finalize() throws IOException {
    574         if (guard != null) {
    575             guard.warnIfOpen();
    576         }
    577 
    578         close();
    579     }
    580 }
    581