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