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