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        createOutputStream(f, false), bufferSize), statistics);
272  }
273  
274  protected OutputStream createOutputStream(Path f, boolean append) 
275      throws IOException {
276    return new LocalFSFileOutputStream(f, append); 
277  }
278  
279  @Override
280  @Deprecated
281  public FSDataOutputStream createNonRecursive(Path f, FsPermission permission,
282      EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize,
283      Progressable progress) throws IOException {
284    if (exists(f) && !flags.contains(CreateFlag.OVERWRITE)) {
285      throw new FileAlreadyExistsException("File already exists: " + f);
286    }
287    return new FSDataOutputStream(new BufferedOutputStream(
288        new LocalFSFileOutputStream(f, false), bufferSize), statistics);
289  }
290
291  @Override
292  public FSDataOutputStream create(Path f, FsPermission permission,
293    boolean overwrite, int bufferSize, short replication, long blockSize,
294    Progressable progress) throws IOException {
295
296    FSDataOutputStream out = create(f,
297        overwrite, bufferSize, replication, blockSize, progress);
298    setPermission(f, permission);
299    return out;
300  }
301
302  @Override
303  public FSDataOutputStream createNonRecursive(Path f, FsPermission permission,
304      boolean overwrite,
305      int bufferSize, short replication, long blockSize,
306      Progressable progress) throws IOException {
307    FSDataOutputStream out = create(f,
308        overwrite, false, bufferSize, replication, blockSize, progress);
309    setPermission(f, permission);
310    return out;
311  }
312
313  @Override
314  public boolean rename(Path src, Path dst) throws IOException {
315    // Attempt rename using Java API.
316    File srcFile = pathToFile(src);
317    File dstFile = pathToFile(dst);
318    if (srcFile.renameTo(dstFile)) {
319      return true;
320    }
321
322    // Enforce POSIX rename behavior that a source directory replaces an existing
323    // destination if the destination is an empty directory.  On most platforms,
324    // this is already handled by the Java API call above.  Some platforms
325    // (notably Windows) do not provide this behavior, so the Java API call above
326    // fails.  Delete destination and attempt rename again.
327    if (this.exists(dst)) {
328      FileStatus sdst = this.getFileStatus(dst);
329      if (sdst.isDirectory() && dstFile.list().length == 0) {
330        if (LOG.isDebugEnabled()) {
331          LOG.debug("Deleting empty destination and renaming " + src + " to " +
332            dst);
333        }
334        if (this.delete(dst, false) && srcFile.renameTo(dstFile)) {
335          return true;
336        }
337      }
338    }
339
340    // The fallback behavior accomplishes the rename by a full copy.
341    if (LOG.isDebugEnabled()) {
342      LOG.debug("Falling through to a copy of " + src + " to " + dst);
343    }
344    return FileUtil.copy(this, src, this, dst, true, getConf());
345  }
346  
347  /**
348   * Delete the given path to a file or directory.
349   * @param p the path to delete
350   * @param recursive to delete sub-directories
351   * @return true if the file or directory and all its contents were deleted
352   * @throws IOException if p is non-empty and recursive is false 
353   */
354  @Override
355  public boolean delete(Path p, boolean recursive) throws IOException {
356    File f = pathToFile(p);
357    if (!f.exists()) {
358      //no path, return false "nothing to delete"
359      return false;
360    }
361    if (f.isFile()) {
362      return f.delete();
363    } else if (!recursive && f.isDirectory() && 
364        (FileUtil.listFiles(f).length != 0)) {
365      throw new IOException("Directory " + f.toString() + " is not empty");
366    }
367    return FileUtil.fullyDelete(f);
368  }
369 
370  @Override
371  public FileStatus[] listStatus(Path f) throws IOException {
372    File localf = pathToFile(f);
373    FileStatus[] results;
374
375    if (!localf.exists()) {
376      throw new FileNotFoundException("File " + f + " does not exist");
377    }
378
379    if (localf.isDirectory()) {
380      String[] names = localf.list();
381      if (names == null) {
382        return null;
383      }
384      results = new FileStatus[names.length];
385      int j = 0;
386      for (int i = 0; i < names.length; i++) {
387        try {
388          // Assemble the path using the Path 3 arg constructor to make sure
389          // paths with colon are properly resolved on Linux
390          results[j] = getFileStatus(new Path(f, new Path(null, null,
391                                                          names[i])));
392          j++;
393        } catch (FileNotFoundException e) {
394          // ignore the files not found since the dir list may have have
395          // changed 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    if (!useDeprecatedFileStatus) {
405      return new FileStatus[] { getFileStatus(f) };
406    }
407    return new FileStatus[] {
408        new DeprecatedRawLocalFileStatus(localf,
409        getDefaultBlockSize(f), this) };
410  }
411  
412  protected boolean mkOneDir(File p2f) throws IOException {
413    return p2f.mkdir();
414  }
415
416  /**
417   * Creates the specified directory hierarchy. Does not
418   * treat existence as an error.
419   */
420  @Override
421  public boolean mkdirs(Path f) throws IOException {
422    if(f == null) {
423      throw new IllegalArgumentException("mkdirs path arg is null");
424    }
425    Path parent = f.getParent();
426    File p2f = pathToFile(f);
427    File parent2f = null;
428    if(parent != null) {
429      parent2f = pathToFile(parent);
430      if(parent2f != null && parent2f.exists() && !parent2f.isDirectory()) {
431        throw new ParentNotDirectoryException("Parent path is not a directory: "
432            + parent);
433      }
434    }
435    if (p2f.exists() && !p2f.isDirectory()) {
436      throw new FileNotFoundException("Destination exists" +
437              " and is not a directory: " + p2f.getCanonicalPath());
438    }
439    return (parent == null || parent2f.exists() || mkdirs(parent)) &&
440      (mkOneDir(p2f) || p2f.isDirectory());
441  }
442
443  @Override
444  public boolean mkdirs(Path f, FsPermission permission) throws IOException {
445    boolean b = mkdirs(f);
446    if(b) {
447      setPermission(f, permission);
448    }
449    return b;
450  }
451  
452
453  @Override
454  protected boolean primitiveMkdir(Path f, FsPermission absolutePermission)
455    throws IOException {
456    boolean b = mkdirs(f);
457    setPermission(f, absolutePermission);
458    return b;
459  }
460  
461  
462  @Override
463  public Path getHomeDirectory() {
464    return this.makeQualified(new Path(System.getProperty("user.home")));
465  }
466
467  /**
468   * Set the working directory to the given directory.
469   */
470  @Override
471  public void setWorkingDirectory(Path newDir) {
472    workingDir = makeAbsolute(newDir);
473    checkPath(workingDir);
474  }
475  
476  @Override
477  public Path getWorkingDirectory() {
478    return workingDir;
479  }
480  
481  @Override
482  protected Path getInitialWorkingDirectory() {
483    return this.makeQualified(new Path(System.getProperty("user.dir")));
484  }
485
486  @Override
487  public FsStatus getStatus(Path p) throws IOException {
488    File partition = pathToFile(p == null ? new Path("/") : p);
489    //File provides getUsableSpace() and getFreeSpace()
490    //File provides no API to obtain used space, assume used = total - free
491    return new FsStatus(partition.getTotalSpace(), 
492      partition.getTotalSpace() - partition.getFreeSpace(),
493      partition.getFreeSpace());
494  }
495  
496  // In the case of the local filesystem, we can just rename the file.
497  @Override
498  public void moveFromLocalFile(Path src, Path dst) throws IOException {
499    rename(src, dst);
500  }
501  
502  // We can write output directly to the final location
503  @Override
504  public Path startLocalOutput(Path fsOutputFile, Path tmpLocalFile)
505    throws IOException {
506    return fsOutputFile;
507  }
508  
509  // It's in the right place - nothing to do.
510  @Override
511  public void completeLocalOutput(Path fsWorkingFile, Path tmpLocalFile)
512    throws IOException {
513  }
514  
515  @Override
516  public void close() throws IOException {
517    super.close();
518  }
519  
520  @Override
521  public String toString() {
522    return "LocalFS";
523  }
524  
525  @Override
526  public FileStatus getFileStatus(Path f) throws IOException {
527    return getFileLinkStatusInternal(f, true);
528  }
529
530  @Deprecated
531  private FileStatus deprecatedGetFileStatus(Path f) throws IOException {
532    File path = pathToFile(f);
533    if (path.exists()) {
534      return new DeprecatedRawLocalFileStatus(pathToFile(f),
535          getDefaultBlockSize(f), this);
536    } else {
537      throw new FileNotFoundException("File " + f + " does not exist");
538    }
539  }
540
541  @Deprecated
542  static class DeprecatedRawLocalFileStatus extends FileStatus {
543    /* We can add extra fields here. It breaks at least CopyFiles.FilePair().
544     * We recognize if the information is already loaded by check if
545     * onwer.equals("").
546     */
547    private boolean isPermissionLoaded() {
548      return !super.getOwner().isEmpty(); 
549    }
550    
551    DeprecatedRawLocalFileStatus(File f, long defaultBlockSize, FileSystem fs) {
552      super(f.length(), f.isDirectory(), 1, defaultBlockSize,
553          f.lastModified(), new Path(f.getPath()).makeQualified(fs.getUri(),
554            fs.getWorkingDirectory()));
555    }
556    
557    @Override
558    public FsPermission getPermission() {
559      if (!isPermissionLoaded()) {
560        loadPermissionInfo();
561      }
562      return super.getPermission();
563    }
564
565    @Override
566    public String getOwner() {
567      if (!isPermissionLoaded()) {
568        loadPermissionInfo();
569      }
570      return super.getOwner();
571    }
572
573    @Override
574    public String getGroup() {
575      if (!isPermissionLoaded()) {
576        loadPermissionInfo();
577      }
578      return super.getGroup();
579    }
580
581    /// loads permissions, owner, and group from `ls -ld`
582    private void loadPermissionInfo() {
583      IOException e = null;
584      try {
585        String output = FileUtil.execCommand(new File(getPath().toUri()), 
586            Shell.getGetPermissionCommand());
587        StringTokenizer t =
588            new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX);
589        //expected format
590        //-rw-------    1 username groupname ...
591        String permission = t.nextToken();
592        if (permission.length() > FsPermission.MAX_PERMISSION_LENGTH) {
593          //files with ACLs might have a '+'
594          permission = permission.substring(0,
595            FsPermission.MAX_PERMISSION_LENGTH);
596        }
597        setPermission(FsPermission.valueOf(permission));
598        t.nextToken();
599
600        String owner = t.nextToken();
601        // If on windows domain, token format is DOMAIN\\user and we want to
602        // extract only the user name
603        if (Shell.WINDOWS) {
604          int i = owner.indexOf('\\');
605          if (i != -1)
606            owner = owner.substring(i + 1);
607        }
608        setOwner(owner);
609
610        setGroup(t.nextToken());
611      } catch (Shell.ExitCodeException ioe) {
612        if (ioe.getExitCode() != 1) {
613          e = ioe;
614        } else {
615          setPermission(null);
616          setOwner(null);
617          setGroup(null);
618        }
619      } catch (IOException ioe) {
620        e = ioe;
621      } finally {
622        if (e != null) {
623          throw new RuntimeException("Error while running command to get " +
624                                     "file permissions : " + 
625                                     StringUtils.stringifyException(e));
626        }
627      }
628    }
629
630    @Override
631    public void write(DataOutput out) throws IOException {
632      if (!isPermissionLoaded()) {
633        loadPermissionInfo();
634      }
635      super.write(out);
636    }
637  }
638
639  /**
640   * Use the command chown to set owner.
641   */
642  @Override
643  public void setOwner(Path p, String username, String groupname)
644    throws IOException {
645    FileUtil.setOwner(pathToFile(p), username, groupname);
646  }
647
648  /**
649   * Use the command chmod to set permission.
650   */
651  @Override
652  public void setPermission(Path p, FsPermission permission)
653    throws IOException {
654    if (NativeIO.isAvailable()) {
655      NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(),
656                     permission.toShort());
657    } else {
658      String perm = String.format("%04o", permission.toShort());
659      Shell.execCommand(Shell.getSetPermissionCommand(perm, false,
660        FileUtil.makeShellPath(pathToFile(p), true)));
661    }
662  }
663 
664  /**
665   * Sets the {@link Path}'s last modified time <em>only</em> to the given
666   * valid time.
667   *
668   * @param mtime the modification time to set (only if greater than zero).
669   * @param atime currently ignored.
670   * @throws IOException if setting the last modified time fails.
671   */
672  @Override
673  public void setTimes(Path p, long mtime, long atime) throws IOException {
674    File f = pathToFile(p);
675    if(mtime >= 0) {
676      if(!f.setLastModified(mtime)) {
677        throw new IOException(
678          "couldn't set last-modified time to " +
679          mtime +
680          " for " +
681          f.getAbsolutePath());
682      }
683    }
684  }
685
686  @Override
687  public boolean supportsSymlinks() {
688    return true;
689  }
690
691  @SuppressWarnings("deprecation")
692  @Override
693  public void createSymlink(Path target, Path link, boolean createParent)
694      throws IOException {
695    if (!FileSystem.areSymlinksEnabled()) {
696      throw new UnsupportedOperationException("Symlinks not supported");
697    }
698    final String targetScheme = target.toUri().getScheme();
699    if (targetScheme != null && !"file".equals(targetScheme)) {
700      throw new IOException("Unable to create symlink to non-local file "+
701                            "system: "+target.toString());
702    }
703    if (createParent) {
704      mkdirs(link.getParent());
705    }
706
707    // NB: Use createSymbolicLink in java.nio.file.Path once available
708    int result = FileUtil.symLink(target.toString(),
709        makeAbsolute(link).toString());
710    if (result != 0) {
711      throw new IOException("Error " + result + " creating symlink " +
712          link + " to " + target);
713    }
714  }
715
716  /**
717   * Return a FileStatus representing the given path. If the path refers
718   * to a symlink return a FileStatus representing the link rather than
719   * the object the link refers to.
720   */
721  @Override
722  public FileStatus getFileLinkStatus(final Path f) throws IOException {
723    FileStatus fi = getFileLinkStatusInternal(f, false);
724    // getFileLinkStatus is supposed to return a symlink with a
725    // qualified path
726    if (fi.isSymlink()) {
727      Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(),
728          fi.getPath(), fi.getSymlink());
729      fi.setSymlink(targetQual);
730    }
731    return fi;
732  }
733
734  /**
735   * Public {@link FileStatus} methods delegate to this function, which in turn
736   * either call the new {@link Stat} based implementation or the deprecated
737   * methods based on platform support.
738   * 
739   * @param f Path to stat
740   * @param dereference whether to dereference the final path component if a
741   *          symlink
742   * @return FileStatus of f
743   * @throws IOException
744   */
745  private FileStatus getFileLinkStatusInternal(final Path f,
746      boolean dereference) throws IOException {
747    if (!useDeprecatedFileStatus) {
748      return getNativeFileLinkStatus(f, dereference);
749    } else if (dereference) {
750      return deprecatedGetFileStatus(f);
751    } else {
752      return deprecatedGetFileLinkStatusInternal(f);
753    }
754  }
755
756  /**
757   * Deprecated. Remains for legacy support. Should be removed when {@link Stat}
758   * gains support for Windows and other operating systems.
759   */
760  @Deprecated
761  private FileStatus deprecatedGetFileLinkStatusInternal(final Path f)
762      throws IOException {
763    String target = FileUtil.readLink(new File(f.toString()));
764
765    try {
766      FileStatus fs = getFileStatus(f);
767      // If f refers to a regular file or directory
768      if (target.isEmpty()) {
769        return fs;
770      }
771      // Otherwise f refers to a symlink
772      return new FileStatus(fs.getLen(),
773          false,
774          fs.getReplication(),
775          fs.getBlockSize(),
776          fs.getModificationTime(),
777          fs.getAccessTime(),
778          fs.getPermission(),
779          fs.getOwner(),
780          fs.getGroup(),
781          new Path(target),
782          f);
783    } catch (FileNotFoundException e) {
784      /* The exists method in the File class returns false for dangling
785       * links so we can get a FileNotFoundException for links that exist.
786       * It's also possible that we raced with a delete of the link. Use
787       * the readBasicFileAttributes method in java.nio.file.attributes
788       * when available.
789       */
790      if (!target.isEmpty()) {
791        return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(),
792            "", "", new Path(target), f);
793      }
794      // f refers to a file or directory that does not exist
795      throw e;
796    }
797  }
798  /**
799   * Calls out to platform's native stat(1) implementation to get file metadata
800   * (permissions, user, group, atime, mtime, etc). This works around the lack
801   * of lstat(2) in Java 6.
802   * 
803   *  Currently, the {@link Stat} class used to do this only supports Linux
804   *  and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)}
805   *  implementation (deprecated) remains further OS support is added.
806   *
807   * @param f File to stat
808   * @param dereference whether to dereference symlinks
809   * @return FileStatus of f
810   * @throws IOException
811   */
812  private FileStatus getNativeFileLinkStatus(final Path f,
813      boolean dereference) throws IOException {
814    checkPath(f);
815    Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this);
816    FileStatus status = stat.getFileStatus();
817    return status;
818  }
819
820  @Override
821  public Path getLinkTarget(Path f) throws IOException {
822    FileStatus fi = getFileLinkStatusInternal(f, false);
823    // return an unqualified symlink target
824    return fi.getSymlink();
825  }
826}