001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019
020package org.apache.hadoop.fs;
021
022import com.google.common.annotations.VisibleForTesting;
023
024import java.io.BufferedOutputStream;
025import java.io.DataOutput;
026import java.io.EOFException;
027import java.io.File;
028import java.io.FileInputStream;
029import java.io.FileNotFoundException;
030import java.io.FileOutputStream;
031import java.io.IOException;
032import java.io.OutputStream;
033import java.io.FileDescriptor;
034import java.net.URI;
035import java.nio.ByteBuffer;
036import java.nio.file.AccessDeniedException;
037import java.util.Arrays;
038import java.util.EnumSet;
039import java.util.StringTokenizer;
040
041import org.apache.hadoop.classification.InterfaceAudience;
042import org.apache.hadoop.classification.InterfaceStability;
043import org.apache.hadoop.conf.Configuration;
044import org.apache.hadoop.fs.permission.FsPermission;
045import org.apache.hadoop.io.IOUtils;
046import org.apache.hadoop.io.nativeio.NativeIO;
047import org.apache.hadoop.util.Progressable;
048import org.apache.hadoop.util.Shell;
049import org.apache.hadoop.util.StringUtils;
050
051/****************************************************************
052 * Implement the FileSystem API for the raw local filesystem.
053 *
054 *****************************************************************/
055@InterfaceAudience.Public
056@InterfaceStability.Stable
057public class RawLocalFileSystem extends FileSystem {
058  static final URI NAME = URI.create("file:///");
059  private Path workingDir;
060  // Temporary workaround for HADOOP-9652.
061  private static boolean useDeprecatedFileStatus = true;
062
063  @VisibleForTesting
064  public static void useStatIfAvailable() {
065    useDeprecatedFileStatus = !Stat.isAvailable();
066  }
067  
068  public RawLocalFileSystem() {
069    workingDir = getInitialWorkingDirectory();
070  }
071  
072  private Path makeAbsolute(Path f) {
073    if (f.isAbsolute()) {
074      return f;
075    } else {
076      return new Path(workingDir, f);
077    }
078  }
079  
080  /** Convert a path to a File. */
081  public File pathToFile(Path path) {
082    checkPath(path);
083    if (!path.isAbsolute()) {
084      path = new Path(getWorkingDirectory(), path);
085    }
086    return new File(path.toUri().getPath());
087  }
088
089  @Override
090  public URI getUri() { return NAME; }
091  
092  @Override
093  public void initialize(URI uri, Configuration conf) throws IOException {
094    super.initialize(uri, conf);
095    setConf(conf);
096  }
097  
098  /*******************************************************
099   * For open()'s FSInputStream.
100   *******************************************************/
101  class LocalFSFileInputStream extends FSInputStream implements HasFileDescriptor {
102    private FileInputStream fis;
103    private long position;
104
105    public LocalFSFileInputStream(Path f) throws IOException {
106      fis = new FileInputStream(pathToFile(f));
107    }
108    
109    @Override
110    public void seek(long pos) throws IOException {
111      if (pos < 0) {
112        throw new EOFException(
113          FSExceptionMessages.NEGATIVE_SEEK);
114      }
115      fis.getChannel().position(pos);
116      this.position = pos;
117    }
118    
119    @Override
120    public long getPos() throws IOException {
121      return this.position;
122    }
123    
124    @Override
125    public boolean seekToNewSource(long targetPos) throws IOException {
126      return false;
127    }
128    
129    /*
130     * Just forward to the fis
131     */
132    @Override
133    public int available() throws IOException { return fis.available(); }
134    @Override
135    public void close() throws IOException { fis.close(); }
136    @Override
137    public boolean markSupported() { return false; }
138    
139    @Override
140    public int read() throws IOException {
141      try {
142        int value = fis.read();
143        if (value >= 0) {
144          this.position++;
145          statistics.incrementBytesRead(1);
146        }
147        return value;
148      } catch (IOException e) {                 // unexpected exception
149        throw new FSError(e);                   // assume native fs error
150      }
151    }
152    
153    @Override
154    public int read(byte[] b, int off, int len) throws IOException {
155      try {
156        int value = fis.read(b, off, len);
157        if (value > 0) {
158          this.position += value;
159          statistics.incrementBytesRead(value);
160        }
161        return value;
162      } catch (IOException e) {                 // unexpected exception
163        throw new FSError(e);                   // assume native fs error
164      }
165    }
166    
167    @Override
168    public int read(long position, byte[] b, int off, int len)
169      throws IOException {
170      ByteBuffer bb = ByteBuffer.wrap(b, off, len);
171      try {
172        int value = fis.getChannel().read(bb, position);
173        if (value > 0) {
174          statistics.incrementBytesRead(value);
175        }
176        return value;
177      } catch (IOException e) {
178        throw new FSError(e);
179      }
180    }
181    
182    @Override
183    public long skip(long n) throws IOException {
184      long value = fis.skip(n);
185      if (value > 0) {
186        this.position += value;
187      }
188      return value;
189    }
190
191    @Override
192    public FileDescriptor getFileDescriptor() throws IOException {
193      return fis.getFD();
194    }
195  }
196  
197  @Override
198  public FSDataInputStream open(Path f, int bufferSize) throws IOException {
199    if (!exists(f)) {
200      throw new FileNotFoundException(f.toString());
201    }
202    return new FSDataInputStream(new BufferedFSInputStream(
203        new LocalFSFileInputStream(f), bufferSize));
204  }
205  
206  /*********************************************************
207   * For create()'s FSOutputStream.
208   *********************************************************/
209  class LocalFSFileOutputStream extends OutputStream {
210    private FileOutputStream fos;
211    
212    private LocalFSFileOutputStream(Path f, boolean append,
213        FsPermission permission) throws IOException {
214      File file = pathToFile(f);
215      if (permission == null) {
216        this.fos = new FileOutputStream(file, append);
217      } else {
218        if (Shell.WINDOWS && NativeIO.isAvailable()) {
219          this.fos = NativeIO.Windows.createFileOutputStreamWithMode(file,
220              append, permission.toShort());
221        } else {
222          this.fos = new FileOutputStream(file, append);
223          boolean success = false;
224          try {
225            setPermission(f, permission);
226            success = true;
227          } finally {
228            if (!success) {
229              IOUtils.cleanup(LOG, this.fos);
230            }
231          }
232        }
233      }
234    }
235    
236    /*
237     * Just forward to the fos
238     */
239    @Override
240    public void close() throws IOException { fos.close(); }
241    @Override
242    public void flush() throws IOException { fos.flush(); }
243    @Override
244    public void write(byte[] b, int off, int len) throws IOException {
245      try {
246        fos.write(b, off, len);
247      } catch (IOException e) {                // unexpected exception
248        throw new FSError(e);                  // assume native fs error
249      }
250    }
251    
252    @Override
253    public void write(int b) throws IOException {
254      try {
255        fos.write(b);
256      } catch (IOException e) {              // unexpected exception
257        throw new FSError(e);                // assume native fs error
258      }
259    }
260  }
261
262  @Override
263  public FSDataOutputStream append(Path f, int bufferSize,
264      Progressable progress) throws IOException {
265    if (!exists(f)) {
266      throw new FileNotFoundException("File " + f + " not found");
267    }
268    FileStatus status = getFileStatus(f);
269    if (status.isDirectory()) {
270      throw new IOException("Cannot append to a diretory (=" + f + " )");
271    }
272    return new FSDataOutputStream(new BufferedOutputStream(
273        createOutputStreamWithMode(f, true, null), bufferSize), statistics,
274        status.getLen());
275  }
276
277  @Override
278  public FSDataOutputStream create(Path f, boolean overwrite, int bufferSize,
279    short replication, long blockSize, Progressable progress)
280    throws IOException {
281    return create(f, overwrite, true, bufferSize, replication, blockSize,
282        progress, null);
283  }
284
285  private FSDataOutputStream create(Path f, boolean overwrite,
286      boolean createParent, int bufferSize, short replication, long blockSize,
287      Progressable progress, FsPermission permission) throws IOException {
288    if (exists(f) && !overwrite) {
289      throw new FileAlreadyExistsException("File already exists: " + f);
290    }
291    Path parent = f.getParent();
292    if (parent != null && !mkdirs(parent)) {
293      throw new IOException("Mkdirs failed to create " + parent.toString());
294    }
295    return new FSDataOutputStream(new BufferedOutputStream(
296        createOutputStreamWithMode(f, false, permission), bufferSize),
297        statistics);
298  }
299  
300  protected OutputStream createOutputStream(Path f, boolean append) 
301      throws IOException {
302    return createOutputStreamWithMode(f, append, null);
303  }
304
305  protected OutputStream createOutputStreamWithMode(Path f, boolean append,
306      FsPermission permission) throws IOException {
307    return new LocalFSFileOutputStream(f, append, permission);
308  }
309  
310  @Override
311  @Deprecated
312  public FSDataOutputStream createNonRecursive(Path f, FsPermission permission,
313      EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize,
314      Progressable progress) throws IOException {
315    if (exists(f) && !flags.contains(CreateFlag.OVERWRITE)) {
316      throw new FileAlreadyExistsException("File already exists: " + f);
317    }
318    return new FSDataOutputStream(new BufferedOutputStream(
319        createOutputStreamWithMode(f, false, permission), bufferSize),
320            statistics);
321  }
322
323  @Override
324  public FSDataOutputStream create(Path f, FsPermission permission,
325    boolean overwrite, int bufferSize, short replication, long blockSize,
326    Progressable progress) throws IOException {
327
328    FSDataOutputStream out = create(f, overwrite, true, bufferSize, replication,
329        blockSize, progress, permission);
330    return out;
331  }
332
333  @Override
334  public FSDataOutputStream createNonRecursive(Path f, FsPermission permission,
335      boolean overwrite,
336      int bufferSize, short replication, long blockSize,
337      Progressable progress) throws IOException {
338    FSDataOutputStream out = create(f, overwrite, false, bufferSize, replication,
339        blockSize, progress, permission);
340    return out;
341  }
342
343  @Override
344  public boolean rename(Path src, Path dst) throws IOException {
345    // Attempt rename using Java API.
346    File srcFile = pathToFile(src);
347    File dstFile = pathToFile(dst);
348    if (srcFile.renameTo(dstFile)) {
349      return true;
350    }
351
352    // Enforce POSIX rename behavior that a source directory replaces an existing
353    // destination if the destination is an empty directory.  On most platforms,
354    // this is already handled by the Java API call above.  Some platforms
355    // (notably Windows) do not provide this behavior, so the Java API call above
356    // fails.  Delete destination and attempt rename again.
357    if (this.exists(dst)) {
358      FileStatus sdst = this.getFileStatus(dst);
359      if (sdst.isDirectory() && dstFile.list().length == 0) {
360        if (LOG.isDebugEnabled()) {
361          LOG.debug("Deleting empty destination and renaming " + src + " to " +
362            dst);
363        }
364        if (this.delete(dst, false) && srcFile.renameTo(dstFile)) {
365          return true;
366        }
367      }
368    }
369
370    // The fallback behavior accomplishes the rename by a full copy.
371    if (LOG.isDebugEnabled()) {
372      LOG.debug("Falling through to a copy of " + src + " to " + dst);
373    }
374    return FileUtil.copy(this, src, this, dst, true, getConf());
375  }
376  
377  /**
378   * Delete the given path to a file or directory.
379   * @param p the path to delete
380   * @param recursive to delete sub-directories
381   * @return true if the file or directory and all its contents were deleted
382   * @throws IOException if p is non-empty and recursive is false 
383   */
384  @Override
385  public boolean delete(Path p, boolean recursive) throws IOException {
386    File f = pathToFile(p);
387    if (!f.exists()) {
388      //no path, return false "nothing to delete"
389      return false;
390    }
391    if (f.isFile()) {
392      return f.delete();
393    } else if (!recursive && f.isDirectory() && 
394        (FileUtil.listFiles(f).length != 0)) {
395      throw new IOException("Directory " + f.toString() + " is not empty");
396    }
397    return FileUtil.fullyDelete(f);
398  }
399 
400  @Override
401  public FileStatus[] listStatus(Path f) throws IOException {
402    File localf = pathToFile(f);
403    FileStatus[] results;
404
405    if (!localf.exists()) {
406      throw new FileNotFoundException("File " + f + " does not exist");
407    }
408
409    if (localf.isDirectory()) {
410      String[] names = localf.list();
411      if (names == null) {
412        if (!localf.canRead()) {
413          throw new AccessDeniedException("cannot open directory " + f +
414              ": Permission denied");
415        }
416        return null;
417      }
418      results = new FileStatus[names.length];
419      int j = 0;
420      for (int i = 0; i < names.length; i++) {
421        try {
422          // Assemble the path using the Path 3 arg constructor to make sure
423          // paths with colon are properly resolved on Linux
424          results[j] = getFileStatus(new Path(f, new Path(null, null,
425                                                          names[i])));
426          j++;
427        } catch (FileNotFoundException e) {
428          // ignore the files not found since the dir list may have have
429          // changed since the names[] list was generated.
430        }
431      }
432      if (j == names.length) {
433        return results;
434      }
435      return Arrays.copyOf(results, j);
436    }
437
438    if (!useDeprecatedFileStatus) {
439      return new FileStatus[] { getFileStatus(f) };
440    }
441    return new FileStatus[] {
442        new DeprecatedRawLocalFileStatus(localf,
443        getDefaultBlockSize(f), this) };
444  }
445  
446  protected boolean mkOneDir(File p2f) throws IOException {
447    return mkOneDirWithMode(new Path(p2f.getAbsolutePath()), p2f, null);
448  }
449
450  protected boolean mkOneDirWithMode(Path p, File p2f, FsPermission permission)
451      throws IOException {
452    if (permission == null) {
453      return p2f.mkdir();
454    } else {
455      if (Shell.WINDOWS && NativeIO.isAvailable()) {
456        try {
457          NativeIO.Windows.createDirectoryWithMode(p2f, permission.toShort());
458          return true;
459        } catch (IOException e) {
460          if (LOG.isDebugEnabled()) {
461            LOG.debug(String.format(
462                "NativeIO.createDirectoryWithMode error, path = %s, mode = %o",
463                p2f, permission.toShort()), e);
464          }
465          return false;
466        }
467      } else {
468        boolean b = p2f.mkdir();
469        if (b) {
470          setPermission(p, permission);
471        }
472        return b;
473      }
474    }
475  }
476
477  /**
478   * Creates the specified directory hierarchy. Does not
479   * treat existence as an error.
480   */
481  @Override
482  public boolean mkdirs(Path f) throws IOException {
483    return mkdirsWithOptionalPermission(f, null);
484  }
485
486  @Override
487  public boolean mkdirs(Path f, FsPermission permission) throws IOException {
488    return mkdirsWithOptionalPermission(f, permission);
489  }
490
491  private boolean mkdirsWithOptionalPermission(Path f, FsPermission permission)
492      throws IOException {
493    if(f == null) {
494      throw new IllegalArgumentException("mkdirs path arg is null");
495    }
496    Path parent = f.getParent();
497    File p2f = pathToFile(f);
498    File parent2f = null;
499    if(parent != null) {
500      parent2f = pathToFile(parent);
501      if(parent2f != null && parent2f.exists() && !parent2f.isDirectory()) {
502        throw new ParentNotDirectoryException("Parent path is not a directory: "
503            + parent);
504      }
505    }
506    if (p2f.exists() && !p2f.isDirectory()) {
507      throw new FileNotFoundException("Destination exists" +
508              " and is not a directory: " + p2f.getCanonicalPath());
509    }
510    return (parent == null || parent2f.exists() || mkdirs(parent)) &&
511      (mkOneDirWithMode(f, p2f, permission) || p2f.isDirectory());
512  }
513  
514  
515  @Override
516  public Path getHomeDirectory() {
517    return this.makeQualified(new Path(System.getProperty("user.home")));
518  }
519
520  /**
521   * Set the working directory to the given directory.
522   */
523  @Override
524  public void setWorkingDirectory(Path newDir) {
525    workingDir = makeAbsolute(newDir);
526    checkPath(workingDir);
527  }
528  
529  @Override
530  public Path getWorkingDirectory() {
531    return workingDir;
532  }
533  
534  @Override
535  protected Path getInitialWorkingDirectory() {
536    return this.makeQualified(new Path(System.getProperty("user.dir")));
537  }
538
539  @Override
540  public FsStatus getStatus(Path p) throws IOException {
541    File partition = pathToFile(p == null ? new Path("/") : p);
542    //File provides getUsableSpace() and getFreeSpace()
543    //File provides no API to obtain used space, assume used = total - free
544    return new FsStatus(partition.getTotalSpace(), 
545      partition.getTotalSpace() - partition.getFreeSpace(),
546      partition.getFreeSpace());
547  }
548  
549  // In the case of the local filesystem, we can just rename the file.
550  @Override
551  public void moveFromLocalFile(Path src, Path dst) throws IOException {
552    rename(src, dst);
553  }
554  
555  // We can write output directly to the final location
556  @Override
557  public Path startLocalOutput(Path fsOutputFile, Path tmpLocalFile)
558    throws IOException {
559    return fsOutputFile;
560  }
561  
562  // It's in the right place - nothing to do.
563  @Override
564  public void completeLocalOutput(Path fsWorkingFile, Path tmpLocalFile)
565    throws IOException {
566  }
567  
568  @Override
569  public void close() throws IOException {
570    super.close();
571  }
572  
573  @Override
574  public String toString() {
575    return "LocalFS";
576  }
577  
578  @Override
579  public FileStatus getFileStatus(Path f) throws IOException {
580    return getFileLinkStatusInternal(f, true);
581  }
582
583  @Deprecated
584  private FileStatus deprecatedGetFileStatus(Path f) throws IOException {
585    File path = pathToFile(f);
586    if (path.exists()) {
587      return new DeprecatedRawLocalFileStatus(pathToFile(f),
588          getDefaultBlockSize(f), this);
589    } else {
590      throw new FileNotFoundException("File " + f + " does not exist");
591    }
592  }
593
594  @Deprecated
595  static class DeprecatedRawLocalFileStatus extends FileStatus {
596    /* We can add extra fields here. It breaks at least CopyFiles.FilePair().
597     * We recognize if the information is already loaded by check if
598     * onwer.equals("").
599     */
600    private boolean isPermissionLoaded() {
601      return !super.getOwner().isEmpty(); 
602    }
603    
604    DeprecatedRawLocalFileStatus(File f, long defaultBlockSize, FileSystem fs) {
605      super(f.length(), f.isDirectory(), 1, defaultBlockSize,
606          f.lastModified(), new Path(f.getPath()).makeQualified(fs.getUri(),
607            fs.getWorkingDirectory()));
608    }
609    
610    @Override
611    public FsPermission getPermission() {
612      if (!isPermissionLoaded()) {
613        loadPermissionInfo();
614      }
615      return super.getPermission();
616    }
617
618    @Override
619    public String getOwner() {
620      if (!isPermissionLoaded()) {
621        loadPermissionInfo();
622      }
623      return super.getOwner();
624    }
625
626    @Override
627    public String getGroup() {
628      if (!isPermissionLoaded()) {
629        loadPermissionInfo();
630      }
631      return super.getGroup();
632    }
633
634    /// loads permissions, owner, and group from `ls -ld`
635    private void loadPermissionInfo() {
636      IOException e = null;
637      try {
638        String output = FileUtil.execCommand(new File(getPath().toUri()), 
639            Shell.getGetPermissionCommand());
640        StringTokenizer t =
641            new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX);
642        //expected format
643        //-rw-------    1 username groupname ...
644        String permission = t.nextToken();
645        if (permission.length() > FsPermission.MAX_PERMISSION_LENGTH) {
646          //files with ACLs might have a '+'
647          permission = permission.substring(0,
648            FsPermission.MAX_PERMISSION_LENGTH);
649        }
650        setPermission(FsPermission.valueOf(permission));
651        t.nextToken();
652
653        String owner = t.nextToken();
654        // If on windows domain, token format is DOMAIN\\user and we want to
655        // extract only the user name
656        if (Shell.WINDOWS) {
657          int i = owner.indexOf('\\');
658          if (i != -1)
659            owner = owner.substring(i + 1);
660        }
661        setOwner(owner);
662
663        setGroup(t.nextToken());
664      } catch (Shell.ExitCodeException ioe) {
665        if (ioe.getExitCode() != 1) {
666          e = ioe;
667        } else {
668          setPermission(null);
669          setOwner(null);
670          setGroup(null);
671        }
672      } catch (IOException ioe) {
673        e = ioe;
674      } finally {
675        if (e != null) {
676          throw new RuntimeException("Error while running command to get " +
677                                     "file permissions : " + 
678                                     StringUtils.stringifyException(e));
679        }
680      }
681    }
682
683    @Override
684    public void write(DataOutput out) throws IOException {
685      if (!isPermissionLoaded()) {
686        loadPermissionInfo();
687      }
688      super.write(out);
689    }
690  }
691
692  /**
693   * Use the command chown to set owner.
694   */
695  @Override
696  public void setOwner(Path p, String username, String groupname)
697    throws IOException {
698    FileUtil.setOwner(pathToFile(p), username, groupname);
699  }
700
701  /**
702   * Use the command chmod to set permission.
703   */
704  @Override
705  public void setPermission(Path p, FsPermission permission)
706    throws IOException {
707    if (NativeIO.isAvailable()) {
708      NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(),
709                     permission.toShort());
710    } else {
711      String perm = String.format("%04o", permission.toShort());
712      Shell.execCommand(Shell.getSetPermissionCommand(perm, false,
713        FileUtil.makeShellPath(pathToFile(p), true)));
714    }
715  }
716 
717  /**
718   * Sets the {@link Path}'s last modified time <em>only</em> to the given
719   * valid time.
720   *
721   * @param mtime the modification time to set (only if greater than zero).
722   * @param atime currently ignored.
723   * @throws IOException if setting the last modified time fails.
724   */
725  @Override
726  public void setTimes(Path p, long mtime, long atime) throws IOException {
727    File f = pathToFile(p);
728    if(mtime >= 0) {
729      if(!f.setLastModified(mtime)) {
730        throw new IOException(
731          "couldn't set last-modified time to " +
732          mtime +
733          " for " +
734          f.getAbsolutePath());
735      }
736    }
737  }
738
739  @Override
740  public boolean supportsSymlinks() {
741    return true;
742  }
743
744  @SuppressWarnings("deprecation")
745  @Override
746  public void createSymlink(Path target, Path link, boolean createParent)
747      throws IOException {
748    if (!FileSystem.areSymlinksEnabled()) {
749      throw new UnsupportedOperationException("Symlinks not supported");
750    }
751    final String targetScheme = target.toUri().getScheme();
752    if (targetScheme != null && !"file".equals(targetScheme)) {
753      throw new IOException("Unable to create symlink to non-local file "+
754                            "system: "+target.toString());
755    }
756    if (createParent) {
757      mkdirs(link.getParent());
758    }
759
760    // NB: Use createSymbolicLink in java.nio.file.Path once available
761    int result = FileUtil.symLink(target.toString(),
762        makeAbsolute(link).toString());
763    if (result != 0) {
764      throw new IOException("Error " + result + " creating symlink " +
765          link + " to " + target);
766    }
767  }
768
769  /**
770   * Return a FileStatus representing the given path. If the path refers
771   * to a symlink return a FileStatus representing the link rather than
772   * the object the link refers to.
773   */
774  @Override
775  public FileStatus getFileLinkStatus(final Path f) throws IOException {
776    FileStatus fi = getFileLinkStatusInternal(f, false);
777    // getFileLinkStatus is supposed to return a symlink with a
778    // qualified path
779    if (fi.isSymlink()) {
780      Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(),
781          fi.getPath(), fi.getSymlink());
782      fi.setSymlink(targetQual);
783    }
784    return fi;
785  }
786
787  /**
788   * Public {@link FileStatus} methods delegate to this function, which in turn
789   * either call the new {@link Stat} based implementation or the deprecated
790   * methods based on platform support.
791   * 
792   * @param f Path to stat
793   * @param dereference whether to dereference the final path component if a
794   *          symlink
795   * @return FileStatus of f
796   * @throws IOException
797   */
798  private FileStatus getFileLinkStatusInternal(final Path f,
799      boolean dereference) throws IOException {
800    if (!useDeprecatedFileStatus) {
801      return getNativeFileLinkStatus(f, dereference);
802    } else if (dereference) {
803      return deprecatedGetFileStatus(f);
804    } else {
805      return deprecatedGetFileLinkStatusInternal(f);
806    }
807  }
808
809  /**
810   * Deprecated. Remains for legacy support. Should be removed when {@link Stat}
811   * gains support for Windows and other operating systems.
812   */
813  @Deprecated
814  private FileStatus deprecatedGetFileLinkStatusInternal(final Path f)
815      throws IOException {
816    String target = FileUtil.readLink(new File(f.toString()));
817
818    try {
819      FileStatus fs = getFileStatus(f);
820      // If f refers to a regular file or directory
821      if (target.isEmpty()) {
822        return fs;
823      }
824      // Otherwise f refers to a symlink
825      return new FileStatus(fs.getLen(),
826          false,
827          fs.getReplication(),
828          fs.getBlockSize(),
829          fs.getModificationTime(),
830          fs.getAccessTime(),
831          fs.getPermission(),
832          fs.getOwner(),
833          fs.getGroup(),
834          new Path(target),
835          f);
836    } catch (FileNotFoundException e) {
837      /* The exists method in the File class returns false for dangling
838       * links so we can get a FileNotFoundException for links that exist.
839       * It's also possible that we raced with a delete of the link. Use
840       * the readBasicFileAttributes method in java.nio.file.attributes
841       * when available.
842       */
843      if (!target.isEmpty()) {
844        return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(),
845            "", "", new Path(target), f);
846      }
847      // f refers to a file or directory that does not exist
848      throw e;
849    }
850  }
851  /**
852   * Calls out to platform's native stat(1) implementation to get file metadata
853   * (permissions, user, group, atime, mtime, etc). This works around the lack
854   * of lstat(2) in Java 6.
855   * 
856   *  Currently, the {@link Stat} class used to do this only supports Linux
857   *  and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)}
858   *  implementation (deprecated) remains further OS support is added.
859   *
860   * @param f File to stat
861   * @param dereference whether to dereference symlinks
862   * @return FileStatus of f
863   * @throws IOException
864   */
865  private FileStatus getNativeFileLinkStatus(final Path f,
866      boolean dereference) throws IOException {
867    checkPath(f);
868    Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this);
869    FileStatus status = stat.getFileStatus();
870    return status;
871  }
872
873  @Override
874  public Path getLinkTarget(Path f) throws IOException {
875    FileStatus fi = getFileLinkStatusInternal(f, false);
876    // return an unqualified symlink target
877    return fi.getSymlink();
878  }
879}