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     static {
 609         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 610             @Override
 611             public Void run() {
 612                 System.loadLibrary("nio");
 613                 return null;
 614             }});
 615     }
 616 
 617 }