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