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.File;
027 import java.io.FileInputStream;
028 import java.io.FileNotFoundException;
029 import java.io.FileOutputStream;
030 import java.io.IOException;
031 import java.io.OutputStream;
032 import java.io.FileDescriptor;
033 import java.net.URI;
034 import java.nio.ByteBuffer;
035 import java.util.Arrays;
036 import java.util.EnumSet;
037 import java.util.StringTokenizer;
038
039 import org.apache.hadoop.classification.InterfaceAudience;
040 import org.apache.hadoop.classification.InterfaceStability;
041 import org.apache.hadoop.conf.Configuration;
042 import org.apache.hadoop.fs.permission.FsPermission;
043 import org.apache.hadoop.io.nativeio.NativeIO;
044 import org.apache.hadoop.util.Progressable;
045 import org.apache.hadoop.util.Shell;
046 import 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
054 public 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 return null;
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 IOException e = null;
558 try {
559 String output = FileUtil.execCommand(new File(getPath().toUri()),
560 Shell.getGetPermissionCommand());
561 StringTokenizer t =
562 new StringTokenizer(output, Shell.TOKEN_SEPARATOR_REGEX);
563 //expected format
564 //-rw------- 1 username groupname ...
565 String permission = t.nextToken();
566 if (permission.length() > 10) { //files with ACLs might have a '+'
567 permission = permission.substring(0, 10);
568 }
569 setPermission(FsPermission.valueOf(permission));
570 t.nextToken();
571
572 String owner = t.nextToken();
573 // If on windows domain, token format is DOMAIN\\user and we want to
574 // extract only the user name
575 if (Shell.WINDOWS) {
576 int i = owner.indexOf('\\');
577 if (i != -1)
578 owner = owner.substring(i + 1);
579 }
580 setOwner(owner);
581
582 setGroup(t.nextToken());
583 } catch (Shell.ExitCodeException ioe) {
584 if (ioe.getExitCode() != 1) {
585 e = ioe;
586 } else {
587 setPermission(null);
588 setOwner(null);
589 setGroup(null);
590 }
591 } catch (IOException ioe) {
592 e = ioe;
593 } finally {
594 if (e != null) {
595 throw new RuntimeException("Error while running command to get " +
596 "file permissions : " +
597 StringUtils.stringifyException(e));
598 }
599 }
600 }
601
602 @Override
603 public void write(DataOutput out) throws IOException {
604 if (!isPermissionLoaded()) {
605 loadPermissionInfo();
606 }
607 super.write(out);
608 }
609 }
610
611 /**
612 * Use the command chown to set owner.
613 */
614 @Override
615 public void setOwner(Path p, String username, String groupname)
616 throws IOException {
617 FileUtil.setOwner(pathToFile(p), username, groupname);
618 }
619
620 /**
621 * Use the command chmod to set permission.
622 */
623 @Override
624 public void setPermission(Path p, FsPermission permission)
625 throws IOException {
626 if (NativeIO.isAvailable()) {
627 NativeIO.POSIX.chmod(pathToFile(p).getCanonicalPath(),
628 permission.toShort());
629 } else {
630 String perm = String.format("%04o", permission.toShort());
631 Shell.execCommand(Shell.getSetPermissionCommand(perm, false,
632 FileUtil.makeShellPath(pathToFile(p), true)));
633 }
634 }
635
636 /**
637 * Sets the {@link Path}'s last modified time <em>only</em> to the given
638 * valid time.
639 *
640 * @param mtime the modification time to set (only if greater than zero).
641 * @param atime currently ignored.
642 * @throws IOException if setting the last modified time fails.
643 */
644 @Override
645 public void setTimes(Path p, long mtime, long atime) throws IOException {
646 File f = pathToFile(p);
647 if(mtime >= 0) {
648 if(!f.setLastModified(mtime)) {
649 throw new IOException(
650 "couldn't set last-modified time to " +
651 mtime +
652 " for " +
653 f.getAbsolutePath());
654 }
655 }
656 }
657
658 @Override
659 public boolean supportsSymlinks() {
660 return true;
661 }
662
663 @SuppressWarnings("deprecation")
664 @Override
665 public void createSymlink(Path target, Path link, boolean createParent)
666 throws IOException {
667 if (!FileSystem.areSymlinksEnabled()) {
668 throw new UnsupportedOperationException("Symlinks not supported");
669 }
670 final String targetScheme = target.toUri().getScheme();
671 if (targetScheme != null && !"file".equals(targetScheme)) {
672 throw new IOException("Unable to create symlink to non-local file "+
673 "system: "+target.toString());
674 }
675 if (createParent) {
676 mkdirs(link.getParent());
677 }
678
679 // NB: Use createSymbolicLink in java.nio.file.Path once available
680 int result = FileUtil.symLink(target.toString(),
681 makeAbsolute(link).toString());
682 if (result != 0) {
683 throw new IOException("Error " + result + " creating symlink " +
684 link + " to " + target);
685 }
686 }
687
688 /**
689 * Return a FileStatus representing the given path. If the path refers
690 * to a symlink return a FileStatus representing the link rather than
691 * the object the link refers to.
692 */
693 @Override
694 public FileStatus getFileLinkStatus(final Path f) throws IOException {
695 FileStatus fi = getFileLinkStatusInternal(f, false);
696 // getFileLinkStatus is supposed to return a symlink with a
697 // qualified path
698 if (fi.isSymlink()) {
699 Path targetQual = FSLinkResolver.qualifySymlinkTarget(this.getUri(),
700 fi.getPath(), fi.getSymlink());
701 fi.setSymlink(targetQual);
702 }
703 return fi;
704 }
705
706 /**
707 * Public {@link FileStatus} methods delegate to this function, which in turn
708 * either call the new {@link Stat} based implementation or the deprecated
709 * methods based on platform support.
710 *
711 * @param f Path to stat
712 * @param dereference whether to dereference the final path component if a
713 * symlink
714 * @return FileStatus of f
715 * @throws IOException
716 */
717 private FileStatus getFileLinkStatusInternal(final Path f,
718 boolean dereference) throws IOException {
719 if (!useDeprecatedFileStatus) {
720 return getNativeFileLinkStatus(f, dereference);
721 } else if (dereference) {
722 return deprecatedGetFileStatus(f);
723 } else {
724 return deprecatedGetFileLinkStatusInternal(f);
725 }
726 }
727
728 /**
729 * Deprecated. Remains for legacy support. Should be removed when {@link Stat}
730 * gains support for Windows and other operating systems.
731 */
732 @Deprecated
733 private FileStatus deprecatedGetFileLinkStatusInternal(final Path f)
734 throws IOException {
735 String target = FileUtil.readLink(new File(f.toString()));
736
737 try {
738 FileStatus fs = getFileStatus(f);
739 // If f refers to a regular file or directory
740 if (target.isEmpty()) {
741 return fs;
742 }
743 // Otherwise f refers to a symlink
744 return new FileStatus(fs.getLen(),
745 false,
746 fs.getReplication(),
747 fs.getBlockSize(),
748 fs.getModificationTime(),
749 fs.getAccessTime(),
750 fs.getPermission(),
751 fs.getOwner(),
752 fs.getGroup(),
753 new Path(target),
754 f);
755 } catch (FileNotFoundException e) {
756 /* The exists method in the File class returns false for dangling
757 * links so we can get a FileNotFoundException for links that exist.
758 * It's also possible that we raced with a delete of the link. Use
759 * the readBasicFileAttributes method in java.nio.file.attributes
760 * when available.
761 */
762 if (!target.isEmpty()) {
763 return new FileStatus(0, false, 0, 0, 0, 0, FsPermission.getDefault(),
764 "", "", new Path(target), f);
765 }
766 // f refers to a file or directory that does not exist
767 throw e;
768 }
769 }
770 /**
771 * Calls out to platform's native stat(1) implementation to get file metadata
772 * (permissions, user, group, atime, mtime, etc). This works around the lack
773 * of lstat(2) in Java 6.
774 *
775 * Currently, the {@link Stat} class used to do this only supports Linux
776 * and FreeBSD, so the old {@link #deprecatedGetFileLinkStatusInternal(Path)}
777 * implementation (deprecated) remains further OS support is added.
778 *
779 * @param f File to stat
780 * @param dereference whether to dereference symlinks
781 * @return FileStatus of f
782 * @throws IOException
783 */
784 private FileStatus getNativeFileLinkStatus(final Path f,
785 boolean dereference) throws IOException {
786 checkPath(f);
787 Stat stat = new Stat(f, getDefaultBlockSize(f), dereference, this);
788 FileStatus status = stat.getFileStatus();
789 return status;
790 }
791
792 @Override
793 public Path getLinkTarget(Path f) throws IOException {
794 FileStatus fi = getFileLinkStatusInternal(f, false);
795 // return an unqualified symlink target
796 return fi.getSymlink();
797 }
798 }