1 /*
   2  * Copyright (c) 2008, 2009, 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) {
 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_TRUNC),
 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                         futimes(fo,
 273                                 attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
 274                                 attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
 275                     } catch (UnixException x) {
 276                         if (flags.failIfUnableToCopyBasic)
 277                             x.rethrowAsIOException(target);
 278                     }
 279                 }
 280                 complete = true;
 281             } finally {
 282                 close(fo);
 283 
 284                 // copy of file or attributes failed so rollback
 285                 if (!complete) {
 286                     try {
 287                         unlink(target);
 288                     } catch (UnixException ignore) { }
 289                 }
 290             }
 291         } finally {
 292             close(fi);
 293         }
 294     }
 295 
 296     // copy symbolic link from source to target
 297     private static void copyLink(UnixPath source,
 298                                  UnixFileAttributes attrs,
 299                                  UnixPath  target,
 300                                  Flags flags)
 301         throws IOException
 302     {
 303         byte[] linktarget = null;
 304         try {
 305             linktarget = readlink(source);
 306         } catch (UnixException x) {
 307             x.rethrowAsIOException(source);
 308         }
 309         try {
 310             symlink(linktarget, target);
 311 
 312             if (flags.copyPosixAttributes) {
 313                 try {
 314                     lchown(target, attrs.uid(), attrs.gid());
 315                 } catch (UnixException x) {
 316                     // ignore since link attributes not required to be copied
 317                 }
 318             }
 319         } catch (UnixException x) {
 320             x.rethrowAsIOException(target);
 321         }
 322     }
 323 
 324     // copy special file from source to target
 325     private static void copySpecial(UnixPath source,
 326                                     UnixFileAttributes attrs,
 327                                     UnixPath  target,
 328                                     Flags flags)
 329         throws IOException
 330     {
 331         try {
 332             mknod(target, attrs.mode(), attrs.rdev());
 333         } catch (UnixException x) {
 334             x.rethrowAsIOException(target);
 335         }
 336         boolean done = false;
 337         try {
 338             if (flags.copyPosixAttributes) {
 339                 try {
 340                     chown(target, attrs.uid(), attrs.gid());
 341                     chmod(target, attrs.mode());
 342                 } catch (UnixException x) {
 343                     if (flags.failIfUnableToCopyPosix)
 344                         x.rethrowAsIOException(target);
 345                 }
 346             }
 347             if (flags.copyBasicAttributes) {
 348                 try {
 349                     utimes(target,
 350                            attrs.lastAccessTime().to(TimeUnit.MICROSECONDS),
 351                            attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS));
 352                 } catch (UnixException x) {
 353                     if (flags.failIfUnableToCopyBasic)
 354                         x.rethrowAsIOException(target);
 355                 }
 356             }
 357             done = true;
 358         } finally {
 359             if (!done) {
 360                 try { unlink(target); } catch (UnixException ignore) { }
 361             }
 362         }
 363     }
 364 
 365     // move file from source to target
 366     static void move(UnixPath source, UnixPath target, CopyOption... options)
 367         throws IOException
 368     {
 369         // permission check
 370         SecurityManager sm = System.getSecurityManager();
 371         if (sm != null) {
 372             source.checkWrite();
 373             target.checkWrite();
 374         }
 375 
 376         // translate options into flags
 377         Flags flags = Flags.fromMoveOptions(options);
 378 
 379         // handle atomic rename case
 380         if (flags.atomicMove) {
 381             try {
 382                 rename(source, target);
 383             } catch (UnixException x) {
 384                 if (x.errno() == EXDEV) {
 385                     throw new AtomicMoveNotSupportedException(
 386                         source.getPathForExecptionMessage(),
 387                         target.getPathForExecptionMessage(),
 388                         x.errorString());
 389                 }
 390                 x.rethrowAsIOException(source, target);
 391             }
 392             return;
 393         }
 394 
 395         // move using rename or copy+delete
 396         UnixFileAttributes sourceAttrs = null;
 397         UnixFileAttributes targetAttrs = null;
 398 
 399         // get attributes of source file (don't follow links)
 400         try {
 401             sourceAttrs = UnixFileAttributes.get(source, false);
 402         } catch (UnixException x) {
 403             x.rethrowAsIOException(source);
 404         }
 405 
 406         // get attributes of target file (don't follow links)
 407         try {
 408             targetAttrs = UnixFileAttributes.get(target, false);
 409         } catch (UnixException x) {
 410             // ignore
 411         }
 412         boolean targetExists = (targetAttrs != null);
 413 
 414         // if the target exists:
 415         // 1. check if source and target are the same file
 416         // 2. throw exception if REPLACE_EXISTING option is not set
 417         // 3. delete target if REPLACE_EXISTING option set
 418         if (targetExists) {
 419             if (sourceAttrs.isSameFile(targetAttrs))
 420                 return;  // nothing to do as files are identical
 421             if (!flags.replaceExisting) {
 422                 throw new FileAlreadyExistsException(
 423                     target.getPathForExecptionMessage());
 424             }
 425 
 426             // attempt to delete target
 427             try {
 428                 if (targetAttrs.isDirectory()) {
 429                     rmdir(target);
 430                 } else {
 431                     unlink(target);
 432                 }
 433             } catch (UnixException x) {
 434                 // target is non-empty directory that can't be replaced.
 435                 if (targetAttrs.isDirectory() &&
 436                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 437                 {
 438                     throw new FileAlreadyExistsException(
 439                         source.getPathForExecptionMessage(),
 440                         target.getPathForExecptionMessage(),
 441                         x.getMessage());
 442                 }
 443                 x.rethrowAsIOException(target);
 444             }
 445         }
 446 
 447         // first try rename
 448         try {
 449             rename(source, target);
 450             return;
 451         } catch (UnixException x) {
 452             if (x.errno() != EXDEV && x.errno() != EISDIR) {
 453                 x.rethrowAsIOException(source, target);
 454             }
 455         }
 456 
 457         // copy source to target
 458         if (sourceAttrs.isDirectory()) {
 459             copyDirectory(source, sourceAttrs, target, flags);
 460         } else {
 461             if (sourceAttrs.isSymbolicLink()) {
 462                 copyLink(source, sourceAttrs, target, flags);
 463             } else {
 464                 if (sourceAttrs.isDevice()) {
 465                     copySpecial(source, sourceAttrs, target, flags);
 466                 } else {
 467                     copyFile(source, sourceAttrs, target, flags, 0L);
 468                 }
 469             }
 470         }
 471 
 472         // delete source
 473         try {
 474             if (sourceAttrs.isDirectory()) {
 475                 rmdir(source);
 476             } else {
 477                 unlink(source);
 478             }
 479         } catch (UnixException x) {
 480             // file was copied but unable to unlink the source file so attempt
 481             // to remove the target and throw a reasonable exception
 482             try {
 483                 if (sourceAttrs.isDirectory()) {
 484                     rmdir(target);
 485                 } else {
 486                     unlink(target);
 487                 }
 488             } catch (UnixException ignore) { }
 489 
 490             if (sourceAttrs.isDirectory() &&
 491                 (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 492             {
 493                 throw new DirectoryNotEmptyException(
 494                     source.getPathForExecptionMessage());
 495             }
 496             x.rethrowAsIOException(source);
 497         }
 498     }
 499 
 500     // copy file from source to target
 501     static void copy(final UnixPath source,
 502                      final UnixPath target,
 503                      CopyOption... options) throws IOException
 504     {
 505         // permission checks
 506         SecurityManager sm = System.getSecurityManager();
 507         if (sm != null) {
 508             source.checkRead();
 509             target.checkWrite();
 510         }
 511 
 512         // translate options into flags
 513         final Flags flags = Flags.fromCopyOptions(options);
 514 
 515         UnixFileAttributes sourceAttrs = null;
 516         UnixFileAttributes targetAttrs = null;
 517 
 518         // get attributes of source file
 519         try {
 520             sourceAttrs = UnixFileAttributes.get(source, flags.followLinks);
 521         } catch (UnixException x) {
 522             x.rethrowAsIOException(source);
 523         }
 524 
 525         // if source file is symbolic link then we must check LinkPermission
 526         if (sm != null && sourceAttrs.isSymbolicLink()) {
 527             sm.checkPermission(new LinkPermission("symbolic"));
 528         }
 529 
 530         // get attributes of target file (don't follow links)
 531         try {
 532             targetAttrs = UnixFileAttributes.get(target, false);
 533         } catch (UnixException x) {
 534             // ignore
 535         }
 536         boolean targetExists = (targetAttrs != null);
 537 
 538         // if the target exists:
 539         // 1. check if source and target are the same file
 540         // 2. throw exception if REPLACE_EXISTING option is not set
 541         // 3. try to unlink the target
 542         if (targetExists) {
 543             if (sourceAttrs.isSameFile(targetAttrs))
 544                 return;  // nothing to do as files are identical
 545             if (!flags.replaceExisting)
 546                 throw new FileAlreadyExistsException(
 547                     target.getPathForExecptionMessage());
 548             try {
 549                 if (targetAttrs.isDirectory()) {
 550                     rmdir(target);
 551                 } else {
 552                     unlink(target);
 553                 }
 554             } catch (UnixException x) {
 555                 // target is non-empty directory that can't be replaced.
 556                 if (targetAttrs.isDirectory() &&
 557                    (x.errno() == EEXIST || x.errno() == ENOTEMPTY))
 558                 {
 559                     throw new FileAlreadyExistsException(
 560                         source.getPathForExecptionMessage(),
 561                         target.getPathForExecptionMessage(),
 562                         x.getMessage());
 563                 }
 564                 x.rethrowAsIOException(target);
 565             }
 566         }
 567 
 568         // do the copy
 569         if (sourceAttrs.isDirectory()) {
 570             copyDirectory(source, sourceAttrs, target, flags);
 571             return;
 572         }
 573         if (sourceAttrs.isSymbolicLink()) {
 574             copyLink(source, sourceAttrs, target, flags);
 575             return;
 576         }
 577         if (!flags.interruptible) {
 578             // non-interruptible file copy
 579             copyFile(source, sourceAttrs, target, flags, 0L);
 580             return;
 581         }
 582 
 583         // interruptible file copy
 584         final UnixFileAttributes attrsToCopy = sourceAttrs;
 585         Cancellable copyTask = new Cancellable() {
 586             @Override public void implRun() throws IOException {
 587                 copyFile(source, attrsToCopy, target, flags,
 588                     addressToPollForCancel());
 589             }
 590         };
 591         try {
 592             Cancellable.runInterruptibly(copyTask);
 593         } catch (ExecutionException e) {
 594             Throwable t = e.getCause();
 595             if (t instanceof IOException)
 596                 throw (IOException)t;
 597             throw new IOException(t);
 598         }
 599     }
 600 
 601     // -- native methods --
 602 
 603     static native void transfer(int dst, int src, long addressToPollForCancel)
 604         throws UnixException;
 605 
 606     static {
 607         AccessController.doPrivileged(new PrivilegedAction<Void>() {
 608             @Override
 609             public Void run() {
 610                 System.loadLibrary("nio");
 611                 return null;
 612             }});
 613     }
 614 
 615 }