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