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