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