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    
020    package org.apache.hadoop.fs;
021    
022    import com.google.common.annotations.VisibleForTesting;
023    
024    import java.io.BufferedOutputStream;
025    import java.io.DataOutput;
026    import java.io.EOFException;
027    import java.io.File;
028    import java.io.FileInputStream;
029    import java.io.FileNotFoundException;
030    import java.io.FileOutputStream;
031    import java.io.IOException;
032    import java.io.OutputStream;
033    import java.io.FileDescriptor;
034    import java.net.URI;
035    import java.nio.ByteBuffer;
036    import java.util.Arrays;
037    import java.util.EnumSet;
038    import java.util.StringTokenizer;
039    
040    import org.apache.hadoop.classification.InterfaceAudience;
041    import org.apache.hadoop.classification.InterfaceStability;
042    import org.apache.hadoop.conf.Configuration;
043    import org.apache.hadoop.fs.permission.FsPermission;
044    import org.apache.hadoop.io.nativeio.NativeIO;
045    import org.apache.hadoop.util.Progressable;
046    import org.apache.hadoop.util.Shell;
047    import 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
055    public 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        if (localf.isFile()) {
379          if (!useDeprecatedFileStatus) {
380            return new FileStatus[] { getFileStatus(f) };
381          }
382          return new FileStatus[] {
383            new DeprecatedRawLocalFileStatus(localf, getDefaultBlockSize(f), this)};
384        }
385    
386        String[] names = localf.list();
387        if (names == null) {
388          return null;
389        }
390        results = new FileStatus[names.length];
391        int j = 0;
392        for (int i = 0; i < names.length; i++) {
393          try {
394            // Assemble the path using the Path 3 arg constructor to make sure
395            // paths with colon are properly resolved on Linux
396            results[j] = getFileStatus(new Path(f, new Path(null, null, names[i])));
397            j++;
398          } catch (FileNotFoundException e) {
399            // ignore the files not found since the dir list may have have changed
400            // since the names[] list was generated.
401          }
402        }
403        if (j == names.length) {
404          return results;
405        }
406        return Arrays.copyOf(results, j);
407      }
408      
409      protected boolean mkOneDir(File p2f) throws IOException {
410        return p2f.mkdir();
411      }
412    
413      /**
414       * Creates the specified directory hierarchy. Does not
415       * treat existence as an error.
416       */
417      @Override
418      public boolean mkdirs(Path f) throws IOException {
419        if(f == null) {
420          throw new IllegalArgumentException("mkdirs path arg is null");
421        }
422        Path parent = f.getParent();
423        File p2f = pathToFile(f);
424        File parent2f = null;
425        if(parent != null) {
426          parent2f = pathToFile(parent);
427          if(parent2f != null && parent2f.exists() && !parent2f.isDirectory()) {
428            throw new ParentNotDirectoryException("Parent path is not a directory: "
429                + parent);
430          }
431        }
432        if (p2f.exists() && !p2f.isDirectory()) {
433          throw new FileNotFoundException("Destination exists" +
434                  " and is not a directory: " + p2f.getCanonicalPath());
435        }
436        return (parent == null || parent2f.exists() || mkdirs(parent)) &&
437          (mkOneDir(p2f) || p2f.isDirectory());
438      }
439    
440      @Override
441      public boolean mkdirs(Path f, FsPermission permission) throws IOException {
442        boolean b = mkdirs(f);
443        if(b) {
444          setPermission(f, permission);
445        }
446        return b;
447      }
448      
449    
450      @Override
451      protected boolean primitiveMkdir(Path f, FsPermission absolutePermission)
452        throws IOException {
453        boolean b = mkdirs(f);
454        setPermission(f, absolutePermission);
455        return b;
456      }
457      
458      
459      @Override
460      public Path getHomeDirectory() {
461        return this.makeQualified(new Path(System.getProperty("user.home")));
462      }
463    
464      /**
465       * Set the working directory to the given directory.
466       */
467      @Override
468      public void setWorkingDirectory(Path newDir) {
469        workingDir = makeAbsolute(newDir);
470        checkPath(workingDir);
471      }
472      
473      @Override
474      public Path getWorkingDirectory() {
475        return workingDir;
476      }
477      
478      @Override
479      protected Path getInitialWorkingDirectory() {
480        return this.makeQualified(new Path(System.getProperty("user.dir")));
481      }
482    
483      @Override
484      public FsStatus getStatus(Path p) throws IOException {
485        File partition = pathToFile(p == null ? new Path("/") : p);
486        //File provides getUsableSpace() and getFreeSpace()
487        //File provides no API to obtain used space, assume used = total - free
488        return new FsStatus(partition.getTotalSpace(), 
489          partition.getTotalSpace() - partition.getFreeSpace(),
490          partition.getFreeSpace());
491      }
492      
493      // In the case of the local filesystem, we can just rename the file.
494      @Override
495      public void moveFromLocalFile(Path src, Path dst) throws IOException {
496        rename(src, dst);
497      }
498      
499      // We can write output directly to the final location
500      @Override
501      public Path startLocalOutput(Path fsOutputFile, Path tmpLocalFile)
502        throws IOException {
503        return fsOutputFile;
504      }
505      
506      // It's in the right place - nothing to do.
507      @Override
508      public void completeLocalOutput(Path fsWorkingFile, Path tmpLocalFile)
509        throws IOException {
510      }
511      
512      @Override
513      public void close() throws IOException {
514        super.close();
515      }
516      
517      @Override
518      public String toString() {
519        return "LocalFS";
520      }
521      
522      @Override
523      public FileStatus getFileStatus(Path f) throws IOException {
524        return getFileLinkStatusInternal(f, true);
525      }
526    
527      @Deprecated
528      private FileStatus deprecatedGetFileStatus(Path f) throws IOException {
529        File path = pathToFile(f);
530        if (path.exists()) {
531          return new DeprecatedRawLocalFileStatus(pathToFile(f),
532              getDefaultBlockSize(f), this);
533        } else {
534          throw new FileNotFoundException("File " + f + " does not exist");
535        }
536      }
537    
538      @Deprecated
539      static class DeprecatedRawLocalFileStatus extends FileStatus {
540        /* We can add extra fields here. It breaks at least CopyFiles.FilePair().
541         * We recognize if the information is already loaded by check if
542         * onwer.equals("").
543         */
544        private boolean isPermissionLoaded() {
545          return !super.getOwner().isEmpty(); 
546        }
547        
548        DeprecatedRawLocalFileStatus(File f, long defaultBlockSize, FileSystem fs) {
549          super(f.length(), f.isDirectory(), 1, defaultBlockSize,
550              f.lastModified(), new Path(f.getPath()).makeQualified(fs.getUri(),
551                fs.getWorkingDirectory()));
552        }
553        
554        @Override
555        public FsPermission getPermission() {
556          if (!isPermissionLoaded()) {
557            loadPermissionInfo();
558          }
559          return super.getPermission();
560        }
561    
562        @Override
563        public String getOwner() {
564          if (!isPermissionLoaded()) {
565            loadPermissionInfo();
566          }
567          return super.getOwner();
568        }
569    
570        @Override
571        public String getGroup() {
572          if (!isPermissionLoaded()) {
573            loadPermissionInfo();
574          }
575          return super.getGroup();
576        }
577    
578        /// loads permissions, owner, and group from `ls -ld`
579        private void loadPermissionInfo() {
580          IOException e = null;
581          try {
582            String output = FileUtil.execCommand(new File(getPath().toUri()), 
583                Shell.getGetPermissionCommand());
584            StringTokenizer t =
585                new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX);
586            //expected format
587            //-rw-------    1 username groupname ...
588            String permission = t.nextToken();
589            if (permission.length() > FsPermission.MAX_PERMISSION_LENGTH) {
590              //files with ACLs might have a '+'
591              permission = permission.substring(0,
592                FsPermission.MAX_PERMISSION_LENGTH);
593            }
594            setPermission(FsPermission.valueOf(permission));
595            t.nextToken();
596    
597            String owner = t.nextToken();
598            // If on windows domain, token format is DOMAIN\\user and we want to
599            // extract only the user name
600            if (Shell.WINDOWS) {
601              int i = owner.indexOf('\\');
602              if (i != -1)
603                owner = owner.substring(i + 1);
604            }
605            setOwner(owner);
606    
607            setGroup(t.nextToken());
608          } catch (Shell.ExitCodeException ioe) {
609            if (ioe.getExitCode() != 1) {
610              e = ioe;
611            } else {
612              setPermission(null);
613              setOwner(null);
614              setGroup(null);
615            }
616          } catch (IOException ioe) {
617            e = ioe;
618          } finally {
619            if (e != null) {
620              throw new RuntimeException("Error while running command to get " +
621                                         "file permissions : " + 
622                                         StringUtils.stringifyException(e));
623            }
624          }
625        }
626    
627        @Override
628        public void write(DataOutput out) throws IOException {
629          if (!isPermissionLoaded()) {
630            loadPermissionInfo();
631          }
632          super.write(out);
633        }
634      }
635    
636      /**
637       * Use the command chown to set owner.
638       */
639      @Override
640      public void setOwner(Path p, String username, String groupname)
641        throws IOException {
642        FileUtil.setOwner(pathToFile(p), username, groupname);
643      }
644    
645      /**
646       * Use the command chmod to set permission.
647       */
648      @Override
649      public void setPermission(Path p, FsPermission permission)
650        throws IOException {
651        if (NativeIO.isAvailable()) {
652          NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(),
653                         permission.toShort());
654        } else {
655          String perm = String.format("%04o", permission.toShort());
656          Shell.execCommand(Shell.getSetPermissionCommand(perm, false,
657            FileUtil.makeShellPath(pathToFile(p), true)));
658        }
659      }
660     
661      /**
662       * Sets the {@link Path}'s last modified time <em>only</em> to the given
663       * valid time.
664       *
665       * @param mtime the modification time to set (only if greater than zero).
666       * @param atime currently ignored.
667       * @throws IOException if setting the last modified time fails.
668       */
669      @Override
670      public void setTimes(Path p, long mtime, long atime) throws IOException {
671        File f = pathToFile(p);
672        if(mtime >= 0) {
673          if(!f.setLastModified(mtime)) {
674            throw new IOException(
675              "couldn't set last-modified time to " +
676              mtime +
677              " for " +
678              f.getAbsolutePath());
679          }
680        }
681      }
682    
683      @Override
684      public boolean supportsSymlinks() {
685        return true;
686      }
687    
688      @SuppressWarnings("deprecation")
689      @Override
690      public void createSymlink(Path target, Path link, boolean createParent)
691          throws IOException {
692        if (!FileSystem.areSymlinksEnabled()) {
693          throw new UnsupportedOperationException("Symlinks not supported");
694        }
695        final String targetScheme = target.toUri().getScheme();
696        if (targetScheme != null && !"file".equals(targetScheme)) {
697          throw new IOException("Unable to create symlink to non-local file "+
698                                "system: "+target.toString());
699        }
700        if (createParent) {
701          mkdirs(link.getParent());
702        }
703    
704        // NB: Use createSymbolicLink in java.nio.file.Path once available
705        int result = FileUtil.symLink(target.toString(),
706            makeAbsolute(link).toString());
707        if (result != 0) {
708          throw new IOException("Error " + result + " creating symlink " +
709              link + " to " + target);
710        }
711      }
712    
713      /**
714       * Return a FileStatus representing the given path. If the path refers
715       * to a symlink return a FileStatus representing the link rather than
716       * the object the link refers to.
717       */
718      @Override
719      public FileStatus getFileLinkStatus(final Path f) throws IOException {
720        FileStatus fi = getFileLinkStatusInternal(f, false);
721        // getFileLinkStatus is supposed to return a symlink with a
722        // qualified path
723        if (fi.isSymlink()) {
724          Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(),
725              fi.getPath(), fi.getSymlink());
726          fi.setSymlink(targetQual);
727        }
728        return fi;
729      }
730    
731      /**
732       * Public {@link FileStatus} methods delegate to this function, which in turn
733       * either call the new {@link Stat} based implementation or the deprecated
734       * methods based on platform support.
735       * 
736       * @param f Path to stat
737       * @param dereference whether to dereference the final path component if a
738       *          symlink
739       * @return FileStatus of f
740       * @throws IOException
741       */
742      private FileStatus getFileLinkStatusInternal(final Path f,
743          boolean dereference) throws IOException {
744        if (!useDeprecatedFileStatus) {
745          return getNativeFileLinkStatus(f, dereference);
746        } else if (dereference) {
747          return deprecatedGetFileStatus(f);
748        } else {
749          return deprecatedGetFileLinkStatusInternal(f);
750        }
751      }
752    
753      /**
754       * Deprecated. Remains for legacy support. Should be removed when {@link Stat}
755       * gains support for Windows and other operating systems.
756       */
757      @Deprecated
758      private FileStatus deprecatedGetFileLinkStatusInternal(final Path f)
759          throws IOException {
760        String target = FileUtil.readLink(new File(f.toString()));
761    
762        try {
763          FileStatus fs = getFileStatus(f);
764          // If f refers to a regular file or directory
765          if (target.isEmpty()) {
766            return fs;
767          }
768          // Otherwise f refers to a symlink
769          return new FileStatus(fs.getLen(),
770              false,
771              fs.getReplication(),
772              fs.getBlockSize(),
773              fs.getModificationTime(),
774              fs.getAccessTime(),
775              fs.getPermission(),
776              fs.getOwner(),
777              fs.getGroup(),
778              new Path(target),
779              f);
780        } catch (FileNotFoundException e) {
781          /* The exists method in the File class returns false for dangling
782           * links so we can get a FileNotFoundException for links that exist.
783           * It's also possible that we raced with a delete of the link. Use
784           * the readBasicFileAttributes method in java.nio.file.attributes
785           * when available.
786           */
787          if (!target.isEmpty()) {
788            return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(),
789                "", "", new Path(target), f);
790          }
791          // f refers to a file or directory that does not exist
792          throw e;
793        }
794      }
795      /**
796       * Calls out to platform's native stat(1) implementation to get file metadata
797       * (permissions, user, group, atime, mtime, etc). This works around the lack
798       * of lstat(2) in Java 6.
799       * 
800       *  Currently, the {@link Stat} class used to do this only supports Linux
801       *  and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)}
802       *  implementation (deprecated) remains further OS support is added.
803       *
804       * @param f File to stat
805       * @param dereference whether to dereference symlinks
806       * @return FileStatus of f
807       * @throws IOException
808       */
809      private FileStatus getNativeFileLinkStatus(final Path f,
810          boolean dereference) throws IOException {
811        checkPath(f);
812        Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this);
813        FileStatus status = stat.getFileStatus();
814        return status;
815      }
816    
817      @Override
818      public Path getLinkTarget(Path f) throws IOException {
819        FileStatus fi = getFileLinkStatusInternal(f, false);
820        // return an unqualified symlink target
821        return fi.getSymlink();
822      }
823    }