1 /*
   2  * Copyright (c) 2008, 2018, 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     // throw a DirectoryNotEmpty exception if appropriate
 377     static void ensureEmptyDir(UnixPath dir) throws IOException {
 378         try {
 379             long ptr = opendir(dir);
 380             try (UnixDirectoryStream stream =
 381                 new UnixDirectoryStream(dir, ptr, e -> true)) {
 382                 if (stream.iterator().hasNext()) {
 383                     throw new DirectoryNotEmptyException(
 384                         dir.getPathForExceptionMessage());
 385                 }
 386             }
 387         } catch (UnixException e) {
 388             e.rethrowAsIOException(dir);
 389         }
 390     }
 391 
 392     // move file from source to target
 393     static void move(UnixPath source, UnixPath target, CopyOption... options)
 394         throws IOException
 395     {
 396         // permission check
 397         SecurityManager sm = System.getSecurityManager();
 398         if (sm != null) {
 399             source.checkWrite();
 400             target.checkWrite();
 401         }
 402 
 403         // translate options into flags
 404         Flags flags = Flags.fromMoveOptions(options);
 405 
 406         // handle atomic rename case
 407         if (flags.atomicMove) {
 408             try {
 409                 rename(source, target);
 410             } catch (UnixException x) {
 411                 if (x.errno() == EXDEV) {
 412                     throw new AtomicMoveNotSupportedException(
 413                         source.getPathForExceptionMessage(),
 414                         target.getPathForExceptionMessage(),
 415                         x.errorString());
 416                 }
 417                 x.rethrowAsIOException(source, target);
 418             }
 419             return;
 420         }
 421 
 422         // move using rename or copy+delete
 423         UnixFileAttributes sourceAttrs = null;
 424         UnixFileAttributes targetAttrs = null;
 425 
 426         // get attributes of source file (don't follow links)
 427         try {
 428             sourceAttrs = UnixFileAttributes.get(source, false);
 429         } catch (UnixException x) {
 430             x.rethrowAsIOException(source);
 431         }
 432 
 433         // get attributes of target file (don't follow links)
 434         try {
 435             targetAttrs = UnixFileAttributes.get(target, false);
 436         } catch (UnixException x) {
 437             // ignore
 438         }
 439         boolean targetExists = (targetAttrs != null);
 440 
 441         // if the target exists:
 442         // 1. check if source and target are the same file
 443         // 2. throw exception if REPLACE_EXISTING option is not set
 444         // 3. delete target if REPLACE_EXISTING option set
 445         if (targetExists) {
 446             if (sourceAttrs.isSameFile(targetAttrs))
 447                 return;  // nothing to do as files are identical
 448             if (!flags.replaceExisting) {
 449                 throw new FileAlreadyExistsException(
 450                     target.getPathForExceptionMessage());
 451             }
 452 
 453             // attempt to delete target
 454             try {
 455                 if (targetAttrs.isDirectory()) {
 456                     rmdir(target);
 457                 } else {
 458                     unlink(target);
 459                 }
 460             } catch (UnixException x) {
 461                 // target is non-empty directory that can't be replaced.
 462                 if (targetAttrs.isDirectory() &&
 463                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 464                 {
 465                     throw new DirectoryNotEmptyException(
 466                         target.getPathForExceptionMessage());
 467                 }
 468                 x.rethrowAsIOException(target);
 469             }
 470         }
 471 
 472         // first try rename
 473         try {
 474             rename(source, target);
 475             return;
 476         } catch (UnixException x) {
 477             if (x.errno() != EXDEV && x.errno() != EISDIR) {
 478                 x.rethrowAsIOException(source, target);
 479             }
 480         }
 481 
 482         // copy source to target
 483         if (sourceAttrs.isDirectory()) {
 484             ensureEmptyDir(source);
 485             copyDirectory(source, sourceAttrs, target, flags);
 486         } else {
 487             if (sourceAttrs.isSymbolicLink()) {
 488                 copyLink(source, sourceAttrs, target, flags);
 489             } else {
 490                 if (sourceAttrs.isDevice()) {
 491                     copySpecial(source, sourceAttrs, target, flags);
 492                 } else {
 493                     copyFile(source, sourceAttrs, target, flags, 0L);
 494                 }
 495             }
 496         }
 497 
 498         // delete source
 499         try {
 500             if (sourceAttrs.isDirectory()) {
 501                 rmdir(source);
 502             } else {
 503                 unlink(source);
 504             }
 505         } catch (UnixException x) {
 506             // file was copied but unable to unlink the source file so attempt
 507             // to remove the target and throw a reasonable exception
 508             try {
 509                 if (sourceAttrs.isDirectory()) {
 510                     rmdir(target);
 511                 } else {
 512                     unlink(target);
 513                 }
 514             } catch (UnixException ignore) { }
 515 
 516             if (sourceAttrs.isDirectory() &&
 517                 (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 518             {
 519                 throw new DirectoryNotEmptyException(
 520                     source.getPathForExceptionMessage());
 521             }
 522             x.rethrowAsIOException(source);
 523         }
 524     }
 525 
 526     // copy file from source to target
 527     static void copy(final UnixPath source,
 528                      final UnixPath target,
 529                      CopyOption... options) throws IOException
 530     {
 531         // permission checks
 532         SecurityManager sm = System.getSecurityManager();
 533         if (sm != null) {
 534             source.checkRead();
 535             target.checkWrite();
 536         }
 537 
 538         // translate options into flags
 539         final Flags flags = Flags.fromCopyOptions(options);
 540 
 541         UnixFileAttributes sourceAttrs = null;
 542         UnixFileAttributes targetAttrs = null;
 543 
 544         // get attributes of source file
 545         try {
 546             sourceAttrs = UnixFileAttributes.get(source, flags.followLinks);
 547         } catch (UnixException x) {
 548             x.rethrowAsIOException(source);
 549         }
 550 
 551         // if source file is symbolic link then we must check LinkPermission
 552         if (sm != null && sourceAttrs.isSymbolicLink()) {
 553             sm.checkPermission(new LinkPermission("symbolic"));
 554         }
 555 
 556         // get attributes of target file (don't follow links)
 557         try {
 558             targetAttrs = UnixFileAttributes.get(target, false);
 559         } catch (UnixException x) {
 560             // ignore
 561         }
 562         boolean targetExists = (targetAttrs != null);
 563 
 564         // if the target exists:
 565         // 1. check if source and target are the same file
 566         // 2. throw exception if REPLACE_EXISTING option is not set
 567         // 3. try to unlink the target
 568         if (targetExists) {
 569             if (sourceAttrs.isSameFile(targetAttrs))
 570                 return;  // nothing to do as files are identical
 571             if (!flags.replaceExisting)
 572                 throw new FileAlreadyExistsException(
 573                     target.getPathForExceptionMessage());
 574             try {
 575                 if (targetAttrs.isDirectory()) {
 576                     rmdir(target);
 577                 } else {
 578                     unlink(target);
 579                 }
 580             } catch (UnixException x) {
 581                 // target is non-empty directory that can't be replaced.
 582                 if (targetAttrs.isDirectory() &&
 583                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 584                 {
 585                     throw new DirectoryNotEmptyException(
 586                         target.getPathForExceptionMessage());
 587                 }
 588                 x.rethrowAsIOException(target);
 589             }
 590         }
 591 
 592         // do the copy
 593         if (sourceAttrs.isDirectory()) {
 594             copyDirectory(source, sourceAttrs, target, flags);
 595             return;
 596         }
 597         if (sourceAttrs.isSymbolicLink()) {
 598             copyLink(source, sourceAttrs, target, flags);
 599             return;
 600         }
 601         if (!flags.interruptible) {
 602             // non-interruptible file copy
 603             copyFile(source, sourceAttrs, target, flags, 0L);
 604             return;
 605         }
 606 
 607         // interruptible file copy
 608         final UnixFileAttributes attrsToCopy = sourceAttrs;
 609         Cancellable copyTask = new Cancellable() {
 610             @Override public void implRun() throws IOException {
 611                 copyFile(source, attrsToCopy, target, flags,
 612                     addressToPollForCancel());
 613             }
 614         };
 615         try {
 616             Cancellable.runInterruptibly(copyTask);
 617         } catch (ExecutionException e) {
 618             Throwable t = e.getCause();
 619             if (t instanceof IOException)
 620                 throw (IOException)t;
 621             throw new IOException(t);
 622         }
 623     }
 624 
 625     // -- native methods --
 626 
 627     static native void transfer(int dst, int src, long addressToPollForCancel)
 628         throws UnixException;
 629 
 630     static {
 631         AccessController.doPrivileged(new PrivilegedAction<>() {
 632             @Override
 633             public Void run() {
 634                 System.loadLibrary("nio");
 635                 return null;
 636             }});
 637     }
 638 
 639 }