|
@@ -20,13 +20,18 @@ package org.apache.hadoop.fs.shell;
|
|
|
|
|
|
import java.io.File;
|
|
|
import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
import java.util.LinkedList;
|
|
|
|
|
|
+import org.apache.hadoop.fs.FSDataOutputStream;
|
|
|
import org.apache.hadoop.fs.Path;
|
|
|
import org.apache.hadoop.fs.shell.PathExceptions.PathExistsException;
|
|
|
import org.apache.hadoop.fs.shell.PathExceptions.PathIOException;
|
|
|
+import org.apache.hadoop.fs.shell.PathExceptions.PathIsDirectoryException;
|
|
|
import org.apache.hadoop.fs.shell.PathExceptions.PathIsNotDirectoryException;
|
|
|
import org.apache.hadoop.fs.shell.PathExceptions.PathNotFoundException;
|
|
|
+import org.apache.hadoop.fs.shell.PathExceptions.PathOperationException;
|
|
|
+import org.apache.hadoop.io.IOUtils;
|
|
|
|
|
|
/**
|
|
|
* Provides: argument processing to ensure the destination is valid
|
|
@@ -106,51 +111,136 @@ abstract class CommandWithDestination extends FsCommand {
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
- protected void processPaths(PathData parent, PathData ... items)
|
|
|
+ protected void processPathArgument(PathData src)
|
|
|
throws IOException {
|
|
|
+ if (src.stat.isDirectory() && src.fs.equals(dst.fs)) {
|
|
|
+ PathData target = getTargetPath(src);
|
|
|
+ String srcPath = src.fs.makeQualified(src.path).toString();
|
|
|
+ String dstPath = dst.fs.makeQualified(target.path).toString();
|
|
|
+ if (dstPath.equals(srcPath)) {
|
|
|
+ PathIOException e = new PathIOException(src.toString(),
|
|
|
+ "are identical");
|
|
|
+ e.setTargetPath(dstPath.toString());
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ if (dstPath.startsWith(srcPath+Path.SEPARATOR)) {
|
|
|
+ PathIOException e = new PathIOException(src.toString(),
|
|
|
+ "is a subdirectory of itself");
|
|
|
+ e.setTargetPath(target.toString());
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ super.processPathArgument(src);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void processPath(PathData src) throws IOException {
|
|
|
+ processPath(src, getTargetPath(src));
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Called with a source and target destination pair
|
|
|
+ * @param src for the operation
|
|
|
+ * @param target for the operation
|
|
|
+ * @throws IOException if anything goes wrong
|
|
|
+ */
|
|
|
+ protected void processPath(PathData src, PathData dst) throws IOException {
|
|
|
+ if (src.stat.isSymlink()) {
|
|
|
+ // TODO: remove when FileContext is supported, this needs to either
|
|
|
+ // copy the symlink or deref the symlink
|
|
|
+ throw new PathOperationException(src.toString());
|
|
|
+ } else if (src.stat.isFile()) {
|
|
|
+ copyFileToTarget(src, dst);
|
|
|
+ } else if (src.stat.isDirectory() && !isRecursive()) {
|
|
|
+ throw new PathIsDirectoryException(src.toString());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected void recursePath(PathData src) throws IOException {
|
|
|
PathData savedDst = dst;
|
|
|
try {
|
|
|
// modify dst as we descend to append the basename of the
|
|
|
// current directory being processed
|
|
|
- if (parent != null) dst = dst.getPathDataForChild(parent);
|
|
|
- super.processPaths(parent, items);
|
|
|
+ dst = getTargetPath(src);
|
|
|
+ if (dst.exists) {
|
|
|
+ if (!dst.stat.isDirectory()) {
|
|
|
+ throw new PathIsNotDirectoryException(dst.toString());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (!dst.fs.mkdirs(dst.path)) {
|
|
|
+ // too bad we have no clue what failed
|
|
|
+ PathIOException e = new PathIOException(dst.toString());
|
|
|
+ e.setOperation("mkdir");
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ dst.refreshStatus(); // need to update stat to know it exists now
|
|
|
+ }
|
|
|
+ super.recursePath(src);
|
|
|
} finally {
|
|
|
dst = savedDst;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- @Override
|
|
|
- protected void processPath(PathData src) throws IOException {
|
|
|
+ protected PathData getTargetPath(PathData src) throws IOException {
|
|
|
PathData target;
|
|
|
- // if the destination is a directory, make target a child path,
|
|
|
- // else use the destination as-is
|
|
|
- if (dst.exists && dst.stat.isDirectory()) {
|
|
|
+ // on the first loop, the dst may be directory or a file, so only create
|
|
|
+ // a child path if dst is a dir; after recursion, it's always a dir
|
|
|
+ if ((getDepth() > 0) || (dst.exists && dst.stat.isDirectory())) {
|
|
|
target = dst.getPathDataForChild(src);
|
|
|
} else {
|
|
|
target = dst;
|
|
|
}
|
|
|
- if (target.exists && !overwrite) {
|
|
|
+ return target;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copies the source file to the target.
|
|
|
+ * @param src item to copy
|
|
|
+ * @param target where to copy the item
|
|
|
+ * @throws IOException if copy fails
|
|
|
+ */
|
|
|
+ protected void copyFileToTarget(PathData src, PathData target) throws IOException {
|
|
|
+ copyStreamToTarget(src.fs.open(src.path), target);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Copies the stream contents to a temporary file. If the copy is
|
|
|
+ * successful, the temporary file will be renamed to the real path,
|
|
|
+ * else the temporary file will be deleted.
|
|
|
+ * @param in the input stream for the copy
|
|
|
+ * @param target where to store the contents of the stream
|
|
|
+ * @throws IOException if copy fails
|
|
|
+ */
|
|
|
+ protected void copyStreamToTarget(InputStream in, PathData target)
|
|
|
+ throws IOException {
|
|
|
+ if (target.exists && (target.stat.isDirectory() || !overwrite)) {
|
|
|
throw new PathExistsException(target.toString());
|
|
|
}
|
|
|
-
|
|
|
- try {
|
|
|
- // invoke processPath with both a source and resolved target
|
|
|
- processPath(src, target);
|
|
|
- } catch (PathIOException e) {
|
|
|
- // add the target unless it already has one
|
|
|
- if (e.getTargetPath() == null) {
|
|
|
+ PathData tempFile = null;
|
|
|
+ try {
|
|
|
+ tempFile = target.createTempFile(target+"._COPYING_");
|
|
|
+ FSDataOutputStream out = target.fs.create(tempFile.path, true);
|
|
|
+ IOUtils.copyBytes(in, out, getConf(), true);
|
|
|
+ // the rename method with an option to delete the target is deprecated
|
|
|
+ if (target.exists && !target.fs.delete(target.path, false)) {
|
|
|
+ // too bad we don't know why it failed
|
|
|
+ PathIOException e = new PathIOException(target.toString());
|
|
|
+ e.setOperation("delete");
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ if (!tempFile.fs.rename(tempFile.path, target.path)) {
|
|
|
+ // too bad we don't know why it failed
|
|
|
+ PathIOException e = new PathIOException(tempFile.toString());
|
|
|
+ e.setOperation("rename");
|
|
|
e.setTargetPath(target.toString());
|
|
|
+ throw e;
|
|
|
+ }
|
|
|
+ tempFile = null;
|
|
|
+ } finally {
|
|
|
+ if (tempFile != null) {
|
|
|
+ tempFile.fs.delete(tempFile.path, false);
|
|
|
}
|
|
|
- throw e;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- /**
|
|
|
- * Called with a source and target destination pair
|
|
|
- * @param src for the operation
|
|
|
- * @param target for the operation
|
|
|
- * @throws IOException if anything goes wrong
|
|
|
- */
|
|
|
- protected abstract void processPath(PathData src, PathData target)
|
|
|
- throws IOException;
|
|
|
}
|