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    try (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    }
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    String[] cmd = Shell.getSymlinkCommand(
788        targetFile.toString(),
789        linkFile.toString());
790
791    ShellCommandExecutor shExec;
792    try {
793      if (Shell.WINDOWS &&
794          linkFile.getParentFile() != null &&
795          !new Path(target).isAbsolute()) {
796        // Relative links on Windows must be resolvable at the time of
797        // creation. To ensure this we run the shell command in the directory
798        // of the link.
799        //
800        shExec = new ShellCommandExecutor(cmd, linkFile.getParentFile());
801      } else {
802        shExec = new ShellCommandExecutor(cmd);
803      }
804      shExec.execute();
805    } catch (Shell.ExitCodeException ec) {
806      int returnVal = ec.getExitCode();
807      if (Shell.WINDOWS && returnVal == SYMLINK_NO_PRIVILEGE) {
808        LOG.warn("Fail to create symbolic links on Windows. "
809            + "The default security settings in Windows disallow non-elevated "
810            + "administrators and all non-administrators from creating symbolic links. "
811            + "This behavior can be changed in the Local Security Policy management console");
812      } else if (returnVal != 0) {
813        LOG.warn("Command '" + StringUtils.join(" ", cmd) + "' failed "
814            + returnVal + " with: " + ec.getMessage());
815      }
816      return returnVal;
817    } catch (IOException e) {
818      if (LOG.isDebugEnabled()) {
819        LOG.debug("Error while create symlink " + linkname + " to " + target
820            + "." + " Exception: " + StringUtils.stringifyException(e));
821      }
822      throw e;
823    }
824    return shExec.getExitCode();
825  }
826
827  /**
828   * Change the permissions on a filename.
829   * @param filename the name of the file to change
830   * @param perm the permission string
831   * @return the exit code from the command
832   * @throws IOException
833   * @throws InterruptedException
834   */
835  public static int chmod(String filename, String perm
836                          ) throws IOException, InterruptedException {
837    return chmod(filename, perm, false);
838  }
839
840  /**
841   * Change the permissions on a file / directory, recursively, if
842   * needed.
843   * @param filename name of the file whose permissions are to change
844   * @param perm permission string
845   * @param recursive true, if permissions should be changed recursively
846   * @return the exit code from the command.
847   * @throws IOException
848   */
849  public static int chmod(String filename, String perm, boolean recursive)
850                            throws IOException {
851    String [] cmd = Shell.getSetPermissionCommand(perm, recursive);
852    String[] args = new String[cmd.length + 1];
853    System.arraycopy(cmd, 0, args, 0, cmd.length);
854    args[cmd.length] = new File(filename).getPath();
855    ShellCommandExecutor shExec = new ShellCommandExecutor(args);
856    try {
857      shExec.execute();
858    }catch(IOException e) {
859      if(LOG.isDebugEnabled()) {
860        LOG.debug("Error while changing permission : " + filename
861                  +" Exception: " + StringUtils.stringifyException(e));
862      }
863    }
864    return shExec.getExitCode();
865  }
866
867  /**
868   * Set the ownership on a file / directory. User name and group name
869   * cannot both be null.
870   * @param file the file to change
871   * @param username the new user owner name
872   * @param groupname the new group owner name
873   * @throws IOException
874   */
875  public static void setOwner(File file, String username,
876      String groupname) throws IOException {
877    if (username == null && groupname == null) {
878      throw new IOException("username == null && groupname == null");
879    }
880    String arg = (username == null ? "" : username)
881        + (groupname == null ? "" : ":" + groupname);
882    String [] cmd = Shell.getSetOwnerCommand(arg);
883    execCommand(file, cmd);
884  }
885
886  /**
887   * Platform independent implementation for {@link File#setReadable(boolean)}
888   * File#setReadable does not work as expected on Windows.
889   * @param f input file
890   * @param readable
891   * @return true on success, false otherwise
892   */
893  public static boolean setReadable(File f, boolean readable) {
894    if (Shell.WINDOWS) {
895      try {
896        String permission = readable ? "u+r" : "u-r";
897        FileUtil.chmod(f.getCanonicalPath(), permission, false);
898        return true;
899      } catch (IOException ex) {
900        return false;
901      }
902    } else {
903      return f.setReadable(readable);
904    }
905  }
906
907  /**
908   * Platform independent implementation for {@link File#setWritable(boolean)}
909   * File#setWritable does not work as expected on Windows.
910   * @param f input file
911   * @param writable
912   * @return true on success, false otherwise
913   */
914  public static boolean setWritable(File f, boolean writable) {
915    if (Shell.WINDOWS) {
916      try {
917        String permission = writable ? "u+w" : "u-w";
918        FileUtil.chmod(f.getCanonicalPath(), permission, false);
919        return true;
920      } catch (IOException ex) {
921        return false;
922      }
923    } else {
924      return f.setWritable(writable);
925    }
926  }
927
928  /**
929   * Platform independent implementation for {@link File#setExecutable(boolean)}
930   * File#setExecutable does not work as expected on Windows.
931   * Note: revoking execute permission on folders does not have the same
932   * behavior on Windows as on Unix platforms. Creating, deleting or renaming
933   * a file within that folder will still succeed on Windows.
934   * @param f input file
935   * @param executable
936   * @return true on success, false otherwise
937   */
938  public static boolean setExecutable(File f, boolean executable) {
939    if (Shell.WINDOWS) {
940      try {
941        String permission = executable ? "u+x" : "u-x";
942        FileUtil.chmod(f.getCanonicalPath(), permission, false);
943        return true;
944      } catch (IOException ex) {
945        return false;
946      }
947    } else {
948      return f.setExecutable(executable);
949    }
950  }
951
952  /**
953   * Platform independent implementation for {@link File#canRead()}
954   * @param f input file
955   * @return On Unix, same as {@link File#canRead()}
956   *         On Windows, true if process has read access on the path
957   */
958  public static boolean canRead(File f) {
959    if (Shell.WINDOWS) {
960      try {
961        return NativeIO.Windows.access(f.getCanonicalPath(),
962            NativeIO.Windows.AccessRight.ACCESS_READ);
963      } catch (IOException e) {
964        return false;
965      }
966    } else {
967      return f.canRead();
968    }
969  }
970
971  /**
972   * Platform independent implementation for {@link File#canWrite()}
973   * @param f input file
974   * @return On Unix, same as {@link File#canWrite()}
975   *         On Windows, true if process has write access on the path
976   */
977  public static boolean canWrite(File f) {
978    if (Shell.WINDOWS) {
979      try {
980        return NativeIO.Windows.access(f.getCanonicalPath(),
981            NativeIO.Windows.AccessRight.ACCESS_WRITE);
982      } catch (IOException e) {
983        return false;
984      }
985    } else {
986      return f.canWrite();
987    }
988  }
989
990  /**
991   * Platform independent implementation for {@link File#canExecute()}
992   * @param f input file
993   * @return On Unix, same as {@link File#canExecute()}
994   *         On Windows, true if process has execute access on the path
995   */
996  public static boolean canExecute(File f) {
997    if (Shell.WINDOWS) {
998      try {
999        return NativeIO.Windows.access(f.getCanonicalPath(),
1000            NativeIO.Windows.AccessRight.ACCESS_EXECUTE);
1001      } catch (IOException e) {
1002        return false;
1003      }
1004    } else {
1005      return f.canExecute();
1006    }
1007  }
1008
1009  /**
1010   * Set permissions to the required value. Uses the java primitives instead
1011   * of forking if group == other.
1012   * @param f the file to change
1013   * @param permission the new permissions
1014   * @throws IOException
1015   */
1016  public static void setPermission(File f, FsPermission permission
1017                                   ) throws IOException {
1018    FsAction user = permission.getUserAction();
1019    FsAction group = permission.getGroupAction();
1020    FsAction other = permission.getOtherAction();
1021
1022    // use the native/fork if the group/other permissions are different
1023    // or if the native is available or on Windows
1024    if (group != other || NativeIO.isAvailable() || Shell.WINDOWS) {
1025      execSetPermission(f, permission);
1026      return;
1027    }
1028
1029    boolean rv = true;
1030
1031    // read perms
1032    rv = f.setReadable(group.implies(FsAction.READ), false);
1033    checkReturnValue(rv, f, permission);
1034    if (group.implies(FsAction.READ) != user.implies(FsAction.READ)) {
1035      rv = f.setReadable(user.implies(FsAction.READ), true);
1036      checkReturnValue(rv, f, permission);
1037    }
1038
1039    // write perms
1040    rv = f.setWritable(group.implies(FsAction.WRITE), false);
1041    checkReturnValue(rv, f, permission);
1042    if (group.implies(FsAction.WRITE) != user.implies(FsAction.WRITE)) {
1043      rv = f.setWritable(user.implies(FsAction.WRITE), true);
1044      checkReturnValue(rv, f, permission);
1045    }
1046
1047    // exec perms
1048    rv = f.setExecutable(group.implies(FsAction.EXECUTE), false);
1049    checkReturnValue(rv, f, permission);
1050    if (group.implies(FsAction.EXECUTE) != user.implies(FsAction.EXECUTE)) {
1051      rv = f.setExecutable(user.implies(FsAction.EXECUTE), true);
1052      checkReturnValue(rv, f, permission);
1053    }
1054  }
1055
1056  private static void checkReturnValue(boolean rv, File p,
1057                                       FsPermission permission
1058                                       ) throws IOException {
1059    if (!rv) {
1060      throw new IOException("Failed to set permissions of path: " + p +
1061                            " to " +
1062                            String.format("%04o", permission.toShort()));
1063    }
1064  }
1065
1066  private static void execSetPermission(File f,
1067                                        FsPermission permission
1068                                       )  throws IOException {
1069    if (NativeIO.isAvailable()) {
1070      NativeIO.POSIX.chmod(f.getCanonicalPath(), permission.toShort());
1071    } else {
1072      execCommand(f, Shell.getSetPermissionCommand(
1073                  String.format("%04o", permission.toShort()), false));
1074    }
1075  }
1076
1077  static String execCommand(File f, String... cmd) throws IOException {
1078    String[] args = new String[cmd.length + 1];
1079    System.arraycopy(cmd, 0, args, 0, cmd.length);
1080    args[cmd.length] = f.getCanonicalPath();
1081    String output = Shell.execCommand(args);
1082    return output;
1083  }
1084
1085  /**
1086   * Create a tmp file for a base file.
1087   * @param basefile the base file of the tmp
1088   * @param prefix file name prefix of tmp
1089   * @param isDeleteOnExit if true, the tmp will be deleted when the VM exits
1090   * @return a newly created tmp file
1091   * @exception IOException If a tmp file cannot created
1092   * @see java.io.File#createTempFile(String, String, File)
1093   * @see java.io.File#deleteOnExit()
1094   */
1095  public static final File createLocalTempFile(final File basefile,
1096                                               final String prefix,
1097                                               final boolean isDeleteOnExit)
1098    throws IOException {
1099    File tmp = File.createTempFile(prefix + basefile.getName(),
1100                                   "", basefile.getParentFile());
1101    if (isDeleteOnExit) {
1102      tmp.deleteOnExit();
1103    }
1104    return tmp;
1105  }
1106
1107  /**
1108   * Move the src file to the name specified by target.
1109   * @param src the source file
1110   * @param target the target file
1111   * @exception IOException If this operation fails
1112   */
1113  public static void replaceFile(File src, File target) throws IOException {
1114    /* renameTo() has two limitations on Windows platform.
1115     * src.renameTo(target) fails if
1116     * 1) If target already exists OR
1117     * 2) If target is already open for reading/writing.
1118     */
1119    if (!src.renameTo(target)) {
1120      int retries = 5;
1121      while (target.exists() && !target.delete() && retries-- >= 0) {
1122        try {
1123          Thread.sleep(1000);
1124        } catch (InterruptedException e) {
1125          throw new IOException("replaceFile interrupted.");
1126        }
1127      }
1128      if (!src.renameTo(target)) {
1129        throw new IOException("Unable to rename " + src +
1130                              " to " + target);
1131      }
1132    }
1133  }
1134
1135  /**
1136   * A wrapper for {@link File#listFiles()}. This java.io API returns null
1137   * when a dir is not a directory or for any I/O error. Instead of having
1138   * null check everywhere File#listFiles() is used, we will add utility API
1139   * to get around this problem. For the majority of cases where we prefer
1140   * an IOException to be thrown.
1141   * @param dir directory for which listing should be performed
1142   * @return list of files or empty list
1143   * @exception IOException for invalid directory or for a bad disk.
1144   */
1145  public static File[] listFiles(File dir) throws IOException {
1146    File[] files = dir.listFiles();
1147    if(files == null) {
1148      throw new IOException("Invalid directory or I/O error occurred for dir: "
1149                + dir.toString());
1150    }
1151    return files;
1152  }
1153
1154  /**
1155   * A wrapper for {@link File#list()}. This java.io API returns null
1156   * when a dir is not a directory or for any I/O error. Instead of having
1157   * null check everywhere File#list() is used, we will add utility API
1158   * to get around this problem. For the majority of cases where we prefer
1159   * an IOException to be thrown.
1160   * @param dir directory for which listing should be performed
1161   * @return list of file names or empty string list
1162   * @exception IOException for invalid directory or for a bad disk.
1163   */
1164  public static String[] list(File dir) throws IOException {
1165    String[] fileNames = dir.list();
1166    if(fileNames == null) {
1167      throw new IOException("Invalid directory or I/O error occurred for dir: "
1168                + dir.toString());
1169    }
1170    return fileNames;
1171  }
1172
1173  public static String[] createJarWithClassPath(String inputClassPath, Path pwd,
1174      Map<String, String> callerEnv) throws IOException {
1175    return createJarWithClassPath(inputClassPath, pwd, pwd, callerEnv);
1176  }
1177
1178  /**
1179   * Create a jar file at the given path, containing a manifest with a classpath
1180   * that references all specified entries.
1181   *
1182   * Some platforms may have an upper limit on command line length.  For example,
1183   * the maximum command line length on Windows is 8191 characters, but the
1184   * length of the classpath may exceed this.  To work around this limitation,
1185   * use this method to create a small intermediate jar with a manifest that
1186   * contains the full classpath.  It returns the absolute path to the new jar,
1187   * which the caller may set as the classpath for a new process.
1188   *
1189   * Environment variable evaluation is not supported within a jar manifest, so
1190   * this method expands environment variables before inserting classpath entries
1191   * to the manifest.  The method parses environment variables according to
1192   * platform-specific syntax (%VAR% on Windows, or $VAR otherwise).  On Windows,
1193   * environment variables are case-insensitive.  For example, %VAR% and %var%
1194   * evaluate to the same value.
1195   *
1196   * Specifying the classpath in a jar manifest does not support wildcards, so
1197   * this method expands wildcards internally.  Any classpath entry that ends
1198   * with * is translated to all files at that path with extension .jar or .JAR.
1199   *
1200   * @param inputClassPath String input classpath to bundle into the jar manifest
1201   * @param pwd Path to working directory to save jar
1202   * @param targetDir path to where the jar execution will have its working dir
1203   * @param callerEnv Map<String, String> caller's environment variables to use
1204   *   for expansion
1205   * @return String[] with absolute path to new jar in position 0 and
1206   *   unexpanded wild card entry path in position 1
1207   * @throws IOException if there is an I/O error while writing the jar file
1208   */
1209  public static String[] createJarWithClassPath(String inputClassPath, Path pwd,
1210      Path targetDir,
1211      Map<String, String> callerEnv) throws IOException {
1212    // Replace environment variables, case-insensitive on Windows
1213    @SuppressWarnings("unchecked")
1214    Map<String, String> env = Shell.WINDOWS ? new CaseInsensitiveMap(callerEnv) :
1215      callerEnv;
1216    String[] classPathEntries = inputClassPath.split(File.pathSeparator);
1217    for (int i = 0; i < classPathEntries.length; ++i) {
1218      classPathEntries[i] = StringUtils.replaceTokens(classPathEntries[i],
1219        StringUtils.ENV_VAR_PATTERN, env);
1220    }
1221    File workingDir = new File(pwd.toString());
1222    if (!workingDir.mkdirs()) {
1223      // If mkdirs returns false because the working directory already exists,
1224      // then this is acceptable.  If it returns false due to some other I/O
1225      // error, then this method will fail later with an IOException while saving
1226      // the jar.
1227      LOG.debug("mkdirs false for " + workingDir + ", execution will continue");
1228    }
1229
1230    StringBuilder unexpandedWildcardClasspath = new StringBuilder();
1231    // Append all entries
1232    List<String> classPathEntryList = new ArrayList<String>(
1233      classPathEntries.length);
1234    for (String classPathEntry: classPathEntries) {
1235      if (classPathEntry.length() == 0) {
1236        continue;
1237      }
1238      if (classPathEntry.endsWith("*")) {
1239        boolean foundWildCardJar = false;
1240        // Append all jars that match the wildcard
1241        Path globPath = new Path(classPathEntry).suffix("{.jar,.JAR}");
1242        FileStatus[] wildcardJars = FileContext.getLocalFSFileContext().util()
1243          .globStatus(globPath);
1244        if (wildcardJars != null) {
1245          for (FileStatus wildcardJar: wildcardJars) {
1246            foundWildCardJar = true;
1247            classPathEntryList.add(wildcardJar.getPath().toUri().toURL()
1248              .toExternalForm());
1249          }
1250        }
1251        if (!foundWildCardJar) {
1252          unexpandedWildcardClasspath.append(File.pathSeparator);
1253          unexpandedWildcardClasspath.append(classPathEntry);
1254        }
1255      } else {
1256        // Append just this entry
1257        File fileCpEntry = null;
1258        if(!new Path(classPathEntry).isAbsolute()) {
1259          fileCpEntry = new File(targetDir.toString(), classPathEntry);
1260        }
1261        else {
1262          fileCpEntry = new File(classPathEntry);
1263        }
1264        String classPathEntryUrl = fileCpEntry.toURI().toURL()
1265          .toExternalForm();
1266
1267        // File.toURI only appends trailing '/' if it can determine that it is a
1268        // directory that already exists.  (See JavaDocs.)  If this entry had a
1269        // trailing '/' specified by the caller, then guarantee that the
1270        // classpath entry in the manifest has a trailing '/', and thus refers to
1271        // a directory instead of a file.  This can happen if the caller is
1272        // creating a classpath jar referencing a directory that hasn't been
1273        // created yet, but will definitely be created before running.
1274        if (classPathEntry.endsWith(Path.SEPARATOR) &&
1275            !classPathEntryUrl.endsWith(Path.SEPARATOR)) {
1276          classPathEntryUrl = classPathEntryUrl + Path.SEPARATOR;
1277        }
1278        classPathEntryList.add(classPathEntryUrl);
1279      }
1280    }
1281    String jarClassPath = StringUtils.join(" ", classPathEntryList);
1282
1283    // Create the manifest
1284    Manifest jarManifest = new Manifest();
1285    jarManifest.getMainAttributes().putValue(
1286        Attributes.Name.MANIFEST_VERSION.toString(), "1.0");
1287    jarManifest.getMainAttributes().putValue(
1288        Attributes.Name.CLASS_PATH.toString(), jarClassPath);
1289
1290    // Write the manifest to output JAR file
1291    File classPathJar = File.createTempFile("classpath-", ".jar", workingDir);
1292    FileOutputStream fos = null;
1293    BufferedOutputStream bos = null;
1294    JarOutputStream jos = null;
1295    try {
1296      fos = new FileOutputStream(classPathJar);
1297      bos = new BufferedOutputStream(fos);
1298      jos = new JarOutputStream(bos, jarManifest);
1299    } finally {
1300      IOUtils.cleanup(LOG, jos, bos, fos);
1301    }
1302    String[] jarCp = {classPathJar.getCanonicalPath(),
1303                        unexpandedWildcardClasspath.toString()};
1304    return jarCp;
1305  }
1306
1307  public static boolean compareFs(FileSystem srcFs, FileSystem destFs) {
1308    if (srcFs==null || destFs==null) {
1309      return false;
1310    }
1311    URI srcUri = srcFs.getUri();
1312    URI dstUri = destFs.getUri();
1313    if (srcUri.getScheme()==null) {
1314      return false;
1315    }
1316    if (!srcUri.getScheme().equals(dstUri.getScheme())) {
1317      return false;
1318    }
1319    String srcHost = srcUri.getHost();
1320    String dstHost = dstUri.getHost();
1321    if ((srcHost!=null) && (dstHost!=null)) {
1322      if (srcHost.equals(dstHost)) {
1323        return srcUri.getPort()==dstUri.getPort();
1324      }
1325      try {
1326        srcHost = InetAddress.getByName(srcHost).getCanonicalHostName();
1327        dstHost = InetAddress.getByName(dstHost).getCanonicalHostName();
1328      } catch (UnknownHostException ue) {
1329        if (LOG.isDebugEnabled()) {
1330          LOG.debug("Could not compare file-systems. Unknown host: ", ue);
1331        }
1332        return false;
1333      }
1334      if (!srcHost.equals(dstHost)) {
1335        return false;
1336      }
1337    } else if (srcHost==null && dstHost!=null) {
1338      return false;
1339    } else if (srcHost!=null) {
1340      return false;
1341    }
1342    // check for ports
1343    return srcUri.getPort()==dstUri.getPort();
1344  }
1345}