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