001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements. See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership. The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License. You may obtain a copy of the License at
009 *
010 * http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package org.apache.hadoop.fs;
020
021 import java.io.*;
022 import java.util.ArrayList;
023 import java.util.Arrays;
024 import java.util.Enumeration;
025 import java.util.List;
026 import java.util.Map;
027 import java.util.jar.Attributes;
028 import java.util.jar.JarOutputStream;
029 import java.util.jar.Manifest;
030 import java.util.zip.GZIPInputStream;
031 import java.util.zip.ZipEntry;
032 import java.util.zip.ZipFile;
033
034 import org.apache.commons.collections.map.CaseInsensitiveMap;
035 import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
036 import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
037 import org.apache.hadoop.classification.InterfaceAudience;
038 import org.apache.hadoop.classification.InterfaceStability;
039 import org.apache.hadoop.conf.Configuration;
040 import org.apache.hadoop.fs.permission.FsAction;
041 import org.apache.hadoop.fs.permission.FsPermission;
042 import org.apache.hadoop.io.IOUtils;
043 import org.apache.hadoop.io.nativeio.NativeIO;
044 import org.apache.hadoop.util.StringUtils;
045 import org.apache.hadoop.util.Shell;
046 import org.apache.hadoop.util.Shell.ShellCommandExecutor;
047 import org.apache.commons.logging.Log;
048 import org.apache.commons.logging.LogFactory;
049
050 /**
051 * A collection of file-processing util methods
052 */
053 @InterfaceAudience.Public
054 @InterfaceStability.Evolving
055 public class FileUtil {
056
057 private static final Log LOG = LogFactory.getLog(FileUtil.class);
058
059 /* The error code is defined in winutils to indicate insufficient
060 * privilege to create symbolic links. This value need to keep in
061 * sync with the constant of the same name in:
062 * "src\winutils\common.h"
063 * */
064 public static final int SYMLINK_NO_PRIVILEGE = 2;
065
066 /**
067 * convert an array of FileStatus to an array of Path
068 *
069 * @param stats
070 * an array of FileStatus objects
071 * @return an array of paths corresponding to the input
072 */
073 public static Path[] stat2Paths(FileStatus[] stats) {
074 if (stats == null)
075 return null;
076 Path[] ret = new Path[stats.length];
077 for (int i = 0; i < stats.length; ++i) {
078 ret[i] = stats[i].getPath();
079 }
080 return ret;
081 }
082
083 /**
084 * convert an array of FileStatus to an array of Path.
085 * If stats if null, return path
086 * @param stats
087 * an array of FileStatus objects
088 * @param path
089 * default path to return in stats is null
090 * @return an array of paths corresponding to the input
091 */
092 public static Path[] stat2Paths(FileStatus[] stats, Path path) {
093 if (stats == null)
094 return new Path[]{path};
095 else
096 return stat2Paths(stats);
097 }
098
099 /**
100 * Delete a directory and all its contents. If
101 * we return false, the directory may be partially-deleted.
102 * (1) If dir is symlink to a file, the symlink is deleted. The file pointed
103 * to by the symlink is not deleted.
104 * (2) If dir is symlink to a directory, symlink is deleted. The directory
105 * pointed to by symlink is not deleted.
106 * (3) If dir is a normal file, it is deleted.
107 * (4) If dir is a normal directory, then dir and all its contents recursively
108 * are deleted.
109 */
110 public static boolean fullyDelete(final File dir) {
111 return fullyDelete(dir, false);
112 }
113
114 /**
115 * Delete a directory and all its contents. If
116 * we return false, the directory may be partially-deleted.
117 * (1) If dir is symlink to a file, the symlink is deleted. The file pointed
118 * to by the symlink is not deleted.
119 * (2) If dir is symlink to a directory, symlink is deleted. The directory
120 * pointed to by symlink is not deleted.
121 * (3) If dir is a normal file, it is deleted.
122 * (4) If dir is a normal directory, then dir and all its contents recursively
123 * are deleted.
124 * @param dir the file or directory to be deleted
125 * @param tryGrantPermissions true if permissions should be modified to delete a file.
126 * @return true on success false on failure.
127 */
128 public static boolean fullyDelete(final File dir, boolean tryGrantPermissions) {
129 if (tryGrantPermissions) {
130 // try to chmod +rwx the parent folder of the 'dir':
131 File parent = dir.getParentFile();
132 grantPermissions(parent);
133 }
134 if (deleteImpl(dir, false)) {
135 // dir is (a) normal file, (b) symlink to a file, (c) empty directory or
136 // (d) symlink to a directory
137 return true;
138 }
139 // handle nonempty directory deletion
140 if (!fullyDeleteContents(dir, tryGrantPermissions)) {
141 return false;
142 }
143 return deleteImpl(dir, true);
144 }
145
146 /**
147 * Returns the target of the given symlink. Returns the empty string if
148 * the given path does not refer to a symlink or there is an error
149 * accessing the symlink.
150 * @param f File representing the symbolic link.
151 * @return The target of the symbolic link, empty string on error or if not
152 * a symlink.
153 */
154 public static String readLink(File f) {
155 /* NB: Use readSymbolicLink in java.nio.file.Path once available. Could
156 * use getCanonicalPath in File to get the target of the symlink but that
157 * does not indicate if the given path refers to a symlink.
158 */
159 try {
160 return Shell.execCommand(
161 Shell.getReadlinkCommand(f.toString())).trim();
162 } catch (IOException x) {
163 return "";
164 }
165 }
166
167 /*
168 * Pure-Java implementation of "chmod +rwx f".
169 */
170 private static void grantPermissions(final File f) {
171 FileUtil.setExecutable(f, true);
172 FileUtil.setReadable(f, true);
173 FileUtil.setWritable(f, true);
174 }
175
176 private static boolean deleteImpl(final File f, final boolean doLog) {
177 if (f == null) {
178 LOG.warn("null file argument.");
179 return false;
180 }
181 final boolean wasDeleted = f.delete();
182 if (wasDeleted) {
183 return true;
184 }
185 final boolean ex = f.exists();
186 if (doLog && ex) {
187 LOG.warn("Failed to delete file or dir ["
188 + f.getAbsolutePath() + "]: it still exists.");
189 }
190 return !ex;
191 }
192
193 /**
194 * Delete the contents of a directory, not the directory itself. If
195 * we return false, the directory may be partially-deleted.
196 * If dir is a symlink to a directory, all the contents of the actual
197 * directory pointed to by dir will be deleted.
198 */
199 public static boolean fullyDeleteContents(final File dir) {
200 return fullyDeleteContents(dir, false);
201 }
202
203 /**
204 * Delete the contents of a directory, not the directory itself. If
205 * we return false, the directory may be partially-deleted.
206 * If dir is a symlink to a directory, all the contents of the actual
207 * directory pointed to by dir will be deleted.
208 * @param tryGrantPermissions if 'true', try grant +rwx permissions to this
209 * and all the underlying directories before trying to delete their contents.
210 */
211 public static boolean fullyDeleteContents(final File dir, final boolean tryGrantPermissions) {
212 if (tryGrantPermissions) {
213 // to be able to list the dir and delete files from it
214 // we must grant the dir rwx permissions:
215 grantPermissions(dir);
216 }
217 boolean deletionSucceeded = true;
218 final File[] contents = dir.listFiles();
219 if (contents != null) {
220 for (int i = 0; i < contents.length; i++) {
221 if (contents[i].isFile()) {
222 if (!deleteImpl(contents[i], true)) {// normal file or symlink to another file
223 deletionSucceeded = false;
224 continue; // continue deletion of other files/dirs under dir
225 }
226 } else {
227 // Either directory or symlink to another directory.
228 // Try deleting the directory as this might be a symlink
229 boolean b = false;
230 b = deleteImpl(contents[i], false);
231 if (b){
232 //this was indeed a symlink or an empty directory
233 continue;
234 }
235 // if not an empty directory or symlink let
236 // fullydelete handle it.
237 if (!fullyDelete(contents[i], tryGrantPermissions)) {
238 deletionSucceeded = false;
239 // continue deletion of other files/dirs under dir
240 }
241 }
242 }
243 }
244 return deletionSucceeded;
245 }
246
247 /**
248 * Recursively delete a directory.
249 *
250 * @param fs {@link FileSystem} on which the path is present
251 * @param dir directory to recursively delete
252 * @throws IOException
253 * @deprecated Use {@link FileSystem#delete(Path, boolean)}
254 */
255 @Deprecated
256 public static void fullyDelete(FileSystem fs, Path dir)
257 throws IOException {
258 fs.delete(dir, true);
259 }
260
261 //
262 // If the destination is a subdirectory of the source, then
263 // generate exception
264 //
265 private static void checkDependencies(FileSystem srcFS,
266 Path src,
267 FileSystem dstFS,
268 Path dst)
269 throws IOException {
270 if (srcFS == dstFS) {
271 String srcq = src.makeQualified(srcFS).toString() + Path.SEPARATOR;
272 String dstq = dst.makeQualified(dstFS).toString() + Path.SEPARATOR;
273 if (dstq.startsWith(srcq)) {
274 if (srcq.length() == dstq.length()) {
275 throw new IOException("Cannot copy " + src + " to itself.");
276 } else {
277 throw new IOException("Cannot copy " + src + " to its subdirectory " +
278 dst);
279 }
280 }
281 }
282 }
283
284 /** Copy files between FileSystems. */
285 public static boolean copy(FileSystem srcFS, Path src,
286 FileSystem dstFS, Path dst,
287 boolean deleteSource,
288 Configuration conf) throws IOException {
289 return copy(srcFS, src, dstFS, dst, deleteSource, true, conf);
290 }
291
292 public static boolean copy(FileSystem srcFS, Path[] srcs,
293 FileSystem dstFS, Path dst,
294 boolean deleteSource,
295 boolean overwrite, Configuration conf)
296 throws IOException {
297 boolean gotException = false;
298 boolean returnVal = true;
299 StringBuilder exceptions = new StringBuilder();
300
301 if (srcs.length == 1)
302 return copy(srcFS, srcs[0], dstFS, dst, deleteSource, overwrite, conf);
303
304 // Check if dest is directory
305 if (!dstFS.exists(dst)) {
306 throw new IOException("`" + dst +"': specified destination directory " +
307 "does not exist");
308 } else {
309 FileStatus sdst = dstFS.getFileStatus(dst);
310 if (!sdst.isDirectory())
311 throw new IOException("copying multiple files, but last argument `" +
312 dst + "' is not a directory");
313 }
314
315 for (Path src : srcs) {
316 try {
317 if (!copy(srcFS, src, dstFS, dst, deleteSource, overwrite, conf))
318 returnVal = false;
319 } catch (IOException e) {
320 gotException = true;
321 exceptions.append(e.getMessage());
322 exceptions.append("\n");
323 }
324 }
325 if (gotException) {
326 throw new IOException(exceptions.toString());
327 }
328 return returnVal;
329 }
330
331 /** Copy files between FileSystems. */
332 public static boolean copy(FileSystem srcFS, Path src,
333 FileSystem dstFS, Path dst,
334 boolean deleteSource,
335 boolean overwrite,
336 Configuration conf) throws IOException {
337 FileStatus fileStatus = srcFS.getFileStatus(src);
338 return copy(srcFS, fileStatus, dstFS, dst, deleteSource, overwrite, conf);
339 }
340
341 /** Copy files between FileSystems. */
342 public static boolean copy(FileSystem srcFS, FileStatus srcStatus,
343 FileSystem dstFS, Path dst,
344 boolean deleteSource,
345 boolean overwrite,
346 Configuration conf) throws IOException {
347 Path src = srcStatus.getPath();
348 dst = checkDest(src.getName(), dstFS, dst, overwrite);
349 if (srcStatus.isDirectory()) {
350 checkDependencies(srcFS, src, dstFS, dst);
351 if (!dstFS.mkdirs(dst)) {
352 return false;
353 }
354 FileStatus contents[] = srcFS.listStatus(src);
355 for (int i = 0; i < contents.length; i++) {
356 copy(srcFS, contents[i], dstFS,
357 new Path(dst, contents[i].getPath().getName()),
358 deleteSource, overwrite, conf);
359 }
360 } else {
361 InputStream in=null;
362 OutputStream out = null;
363 try {
364 in = srcFS.open(src);
365 out = dstFS.create(dst, overwrite);
366 IOUtils.copyBytes(in, out, conf, true);
367 } catch (IOException e) {
368 IOUtils.closeStream(out);
369 IOUtils.closeStream(in);
370 throw e;
371 }
372 }
373 if (deleteSource) {
374 return srcFS.delete(src, true);
375 } else {
376 return true;
377 }
378
379 }
380
381 /** Copy all files in a directory to one output file (merge). */
382 public static boolean copyMerge(FileSystem srcFS, Path srcDir,
383 FileSystem dstFS, Path dstFile,
384 boolean deleteSource,
385 Configuration conf, String addString) throws IOException {
386 dstFile = checkDest(srcDir.getName(), dstFS, dstFile, false);
387
388 if (!srcFS.getFileStatus(srcDir).isDirectory())
389 return false;
390
391 OutputStream out = dstFS.create(dstFile);
392
393 try {
394 FileStatus contents[] = srcFS.listStatus(srcDir);
395 Arrays.sort(contents);
396 for (int i = 0; i < contents.length; i++) {
397 if (contents[i].isFile()) {
398 InputStream in = srcFS.open(contents[i].getPath());
399 try {
400 IOUtils.copyBytes(in, out, conf, false);
401 if (addString!=null)
402 out.write(addString.getBytes("UTF-8"));
403
404 } finally {
405 in.close();
406 }
407 }
408 }
409 } finally {
410 out.close();
411 }
412
413
414 if (deleteSource) {
415 return srcFS.delete(srcDir, true);
416 } else {
417 return true;
418 }
419 }
420
421 /** Copy local files to a FileSystem. */
422 public static boolean copy(File src,
423 FileSystem dstFS, Path dst,
424 boolean deleteSource,
425 Configuration conf) throws IOException {
426 dst = checkDest(src.getName(), dstFS, dst, false);
427
428 if (src.isDirectory()) {
429 if (!dstFS.mkdirs(dst)) {
430 return false;
431 }
432 File contents[] = listFiles(src);
433 for (int i = 0; i < contents.length; i++) {
434 copy(contents[i], dstFS, new Path(dst, contents[i].getName()),
435 deleteSource, conf);
436 }
437 } else if (src.isFile()) {
438 InputStream in = null;
439 OutputStream out =null;
440 try {
441 in = new FileInputStream(src);
442 out = dstFS.create(dst);
443 IOUtils.copyBytes(in, out, conf);
444 } catch (IOException e) {
445 IOUtils.closeStream( out );
446 IOUtils.closeStream( in );
447 throw e;
448 }
449 } else {
450 throw new IOException(src.toString() +
451 ": No such file or directory");
452 }
453 if (deleteSource) {
454 return FileUtil.fullyDelete(src);
455 } else {
456 return true;
457 }
458 }
459
460 /** Copy FileSystem files to local files. */
461 public static boolean copy(FileSystem srcFS, Path src,
462 File dst, boolean deleteSource,
463 Configuration conf) throws IOException {
464 FileStatus filestatus = srcFS.getFileStatus(src);
465 return copy(srcFS, filestatus, dst, deleteSource, conf);
466 }
467
468 /** Copy FileSystem files to local files. */
469 private static boolean copy(FileSystem srcFS, FileStatus srcStatus,
470 File dst, boolean deleteSource,
471 Configuration conf) throws IOException {
472 Path src = srcStatus.getPath();
473 if (srcStatus.isDirectory()) {
474 if (!dst.mkdirs()) {
475 return false;
476 }
477 FileStatus contents[] = srcFS.listStatus(src);
478 for (int i = 0; i < contents.length; i++) {
479 copy(srcFS, contents[i],
480 new File(dst, contents[i].getPath().getName()),
481 deleteSource, conf);
482 }
483 } else {
484 InputStream in = srcFS.open(src);
485 IOUtils.copyBytes(in, new FileOutputStream(dst), conf);
486 }
487 if (deleteSource) {
488 return srcFS.delete(src, true);
489 } else {
490 return true;
491 }
492 }
493
494 private static Path checkDest(String srcName, FileSystem dstFS, Path dst,
495 boolean overwrite) throws IOException {
496 if (dstFS.exists(dst)) {
497 FileStatus sdst = dstFS.getFileStatus(dst);
498 if (sdst.isDirectory()) {
499 if (null == srcName) {
500 throw new IOException("Target " + dst + " is a directory");
501 }
502 return checkDest(null, dstFS, new Path(dst, srcName), overwrite);
503 } else if (!overwrite) {
504 throw new IOException("Target " + dst + " already exists");
505 }
506 }
507 return dst;
508 }
509
510 /**
511 * Convert a os-native filename to a path that works for the shell.
512 * @param filename The filename to convert
513 * @return The unix pathname
514 * @throws IOException on windows, there can be problems with the subprocess
515 */
516 public static String makeShellPath(String filename) throws IOException {
517 return filename;
518 }
519
520 /**
521 * Convert a os-native filename to a path that works for the shell.
522 * @param file The filename to convert
523 * @return The unix pathname
524 * @throws IOException on windows, there can be problems with the subprocess
525 */
526 public static String makeShellPath(File file) throws IOException {
527 return makeShellPath(file, false);
528 }
529
530 /**
531 * Convert a os-native filename to a path that works for the shell.
532 * @param file The filename to convert
533 * @param makeCanonicalPath
534 * Whether to make canonical path for the file passed
535 * @return The unix pathname
536 * @throws IOException on windows, there can be problems with the subprocess
537 */
538 public static String makeShellPath(File file, boolean makeCanonicalPath)
539 throws IOException {
540 if (makeCanonicalPath) {
541 return makeShellPath(file.getCanonicalPath());
542 } else {
543 return makeShellPath(file.toString());
544 }
545 }
546
547 /**
548 * Takes an input dir and returns the du on that local directory. Very basic
549 * implementation.
550 *
551 * @param dir
552 * The input dir to get the disk space of this local dir
553 * @return The total disk space of the input local directory
554 */
555 public static long getDU(File dir) {
556 long size = 0;
557 if (!dir.exists())
558 return 0;
559 if (!dir.isDirectory()) {
560 return dir.length();
561 } else {
562 File[] allFiles = dir.listFiles();
563 if(allFiles != null) {
564 for (int i = 0; i < allFiles.length; i++) {
565 boolean isSymLink;
566 try {
567 isSymLink = org.apache.commons.io.FileUtils.isSymlink(allFiles[i]);
568 } catch(IOException ioe) {
569 isSymLink = true;
570 }
571 if(!isSymLink) {
572 size += getDU(allFiles[i]);
573 }
574 }
575 }
576 return size;
577 }
578 }
579
580 /**
581 * Given a File input it will unzip the file in a the unzip directory
582 * passed as the second parameter
583 * @param inFile The zip file as input
584 * @param unzipDir The unzip directory where to unzip the zip file.
585 * @throws IOException
586 */
587 public static void unZip(File inFile, File unzipDir) throws IOException {
588 Enumeration<? extends ZipEntry> entries;
589 ZipFile zipFile = new ZipFile(inFile);
590
591 try {
592 entries = zipFile.entries();
593 while (entries.hasMoreElements()) {
594 ZipEntry entry = entries.nextElement();
595 if (!entry.isDirectory()) {
596 InputStream in = zipFile.getInputStream(entry);
597 try {
598 File file = new File(unzipDir, entry.getName());
599 if (!file.getParentFile().mkdirs()) {
600 if (!file.getParentFile().isDirectory()) {
601 throw new IOException("Mkdirs failed to create " +
602 file.getParentFile().toString());
603 }
604 }
605 OutputStream out = new FileOutputStream(file);
606 try {
607 byte[] buffer = new byte[8192];
608 int i;
609 while ((i = in.read(buffer)) != -1) {
610 out.write(buffer, 0, i);
611 }
612 } finally {
613 out.close();
614 }
615 } finally {
616 in.close();
617 }
618 }
619 }
620 } finally {
621 zipFile.close();
622 }
623 }
624
625 /**
626 * Given a Tar File as input it will untar the file in a the untar directory
627 * passed as the second parameter
628 *
629 * This utility will untar ".tar" files and ".tar.gz","tgz" files.
630 *
631 * @param inFile The tar file as input.
632 * @param untarDir The untar directory where to untar the tar file.
633 * @throws IOException
634 */
635 public static void unTar(File inFile, File untarDir) throws IOException {
636 if (!untarDir.mkdirs()) {
637 if (!untarDir.isDirectory()) {
638 throw new IOException("Mkdirs failed to create " + untarDir);
639 }
640 }
641
642 boolean gzipped = inFile.toString().endsWith("gz");
643 if(Shell.WINDOWS) {
644 // Tar is not native to Windows. Use simple Java based implementation for
645 // tests and simple tar archives
646 unTarUsingJava(inFile, untarDir, gzipped);
647 }
648 else {
649 // spawn tar utility to untar archive for full fledged unix behavior such
650 // as resolving symlinks in tar archives
651 unTarUsingTar(inFile, untarDir, gzipped);
652 }
653 }
654
655 private static void unTarUsingTar(File inFile, File untarDir,
656 boolean gzipped) throws IOException {
657 StringBuffer untarCommand = new StringBuffer();
658 if (gzipped) {
659 untarCommand.append(" gzip -dc '");
660 untarCommand.append(FileUtil.makeShellPath(inFile));
661 untarCommand.append("' | (");
662 }
663 untarCommand.append("cd '");
664 untarCommand.append(FileUtil.makeShellPath(untarDir));
665 untarCommand.append("' ; ");
666 untarCommand.append("tar -xf ");
667
668 if (gzipped) {
669 untarCommand.append(" -)");
670 } else {
671 untarCommand.append(FileUtil.makeShellPath(inFile));
672 }
673 String[] shellCmd = { "bash", "-c", untarCommand.toString() };
674 ShellCommandExecutor shexec = new ShellCommandExecutor(shellCmd);
675 shexec.execute();
676 int exitcode = shexec.getExitCode();
677 if (exitcode != 0) {
678 throw new IOException("Error untarring file " + inFile +
679 ". Tar process exited with exit code " + exitcode);
680 }
681 }
682
683 private static void unTarUsingJava(File inFile, File untarDir,
684 boolean gzipped) throws IOException {
685 InputStream inputStream = null;
686 TarArchiveInputStream tis = null;
687 try {
688 if (gzipped) {
689 inputStream = new BufferedInputStream(new GZIPInputStream(
690 new FileInputStream(inFile)));
691 } else {
692 inputStream = new BufferedInputStream(new FileInputStream(inFile));
693 }
694
695 tis = new TarArchiveInputStream(inputStream);
696
697 for (TarArchiveEntry entry = tis.getNextTarEntry(); entry != null;) {
698 unpackEntries(tis, entry, untarDir);
699 entry = tis.getNextTarEntry();
700 }
701 } finally {
702 IOUtils.cleanup(LOG, tis, inputStream);
703 }
704 }
705
706 private static void unpackEntries(TarArchiveInputStream tis,
707 TarArchiveEntry entry, File outputDir) throws IOException {
708 if (entry.isDirectory()) {
709 File subDir = new File(outputDir, entry.getName());
710 if (!subDir.mkdir() && !subDir.isDirectory()) {
711 throw new IOException("Mkdirs failed to create tar internal dir "
712 + outputDir);
713 }
714
715 for (TarArchiveEntry e : entry.getDirectoryEntries()) {
716 unpackEntries(tis, e, subDir);
717 }
718
719 return;
720 }
721
722 File outputFile = new File(outputDir, entry.getName());
723 if (!outputDir.exists()) {
724 if (!outputDir.mkdirs()) {
725 throw new IOException("Mkdirs failed to create tar internal dir "
726 + outputDir);
727 }
728 }
729
730 int count;
731 byte data[] = new byte[2048];
732 BufferedOutputStream outputStream = new BufferedOutputStream(
733 new FileOutputStream(outputFile));
734
735 while ((count = tis.read(data)) != -1) {
736 outputStream.write(data, 0, count);
737 }
738
739 outputStream.flush();
740 outputStream.close();
741 }
742
743 /**
744 * Class for creating hardlinks.
745 * Supports Unix, WindXP.
746 * @deprecated Use {@link org.apache.hadoop.fs.HardLink}
747 */
748 @Deprecated
749 public static class HardLink extends org.apache.hadoop.fs.HardLink {
750 // This is a stub to assist with coordinated change between
751 // COMMON and HDFS projects. It will be removed after the
752 // corresponding change is committed to HDFS.
753 }
754
755 /**
756 * Create a soft link between a src and destination
757 * only on a local disk. HDFS does not support this.
758 * On Windows, when symlink creation fails due to security
759 * setting, we will log a warning. The return code in this
760 * case is 2.
761 *
762 * @param target the target for symlink
763 * @param linkname the symlink
764 * @return 0 on success
765 */
766 public static int symLink(String target, String linkname) throws IOException{
767 // Run the input paths through Java's File so that they are converted to the
768 // native OS form
769 File targetFile = new File(
770 Path.getPathWithoutSchemeAndAuthority(new Path(target)).toString());
771 File linkFile = new File(
772 Path.getPathWithoutSchemeAndAuthority(new Path(linkname)).toString());
773
774 // If not on Java7+, copy a file instead of creating a symlink since
775 // Java6 has close to no support for symlinks on Windows. Specifically
776 // File#length and File#renameTo do not work as expected.
777 // (see HADOOP-9061 for additional details)
778 // We still create symlinks for directories, since the scenario in this
779 // case is different. The directory content could change in which
780 // case the symlink loses its purpose (for example task attempt log folder
781 // is symlinked under userlogs and userlogs are generated afterwards).
782 if (Shell.WINDOWS && !Shell.isJava7OrAbove() && targetFile.isFile()) {
783 try {
784 LOG.warn("FileUtil#symlink: On Windows+Java6, copying file instead " +
785 "of creating a symlink. Copying " + target + " -> " + linkname);
786
787 if (!linkFile.getParentFile().exists()) {
788 LOG.warn("Parent directory " + linkFile.getParent() +
789 " does not exist.");
790 return 1;
791 } else {
792 org.apache.commons.io.FileUtils.copyFile(targetFile, linkFile);
793 }
794 } catch (IOException ex) {
795 LOG.warn("FileUtil#symlink failed to copy the file with error: "
796 + ex.getMessage());
797 // Exit with non-zero exit code
798 return 1;
799 }
800 return 0;
801 }
802
803 String[] cmd = Shell.getSymlinkCommand(
804 targetFile.toString(),
805 linkFile.toString());
806
807 ShellCommandExecutor shExec;
808 try {
809 if (Shell.WINDOWS &&
810 linkFile.getParentFile() != null &&
811 !new Path(target).isAbsolute()) {
812 // Relative links on Windows must be resolvable at the time of
813 // creation. To ensure this we run the shell command in the directory
814 // of the link.
815 //
816 shExec = new ShellCommandExecutor(cmd, linkFile.getParentFile());
817 } else {
818 shExec = new ShellCommandExecutor(cmd);
819 }
820 shExec.execute();
821 } catch (Shell.ExitCodeException ec) {
822 int returnVal = ec.getExitCode();
823 if (Shell.WINDOWS && returnVal == SYMLINK_NO_PRIVILEGE) {
824 LOG.warn("Fail to create symbolic links on Windows. "
825 + "The default security settings in Windows disallow non-elevated "
826 + "administrators and all non-administrators from creating symbolic links. "
827 + "This behavior can be changed in the Local Security Policy management console");
828 } else if (returnVal != 0) {
829 LOG.warn("Command '" + StringUtils.join(" ", cmd) + "' failed "
830 + returnVal + " with: " + ec.getMessage());
831 }
832 return returnVal;
833 } catch (IOException e) {
834 if (LOG.isDebugEnabled()) {
835 LOG.debug("Error while create symlink " + linkname + " to " + target
836 + "." + " Exception: " + StringUtils.stringifyException(e));
837 }
838 throw e;
839 }
840 return shExec.getExitCode();
841 }
842
843 /**
844 * Change the permissions on a filename.
845 * @param filename the name of the file to change
846 * @param perm the permission string
847 * @return the exit code from the command
848 * @throws IOException
849 * @throws InterruptedException
850 */
851 public static int chmod(String filename, String perm
852 ) throws IOException, InterruptedException {
853 return chmod(filename, perm, false);
854 }
855
856 /**
857 * Change the permissions on a file / directory, recursively, if
858 * needed.
859 * @param filename name of the file whose permissions are to change
860 * @param perm permission string
861 * @param recursive true, if permissions should be changed recursively
862 * @return the exit code from the command.
863 * @throws IOException
864 */
865 public static int chmod(String filename, String perm, boolean recursive)
866 throws IOException {
867 String [] cmd = Shell.getSetPermissionCommand(perm, recursive);
868 String[] args = new String[cmd.length + 1];
869 System.arraycopy(cmd, 0, args, 0, cmd.length);
870 args[cmd.length] = new File(filename).getPath();
871 ShellCommandExecutor shExec = new ShellCommandExecutor(args);
872 try {
873 shExec.execute();
874 }catch(IOException e) {
875 if(LOG.isDebugEnabled()) {
876 LOG.debug("Error while changing permission : " + filename
877 +" Exception: " + StringUtils.stringifyException(e));
878 }
879 }
880 return shExec.getExitCode();
881 }
882
883 /**
884 * Set the ownership on a file / directory. User name and group name
885 * cannot both be null.
886 * @param file the file to change
887 * @param username the new user owner name
888 * @param groupname the new group owner name
889 * @throws IOException
890 */
891 public static void setOwner(File file, String username,
892 String groupname) throws IOException {
893 if (username == null && groupname == null) {
894 throw new IOException("username == null && groupname == null");
895 }
896 String arg = (username == null ? "" : username)
897 + (groupname == null ? "" : ":" + groupname);
898 String [] cmd = Shell.getSetOwnerCommand(arg);
899 execCommand(file, cmd);
900 }
901
902 /**
903 * Platform independent implementation for {@link File#setReadable(boolean)}
904 * File#setReadable does not work as expected on Windows.
905 * @param f input file
906 * @param readable
907 * @return true on success, false otherwise
908 */
909 public static boolean setReadable(File f, boolean readable) {
910 if (Shell.WINDOWS) {
911 try {
912 String permission = readable ? "u+r" : "u-r";
913 FileUtil.chmod(f.getCanonicalPath(), permission, false);
914 return true;
915 } catch (IOException ex) {
916 return false;
917 }
918 } else {
919 return f.setReadable(readable);
920 }
921 }
922
923 /**
924 * Platform independent implementation for {@link File#setWritable(boolean)}
925 * File#setWritable does not work as expected on Windows.
926 * @param f input file
927 * @param writable
928 * @return true on success, false otherwise
929 */
930 public static boolean setWritable(File f, boolean writable) {
931 if (Shell.WINDOWS) {
932 try {
933 String permission = writable ? "u+w" : "u-w";
934 FileUtil.chmod(f.getCanonicalPath(), permission, false);
935 return true;
936 } catch (IOException ex) {
937 return false;
938 }
939 } else {
940 return f.setWritable(writable);
941 }
942 }
943
944 /**
945 * Platform independent implementation for {@link File#setExecutable(boolean)}
946 * File#setExecutable does not work as expected on Windows.
947 * Note: revoking execute permission on folders does not have the same
948 * behavior on Windows as on Unix platforms. Creating, deleting or renaming
949 * a file within that folder will still succeed on Windows.
950 * @param f input file
951 * @param executable
952 * @return true on success, false otherwise
953 */
954 public static boolean setExecutable(File f, boolean executable) {
955 if (Shell.WINDOWS) {
956 try {
957 String permission = executable ? "u+x" : "u-x";
958 FileUtil.chmod(f.getCanonicalPath(), permission, false);
959 return true;
960 } catch (IOException ex) {
961 return false;
962 }
963 } else {
964 return f.setExecutable(executable);
965 }
966 }
967
968 /**
969 * Platform independent implementation for {@link File#canRead()}
970 * @param f input file
971 * @return On Unix, same as {@link File#canRead()}
972 * On Windows, true if process has read access on the path
973 */
974 public static boolean canRead(File f) {
975 if (Shell.WINDOWS) {
976 try {
977 return NativeIO.Windows.access(f.getCanonicalPath(),
978 NativeIO.Windows.AccessRight.ACCESS_READ);
979 } catch (IOException e) {
980 return false;
981 }
982 } else {
983 return f.canRead();
984 }
985 }
986
987 /**
988 * Platform independent implementation for {@link File#canWrite()}
989 * @param f input file
990 * @return On Unix, same as {@link File#canWrite()}
991 * On Windows, true if process has write access on the path
992 */
993 public static boolean canWrite(File f) {
994 if (Shell.WINDOWS) {
995 try {
996 return NativeIO.Windows.access(f.getCanonicalPath(),
997 NativeIO.Windows.AccessRight.ACCESS_WRITE);
998 } catch (IOException e) {
999 return false;
1000 }
1001 } else {
1002 return f.canWrite();
1003 }
1004 }
1005
1006 /**
1007 * Platform independent implementation for {@link File#canExecute()}
1008 * @param f input file
1009 * @return On Unix, same as {@link File#canExecute()}
1010 * On Windows, true if process has execute access on the path
1011 */
1012 public static boolean canExecute(File f) {
1013 if (Shell.WINDOWS) {
1014 try {
1015 return NativeIO.Windows.access(f.getCanonicalPath(),
1016 NativeIO.Windows.AccessRight.ACCESS_EXECUTE);
1017 } catch (IOException e) {
1018 return false;
1019 }
1020 } else {
1021 return f.canExecute();
1022 }
1023 }
1024
1025 /**
1026 * Set permissions to the required value. Uses the java primitives instead
1027 * of forking if group == other.
1028 * @param f the file to change
1029 * @param permission the new permissions
1030 * @throws IOException
1031 */
1032 public static void setPermission(File f, FsPermission permission
1033 ) throws IOException {
1034 FsAction user = permission.getUserAction();
1035 FsAction group = permission.getGroupAction();
1036 FsAction other = permission.getOtherAction();
1037
1038 // use the native/fork if the group/other permissions are different
1039 // or if the native is available or on Windows
1040 if (group != other || NativeIO.isAvailable() || Shell.WINDOWS) {
1041 execSetPermission(f, permission);
1042 return;
1043 }
1044
1045 boolean rv = true;
1046
1047 // read perms
1048 rv = f.setReadable(group.implies(FsAction.READ), false);
1049 checkReturnValue(rv, f, permission);
1050 if (group.implies(FsAction.READ) != user.implies(FsAction.READ)) {
1051 rv = f.setReadable(user.implies(FsAction.READ), true);
1052 checkReturnValue(rv, f, permission);
1053 }
1054
1055 // write perms
1056 rv = f.setWritable(group.implies(FsAction.WRITE), false);
1057 checkReturnValue(rv, f, permission);
1058 if (group.implies(FsAction.WRITE) != user.implies(FsAction.WRITE)) {
1059 rv = f.setWritable(user.implies(FsAction.WRITE), true);
1060 checkReturnValue(rv, f, permission);
1061 }
1062
1063 // exec perms
1064 rv = f.setExecutable(group.implies(FsAction.EXECUTE), false);
1065 checkReturnValue(rv, f, permission);
1066 if (group.implies(FsAction.EXECUTE) != user.implies(FsAction.EXECUTE)) {
1067 rv = f.setExecutable(user.implies(FsAction.EXECUTE), true);
1068 checkReturnValue(rv, f, permission);
1069 }
1070 }
1071
1072 private static void checkReturnValue(boolean rv, File p,
1073 FsPermission permission
1074 ) throws IOException {
1075 if (!rv) {
1076 throw new IOException("Failed to set permissions of path: " + p +
1077 " to " +
1078 String.format("%04o", permission.toShort()));
1079 }
1080 }
1081
1082 private static void execSetPermission(File f,
1083 FsPermission permission
1084 ) throws IOException {
1085 if (NativeIO.isAvailable()) {
1086 NativeIO.POSIX.chmod(f.getCanonicalPath(), permission.toShort());
1087 } else {
1088 execCommand(f, Shell.getSetPermissionCommand(
1089 String.format("%04o", permission.toShort()), false));
1090 }
1091 }
1092
1093 static String execCommand(File f, String... cmd) throws IOException {
1094 String[] args = new String[cmd.length + 1];
1095 System.arraycopy(cmd, 0, args, 0, cmd.length);
1096 args[cmd.length] = f.getCanonicalPath();
1097 String output = Shell.execCommand(args);
1098 return output;
1099 }
1100
1101 /**
1102 * Create a tmp file for a base file.
1103 * @param basefile the base file of the tmp
1104 * @param prefix file name prefix of tmp
1105 * @param isDeleteOnExit if true, the tmp will be deleted when the VM exits
1106 * @return a newly created tmp file
1107 * @exception IOException If a tmp file cannot created
1108 * @see java.io.File#createTempFile(String, String, File)
1109 * @see java.io.File#deleteOnExit()
1110 */
1111 public static final File createLocalTempFile(final File basefile,
1112 final String prefix,
1113 final boolean isDeleteOnExit)
1114 throws IOException {
1115 File tmp = File.createTempFile(prefix + basefile.getName(),
1116 "", basefile.getParentFile());
1117 if (isDeleteOnExit) {
1118 tmp.deleteOnExit();
1119 }
1120 return tmp;
1121 }
1122
1123 /**
1124 * Move the src file to the name specified by target.
1125 * @param src the source file
1126 * @param target the target file
1127 * @exception IOException If this operation fails
1128 */
1129 public static void replaceFile(File src, File target) throws IOException {
1130 /* renameTo() has two limitations on Windows platform.
1131 * src.renameTo(target) fails if
1132 * 1) If target already exists OR
1133 * 2) If target is already open for reading/writing.
1134 */
1135 if (!src.renameTo(target)) {
1136 int retries = 5;
1137 while (target.exists() && !target.delete() && retries-- >= 0) {
1138 try {
1139 Thread.sleep(1000);
1140 } catch (InterruptedException e) {
1141 throw new IOException("replaceFile interrupted.");
1142 }
1143 }
1144 if (!src.renameTo(target)) {
1145 throw new IOException("Unable to rename " + src +
1146 " to " + target);
1147 }
1148 }
1149 }
1150
1151 /**
1152 * A wrapper for {@link File#listFiles()}. This java.io API returns null
1153 * when a dir is not a directory or for any I/O error. Instead of having
1154 * null check everywhere File#listFiles() is used, we will add utility API
1155 * to get around this problem. For the majority of cases where we prefer
1156 * an IOException to be thrown.
1157 * @param dir directory for which listing should be performed
1158 * @return list of files or empty list
1159 * @exception IOException for invalid directory or for a bad disk.
1160 */
1161 public static File[] listFiles(File dir) throws IOException {
1162 File[] files = dir.listFiles();
1163 if(files == null) {
1164 throw new IOException("Invalid directory or I/O error occurred for dir: "
1165 + dir.toString());
1166 }
1167 return files;
1168 }
1169
1170 /**
1171 * A wrapper for {@link File#list()}. This java.io API returns null
1172 * when a dir is not a directory or for any I/O error. Instead of having
1173 * null check everywhere File#list() is used, we will add utility API
1174 * to get around this problem. For the majority of cases where we prefer
1175 * an IOException to be thrown.
1176 * @param dir directory for which listing should be performed
1177 * @return list of file names or empty string list
1178 * @exception IOException for invalid directory or for a bad disk.
1179 */
1180 public static String[] list(File dir) throws IOException {
1181 String[] fileNames = dir.list();
1182 if(fileNames == null) {
1183 throw new IOException("Invalid directory or I/O error occurred for dir: "
1184 + dir.toString());
1185 }
1186 return fileNames;
1187 }
1188
1189 /**
1190 * Create a jar file at the given path, containing a manifest with a classpath
1191 * that references all specified entries.
1192 *
1193 * Some platforms may have an upper limit on command line length. For example,
1194 * the maximum command line length on Windows is 8191 characters, but the
1195 * length of the classpath may exceed this. To work around this limitation,
1196 * use this method to create a small intermediate jar with a manifest that
1197 * contains the full classpath. It returns the absolute path to the new jar,
1198 * which the caller may set as the classpath for a new process.
1199 *
1200 * Environment variable evaluation is not supported within a jar manifest, so
1201 * this method expands environment variables before inserting classpath entries
1202 * to the manifest. The method parses environment variables according to
1203 * platform-specific syntax (%VAR% on Windows, or $VAR otherwise). On Windows,
1204 * environment variables are case-insensitive. For example, %VAR% and %var%
1205 * evaluate to the same value.
1206 *
1207 * Specifying the classpath in a jar manifest does not support wildcards, so
1208 * this method expands wildcards internally. Any classpath entry that ends
1209 * with * is translated to all files at that path with extension .jar or .JAR.
1210 *
1211 * @param inputClassPath String input classpath to bundle into the jar manifest
1212 * @param pwd Path to working directory to save jar
1213 * @param callerEnv Map<String, String> caller's environment variables to use
1214 * for expansion
1215 * @return String absolute path to new jar
1216 * @throws IOException if there is an I/O error while writing the jar file
1217 */
1218 public static String createJarWithClassPath(String inputClassPath, Path pwd,
1219 Map<String, String> callerEnv) throws IOException {
1220 // Replace environment variables, case-insensitive on Windows
1221 @SuppressWarnings("unchecked")
1222 Map<String, String> env = Shell.WINDOWS ? new CaseInsensitiveMap(callerEnv) :
1223 callerEnv;
1224 String[] classPathEntries = inputClassPath.split(File.pathSeparator);
1225 for (int i = 0; i < classPathEntries.length; ++i) {
1226 classPathEntries[i] = StringUtils.replaceTokens(classPathEntries[i],
1227 StringUtils.ENV_VAR_PATTERN, env);
1228 }
1229 File workingDir = new File(pwd.toString());
1230 if (!workingDir.mkdirs()) {
1231 // If mkdirs returns false because the working directory already exists,
1232 // then this is acceptable. If it returns false due to some other I/O
1233 // error, then this method will fail later with an IOException while saving
1234 // the jar.
1235 LOG.debug("mkdirs false for " + workingDir + ", execution will continue");
1236 }
1237
1238 // Append all entries
1239 List<String> classPathEntryList = new ArrayList<String>(
1240 classPathEntries.length);
1241 for (String classPathEntry: classPathEntries) {
1242 if (classPathEntry.length() == 0) {
1243 continue;
1244 }
1245 if (classPathEntry.endsWith("*")) {
1246 // Append all jars that match the wildcard
1247 Path globPath = new Path(classPathEntry).suffix("{.jar,.JAR}");
1248 FileStatus[] wildcardJars = FileContext.getLocalFSFileContext().util()
1249 .globStatus(globPath);
1250 if (wildcardJars != null) {
1251 for (FileStatus wildcardJar: wildcardJars) {
1252 classPathEntryList.add(wildcardJar.getPath().toUri().toURL()
1253 .toExternalForm());
1254 }
1255 }
1256 } else {
1257 // Append just this entry
1258 File fileCpEntry = null;
1259 if(!new Path(classPathEntry).isAbsolute()) {
1260 fileCpEntry = new File(workingDir, classPathEntry);
1261 }
1262 else {
1263 fileCpEntry = new File(classPathEntry);
1264 }
1265 String classPathEntryUrl = fileCpEntry.toURI().toURL()
1266 .toExternalForm();
1267
1268 // File.toURI only appends trailing '/' if it can determine that it is a
1269 // directory that already exists. (See JavaDocs.) If this entry had a
1270 // trailing '/' specified by the caller, then guarantee that the
1271 // classpath entry in the manifest has a trailing '/', and thus refers to
1272 // a directory instead of a file. This can happen if the caller is
1273 // creating a classpath jar referencing a directory that hasn't been
1274 // created yet, but will definitely be created before running.
1275 if (classPathEntry.endsWith(Path.SEPARATOR) &&
1276 !classPathEntryUrl.endsWith(Path.SEPARATOR)) {
1277 classPathEntryUrl = classPathEntryUrl + Path.SEPARATOR;
1278 }
1279 classPathEntryList.add(classPathEntryUrl);
1280 }
1281 }
1282 String jarClassPath = StringUtils.join(" ", classPathEntryList);
1283
1284 // Create the manifest
1285 Manifest jarManifest = new Manifest();
1286 jarManifest.getMainAttributes().putValue(
1287 Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
1288 jarManifest.getMainAttributes().putValue(
1289 Attributes.Name.CLASS_PATH.toString(), jarClassPath);
1290
1291 // Write the manifest to output JAR file
1292 File classPathJar = File.createTempFile("classpath-", ".jar", workingDir);
1293 FileOutputStream fos = null;
1294 BufferedOutputStream bos = null;
1295 JarOutputStream jos = null;
1296 try {
1297 fos = new FileOutputStream(classPathJar);
1298 bos = new BufferedOutputStream(fos);
1299 jos = new JarOutputStream(bos, jarManifest);
1300 } finally {
1301 IOUtils.cleanup(LOG, jos, bos, fos);
1302 }
1303
1304 return classPathJar.getCanonicalPath();
1305 }
1306 }