Home | History | Annotate | Download | only in fs
      1 /*
      2  * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
      3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
      4  *
      5  * This code is free software; you can redistribute it and/or modify it
      6  * under the terms of the GNU General Public License version 2 only, as
      7  * published by the Free Software Foundation.  Oracle designates this
      8  * particular file as subject to the "Classpath" exception as provided
      9  * by Oracle in the LICENSE file that accompanied this code.
     10  *
     11  * This code is distributed in the hope that it will be useful, but WITHOUT
     12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
     13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
     14  * version 2 for more details (a copy is included in the LICENSE file that
     15  * accompanied this code).
     16  *
     17  * You should have received a copy of the GNU General Public License version
     18  * 2 along with this work; if not, write to the Free Software Foundation,
     19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
     20  *
     21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
     22  * or visit www.oracle.com if you need additional information or have any
     23  * questions.
     24  */
     25 
     26 package sun.nio.fs;
     27 
     28 import java.nio.file.*;
     29 import java.io.IOException;
     30 import java.security.AccessController;
     31 import java.security.PrivilegedAction;
     32 import java.util.concurrent.ExecutionException;
     33 import java.util.concurrent.TimeUnit;
     34 import com.sun.nio.file.ExtendedCopyOption;
     35 
     36 import static sun.nio.fs.UnixNativeDispatcher.*;
     37 import static sun.nio.fs.UnixConstants.*;
     38 
     39 
     40 /**
     41  * Unix implementation of Path#copyTo and Path#moveTo methods.
     42  */
     43 
     44 class UnixCopyFile {
     45     private UnixCopyFile() {  }
     46 
     47     // The flags that control how a file is copied or moved
     48     private static class Flags {
     49         boolean replaceExisting;
     50         boolean atomicMove;
     51         boolean followLinks;
     52         boolean interruptible;
     53 
     54         // the attributes to copy
     55         boolean copyBasicAttributes;
     56         boolean copyPosixAttributes;
     57         boolean copyNonPosixAttributes;
     58 
     59         // flags that indicate if we should fail if attributes cannot be copied
     60         boolean failIfUnableToCopyBasic;
     61         boolean failIfUnableToCopyPosix;
     62         boolean failIfUnableToCopyNonPosix;
     63 
     64         static Flags fromCopyOptions(CopyOption... options) {
     65             Flags flags = new Flags();
     66             flags.followLinks = true;
     67             for (CopyOption option: options) {
     68                 if (option == StandardCopyOption.REPLACE_EXISTING) {
     69                     flags.replaceExisting = true;
     70                     continue;
     71                 }
     72                 if (option == LinkOption.NOFOLLOW_LINKS) {
     73                     flags.followLinks = false;
     74                     continue;
     75                 }
     76                 if (option == StandardCopyOption.COPY_ATTRIBUTES) {
     77                     // copy all attributes but only fail if basic attributes
     78                     // cannot be copied
     79                     flags.copyBasicAttributes = true;
     80                     flags.copyPosixAttributes = true;
     81                     flags.copyNonPosixAttributes = true;
     82                     flags.failIfUnableToCopyBasic = true;
     83                     continue;
     84                 }
     85                 if (option == ExtendedCopyOption.INTERRUPTIBLE) {
     86                     flags.interruptible = true;
     87                     continue;
     88                 }
     89                 if (option == null)
     90                     throw new NullPointerException();
     91                 throw new UnsupportedOperationException("Unsupported copy option");
     92             }
     93             return flags;
     94         }
     95 
     96         static Flags fromMoveOptions(CopyOption... options) {
     97             Flags flags = new Flags();
     98             for (CopyOption option: options) {
     99                 if (option == StandardCopyOption.ATOMIC_MOVE) {
    100                     flags.atomicMove = true;
    101                     continue;
    102                 }
    103                 if (option == StandardCopyOption.REPLACE_EXISTING) {
    104                     flags.replaceExisting = true;
    105                     continue;
    106                 }
    107                 if (option == LinkOption.NOFOLLOW_LINKS) {
    108                     // ignore
    109                     continue;
    110                 }
    111                 if (option == null)
    112                     throw new NullPointerException();
    113                 throw new UnsupportedOperationException("Unsupported copy option");
    114             }
    115 
    116             // a move requires that all attributes be copied but only fail if
    117             // the basic attributes cannot be copied
    118             flags.copyBasicAttributes = true;
    119             flags.copyPosixAttributes = true;
    120             flags.copyNonPosixAttributes = true;
    121             flags.failIfUnableToCopyBasic = true;
    122             return flags;
    123         }
    124     }
    125 
    126     // copy directory from source to target
    127     private static void copyDirectory(UnixPath source,
    128                                       UnixFileAttributes attrs,
    129                                       UnixPath target,
    130                                       Flags flags)
    131         throws IOException
    132     {
    133         try {
    134             mkdir(target, attrs.mode());
    135         } catch (UnixException x) {
    136             x.rethrowAsIOException(target);
    137         }
    138 
    139         // no attributes to copy
    140         if (!flags.copyBasicAttributes &&
    141             !flags.copyPosixAttributes &&
    142             !flags.copyNonPosixAttributes) return;
    143 
    144         // open target directory if possible (this can fail when copying a
    145         // directory for which we don't have read access).
    146         int dfd = -1;
    147         try {
    148             dfd = open(target, O_RDONLY, 0);
    149         } catch (UnixException x) {
    150             // access to target directory required to copy named attributes
    151             if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) {
    152                 try { rmdir(target); } catch (UnixException ignore) { }
    153                 x.rethrowAsIOException(target);
    154             }
    155         }
    156 
    157         boolean done = false;
    158         try {
    159             // copy owner/group/permissions
    160             if (flags.copyPosixAttributes){
    161                 try {
    162                     if (dfd >= 0) {
    163                         fchown(dfd, attrs.uid(), attrs.gid());
    164                         fchmod(dfd, attrs.mode());
    165                     } else {
    166                         chown(target, attrs.uid(), attrs.gid());
    167                         chmod(target, attrs.mode());
    168                     }
    169                 } catch (UnixException x) {
    170                     // unable to set owner/group
    171                     if (flags.failIfUnableToCopyPosix)
    172                         x.rethrowAsIOException(target);
    173                 }
    174             }
    175             // copy other attributes
    176             if (flags.copyNonPosixAttributes && (dfd >= 0)) {
    177                 int sfd = -1;
    178                 try {
    179                     sfd = open(source, O_RDONLY, 0);
    180                 } catch (UnixException x) {
    181                     if (flags.failIfUnableToCopyNonPosix)
    182                         x.rethrowAsIOException(source);
    183                 }
    184                 if (sfd >= 0) {
    185                     source.getFileSystem().copyNonPosixAttributes(sfd, dfd);
    186                     close(sfd);
    187                 }
    188             }
    189             // copy time stamps last
    190             if (flags.copyBasicAttributes) {
    191                 try {
    192                     if (dfd >= 0 && futimesSupported()) {
    193                         futimes(dfd,
    194                                 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
    195                                 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
    196                     } else {
    197                         utimes(target,
    198                                attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
    199                                attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
    200                     }
    201                 } catch (UnixException x) {
    202                     // unable to set times
    203                     if (flags.failIfUnableToCopyBasic)
    204                         x.rethrowAsIOException(target);
    205                 }
    206             }
    207             done = true;
    208         } finally {
    209             if (dfd >= 0)
    210                 close(dfd);
    211             if (!done) {
    212                 // rollback
    213                 try { rmdir(target); } catch (UnixException ignore) { }
    214             }
    215         }
    216     }
    217 
    218     // copy regular file from source to target
    219     private static void copyFile(UnixPath source,
    220                                  UnixFileAttributes attrs,
    221                                  UnixPath  target,
    222                                  Flags flags,
    223                                  long addressToPollForCancel)
    224         throws IOException
    225     {
    226         int fi = -1;
    227         try {
    228             fi = open(source, O_RDONLY, 0);
    229         } catch (UnixException x) {
    230             x.rethrowAsIOException(source);
    231         }
    232 
    233         try {
    234             // open new file
    235             int fo = -1;
    236             try {
    237                 fo = open(target,
    238                            (O_WRONLY |
    239                             O_CREAT |
    240                             O_EXCL),
    241                            attrs.mode());
    242             } catch (UnixException x) {
    243                 x.rethrowAsIOException(target);
    244             }
    245 
    246             // set to true when file and attributes copied
    247             boolean complete = false;
    248             try {
    249                 // transfer bytes to target file
    250                 try {
    251                     transfer(fo, fi, addressToPollForCancel);
    252                 } catch (UnixException x) {
    253                     x.rethrowAsIOException(source, target);
    254                 }
    255                 // copy owner/permissions
    256                 if (flags.copyPosixAttributes) {
    257                     try {
    258                         fchown(fo, attrs.uid(), attrs.gid());
    259                         fchmod(fo, attrs.mode());
    260                     } catch (UnixException x) {
    261                         if (flags.failIfUnableToCopyPosix)
    262                             x.rethrowAsIOException(target);
    263                     }
    264                 }
    265                 // copy non POSIX attributes (depends on file system)
    266                 if (flags.copyNonPosixAttributes) {
    267                     source.getFileSystem().copyNonPosixAttributes(fi, fo);
    268                 }
    269                 // copy time attributes
    270                 if (flags.copyBasicAttributes) {
    271                     try {
    272                         if (futimesSupported()) {
    273                             futimes(fo,
    274                                     attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
    275                                     attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
    276                         } else {
    277                             utimes(target,
    278                                    attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
    279                                    attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
    280                         }
    281                     } catch (UnixException x) {
    282                         if (flags.failIfUnableToCopyBasic)
    283                             x.rethrowAsIOException(target);
    284                     }
    285                 }
    286                 complete = true;
    287             } finally {
    288                 close(fo);
    289 
    290                 // copy of file or attributes failed so rollback
    291                 if (!complete) {
    292                     try {
    293                         unlink(target);
    294                     } catch (UnixException ignore) { }
    295                 }
    296             }
    297         } finally {
    298             close(fi);
    299         }
    300     }
    301 
    302     // copy symbolic link from source to target
    303     private static void copyLink(UnixPath source,
    304                                  UnixFileAttributes attrs,
    305                                  UnixPath  target,
    306                                  Flags flags)
    307         throws IOException
    308     {
    309         byte[] linktarget = null;
    310         try {
    311             linktarget = readlink(source);
    312         } catch (UnixException x) {
    313             x.rethrowAsIOException(source);
    314         }
    315         try {
    316             symlink(linktarget, target);
    317 
    318             if (flags.copyPosixAttributes) {
    319                 try {
    320                     lchown(target, attrs.uid(), attrs.gid());
    321                 } catch (UnixException x) {
    322                     // ignore since link attributes not required to be copied
    323                 }
    324             }
    325         } catch (UnixException x) {
    326             x.rethrowAsIOException(target);
    327         }
    328     }
    329 
    330     // copy special file from source to target
    331     private static void copySpecial(UnixPath source,
    332                                     UnixFileAttributes attrs,
    333                                     UnixPath  target,
    334                                     Flags flags)
    335         throws IOException
    336     {
    337         try {
    338             mknod(target, attrs.mode(), attrs.rdev());
    339         } catch (UnixException x) {
    340             x.rethrowAsIOException(target);
    341         }
    342         boolean done = false;
    343         try {
    344             if (flags.copyPosixAttributes) {
    345                 try {
    346                     chown(target, attrs.uid(), attrs.gid());
    347                     chmod(target, attrs.mode());
    348                 } catch (UnixException x) {
    349                     if (flags.failIfUnableToCopyPosix)
    350                         x.rethrowAsIOException(target);
    351                 }
    352             }
    353             if (flags.copyBasicAttributes) {
    354                 try {
    355                     utimes(target,
    356                            attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
    357                            attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
    358                 } catch (UnixException x) {
    359                     if (flags.failIfUnableToCopyBasic)
    360                         x.rethrowAsIOException(target);
    361                 }
    362             }
    363             done = true;
    364         } finally {
    365             if (!done) {
    366                 try { unlink(target); } catch (UnixException ignore) { }
    367             }
    368         }
    369     }
    370 
    371     // move file from source to target
    372     static void move(UnixPath source, UnixPath target, CopyOption... options)
    373         throws IOException
    374     {
    375         // permission check
    376         SecurityManager sm = System.getSecurityManager();
    377         if (sm != null) {
    378             source.checkWrite();
    379             target.checkWrite();
    380         }
    381 
    382         // translate options into flags
    383         Flags flags = Flags.fromMoveOptions(options);
    384 
    385         // handle atomic rename case
    386         if (flags.atomicMove) {
    387             try {
    388                 rename(source, target);
    389             } catch (UnixException x) {
    390                 if (x.errno() == EXDEV) {
    391                     throw new AtomicMoveNotSupportedException(
    392                         source.getPathForExceptionMessage(),
    393                         target.getPathForExceptionMessage(),
    394                         x.errorString());
    395                 }
    396                 x.rethrowAsIOException(source, target);
    397             }
    398             return;
    399         }
    400 
    401         // move using rename or copy+delete
    402         UnixFileAttributes sourceAttrs = null;
    403         UnixFileAttributes targetAttrs = null;
    404 
    405         // get attributes of source file (don't follow links)
    406         try {
    407             sourceAttrs = UnixFileAttributes.get(source, false);
    408         } catch (UnixException x) {
    409             x.rethrowAsIOException(source);
    410         }
    411 
    412         // get attributes of target file (don't follow links)
    413         try {
    414             targetAttrs = UnixFileAttributes.get(target, false);
    415         } catch (UnixException x) {
    416             // ignore
    417         }
    418         boolean targetExists = (targetAttrs != null);
    419 
    420         // if the target exists:
    421         // 1. check if source and target are the same file
    422         // 2. throw exception if REPLACE_EXISTING option is not set
    423         // 3. delete target if REPLACE_EXISTING option set
    424         if (targetExists) {
    425             if (sourceAttrs.isSameFile(targetAttrs))
    426                 return;  // nothing to do as files are identical
    427             if (!flags.replaceExisting) {
    428                 throw new FileAlreadyExistsException(
    429                     target.getPathForExceptionMessage());
    430             }
    431 
    432             // attempt to delete target
    433             try {
    434                 if (targetAttrs.isDirectory()) {
    435                     rmdir(target);
    436                 } else {
    437                     unlink(target);
    438                 }
    439             } catch (UnixException x) {
    440                 // target is non-empty directory that can't be replaced.
    441                 if (targetAttrs.isDirectory() &&
    442                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
    443                 {
    444                     throw new DirectoryNotEmptyException(
    445                         target.getPathForExceptionMessage());
    446                 }
    447                 x.rethrowAsIOException(target);
    448             }
    449         }
    450 
    451         // first try rename
    452         try {
    453             rename(source, target);
    454             return;
    455         } catch (UnixException x) {
    456             if (x.errno() != EXDEV && x.errno() != EISDIR) {
    457                 x.rethrowAsIOException(source, target);
    458             }
    459         }
    460 
    461         // copy source to target
    462         if (sourceAttrs.isDirectory()) {
    463             copyDirectory(source, sourceAttrs, target, flags);
    464         } else {
    465             if (sourceAttrs.isSymbolicLink()) {
    466                 copyLink(source, sourceAttrs, target, flags);
    467             } else {
    468                 if (sourceAttrs.isDevice()) {
    469                     copySpecial(source, sourceAttrs, target, flags);
    470                 } else {
    471                     copyFile(source, sourceAttrs, target, flags, 0L);
    472                 }
    473             }
    474         }
    475 
    476         // delete source
    477         try {
    478             if (sourceAttrs.isDirectory()) {
    479                 rmdir(source);
    480             } else {
    481                 unlink(source);
    482             }
    483         } catch (UnixException x) {
    484             // file was copied but unable to unlink the source file so attempt
    485             // to remove the target and throw a reasonable exception
    486             try {
    487                 if (sourceAttrs.isDirectory()) {
    488                     rmdir(target);
    489                 } else {
    490                     unlink(target);
    491                 }
    492             } catch (UnixException ignore) { }
    493 
    494             if (sourceAttrs.isDirectory() &&
    495                 (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
    496             {
    497                 throw new DirectoryNotEmptyException(
    498                     source.getPathForExceptionMessage());
    499             }
    500             x.rethrowAsIOException(source);
    501         }
    502     }
    503 
    504     // copy file from source to target
    505     static void copy(final UnixPath source,
    506                      final UnixPath target,
    507                      CopyOption... options) throws IOException
    508     {
    509         // permission checks
    510         SecurityManager sm = System.getSecurityManager();
    511         if (sm != null) {
    512             source.checkRead();
    513             target.checkWrite();
    514         }
    515 
    516         // translate options into flags
    517         final Flags flags = Flags.fromCopyOptions(options);
    518 
    519         UnixFileAttributes sourceAttrs = null;
    520         UnixFileAttributes targetAttrs = null;
    521 
    522         // get attributes of source file
    523         try {
    524             sourceAttrs = UnixFileAttributes.get(source, flags.followLinks);
    525         } catch (UnixException x) {
    526             x.rethrowAsIOException(source);
    527         }
    528 
    529         // if source file is symbolic link then we must check LinkPermission
    530         if (sm != null && sourceAttrs.isSymbolicLink()) {
    531             sm.checkPermission(new LinkPermission("symbolic"));
    532         }
    533 
    534         // get attributes of target file (don't follow links)
    535         try {
    536             targetAttrs = UnixFileAttributes.get(target, false);
    537         } catch (UnixException x) {
    538             // ignore
    539         }
    540         boolean targetExists = (targetAttrs != null);
    541 
    542         // if the target exists:
    543         // 1. check if source and target are the same file
    544         // 2. throw exception if REPLACE_EXISTING option is not set
    545         // 3. try to unlink the target
    546         if (targetExists) {
    547             if (sourceAttrs.isSameFile(targetAttrs))
    548                 return;  // nothing to do as files are identical
    549             if (!flags.replaceExisting)
    550                 throw new FileAlreadyExistsException(
    551                     target.getPathForExceptionMessage());
    552             try {
    553                 if (targetAttrs.isDirectory()) {
    554                     rmdir(target);
    555                 } else {
    556                     unlink(target);
    557                 }
    558             } catch (UnixException x) {
    559                 // target is non-empty directory that can't be replaced.
    560                 if (targetAttrs.isDirectory() &&
    561                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
    562                 {
    563                     throw new DirectoryNotEmptyException(
    564                         target.getPathForExceptionMessage());
    565                 }
    566                 x.rethrowAsIOException(target);
    567             }
    568         }
    569 
    570         // do the copy
    571         if (sourceAttrs.isDirectory()) {
    572             copyDirectory(source, sourceAttrs, target, flags);
    573             return;
    574         }
    575         if (sourceAttrs.isSymbolicLink()) {
    576             copyLink(source, sourceAttrs, target, flags);
    577             return;
    578         }
    579         if (!flags.interruptible) {
    580             // non-interruptible file copy
    581             copyFile(source, sourceAttrs, target, flags, 0L);
    582             return;
    583         }
    584 
    585         // interruptible file copy
    586         final UnixFileAttributes attrsToCopy = sourceAttrs;
    587         Cancellable copyTask = new Cancellable() {
    588             @Override public void implRun() throws IOException {
    589                 copyFile(source, attrsToCopy, target, flags,
    590                     addressToPollForCancel());
    591             }
    592         };
    593         try {
    594             Cancellable.runInterruptibly(copyTask);
    595         } catch (ExecutionException e) {
    596             Throwable t = e.getCause();
    597             if (t instanceof IOException)
    598                 throw (IOException)t;
    599             throw new IOException(t);
    600         }
    601     }
    602 
    603     // -- native methods --
    604 
    605     static native void transfer(int dst, int src, long addressToPollForCancel)
    606         throws UnixException;
    607 
    608     // Android-removed: Code to load native libraries, doesn't make sense on Android.
    609     /*
    610     static {
    611         AccessController.doPrivileged(new PrivilegedAction<Void>() {
    612             @Override
    613             public Void run() {
    614                 System.loadLibrary("nio");
    615                 return null;
    616             }});
    617     }
    618     */
    619 
    620 }
    621