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