/*
 * Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.nio.fs;

import java.io.IOException;
import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryNotEmptyException;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.LinkOption;
import java.nio.file.LinkPermission;
import java.nio.file.StandardCopyOption;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

import static sun.nio.fs.UnixNativeDispatcher.*;
import static sun.nio.fs.UnixConstants.*;


Unix implementation of Path#copyTo and Path#moveTo methods.
/** * Unix implementation of Path#copyTo and Path#moveTo methods. */
class UnixCopyFile { private UnixCopyFile() { } // The flags that control how a file is copied or moved private static class Flags { boolean replaceExisting; boolean atomicMove; boolean followLinks; boolean interruptible; // the attributes to copy boolean copyBasicAttributes; boolean copyPosixAttributes; boolean copyNonPosixAttributes; // flags that indicate if we should fail if attributes cannot be copied boolean failIfUnableToCopyBasic; boolean failIfUnableToCopyPosix; boolean failIfUnableToCopyNonPosix; static Flags fromCopyOptions(CopyOption... options) { Flags flags = new Flags(); flags.followLinks = true; for (CopyOption option: options) { if (option == StandardCopyOption.REPLACE_EXISTING) { flags.replaceExisting = true; continue; } if (option == LinkOption.NOFOLLOW_LINKS) { flags.followLinks = false; continue; } if (option == StandardCopyOption.COPY_ATTRIBUTES) { // copy all attributes but only fail if basic attributes // cannot be copied flags.copyBasicAttributes = true; flags.copyPosixAttributes = true; flags.copyNonPosixAttributes = true; flags.failIfUnableToCopyBasic = true; continue; } if (ExtendedOptions.INTERRUPTIBLE.matches(option)) { flags.interruptible = true; continue; } if (option == null) throw new NullPointerException(); throw new UnsupportedOperationException("Unsupported copy option"); } return flags; } static Flags fromMoveOptions(CopyOption... options) { Flags flags = new Flags(); for (CopyOption option: options) { if (option == StandardCopyOption.ATOMIC_MOVE) { flags.atomicMove = true; continue; } if (option == StandardCopyOption.REPLACE_EXISTING) { flags.replaceExisting = true; continue; } if (option == LinkOption.NOFOLLOW_LINKS) { // ignore continue; } if (option == null) throw new NullPointerException(); throw new UnsupportedOperationException("Unsupported copy option"); } // a move requires that all attributes be copied but only fail if // the basic attributes cannot be copied flags.copyBasicAttributes = true; flags.copyPosixAttributes = true; flags.copyNonPosixAttributes = true; flags.failIfUnableToCopyBasic = true; return flags; } } // copy directory from source to target private static void copyDirectory(UnixPath source, UnixFileAttributes attrs, UnixPath target, Flags flags) throws IOException { try { mkdir(target, attrs.mode()); } catch (UnixException x) { x.rethrowAsIOException(target); } // no attributes to copy if (!flags.copyBasicAttributes && !flags.copyPosixAttributes && !flags.copyNonPosixAttributes) return; // open target directory if possible (this can fail when copying a // directory for which we don't have read access). int dfd = -1; try { dfd = open(target, O_RDONLY, 0); } catch (UnixException x) { // access to target directory required to copy named attributes if (flags.copyNonPosixAttributes && flags.failIfUnableToCopyNonPosix) { try { rmdir(target); } catch (UnixException ignore) { } x.rethrowAsIOException(target); } } boolean done = false; try { // copy owner/group/permissions if (flags.copyPosixAttributes){ try { if (dfd >= 0) { fchown(dfd, attrs.uid(), attrs.gid()); fchmod(dfd, attrs.mode()); } else { chown(target, attrs.uid(), attrs.gid()); chmod(target, attrs.mode()); } } catch (UnixException x) { // unable to set owner/group if (flags.failIfUnableToCopyPosix) x.rethrowAsIOException(target); } } // copy other attributes if (flags.copyNonPosixAttributes && (dfd >= 0)) { int sfd = -1; try { sfd = open(source, O_RDONLY, 0); } catch (UnixException x) { if (flags.failIfUnableToCopyNonPosix) x.rethrowAsIOException(source); } if (sfd >= 0) { source.getFileSystem().copyNonPosixAttributes(sfd, dfd); close(sfd); } } // copy time stamps last if (flags.copyBasicAttributes) { try { if (dfd >= 0 && futimesSupported()) { futimes(dfd, attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); } else { utimes(target, attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); } } catch (UnixException x) { // unable to set times if (flags.failIfUnableToCopyBasic) x.rethrowAsIOException(target); } } done = true; } finally { if (dfd >= 0) close(dfd); if (!done) { // rollback try { rmdir(target); } catch (UnixException ignore) { } } } } // copy regular file from source to target private static void copyFile(UnixPath source, UnixFileAttributes attrs, UnixPath target, Flags flags, long addressToPollForCancel) throws IOException { int fi = -1; try { fi = open(source, O_RDONLY, 0); } catch (UnixException x) { x.rethrowAsIOException(source); } try { // open new file int fo = -1; try { fo = open(target, (O_WRONLY | O_CREAT | O_EXCL), attrs.mode()); } catch (UnixException x) { x.rethrowAsIOException(target); } // set to true when file and attributes copied boolean complete = false; try { // transfer bytes to target file try { transfer(fo, fi, addressToPollForCancel); } catch (UnixException x) { x.rethrowAsIOException(source, target); } // copy owner/permissions if (flags.copyPosixAttributes) { try { fchown(fo, attrs.uid(), attrs.gid()); fchmod(fo, attrs.mode()); } catch (UnixException x) { if (flags.failIfUnableToCopyPosix) x.rethrowAsIOException(target); } } // copy non POSIX attributes (depends on file system) if (flags.copyNonPosixAttributes) { source.getFileSystem().copyNonPosixAttributes(fi, fo); } // copy time attributes if (flags.copyBasicAttributes) { try { if (futimesSupported()) { futimes(fo, attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); } else { utimes(target, attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); } } catch (UnixException x) { if (flags.failIfUnableToCopyBasic) x.rethrowAsIOException(target); } } complete = true; } finally { close(fo); // copy of file or attributes failed so rollback if (!complete) { try { unlink(target); } catch (UnixException ignore) { } } } } finally { close(fi); } } // copy symbolic link from source to target private static void copyLink(UnixPath source, UnixFileAttributes attrs, UnixPath target, Flags flags) throws IOException { byte[] linktarget = null; try { linktarget = readlink(source); } catch (UnixException x) { x.rethrowAsIOException(source); } try { symlink(linktarget, target); if (flags.copyPosixAttributes) { try { lchown(target, attrs.uid(), attrs.gid()); } catch (UnixException x) { // ignore since link attributes not required to be copied } } } catch (UnixException x) { x.rethrowAsIOException(target); } } // copy special file from source to target private static void copySpecial(UnixPath source, UnixFileAttributes attrs, UnixPath target, Flags flags) throws IOException { try { mknod(target, attrs.mode(), attrs.rdev()); } catch (UnixException x) { x.rethrowAsIOException(target); } boolean done = false; try { if (flags.copyPosixAttributes) { try { chown(target, attrs.uid(), attrs.gid()); chmod(target, attrs.mode()); } catch (UnixException x) { if (flags.failIfUnableToCopyPosix) x.rethrowAsIOException(target); } } if (flags.copyBasicAttributes) { try { utimes(target, attrs.lastAccessTime().to(TimeUnit.MICROSECONDS), attrs.lastModifiedTime().to(TimeUnit.MICROSECONDS)); } catch (UnixException x) { if (flags.failIfUnableToCopyBasic) x.rethrowAsIOException(target); } } done = true; } finally { if (!done) { try { unlink(target); } catch (UnixException ignore) { } } } } // move file from source to target static void move(UnixPath source, UnixPath target, CopyOption... options) throws IOException { // permission check SecurityManager sm = System.getSecurityManager(); if (sm != null) { source.checkWrite(); target.checkWrite(); } // translate options into flags Flags flags = Flags.fromMoveOptions(options); // handle atomic rename case if (flags.atomicMove) { try { rename(source, target); } catch (UnixException x) { if (x.errno() == EXDEV) { throw new AtomicMoveNotSupportedException( source.getPathForExceptionMessage(), target.getPathForExceptionMessage(), x.errorString()); } x.rethrowAsIOException(source, target); } return; } // move using rename or copy+delete UnixFileAttributes sourceAttrs = null; UnixFileAttributes targetAttrs = null; // get attributes of source file (don't follow links) try { sourceAttrs = UnixFileAttributes.get(source, false); } catch (UnixException x) { x.rethrowAsIOException(source); } // get attributes of target file (don't follow links) try { targetAttrs = UnixFileAttributes.get(target, false); } catch (UnixException x) { // ignore } boolean targetExists = (targetAttrs != null); // if the target exists: // 1. check if source and target are the same file // 2. throw exception if REPLACE_EXISTING option is not set // 3. delete target if REPLACE_EXISTING option set if (targetExists) { if (sourceAttrs.isSameFile(targetAttrs)) return; // nothing to do as files are identical if (!flags.replaceExisting) { throw new FileAlreadyExistsException( target.getPathForExceptionMessage()); } // attempt to delete target try { if (targetAttrs.isDirectory()) { rmdir(target); } else { unlink(target); } } catch (UnixException x) { // target is non-empty directory that can't be replaced. if (targetAttrs.isDirectory() && (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) { throw new DirectoryNotEmptyException( target.getPathForExceptionMessage()); } x.rethrowAsIOException(target); } } // first try rename try { rename(source, target); return; } catch (UnixException x) { if (x.errno() != EXDEV && x.errno() != EISDIR) { x.rethrowAsIOException(source, target); } } // copy source to target if (sourceAttrs.isDirectory()) { copyDirectory(source, sourceAttrs, target, flags); } else { if (sourceAttrs.isSymbolicLink()) { copyLink(source, sourceAttrs, target, flags); } else { if (sourceAttrs.isDevice()) { copySpecial(source, sourceAttrs, target, flags); } else { copyFile(source, sourceAttrs, target, flags, 0L); } } } // delete source try { if (sourceAttrs.isDirectory()) { rmdir(source); } else { unlink(source); } } catch (UnixException x) { // file was copied but unable to unlink the source file so attempt // to remove the target and throw a reasonable exception try { if (sourceAttrs.isDirectory()) { rmdir(target); } else { unlink(target); } } catch (UnixException ignore) { } if (sourceAttrs.isDirectory() && (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) { throw new DirectoryNotEmptyException( source.getPathForExceptionMessage()); } x.rethrowAsIOException(source); } } // copy file from source to target static void copy(final UnixPath source, final UnixPath target, CopyOption... options) throws IOException { // permission checks SecurityManager sm = System.getSecurityManager(); if (sm != null) { source.checkRead(); target.checkWrite(); } // translate options into flags final Flags flags = Flags.fromCopyOptions(options); UnixFileAttributes sourceAttrs = null; UnixFileAttributes targetAttrs = null; // get attributes of source file try { sourceAttrs = UnixFileAttributes.get(source, flags.followLinks); } catch (UnixException x) { x.rethrowAsIOException(source); } // if source file is symbolic link then we must check LinkPermission if (sm != null && sourceAttrs.isSymbolicLink()) { sm.checkPermission(new LinkPermission("symbolic")); } // get attributes of target file (don't follow links) try { targetAttrs = UnixFileAttributes.get(target, false); } catch (UnixException x) { // ignore } boolean targetExists = (targetAttrs != null); // if the target exists: // 1. check if source and target are the same file // 2. throw exception if REPLACE_EXISTING option is not set // 3. try to unlink the target if (targetExists) { if (sourceAttrs.isSameFile(targetAttrs)) return; // nothing to do as files are identical if (!flags.replaceExisting) throw new FileAlreadyExistsException( target.getPathForExceptionMessage()); try { if (targetAttrs.isDirectory()) { rmdir(target); } else { unlink(target); } } catch (UnixException x) { // target is non-empty directory that can't be replaced. if (targetAttrs.isDirectory() && (x.errno() == EEXIST || x.errno() == ENOTEMPTY)) { throw new DirectoryNotEmptyException( target.getPathForExceptionMessage()); } x.rethrowAsIOException(target); } } // do the copy if (sourceAttrs.isDirectory()) { copyDirectory(source, sourceAttrs, target, flags); return; } if (sourceAttrs.isSymbolicLink()) { copyLink(source, sourceAttrs, target, flags); return; } if (!flags.interruptible) { // non-interruptible file copy copyFile(source, sourceAttrs, target, flags, 0L); return; } // interruptible file copy final UnixFileAttributes attrsToCopy = sourceAttrs; Cancellable copyTask = new Cancellable() { @Override public void implRun() throws IOException { copyFile(source, attrsToCopy, target, flags, addressToPollForCancel()); } }; try { Cancellable.runInterruptibly(copyTask); } catch (ExecutionException e) { Throwable t = e.getCause(); if (t instanceof IOException) throw (IOException)t; throw new IOException(t); } } // -- native methods -- static native void transfer(int dst, int src, long addressToPollForCancel) throws UnixException; static { AccessController.doPrivileged(new PrivilegedAction<>() { @Override public Void run() { System.loadLibrary("nio"); return null; }}); } }