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 */
018package org.apache.hadoop.fs.viewfs;
019
020import static org.apache.hadoop.fs.viewfs.Constants.PERMISSION_555;
021
022import java.io.FileNotFoundException;
023import java.io.IOException;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.util.Arrays;
027import java.util.Collection;
028import java.util.EnumSet;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033import java.util.Map.Entry;
034
035import org.apache.hadoop.classification.InterfaceAudience;
036import org.apache.hadoop.classification.InterfaceStability;
037import org.apache.hadoop.conf.Configuration;
038import org.apache.hadoop.fs.BlockLocation;
039import org.apache.hadoop.fs.BlockStoragePolicySpi;
040import org.apache.hadoop.fs.ContentSummary;
041import org.apache.hadoop.fs.CreateFlag;
042import org.apache.hadoop.fs.FSDataInputStream;
043import org.apache.hadoop.fs.FSDataOutputStream;
044import org.apache.hadoop.fs.FileAlreadyExistsException;
045import org.apache.hadoop.fs.FileChecksum;
046import org.apache.hadoop.fs.FileStatus;
047import org.apache.hadoop.fs.FileSystem;
048import org.apache.hadoop.fs.FsConstants;
049import org.apache.hadoop.fs.FsServerDefaults;
050import org.apache.hadoop.fs.LocatedFileStatus;
051import org.apache.hadoop.fs.Path;
052import org.apache.hadoop.fs.PathFilter;
053import org.apache.hadoop.fs.QuotaUsage;
054import org.apache.hadoop.fs.RemoteIterator;
055import org.apache.hadoop.fs.UnsupportedFileSystemException;
056import org.apache.hadoop.fs.XAttrSetFlag;
057import org.apache.hadoop.fs.permission.AclEntry;
058import org.apache.hadoop.fs.permission.AclStatus;
059import org.apache.hadoop.fs.permission.AclUtil;
060import org.apache.hadoop.fs.permission.FsAction;
061import org.apache.hadoop.fs.permission.FsPermission;
062import org.apache.hadoop.fs.viewfs.InodeTree.INode;
063import org.apache.hadoop.fs.viewfs.InodeTree.INodeLink;
064import org.apache.hadoop.security.AccessControlException;
065import org.apache.hadoop.security.UserGroupInformation;
066import org.apache.hadoop.util.Progressable;
067import org.apache.hadoop.util.Time;
068
069/**
070 * ViewFileSystem (extends the FileSystem interface) implements a client-side
071 * mount table. Its spec and implementation is identical to {@link ViewFs}.
072 */
073
074@InterfaceAudience.Public
075@InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */
076public class ViewFileSystem extends FileSystem {
077
078  private static final Path ROOT_PATH = new Path(Path.SEPARATOR);
079
080  static AccessControlException readOnlyMountTable(final String operation,
081      final String p) {
082    return new AccessControlException( 
083        "InternalDir of ViewFileSystem is readonly; operation=" + operation + 
084        "Path=" + p);
085  }
086  static AccessControlException readOnlyMountTable(final String operation,
087      final Path p) {
088    return readOnlyMountTable(operation, p.toString());
089  }
090  
091  static public class MountPoint {
092    private Path src;       // the src of the mount
093    private URI[] targets; //  target of the mount; Multiple targets imply mergeMount
094    MountPoint(Path srcPath, URI[] targetURIs) {
095      src = srcPath;
096      targets = targetURIs;
097    }
098    Path getSrc() {
099      return src;
100    }
101    URI[] getTargets() {
102      return targets;
103    }
104  }
105  
106  final long creationTime; // of the the mount table
107  final UserGroupInformation ugi; // the user/group of user who created mtable
108  URI myUri;
109  private Path workingDir;
110  Configuration config;
111  InodeTree<FileSystem> fsState;  // the fs state; ie the mount table
112  Path homeDir = null;
113  
114  /**
115   * Make the path Absolute and get the path-part of a pathname.
116   * Checks that URI matches this file system 
117   * and that the path-part is a valid name.
118   * 
119   * @param p path
120   * @return path-part of the Path p
121   */
122  private String getUriPath(final Path p) {
123    checkPath(p);
124    return makeAbsolute(p).toUri().getPath();
125  }
126  
127  private Path makeAbsolute(final Path f) {
128    return f.isAbsolute() ? f : new Path(workingDir, f);
129  }
130  
131  /**
132   * This is the  constructor with the signature needed by
133   * {@link FileSystem#createFileSystem(URI, Configuration)}
134   * 
135   * After this constructor is called initialize() is called.
136   * @throws IOException 
137   */
138  public ViewFileSystem() throws IOException {
139    ugi = UserGroupInformation.getCurrentUser();
140    creationTime = Time.now();
141  }
142
143  /**
144   * Return the protocol scheme for the FileSystem.
145   * <p/>
146   *
147   * @return <code>viewfs</code>
148   */
149  @Override
150  public String getScheme() {
151    return "viewfs";
152  }
153
154  /**
155   * Called after a new FileSystem instance is constructed.
156   * @param theUri a uri whose authority section names the host, port, etc. for
157   *          this FileSystem
158   * @param conf the configuration
159   */
160  @Override
161  public void initialize(final URI theUri, final Configuration conf)
162      throws IOException {
163    super.initialize(theUri, conf);
164    setConf(conf);
165    config = conf;
166    // Now build  client side view (i.e. client side mount table) from config.
167    final String authority = theUri.getAuthority();
168    try {
169      myUri = new URI(FsConstants.VIEWFS_SCHEME, authority, "/", null, null);
170      fsState = new InodeTree<FileSystem>(conf, authority) {
171
172        @Override
173        protected
174        FileSystem getTargetFileSystem(final URI uri)
175          throws URISyntaxException, IOException {
176            return new ChRootedFileSystem(uri, config);
177        }
178
179        @Override
180        protected
181        FileSystem getTargetFileSystem(final INodeDir<FileSystem> dir)
182          throws URISyntaxException {
183          return new InternalDirOfViewFs(dir, creationTime, ugi, myUri);
184        }
185
186        @Override
187        protected
188        FileSystem getTargetFileSystem(URI[] mergeFsURIList)
189            throws URISyntaxException, UnsupportedFileSystemException {
190          throw new UnsupportedFileSystemException("mergefs not implemented");
191          // return MergeFs.createMergeFs(mergeFsURIList, config);
192        }
193      };
194      workingDir = this.getHomeDirectory();
195    } catch (URISyntaxException e) {
196      throw new IOException("URISyntax exception: " + theUri);
197    }
198
199  }
200  
201  
202  /**
203   * Convenience Constructor for apps to call directly
204   * @param theUri which must be that of ViewFileSystem
205   * @param conf
206   * @throws IOException
207   */
208  ViewFileSystem(final URI theUri, final Configuration conf)
209    throws IOException {
210    this();
211    initialize(theUri, conf);
212  }
213  
214  /**
215   * Convenience Constructor for apps to call directly
216   * @param conf
217   * @throws IOException
218   */
219  public ViewFileSystem(final Configuration conf) throws IOException {
220    this(FsConstants.VIEWFS_URI, conf);
221  }
222  
223  public Path getTrashCanLocation(final Path f) throws FileNotFoundException {
224    final InodeTree.ResolveResult<FileSystem> res = 
225      fsState.resolve(getUriPath(f), true);
226    return res.isInternalDir() ? null : res.targetFileSystem.getHomeDirectory();
227  }
228  
229  @Override
230  public URI getUri() {
231    return myUri;
232  }
233  
234  @Override
235  public Path resolvePath(final Path f)
236      throws IOException {
237    final InodeTree.ResolveResult<FileSystem> res;
238      res = fsState.resolve(getUriPath(f), true);
239    if (res.isInternalDir()) {
240      return f;
241    }
242    return res.targetFileSystem.resolvePath(res.remainingPath);
243  }
244  
245  @Override
246  public Path getHomeDirectory() {
247    if (homeDir == null) {
248      String base = fsState.getHomeDirPrefixValue();
249      if (base == null) {
250        base = "/user";
251      }
252      homeDir = (base.equals("/") ? 
253          this.makeQualified(new Path(base + ugi.getShortUserName())):
254          this.makeQualified(new Path(base + "/" + ugi.getShortUserName())));
255    }
256    return homeDir;
257  }
258  
259  @Override
260  public Path getWorkingDirectory() {
261    return workingDir;
262  }
263
264  @Override
265  public void setWorkingDirectory(final Path new_dir) {
266    getUriPath(new_dir); // this validates the path
267    workingDir = makeAbsolute(new_dir);
268  }
269  
270  @Override
271  public FSDataOutputStream append(final Path f, final int bufferSize,
272      final Progressable progress) throws IOException {
273    InodeTree.ResolveResult<FileSystem> res = 
274      fsState.resolve(getUriPath(f), true);
275    return res.targetFileSystem.append(res.remainingPath, bufferSize, progress);
276  }
277  
278  @Override
279  public FSDataOutputStream createNonRecursive(Path f, FsPermission permission,
280      EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize,
281      Progressable progress) throws IOException {
282    InodeTree.ResolveResult<FileSystem> res;
283    try {
284      res = fsState.resolve(getUriPath(f), false);
285    } catch (FileNotFoundException e) {
286        throw readOnlyMountTable("create", f);
287    }
288    assert(res.remainingPath != null);
289    return res.targetFileSystem.createNonRecursive(res.remainingPath, permission,
290        flags, bufferSize, replication, blockSize, progress);
291  }
292  
293  @Override
294  public FSDataOutputStream create(final Path f, final FsPermission permission,
295      final boolean overwrite, final int bufferSize, final short replication,
296      final long blockSize, final Progressable progress) throws IOException {
297    InodeTree.ResolveResult<FileSystem> res;
298    try {
299      res = fsState.resolve(getUriPath(f), false);
300    } catch (FileNotFoundException e) {
301        throw readOnlyMountTable("create", f);
302    }
303    assert(res.remainingPath != null);
304    return res.targetFileSystem.create(res.remainingPath, permission,
305        overwrite, bufferSize, replication, blockSize, progress);
306  }
307
308  
309  @Override
310  public boolean delete(final Path f, final boolean recursive)
311      throws AccessControlException, FileNotFoundException,
312      IOException {
313    InodeTree.ResolveResult<FileSystem> res = 
314      fsState.resolve(getUriPath(f), true);
315    // If internal dir or target is a mount link (ie remainingPath is Slash)
316    if (res.isInternalDir() || res.remainingPath == InodeTree.SlashPath) {
317      throw readOnlyMountTable("delete", f);
318    }
319    return res.targetFileSystem.delete(res.remainingPath, recursive);
320  }
321  
322  @Override
323  @SuppressWarnings("deprecation")
324  public boolean delete(final Path f)
325      throws AccessControlException, FileNotFoundException,
326      IOException {
327      return delete(f, true);
328  }
329  
330  @Override
331  public BlockLocation[] getFileBlockLocations(FileStatus fs, 
332      long start, long len) throws IOException {
333    final InodeTree.ResolveResult<FileSystem> res = 
334      fsState.resolve(getUriPath(fs.getPath()), true);
335    return res.targetFileSystem.getFileBlockLocations(
336        new ViewFsFileStatus(fs, res.remainingPath), start, len);
337  }
338
339  @Override
340  public FileChecksum getFileChecksum(final Path f)
341      throws AccessControlException, FileNotFoundException,
342      IOException {
343    InodeTree.ResolveResult<FileSystem> res = 
344      fsState.resolve(getUriPath(f), true);
345    return res.targetFileSystem.getFileChecksum(res.remainingPath);
346  }
347
348
349  private static FileStatus fixFileStatus(FileStatus orig,
350      Path qualified) throws IOException {
351    // FileStatus#getPath is a fully qualified path relative to the root of
352    // target file system.
353    // We need to change it to viewfs URI - relative to root of mount table.
354
355    // The implementors of RawLocalFileSystem were trying to be very smart.
356    // They implement FileStatus#getOwner lazily -- the object
357    // returned is really a RawLocalFileSystem that expect the
358    // FileStatus#getPath to be unchanged so that it can get owner when needed.
359    // Hence we need to interpose a new ViewFileSystemFileStatus that
360    // works around.
361    if ("file".equals(orig.getPath().toUri().getScheme())) {
362      orig = wrapLocalFileStatus(orig, qualified);
363    }
364
365    orig.setPath(qualified);
366    return orig;
367  }
368
369  private static FileStatus wrapLocalFileStatus(FileStatus orig,
370      Path qualified) {
371    return orig instanceof LocatedFileStatus
372        ? new ViewFsLocatedFileStatus((LocatedFileStatus)orig, qualified)
373        : new ViewFsFileStatus(orig, qualified);
374  }
375
376
377  @Override
378  public FileStatus getFileStatus(final Path f) throws AccessControlException,
379      FileNotFoundException, IOException {
380    InodeTree.ResolveResult<FileSystem> res =
381      fsState.resolve(getUriPath(f), true);
382    FileStatus status =  res.targetFileSystem.getFileStatus(res.remainingPath);
383    return fixFileStatus(status, this.makeQualified(f));
384  }
385  
386  @Override
387  public void access(Path path, FsAction mode) throws AccessControlException,
388      FileNotFoundException, IOException {
389    InodeTree.ResolveResult<FileSystem> res =
390      fsState.resolve(getUriPath(path), true);
391    res.targetFileSystem.access(res.remainingPath, mode);
392  }
393
394  @Override
395  public FileStatus[] listStatus(final Path f) throws AccessControlException,
396      FileNotFoundException, IOException {
397    InodeTree.ResolveResult<FileSystem> res =
398      fsState.resolve(getUriPath(f), true);
399    
400    FileStatus[] statusLst = res.targetFileSystem.listStatus(res.remainingPath);
401    if (!res.isInternalDir()) {
402      // We need to change the name in the FileStatus as described in
403      // {@link #getFileStatus }
404      int i = 0;
405      for (FileStatus status : statusLst) {
406          statusLst[i++] = fixFileStatus(status,
407              getChrootedPath(res, status, f));
408      }
409    }
410    return statusLst;
411  }
412
413  @Override
414  public RemoteIterator<LocatedFileStatus>listLocatedStatus(final Path f,
415      final PathFilter filter) throws FileNotFoundException, IOException {
416    final InodeTree.ResolveResult<FileSystem> res = fsState
417        .resolve(getUriPath(f), true);
418    final RemoteIterator<LocatedFileStatus> statusIter = res.targetFileSystem
419        .listLocatedStatus(res.remainingPath);
420
421    if (res.isInternalDir()) {
422      return statusIter;
423    }
424
425    return new RemoteIterator<LocatedFileStatus>() {
426      @Override
427      public boolean hasNext() throws IOException {
428        return statusIter.hasNext();
429      }
430
431      @Override
432      public LocatedFileStatus next() throws IOException {
433        final LocatedFileStatus status = statusIter.next();
434        return (LocatedFileStatus)fixFileStatus(status,
435            getChrootedPath(res, status, f));
436      }
437    };
438  }
439
440  private Path getChrootedPath(InodeTree.ResolveResult<FileSystem> res,
441      FileStatus status, Path f) throws IOException {
442    final String suffix = ((ChRootedFileSystem)res.targetFileSystem)
443        .stripOutRoot(status.getPath());
444    return this.makeQualified(
445        suffix.length() == 0 ? f : new Path(res.resolvedPath, suffix));
446  }
447
448  @Override
449  public boolean mkdirs(final Path dir, final FsPermission permission)
450      throws IOException {
451    InodeTree.ResolveResult<FileSystem> res = 
452      fsState.resolve(getUriPath(dir), false);
453   return  res.targetFileSystem.mkdirs(res.remainingPath, permission);
454  }
455
456  @Override
457  public FSDataInputStream open(final Path f, final int bufferSize)
458      throws AccessControlException, FileNotFoundException,
459      IOException {
460    InodeTree.ResolveResult<FileSystem> res = 
461        fsState.resolve(getUriPath(f), true);
462    return res.targetFileSystem.open(res.remainingPath, bufferSize);
463  }
464
465  
466  @Override
467  public boolean rename(final Path src, final Path dst) throws IOException {
468    // passing resolveLastComponet as false to catch renaming a mount point to 
469    // itself. We need to catch this as an internal operation and fail.
470    InodeTree.ResolveResult<FileSystem> resSrc = 
471      fsState.resolve(getUriPath(src), false); 
472  
473    if (resSrc.isInternalDir()) {
474      throw readOnlyMountTable("rename", src);
475    }
476      
477    InodeTree.ResolveResult<FileSystem> resDst = 
478      fsState.resolve(getUriPath(dst), false);
479    if (resDst.isInternalDir()) {
480          throw readOnlyMountTable("rename", dst);
481    }
482    /**
483    // Alternate 1: renames within same file system - valid but we disallow
484    // Alternate 2: (as described in next para - valid but we have disallowed it
485    //
486    // Note we compare the URIs. the URIs include the link targets. 
487    // hence we allow renames across mount links as long as the mount links
488    // point to the same target.
489    if (!resSrc.targetFileSystem.getUri().equals(
490              resDst.targetFileSystem.getUri())) {
491      throw new IOException("Renames across Mount points not supported");
492    }
493    */
494    
495    //
496    // Alternate 3 : renames ONLY within the the same mount links.
497    //
498    if (resSrc.targetFileSystem !=resDst.targetFileSystem) {
499      throw new IOException("Renames across Mount points not supported");
500    }
501    return resSrc.targetFileSystem.rename(resSrc.remainingPath,
502        resDst.remainingPath);
503  }
504
505  @Override
506  public boolean truncate(final Path f, final long newLength)
507      throws IOException {
508    InodeTree.ResolveResult<FileSystem> res =
509        fsState.resolve(getUriPath(f), true);
510    return res.targetFileSystem.truncate(f, newLength);
511  }
512  
513  @Override
514  public void setOwner(final Path f, final String username,
515      final String groupname) throws AccessControlException,
516      FileNotFoundException,
517      IOException {
518    InodeTree.ResolveResult<FileSystem> res = 
519      fsState.resolve(getUriPath(f), true);
520    res.targetFileSystem.setOwner(res.remainingPath, username, groupname); 
521  }
522
523  @Override
524  public void setPermission(final Path f, final FsPermission permission)
525      throws AccessControlException, FileNotFoundException,
526      IOException {
527    InodeTree.ResolveResult<FileSystem> res = 
528      fsState.resolve(getUriPath(f), true);
529    res.targetFileSystem.setPermission(res.remainingPath, permission); 
530  }
531
532  @Override
533  public boolean setReplication(final Path f, final short replication)
534      throws AccessControlException, FileNotFoundException,
535      IOException {
536    InodeTree.ResolveResult<FileSystem> res = 
537      fsState.resolve(getUriPath(f), true);
538    return res.targetFileSystem.setReplication(res.remainingPath, replication);
539  }
540
541  @Override
542  public void setTimes(final Path f, final long mtime, final long atime)
543      throws AccessControlException, FileNotFoundException,
544      IOException {
545    InodeTree.ResolveResult<FileSystem> res = 
546      fsState.resolve(getUriPath(f), true);
547    res.targetFileSystem.setTimes(res.remainingPath, mtime, atime); 
548  }
549
550  @Override
551  public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
552      throws IOException {
553    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path),
554        true);
555    res.targetFileSystem.modifyAclEntries(res.remainingPath, aclSpec);
556  }
557
558  @Override
559  public void removeAclEntries(Path path, List<AclEntry> aclSpec)
560      throws IOException {
561    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path),
562        true);
563    res.targetFileSystem.removeAclEntries(res.remainingPath, aclSpec);
564  }
565
566  @Override
567  public void removeDefaultAcl(Path path)
568      throws IOException {
569    InodeTree.ResolveResult<FileSystem> res =
570      fsState.resolve(getUriPath(path), true);
571    res.targetFileSystem.removeDefaultAcl(res.remainingPath);
572  }
573
574  @Override
575  public void removeAcl(Path path)
576      throws IOException {
577    InodeTree.ResolveResult<FileSystem> res =
578      fsState.resolve(getUriPath(path), true);
579    res.targetFileSystem.removeAcl(res.remainingPath);
580  }
581
582  @Override
583  public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
584    InodeTree.ResolveResult<FileSystem> res =
585      fsState.resolve(getUriPath(path), true);
586    res.targetFileSystem.setAcl(res.remainingPath, aclSpec);
587  }
588
589  @Override
590  public AclStatus getAclStatus(Path path) throws IOException {
591    InodeTree.ResolveResult<FileSystem> res =
592      fsState.resolve(getUriPath(path), true);
593    return res.targetFileSystem.getAclStatus(res.remainingPath);
594  }
595
596  @Override
597  public void setXAttr(Path path, String name, byte[] value,
598      EnumSet<XAttrSetFlag> flag) throws IOException {
599    InodeTree.ResolveResult<FileSystem> res =
600        fsState.resolve(getUriPath(path), true);
601    res.targetFileSystem.setXAttr(res.remainingPath, name, value, flag);
602  }
603
604  @Override
605  public byte[] getXAttr(Path path, String name) throws IOException {
606    InodeTree.ResolveResult<FileSystem> res =
607        fsState.resolve(getUriPath(path), true);
608    return res.targetFileSystem.getXAttr(res.remainingPath, name);
609  }
610
611  @Override
612  public Map<String, byte[]> getXAttrs(Path path) throws IOException {
613    InodeTree.ResolveResult<FileSystem> res =
614        fsState.resolve(getUriPath(path), true);
615    return res.targetFileSystem.getXAttrs(res.remainingPath);
616  }
617
618  @Override
619  public Map<String, byte[]> getXAttrs(Path path, List<String> names)
620      throws IOException {
621    InodeTree.ResolveResult<FileSystem> res =
622        fsState.resolve(getUriPath(path), true);
623    return res.targetFileSystem.getXAttrs(res.remainingPath, names);
624  }
625
626  @Override
627  public List<String> listXAttrs(Path path) throws IOException {
628    InodeTree.ResolveResult<FileSystem> res =
629      fsState.resolve(getUriPath(path), true);
630    return res.targetFileSystem.listXAttrs(res.remainingPath);
631  }
632
633  @Override
634  public void removeXAttr(Path path, String name) throws IOException {
635    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path),
636        true);
637    res.targetFileSystem.removeXAttr(res.remainingPath, name);
638  }
639
640  @Override
641  public void setVerifyChecksum(final boolean verifyChecksum) { 
642    List<InodeTree.MountPoint<FileSystem>> mountPoints = 
643        fsState.getMountPoints();
644    for (InodeTree.MountPoint<FileSystem> mount : mountPoints) {
645      mount.target.targetFileSystem.setVerifyChecksum(verifyChecksum);
646    }
647  }
648  
649  @Override
650  public long getDefaultBlockSize() {
651    throw new NotInMountpointException("getDefaultBlockSize");
652  }
653
654  @Override
655  public short getDefaultReplication() {
656    throw new NotInMountpointException("getDefaultReplication");
657  }
658
659  @Override
660  public FsServerDefaults getServerDefaults() throws IOException {
661    throw new NotInMountpointException("getServerDefaults");
662  }
663
664  @Override
665  public long getDefaultBlockSize(Path f) {
666    try {
667      InodeTree.ResolveResult<FileSystem> res =
668        fsState.resolve(getUriPath(f), true);
669      return res.targetFileSystem.getDefaultBlockSize(res.remainingPath);
670    } catch (FileNotFoundException e) {
671      throw new NotInMountpointException(f, "getDefaultBlockSize"); 
672    }
673  }
674
675  @Override
676  public short getDefaultReplication(Path f) {
677    try {
678      InodeTree.ResolveResult<FileSystem> res =
679        fsState.resolve(getUriPath(f), true);
680      return res.targetFileSystem.getDefaultReplication(res.remainingPath);
681    } catch (FileNotFoundException e) {
682      throw new NotInMountpointException(f, "getDefaultReplication"); 
683    }
684  }
685
686  @Override
687  public FsServerDefaults getServerDefaults(Path f) throws IOException {
688    InodeTree.ResolveResult<FileSystem> res =
689      fsState.resolve(getUriPath(f), true);
690    return res.targetFileSystem.getServerDefaults(res.remainingPath);    
691  }
692
693  @Override
694  public ContentSummary getContentSummary(Path f) throws IOException {
695    InodeTree.ResolveResult<FileSystem> res =
696      fsState.resolve(getUriPath(f), true);
697    return res.targetFileSystem.getContentSummary(res.remainingPath);
698  }
699
700  @Override
701  public QuotaUsage getQuotaUsage(Path f) throws IOException {
702    InodeTree.ResolveResult<FileSystem> res =
703        fsState.resolve(getUriPath(f), true);
704    return res.targetFileSystem.getQuotaUsage(res.remainingPath);
705  }
706
707  @Override
708  public void setWriteChecksum(final boolean writeChecksum) { 
709    List<InodeTree.MountPoint<FileSystem>> mountPoints = 
710        fsState.getMountPoints();
711    for (InodeTree.MountPoint<FileSystem> mount : mountPoints) {
712      mount.target.targetFileSystem.setWriteChecksum(writeChecksum);
713    }
714  }
715
716  @Override
717  public FileSystem[] getChildFileSystems() {
718    List<InodeTree.MountPoint<FileSystem>> mountPoints =
719        fsState.getMountPoints();
720    Set<FileSystem> children = new HashSet<FileSystem>();
721    for (InodeTree.MountPoint<FileSystem> mountPoint : mountPoints) {
722      FileSystem targetFs = mountPoint.target.targetFileSystem;
723      children.addAll(Arrays.asList(targetFs.getChildFileSystems()));
724    }
725    return children.toArray(new FileSystem[]{});
726  }
727  
728  public MountPoint[] getMountPoints() {
729    List<InodeTree.MountPoint<FileSystem>> mountPoints = 
730                  fsState.getMountPoints();
731    
732    MountPoint[] result = new MountPoint[mountPoints.size()];
733    for ( int i = 0; i < mountPoints.size(); ++i ) {
734      result[i] = new MountPoint(new Path(mountPoints.get(i).src), 
735                              mountPoints.get(i).target.targetDirLinkList);
736    }
737    return result;
738  }
739
740  @Override
741  public Path createSnapshot(Path path, String snapshotName)
742      throws IOException {
743    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path),
744        true);
745    return res.targetFileSystem.createSnapshot(res.remainingPath, snapshotName);
746  }
747
748  @Override
749  public void renameSnapshot(Path path, String snapshotOldName,
750      String snapshotNewName) throws IOException {
751    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path),
752        true);
753    res.targetFileSystem.renameSnapshot(res.remainingPath, snapshotOldName,
754        snapshotNewName);
755  }
756
757  @Override
758  public void deleteSnapshot(Path path, String snapshotName)
759      throws IOException {
760    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path),
761        true);
762    res.targetFileSystem.deleteSnapshot(res.remainingPath, snapshotName);
763  }
764
765  @Override
766  public void setStoragePolicy(Path src, String policyName) throws IOException {
767    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(src),
768        true);
769    res.targetFileSystem.setStoragePolicy(res.remainingPath, policyName);
770  }
771
772  @Override
773  public void unsetStoragePolicy(Path src) throws IOException {
774    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(src),
775        true);
776    res.targetFileSystem.unsetStoragePolicy(res.remainingPath);
777  }
778
779  @Override
780  public BlockStoragePolicySpi getStoragePolicy(Path src) throws IOException {
781    InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(src),
782        true);
783    return res.targetFileSystem.getStoragePolicy(res.remainingPath);
784  }
785
786  @Override
787  public Collection<? extends BlockStoragePolicySpi> getAllStoragePolicies()
788      throws IOException {
789    Collection<BlockStoragePolicySpi> allPolicies = new HashSet<>();
790    for (FileSystem fs : getChildFileSystems()) {
791      try {
792        Collection<? extends BlockStoragePolicySpi> policies =
793            fs.getAllStoragePolicies();
794        allPolicies.addAll(policies);
795      } catch (UnsupportedOperationException e) {
796        // ignored
797      }
798    }
799    return allPolicies;
800  }
801
802  /*
803   * An instance of this class represents an internal dir of the viewFs 
804   * that is internal dir of the mount table.
805   * It is a read only mount tables and create, mkdir or delete operations
806   * are not allowed.
807   * If called on create or mkdir then this target is the parent of the
808   * directory in which one is trying to create or mkdir; hence
809   * in this case the path name passed in is the last component. 
810   * Otherwise this target is the end point of the path and hence
811   * the path name passed in is null. 
812   */
813  static class InternalDirOfViewFs extends FileSystem {
814    final InodeTree.INodeDir<FileSystem>  theInternalDir;
815    final long creationTime; // of the the mount table
816    final UserGroupInformation ugi; // the user/group of user who created mtable
817    final URI myUri;
818    
819    public InternalDirOfViewFs(final InodeTree.INodeDir<FileSystem> dir,
820        final long cTime, final UserGroupInformation ugi, URI uri)
821      throws URISyntaxException {
822      myUri = uri;
823      try {
824        initialize(myUri, new Configuration());
825      } catch (IOException e) {
826        throw new RuntimeException("Cannot occur");
827      }
828      theInternalDir = dir;
829      creationTime = cTime;
830      this.ugi = ugi;
831    }
832
833    static private void checkPathIsSlash(final Path f) throws IOException {
834      if (f != InodeTree.SlashPath) {
835        throw new IOException (
836        "Internal implementation error: expected file name to be /" );
837      }
838    }
839    
840    @Override
841    public URI getUri() {
842      return myUri;
843    }
844
845    @Override
846    public Path getWorkingDirectory() {
847      throw new RuntimeException (
848      "Internal impl error: getWorkingDir should not have been called" );
849    }
850
851    @Override
852    public void setWorkingDirectory(final Path new_dir) {
853      throw new RuntimeException (
854      "Internal impl error: getWorkingDir should not have been called" ); 
855    }
856
857    @Override
858    public FSDataOutputStream append(final Path f, final int bufferSize,
859        final Progressable progress) throws IOException {
860      throw readOnlyMountTable("append", f);
861    }
862
863    @Override
864    public FSDataOutputStream create(final Path f,
865        final FsPermission permission, final boolean overwrite,
866        final int bufferSize, final short replication, final long blockSize,
867        final Progressable progress) throws AccessControlException {
868      throw readOnlyMountTable("create", f);
869    }
870
871    @Override
872    public boolean delete(final Path f, final boolean recursive)
873        throws AccessControlException, IOException {
874      checkPathIsSlash(f);
875      throw readOnlyMountTable("delete", f);
876    }
877    
878    @Override
879    @SuppressWarnings("deprecation")
880    public boolean delete(final Path f)
881        throws AccessControlException, IOException {
882      return delete(f, true);
883    }
884
885    @Override
886    public BlockLocation[] getFileBlockLocations(final FileStatus fs,
887        final long start, final long len) throws 
888        FileNotFoundException, IOException {
889      checkPathIsSlash(fs.getPath());
890      throw new FileNotFoundException("Path points to dir not a file");
891    }
892
893    @Override
894    public FileChecksum getFileChecksum(final Path f)
895        throws FileNotFoundException, IOException {
896      checkPathIsSlash(f);
897      throw new FileNotFoundException("Path points to dir not a file");
898    }
899
900    @Override
901    public FileStatus getFileStatus(Path f) throws IOException {
902      checkPathIsSlash(f);
903      return new FileStatus(0, true, 0, 0, creationTime, creationTime,
904          PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
905
906          new Path(theInternalDir.fullPath).makeQualified(
907              myUri, ROOT_PATH));
908    }
909    
910
911    @Override
912    public FileStatus[] listStatus(Path f) throws AccessControlException,
913        FileNotFoundException, IOException {
914      checkPathIsSlash(f);
915      FileStatus[] result = new FileStatus[theInternalDir.children.size()];
916      int i = 0;
917      for (Entry<String, INode<FileSystem>> iEntry : 
918                                          theInternalDir.children.entrySet()) {
919        INode<FileSystem> inode = iEntry.getValue();
920        if (inode instanceof INodeLink ) {
921          INodeLink<FileSystem> link = (INodeLink<FileSystem>) inode;
922
923          result[i++] = new FileStatus(0, false, 0, 0,
924            creationTime, creationTime, PERMISSION_555,
925            ugi.getUserName(), ugi.getPrimaryGroupName(),
926            link.getTargetLink(),
927            new Path(inode.fullPath).makeQualified(
928                myUri, null));
929        } else {
930          result[i++] = new FileStatus(0, true, 0, 0,
931            creationTime, creationTime, PERMISSION_555,
932            ugi.getUserName(), ugi.getGroupNames()[0],
933            new Path(inode.fullPath).makeQualified(
934                myUri, null));
935        }
936      }
937      return result;
938    }
939
940    @Override
941    public boolean mkdirs(Path dir, FsPermission permission)
942        throws AccessControlException, FileAlreadyExistsException {
943      if (theInternalDir.isRoot && dir == null) {
944        throw new FileAlreadyExistsException("/ already exits");
945      }
946      // Note dir starts with /
947      if (theInternalDir.children.containsKey(dir.toString().substring(1))) {
948        return true; // this is the stupid semantics of FileSystem
949      }
950      throw readOnlyMountTable("mkdirs",  dir);
951    }
952
953    @Override
954    public FSDataInputStream open(Path f, int bufferSize)
955        throws AccessControlException, FileNotFoundException, IOException {
956      checkPathIsSlash(f);
957      throw new FileNotFoundException("Path points to dir not a file");
958    }
959
960    @Override
961    public boolean rename(Path src, Path dst) throws AccessControlException,
962        IOException {
963      checkPathIsSlash(src);
964      checkPathIsSlash(dst);
965      throw readOnlyMountTable("rename", src);     
966    }
967
968    @Override
969    public boolean truncate(Path f, long newLength) throws IOException {
970      throw readOnlyMountTable("truncate", f);
971    }
972
973    @Override
974    public void setOwner(Path f, String username, String groupname)
975        throws AccessControlException, IOException {
976      checkPathIsSlash(f);
977      throw readOnlyMountTable("setOwner", f);
978    }
979
980    @Override
981    public void setPermission(Path f, FsPermission permission)
982        throws AccessControlException, IOException {
983      checkPathIsSlash(f);
984      throw readOnlyMountTable("setPermission", f);    
985    }
986
987    @Override
988    public boolean setReplication(Path f, short replication)
989        throws AccessControlException, IOException {
990      checkPathIsSlash(f);
991      throw readOnlyMountTable("setReplication", f);
992    }
993
994    @Override
995    public void setTimes(Path f, long mtime, long atime)
996        throws AccessControlException, IOException {
997      checkPathIsSlash(f);
998      throw readOnlyMountTable("setTimes", f);    
999    }
1000
1001    @Override
1002    public void setVerifyChecksum(boolean verifyChecksum) {
1003      // Noop for viewfs
1004    }
1005
1006    @Override
1007    public FsServerDefaults getServerDefaults(Path f) throws IOException {
1008      throw new NotInMountpointException(f, "getServerDefaults");
1009    }
1010    
1011    @Override
1012    public long getDefaultBlockSize(Path f) {
1013      throw new NotInMountpointException(f, "getDefaultBlockSize");
1014    }
1015
1016    @Override
1017    public short getDefaultReplication(Path f) {
1018      throw new NotInMountpointException(f, "getDefaultReplication");
1019    }
1020
1021    @Override
1022    public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
1023        throws IOException {
1024      checkPathIsSlash(path);
1025      throw readOnlyMountTable("modifyAclEntries", path);
1026    }
1027
1028    @Override
1029    public void removeAclEntries(Path path, List<AclEntry> aclSpec)
1030        throws IOException {
1031      checkPathIsSlash(path);
1032      throw readOnlyMountTable("removeAclEntries", path);
1033    }
1034
1035    @Override
1036    public void removeDefaultAcl(Path path) throws IOException {
1037      checkPathIsSlash(path);
1038      throw readOnlyMountTable("removeDefaultAcl", path);
1039    }
1040
1041    @Override
1042    public void removeAcl(Path path) throws IOException {
1043      checkPathIsSlash(path);
1044      throw readOnlyMountTable("removeAcl", path);
1045    }
1046
1047    @Override
1048    public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
1049      checkPathIsSlash(path);
1050      throw readOnlyMountTable("setAcl", path);
1051    }
1052
1053    @Override
1054    public AclStatus getAclStatus(Path path) throws IOException {
1055      checkPathIsSlash(path);
1056      return new AclStatus.Builder().owner(ugi.getUserName())
1057          .group(ugi.getPrimaryGroupName())
1058          .addEntries(AclUtil.getMinimalAcl(PERMISSION_555))
1059          .stickyBit(false).build();
1060    }
1061
1062    @Override
1063    public void setXAttr(Path path, String name, byte[] value,
1064                         EnumSet<XAttrSetFlag> flag) throws IOException {
1065      checkPathIsSlash(path);
1066      throw readOnlyMountTable("setXAttr", path);
1067    }
1068
1069    @Override
1070    public byte[] getXAttr(Path path, String name) throws IOException {
1071      throw new NotInMountpointException(path, "getXAttr");
1072    }
1073
1074    @Override
1075    public Map<String, byte[]> getXAttrs(Path path) throws IOException {
1076      throw new NotInMountpointException(path, "getXAttrs");
1077    }
1078
1079    @Override
1080    public Map<String, byte[]> getXAttrs(Path path, List<String> names)
1081        throws IOException {
1082      throw new NotInMountpointException(path, "getXAttrs");
1083    }
1084
1085    @Override
1086    public List<String> listXAttrs(Path path) throws IOException {
1087      throw new NotInMountpointException(path, "listXAttrs");
1088    }
1089
1090    @Override
1091    public void removeXAttr(Path path, String name) throws IOException {
1092      checkPathIsSlash(path);
1093      throw readOnlyMountTable("removeXAttr", path);
1094    }
1095
1096    @Override
1097    public Path createSnapshot(Path path, String snapshotName)
1098        throws IOException {
1099      checkPathIsSlash(path);
1100      throw readOnlyMountTable("createSnapshot", path);
1101    }
1102
1103    @Override
1104    public void renameSnapshot(Path path, String snapshotOldName,
1105        String snapshotNewName) throws IOException {
1106      checkPathIsSlash(path);
1107      throw readOnlyMountTable("renameSnapshot", path);
1108    }
1109
1110    @Override
1111    public void deleteSnapshot(Path path, String snapshotName)
1112        throws IOException {
1113      checkPathIsSlash(path);
1114      throw readOnlyMountTable("deleteSnapshot", path);
1115    }
1116
1117    @Override
1118    public QuotaUsage getQuotaUsage(Path f) throws IOException {
1119      throw new NotInMountpointException(f, "getQuotaUsage");
1120    }
1121
1122    @Override
1123    public void setStoragePolicy(Path src, String policyName)
1124        throws IOException {
1125      checkPathIsSlash(src);
1126      throw readOnlyMountTable("setStoragePolicy", src);
1127    }
1128
1129    @Override
1130    public void unsetStoragePolicy(Path src) throws IOException {
1131      checkPathIsSlash(src);
1132      throw readOnlyMountTable("unsetStoragePolicy", src);
1133    }
1134
1135    @Override
1136    public BlockStoragePolicySpi getStoragePolicy(Path src) throws IOException {
1137      throw new NotInMountpointException(src, "getStoragePolicy");
1138    }
1139
1140    @Override
1141    public Collection<? extends BlockStoragePolicySpi> getAllStoragePolicies()
1142        throws IOException {
1143      Collection<BlockStoragePolicySpi> allPolicies = new HashSet<>();
1144      for (FileSystem fs : getChildFileSystems()) {
1145        try {
1146          Collection<? extends BlockStoragePolicySpi> policies =
1147              fs.getAllStoragePolicies();
1148          allPolicies.addAll(policies);
1149        } catch (UnsupportedOperationException e) {
1150          // ignored
1151        }
1152      }
1153      return allPolicies;
1154    }
1155  }
1156}