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.BlockStoragePolicySpi;
038import org.apache.hadoop.fs.CreateFlag;
039import org.apache.hadoop.fs.FSDataInputStream;
040import org.apache.hadoop.fs.FSDataOutputStream;
041import org.apache.hadoop.fs.FileAlreadyExistsException;
042import org.apache.hadoop.fs.FileChecksum;
043import org.apache.hadoop.fs.FileStatus;
044import org.apache.hadoop.fs.FsConstants;
045import org.apache.hadoop.fs.FsServerDefaults;
046import org.apache.hadoop.fs.FsStatus;
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.defaultFS < = 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  public FsServerDefaults getServerDefaults() throws IOException {
243    return LocalConfigKeys.getServerDefaults(); 
244  }
245
246  @Override
247  public int getUriDefaultPort() {
248    return -1;
249  }
250 
251  @Override
252  public Path getHomeDirectory() {
253    if (homeDir == null) {
254      String base = fsState.getHomeDirPrefixValue();
255      if (base == null) {
256        base = "/user";
257      }
258      homeDir = (base.equals("/") ? 
259        this.makeQualified(new Path(base + ugi.getShortUserName())):
260        this.makeQualified(new Path(base + "/" + ugi.getShortUserName())));
261    }
262    return homeDir;
263  }
264  
265  @Override
266  public Path resolvePath(final Path f) throws FileNotFoundException,
267          AccessControlException, UnresolvedLinkException, IOException {
268    final InodeTree.ResolveResult<AbstractFileSystem> res;
269      res = fsState.resolve(getUriPath(f), true);
270    if (res.isInternalDir()) {
271      return f;
272    }
273    return res.targetFileSystem.resolvePath(res.remainingPath);
274
275  }
276  
277  @Override
278  public FSDataOutputStream createInternal(final Path f,
279      final EnumSet<CreateFlag> flag, final FsPermission absolutePermission,
280      final int bufferSize, final short replication, final long blockSize,
281      final Progressable progress, final ChecksumOpt checksumOpt,
282      final boolean createParent) throws AccessControlException,
283      FileAlreadyExistsException, FileNotFoundException,
284      ParentNotDirectoryException, UnsupportedFileSystemException,
285      UnresolvedLinkException, IOException {
286    InodeTree.ResolveResult<AbstractFileSystem> res;
287    try {
288      res = fsState.resolve(getUriPath(f), false);
289    } catch (FileNotFoundException e) {
290      if (createParent) {
291        throw readOnlyMountTable("create", f);
292      } else {
293        throw e;
294      }
295    }
296    assert(res.remainingPath != null);
297    return res.targetFileSystem.createInternal(res.remainingPath, flag,
298        absolutePermission, bufferSize, replication,
299        blockSize, progress, checksumOpt,
300        createParent);
301  }
302
303  @Override
304  public boolean delete(final Path f, final boolean recursive)
305      throws AccessControlException, FileNotFoundException,
306      UnresolvedLinkException, IOException {
307    InodeTree.ResolveResult<AbstractFileSystem> res = 
308      fsState.resolve(getUriPath(f), true);
309    // If internal dir or target is a mount link (ie remainingPath is Slash)
310    if (res.isInternalDir() || res.remainingPath == InodeTree.SlashPath) {
311      throw new AccessControlException(
312          "Cannot delete internal mount table directory: " + f);
313    }
314    return res.targetFileSystem.delete(res.remainingPath, recursive);
315  }
316
317  @Override
318  public BlockLocation[] getFileBlockLocations(final Path f, final long start,
319      final long len) throws AccessControlException, FileNotFoundException,
320      UnresolvedLinkException, IOException {
321    InodeTree.ResolveResult<AbstractFileSystem> res = 
322      fsState.resolve(getUriPath(f), true);
323    return
324      res.targetFileSystem.getFileBlockLocations(res.remainingPath, start, len);
325  }
326
327  @Override
328  public FileChecksum getFileChecksum(final Path f)
329      throws AccessControlException, FileNotFoundException,
330      UnresolvedLinkException, IOException {
331    InodeTree.ResolveResult<AbstractFileSystem> res = 
332      fsState.resolve(getUriPath(f), true);
333    return res.targetFileSystem.getFileChecksum(res.remainingPath);
334  }
335
336  @Override
337  public FileStatus getFileStatus(final Path f) throws AccessControlException,
338      FileNotFoundException, UnresolvedLinkException, IOException {
339    InodeTree.ResolveResult<AbstractFileSystem> res = 
340      fsState.resolve(getUriPath(f), true);
341
342    //  FileStatus#getPath is a fully qualified path relative to the root of 
343    // target file system.
344    // We need to change it to viewfs URI - relative to root of mount table.
345    
346    // The implementors of RawLocalFileSystem were trying to be very smart.
347    // They implement FileStatus#getOwener lazily -- the object
348    // returned is really a RawLocalFileSystem that expect the
349    // FileStatus#getPath to be unchanged so that it can get owner when needed.
350    // Hence we need to interpose a new ViewFsFileStatus that works around.
351    
352    
353    FileStatus status =  res.targetFileSystem.getFileStatus(res.remainingPath);
354    return new ViewFsFileStatus(status, this.makeQualified(f));
355  }
356
357  @Override
358  public void access(Path path, FsAction mode) throws AccessControlException,
359      FileNotFoundException, UnresolvedLinkException, IOException {
360    InodeTree.ResolveResult<AbstractFileSystem> res =
361      fsState.resolve(getUriPath(path), true);
362    res.targetFileSystem.access(res.remainingPath, mode);
363  }
364
365  @Override
366  public FileStatus getFileLinkStatus(final Path f)
367     throws AccessControlException, FileNotFoundException,
368     UnsupportedFileSystemException, IOException {
369    InodeTree.ResolveResult<AbstractFileSystem> res = 
370      fsState.resolve(getUriPath(f), false); // do not follow mount link
371    return res.targetFileSystem.getFileLinkStatus(res.remainingPath);
372  }
373  
374  @Override
375  public FsStatus getFsStatus() throws AccessControlException,
376      FileNotFoundException, IOException {
377    return new FsStatus(0, 0, 0);
378  }
379
380  @Override
381  public RemoteIterator<FileStatus> listStatusIterator(final Path f)
382    throws AccessControlException, FileNotFoundException,
383    UnresolvedLinkException, IOException {
384    final InodeTree.ResolveResult<AbstractFileSystem> res =
385      fsState.resolve(getUriPath(f), true);
386    final RemoteIterator<FileStatus> fsIter =
387      res.targetFileSystem.listStatusIterator(res.remainingPath);
388    if (res.isInternalDir()) {
389      return fsIter;
390    }
391    
392    return new RemoteIterator<FileStatus>() {
393      final RemoteIterator<FileStatus> myIter;
394      final ChRootedFs targetFs;
395      { // Init
396          myIter = fsIter;
397          targetFs = (ChRootedFs) res.targetFileSystem;
398      }
399      
400      @Override
401      public boolean hasNext() throws IOException {
402        return myIter.hasNext();
403      }
404      
405      @Override
406      public FileStatus next() throws IOException {
407        FileStatus status =  myIter.next();
408        String suffix = targetFs.stripOutRoot(status.getPath());
409        return new ViewFsFileStatus(status, makeQualified(
410            suffix.length() == 0 ? f : new Path(res.resolvedPath, suffix)));
411      }
412    };
413  }
414  
415  @Override
416  public FileStatus[] listStatus(final Path f) throws AccessControlException,
417      FileNotFoundException, UnresolvedLinkException, IOException {
418    InodeTree.ResolveResult<AbstractFileSystem> res =
419      fsState.resolve(getUriPath(f), true);
420    
421    FileStatus[] statusLst = res.targetFileSystem.listStatus(res.remainingPath);
422    if (!res.isInternalDir()) {
423      // We need to change the name in the FileStatus as described in
424      // {@link #getFileStatus }
425      ChRootedFs targetFs;
426      targetFs = (ChRootedFs) res.targetFileSystem;
427      int i = 0;
428      for (FileStatus status : statusLst) {
429          String suffix = targetFs.stripOutRoot(status.getPath());
430          statusLst[i++] = new ViewFsFileStatus(status, this.makeQualified(
431              suffix.length() == 0 ? f : new Path(res.resolvedPath, suffix)));
432      }
433    }
434    return statusLst;
435  }
436
437  @Override
438  public void mkdir(final Path dir, final FsPermission permission,
439      final boolean createParent) throws AccessControlException,
440      FileAlreadyExistsException,
441      FileNotFoundException, UnresolvedLinkException, IOException {
442    InodeTree.ResolveResult<AbstractFileSystem> res = 
443      fsState.resolve(getUriPath(dir), false);
444    res.targetFileSystem.mkdir(res.remainingPath, permission, createParent);
445  }
446
447  @Override
448  public FSDataInputStream open(final Path f, final int bufferSize)
449      throws AccessControlException, FileNotFoundException,
450      UnresolvedLinkException, IOException {
451    InodeTree.ResolveResult<AbstractFileSystem> res = 
452        fsState.resolve(getUriPath(f), true);
453    return res.targetFileSystem.open(res.remainingPath, bufferSize);
454  }
455
456  @Override
457  public boolean truncate(final Path f, final long newLength)
458      throws AccessControlException, FileNotFoundException,
459      UnresolvedLinkException, IOException {
460    InodeTree.ResolveResult<AbstractFileSystem> res =
461        fsState.resolve(getUriPath(f), true);
462    return res.targetFileSystem.truncate(res.remainingPath, newLength);
463  }
464
465  @Override
466  public void renameInternal(final Path src, final Path dst,
467      final boolean overwrite) throws IOException, UnresolvedLinkException {
468    // passing resolveLastComponet as false to catch renaming a mount point 
469    // itself we need to catch this as an internal operation and fail.
470    InodeTree.ResolveResult<AbstractFileSystem> resSrc = 
471      fsState.resolve(getUriPath(src), false); 
472  
473    if (resSrc.isInternalDir()) {
474      throw new AccessControlException(
475          "Cannot Rename within internal dirs of mount table: src=" + src
476              + " is readOnly");
477    }
478
479    InodeTree.ResolveResult<AbstractFileSystem> resDst = 
480                                fsState.resolve(getUriPath(dst), false);
481    if (resDst.isInternalDir()) {
482      throw new AccessControlException(
483          "Cannot Rename within internal dirs of mount table: dest=" + dst
484              + " is readOnly");
485    }
486    
487    /**
488    // Alternate 1: renames within same file system - valid but we disallow
489    // Alternate 2: (as described in next para - valid but we have disallowed it
490    //
491    // Note we compare the URIs. the URIs include the link targets. 
492    // hence we allow renames across mount links as long as the mount links
493    // point to the same target.
494    if (!resSrc.targetFileSystem.getUri().equals(
495              resDst.targetFileSystem.getUri())) {
496      throw new IOException("Renames across Mount points not supported");
497    }
498    */
499    
500    //
501    // Alternate 3 : renames ONLY within the the same mount links.
502    //
503
504    if (resSrc.targetFileSystem !=resDst.targetFileSystem) {
505      throw new IOException("Renames across Mount points not supported");
506    }
507    
508    resSrc.targetFileSystem.renameInternal(resSrc.remainingPath,
509      resDst.remainingPath, overwrite);
510  }
511
512  @Override
513  public void renameInternal(final Path src, final Path dst)
514      throws AccessControlException, FileAlreadyExistsException,
515      FileNotFoundException, ParentNotDirectoryException,
516      UnresolvedLinkException, IOException {
517    renameInternal(src, dst, false);
518  }
519  
520  @Override
521  public boolean supportsSymlinks() {
522    return true;
523  }
524  
525  @Override
526  public void createSymlink(final Path target, final Path link,
527      final boolean createParent) throws IOException, UnresolvedLinkException {
528    InodeTree.ResolveResult<AbstractFileSystem> res;
529    try {
530      res = fsState.resolve(getUriPath(link), false);
531    } catch (FileNotFoundException e) {
532      if (createParent) {
533        throw readOnlyMountTable("createSymlink", link);
534      } else {
535        throw e;
536      }
537    }
538    assert(res.remainingPath != null);
539    res.targetFileSystem.createSymlink(target, res.remainingPath,
540        createParent);  
541  }
542
543  @Override
544  public Path getLinkTarget(final Path f) throws IOException {
545    InodeTree.ResolveResult<AbstractFileSystem> res = 
546      fsState.resolve(getUriPath(f), false); // do not follow mount link
547    return res.targetFileSystem.getLinkTarget(res.remainingPath);
548  }
549
550  @Override
551  public void setOwner(final Path f, final String username,
552      final String groupname) throws AccessControlException,
553      FileNotFoundException, UnresolvedLinkException, IOException {
554    InodeTree.ResolveResult<AbstractFileSystem> res = 
555      fsState.resolve(getUriPath(f), true);
556    res.targetFileSystem.setOwner(res.remainingPath, username, groupname); 
557  }
558
559  @Override
560  public void setPermission(final Path f, final FsPermission permission)
561      throws AccessControlException, FileNotFoundException,
562      UnresolvedLinkException, IOException {
563    InodeTree.ResolveResult<AbstractFileSystem> res = 
564      fsState.resolve(getUriPath(f), true);
565    res.targetFileSystem.setPermission(res.remainingPath, permission); 
566    
567  }
568
569  @Override
570  public boolean setReplication(final Path f, final short replication)
571      throws AccessControlException, FileNotFoundException,
572      UnresolvedLinkException, IOException {
573    InodeTree.ResolveResult<AbstractFileSystem> res = 
574      fsState.resolve(getUriPath(f), true);
575    return res.targetFileSystem.setReplication(res.remainingPath, replication);
576  }
577
578  @Override
579  public void setTimes(final Path f, final long mtime, final long atime)
580      throws AccessControlException, FileNotFoundException,
581      UnresolvedLinkException, IOException {
582    InodeTree.ResolveResult<AbstractFileSystem> res = 
583      fsState.resolve(getUriPath(f), true);
584    res.targetFileSystem.setTimes(res.remainingPath, mtime, atime); 
585  }
586
587  @Override
588  public void setVerifyChecksum(final boolean verifyChecksum)
589      throws AccessControlException, IOException {
590    // This is a file system level operations, however ViewFs 
591    // points to many file systems. Noop for ViewFs. 
592  }
593  
594  public MountPoint[] getMountPoints() {
595    List<InodeTree.MountPoint<AbstractFileSystem>> mountPoints = 
596                  fsState.getMountPoints();
597    
598    MountPoint[] result = new MountPoint[mountPoints.size()];
599    for ( int i = 0; i < mountPoints.size(); ++i ) {
600      result[i] = new MountPoint(new Path(mountPoints.get(i).src), 
601                              mountPoints.get(i).target.targetDirLinkList);
602    }
603    return result;
604  }
605  
606  @Override
607  public List<Token<?>> getDelegationTokens(String renewer) throws IOException {
608    List<InodeTree.MountPoint<AbstractFileSystem>> mountPoints = 
609                fsState.getMountPoints();
610    int initialListSize  = 0;
611    for (InodeTree.MountPoint<AbstractFileSystem> im : mountPoints) {
612      initialListSize += im.target.targetDirLinkList.length; 
613    }
614    List<Token<?>> result = new ArrayList<Token<?>>(initialListSize);
615    for ( int i = 0; i < mountPoints.size(); ++i ) {
616      List<Token<?>> tokens = 
617        mountPoints.get(i).target.targetFileSystem.getDelegationTokens(renewer);
618      if (tokens != null) {
619        result.addAll(tokens);
620      }
621    }
622    return result;
623  }
624
625  @Override
626  public boolean isValidName(String src) {
627    // Prefix validated at mount time and rest of path validated by mount
628    // target.
629    return true;
630  }
631
632  @Override
633  public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
634      throws IOException {
635    InodeTree.ResolveResult<AbstractFileSystem> res =
636        fsState.resolve(getUriPath(path), true);
637    res.targetFileSystem.modifyAclEntries(res.remainingPath, aclSpec);
638  }
639
640  @Override
641  public void removeAclEntries(Path path, List<AclEntry> aclSpec)
642      throws IOException {
643    InodeTree.ResolveResult<AbstractFileSystem> res =
644        fsState.resolve(getUriPath(path), true);
645    res.targetFileSystem.removeAclEntries(res.remainingPath, aclSpec);
646  }
647
648  @Override
649  public void removeDefaultAcl(Path path)
650      throws IOException {
651    InodeTree.ResolveResult<AbstractFileSystem> res =
652        fsState.resolve(getUriPath(path), true);
653    res.targetFileSystem.removeDefaultAcl(res.remainingPath);
654  }
655
656  @Override
657  public void removeAcl(Path path)
658      throws IOException {
659    InodeTree.ResolveResult<AbstractFileSystem> res =
660        fsState.resolve(getUriPath(path), true);
661    res.targetFileSystem.removeAcl(res.remainingPath);
662  }
663
664  @Override
665  public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
666    InodeTree.ResolveResult<AbstractFileSystem> res =
667        fsState.resolve(getUriPath(path), true);
668    res.targetFileSystem.setAcl(res.remainingPath, aclSpec);
669  }
670
671  @Override
672  public AclStatus getAclStatus(Path path) throws IOException {
673    InodeTree.ResolveResult<AbstractFileSystem> res =
674        fsState.resolve(getUriPath(path), true);
675    return res.targetFileSystem.getAclStatus(res.remainingPath);
676  }
677
678  @Override
679  public void setXAttr(Path path, String name, byte[] value,
680                       EnumSet<XAttrSetFlag> flag) throws IOException {
681    InodeTree.ResolveResult<AbstractFileSystem> res =
682        fsState.resolve(getUriPath(path), true);
683    res.targetFileSystem.setXAttr(res.remainingPath, name, value, flag);
684  }
685
686  @Override
687  public byte[] getXAttr(Path path, String name) throws IOException {
688    InodeTree.ResolveResult<AbstractFileSystem> res =
689        fsState.resolve(getUriPath(path), true);
690    return res.targetFileSystem.getXAttr(res.remainingPath, name);
691  }
692
693  @Override
694  public Map<String, byte[]> getXAttrs(Path path) throws IOException {
695    InodeTree.ResolveResult<AbstractFileSystem> res =
696        fsState.resolve(getUriPath(path), true);
697    return res.targetFileSystem.getXAttrs(res.remainingPath);
698  }
699
700  @Override
701  public Map<String, byte[]> getXAttrs(Path path, List<String> names)
702      throws IOException {
703    InodeTree.ResolveResult<AbstractFileSystem> res =
704        fsState.resolve(getUriPath(path), true);
705    return res.targetFileSystem.getXAttrs(res.remainingPath, names);
706  }
707
708  @Override
709  public List<String> listXAttrs(Path path) throws IOException {
710    InodeTree.ResolveResult<AbstractFileSystem> res =
711        fsState.resolve(getUriPath(path), true);
712    return res.targetFileSystem.listXAttrs(res.remainingPath);
713  }
714
715  @Override
716  public void removeXAttr(Path path, String name) throws IOException {
717    InodeTree.ResolveResult<AbstractFileSystem> res =
718        fsState.resolve(getUriPath(path), true);
719    res.targetFileSystem.removeXAttr(res.remainingPath, name);
720  }
721
722  @Override
723  public Path createSnapshot(Path path, String snapshotName)
724      throws IOException {
725    InodeTree.ResolveResult<AbstractFileSystem> res = fsState.resolve(
726        getUriPath(path), true);
727    return res.targetFileSystem.createSnapshot(res.remainingPath, snapshotName);
728  }
729
730  @Override
731  public void renameSnapshot(Path path, String snapshotOldName,
732      String snapshotNewName) throws IOException {
733    InodeTree.ResolveResult<AbstractFileSystem> res = fsState.resolve(
734        getUriPath(path), true);
735    res.targetFileSystem.renameSnapshot(res.remainingPath, snapshotOldName,
736        snapshotNewName);
737  }
738
739  @Override
740  public void deleteSnapshot(Path path, String snapshotName) throws IOException {
741    InodeTree.ResolveResult<AbstractFileSystem> res = fsState.resolve(
742        getUriPath(path), true);
743    res.targetFileSystem.deleteSnapshot(res.remainingPath, snapshotName);
744  }
745
746  @Override
747  public void setStoragePolicy(final Path path, final String policyName)
748      throws IOException {
749    InodeTree.ResolveResult<AbstractFileSystem> res =
750        fsState.resolve(getUriPath(path), true);
751    res.targetFileSystem.setStoragePolicy(res.remainingPath, policyName);
752  }
753
754  @Override
755  public void unsetStoragePolicy(final Path src)
756      throws IOException {
757    InodeTree.ResolveResult<AbstractFileSystem> res =
758        fsState.resolve(getUriPath(src), true);
759    res.targetFileSystem.unsetStoragePolicy(res.remainingPath);
760  }
761
762  /**
763   * Retrieve the storage policy for a given file or directory.
764   *
765   * @param src file or directory path.
766   * @return storage policy for give file.
767   * @throws IOException
768   */
769  public BlockStoragePolicySpi getStoragePolicy(final Path src)
770      throws IOException {
771    InodeTree.ResolveResult<AbstractFileSystem> res =
772        fsState.resolve(getUriPath(src), true);
773    return res.targetFileSystem.getStoragePolicy(res.remainingPath);
774  }
775
776  /*
777   * An instance of this class represents an internal dir of the viewFs 
778   * ie internal dir of the mount table.
779   * It is a ready only mount tbale and create, mkdir or delete operations
780   * are not allowed.
781   * If called on create or mkdir then this target is the parent of the
782   * directory in which one is trying to create or mkdir; hence
783   * in this case the path name passed in is the last component. 
784   * Otherwise this target is the end point of the path and hence
785   * the path name passed in is null. 
786   */
787  static class InternalDirOfViewFs extends AbstractFileSystem {
788    
789    final InodeTree.INodeDir<AbstractFileSystem>  theInternalDir;
790    final long creationTime; // of the the mount table
791    final UserGroupInformation ugi; // the user/group of user who created mtable
792    final URI myUri; // the URI of the outer ViewFs
793    
794    public InternalDirOfViewFs(final InodeTree.INodeDir<AbstractFileSystem> dir,
795        final long cTime, final UserGroupInformation ugi, final URI uri)
796      throws URISyntaxException {
797      super(FsConstants.VIEWFS_URI, FsConstants.VIEWFS_SCHEME, false, -1);
798      theInternalDir = dir;
799      creationTime = cTime;
800      this.ugi = ugi;
801      myUri = uri;
802    }
803
804    static private void checkPathIsSlash(final Path f) throws IOException {
805      if (f != InodeTree.SlashPath) {
806        throw new IOException (
807        "Internal implementation error: expected file name to be /" );
808      }
809    }
810
811    @Override
812    public FSDataOutputStream createInternal(final Path f,
813        final EnumSet<CreateFlag> flag, final FsPermission absolutePermission,
814        final int bufferSize, final short replication, final long blockSize,
815        final Progressable progress, final ChecksumOpt checksumOpt,
816        final boolean createParent) throws AccessControlException,
817        FileAlreadyExistsException, FileNotFoundException,
818        ParentNotDirectoryException, UnsupportedFileSystemException,
819        UnresolvedLinkException, IOException {
820      throw readOnlyMountTable("create", f);
821    }
822
823    @Override
824    public boolean delete(final Path f, final boolean recursive)
825        throws AccessControlException, IOException {
826      checkPathIsSlash(f);
827      throw readOnlyMountTable("delete", f);
828    }
829
830    @Override
831    public BlockLocation[] getFileBlockLocations(final Path f, final long start,
832        final long len) throws FileNotFoundException, IOException {
833      checkPathIsSlash(f);
834      throw new FileNotFoundException("Path points to dir not a file");
835    }
836
837    @Override
838    public FileChecksum getFileChecksum(final Path f)
839        throws FileNotFoundException, IOException {
840      checkPathIsSlash(f);
841      throw new FileNotFoundException("Path points to dir not a file");
842    }
843
844    @Override
845    public FileStatus getFileStatus(final Path f) throws IOException {
846      checkPathIsSlash(f);
847      return new FileStatus(0, true, 0, 0, creationTime, creationTime,
848          PERMISSION_555, ugi.getShortUserName(), ugi.getPrimaryGroupName(),
849          new Path(theInternalDir.fullPath).makeQualified(
850              myUri, null));
851    }
852    
853    @Override
854    public FileStatus getFileLinkStatus(final Path f)
855        throws IOException {
856      // look up i internalDirs children - ignore first Slash
857      INode<AbstractFileSystem> inode =
858        theInternalDir.children.get(f.toUri().toString().substring(1)); 
859      if (inode == null) {
860        throw new FileNotFoundException(
861            "viewFs internal mount table - missing entry:" + f);
862      }
863      FileStatus result;
864      if (inode instanceof INodeLink) {
865        INodeLink<AbstractFileSystem> inodelink = 
866          (INodeLink<AbstractFileSystem>) inode;
867        result = new FileStatus(0, false, 0, 0, creationTime, creationTime,
868            PERMISSION_555, ugi.getShortUserName(), ugi.getPrimaryGroupName(),
869            inodelink.getTargetLink(),
870            new Path(inode.fullPath).makeQualified(
871                myUri, null));
872      } else {
873        result = new FileStatus(0, true, 0, 0, creationTime, creationTime,
874          PERMISSION_555, ugi.getShortUserName(), ugi.getPrimaryGroupName(),
875          new Path(inode.fullPath).makeQualified(
876              myUri, null));
877      }
878      return result;
879    }
880    
881    @Override
882    public FsStatus getFsStatus() {
883      return new FsStatus(0, 0, 0);
884    }
885
886    @Override
887    public FsServerDefaults getServerDefaults() throws IOException {
888      throw new IOException("FsServerDefaults not implemented yet");
889    }
890
891    @Override
892    public int getUriDefaultPort() {
893      return -1;
894    }
895
896    @Override
897    public FileStatus[] listStatus(final Path f) throws AccessControlException,
898        IOException {
899      checkPathIsSlash(f);
900      FileStatus[] result = new FileStatus[theInternalDir.children.size()];
901      int i = 0;
902      for (Entry<String, INode<AbstractFileSystem>> iEntry : 
903                                          theInternalDir.children.entrySet()) {
904        INode<AbstractFileSystem> inode = iEntry.getValue();
905
906        
907        if (inode instanceof INodeLink ) {
908          INodeLink<AbstractFileSystem> link = 
909            (INodeLink<AbstractFileSystem>) inode;
910
911          result[i++] = new FileStatus(0, false, 0, 0,
912            creationTime, creationTime,
913            PERMISSION_555, ugi.getShortUserName(), ugi.getPrimaryGroupName(),
914            link.getTargetLink(),
915            new Path(inode.fullPath).makeQualified(
916                myUri, null));
917        } else {
918          result[i++] = new FileStatus(0, true, 0, 0,
919            creationTime, creationTime,
920            PERMISSION_555, ugi.getShortUserName(), ugi.getGroupNames()[0],
921            new Path(inode.fullPath).makeQualified(
922                myUri, null));
923        }
924      }
925      return result;
926    }
927
928    @Override
929    public void mkdir(final Path dir, final FsPermission permission,
930        final boolean createParent) throws AccessControlException,
931        FileAlreadyExistsException {
932      if (theInternalDir.isRoot && dir == null) {
933        throw new FileAlreadyExistsException("/ already exits");
934      }
935      throw readOnlyMountTable("mkdir", dir);
936    }
937
938    @Override
939    public FSDataInputStream open(final Path f, final int bufferSize)
940        throws FileNotFoundException, IOException {
941      checkPathIsSlash(f);
942      throw new FileNotFoundException("Path points to dir not a file");
943    }
944
945    @Override
946    public boolean truncate(final Path f, final long newLength)
947        throws FileNotFoundException, IOException {
948      checkPathIsSlash(f);
949      throw readOnlyMountTable("truncate", f);
950    }
951
952    @Override
953    public void renameInternal(final Path src, final Path dst)
954        throws AccessControlException, IOException {
955      checkPathIsSlash(src);
956      checkPathIsSlash(dst);
957      throw readOnlyMountTable("rename", src);     
958    }
959
960    @Override
961    public boolean supportsSymlinks() {
962      return true;
963    }
964    
965    @Override
966    public void createSymlink(final Path target, final Path link,
967        final boolean createParent) throws AccessControlException {
968      throw readOnlyMountTable("createSymlink", link);    
969    }
970
971    @Override
972    public Path getLinkTarget(final Path f) throws FileNotFoundException,
973        IOException {
974      return getFileLinkStatus(f).getSymlink();
975    }
976
977    @Override
978    public void setOwner(final Path f, final String username,
979        final String groupname) throws AccessControlException, IOException {
980      checkPathIsSlash(f);
981      throw readOnlyMountTable("setOwner", f);
982    }
983
984    @Override
985    public void setPermission(final Path f, final FsPermission permission)
986        throws AccessControlException, IOException {
987      checkPathIsSlash(f);
988      throw readOnlyMountTable("setPermission", f);    
989    }
990
991    @Override
992    public boolean setReplication(final Path f, final short replication)
993        throws AccessControlException, IOException {
994      checkPathIsSlash(f);
995      throw readOnlyMountTable("setReplication", f);
996    }
997
998    @Override
999    public void setTimes(final Path f, final long mtime, final long atime)
1000        throws AccessControlException, IOException {
1001      checkPathIsSlash(f);
1002      throw readOnlyMountTable("setTimes", f);    
1003    }
1004
1005    @Override
1006    public void setVerifyChecksum(final boolean verifyChecksum)
1007        throws AccessControlException {
1008      throw readOnlyMountTable("setVerifyChecksum", "");   
1009    }
1010
1011    @Override
1012    public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
1013        throws IOException {
1014      checkPathIsSlash(path);
1015      throw readOnlyMountTable("modifyAclEntries", path);
1016    }
1017
1018    @Override
1019    public void removeAclEntries(Path path, List<AclEntry> aclSpec)
1020        throws IOException {
1021      checkPathIsSlash(path);
1022      throw readOnlyMountTable("removeAclEntries", path);
1023    }
1024
1025    @Override
1026    public void removeDefaultAcl(Path path) throws IOException {
1027      checkPathIsSlash(path);
1028      throw readOnlyMountTable("removeDefaultAcl", path);
1029    }
1030
1031    @Override
1032    public void removeAcl(Path path) throws IOException {
1033      checkPathIsSlash(path);
1034      throw readOnlyMountTable("removeAcl", path);
1035    }
1036
1037    @Override
1038    public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
1039      checkPathIsSlash(path);
1040      throw readOnlyMountTable("setAcl", path);
1041    }
1042
1043    @Override
1044    public AclStatus getAclStatus(Path path) throws IOException {
1045      checkPathIsSlash(path);
1046      return new AclStatus.Builder().owner(ugi.getShortUserName())
1047          .group(ugi.getPrimaryGroupName())
1048          .addEntries(AclUtil.getMinimalAcl(PERMISSION_555))
1049          .stickyBit(false).build();
1050    }
1051
1052    @Override
1053    public void setXAttr(Path path, String name, byte[] value,
1054                         EnumSet<XAttrSetFlag> flag) throws IOException {
1055      checkPathIsSlash(path);
1056      throw readOnlyMountTable("setXAttr", path);
1057    }
1058
1059    @Override
1060    public byte[] getXAttr(Path path, String name) throws IOException {
1061      throw new NotInMountpointException(path, "getXAttr");
1062    }
1063
1064    @Override
1065    public Map<String, byte[]> getXAttrs(Path path) throws IOException {
1066      throw new NotInMountpointException(path, "getXAttrs");
1067    }
1068
1069    @Override
1070    public Map<String, byte[]> getXAttrs(Path path, List<String> names)
1071        throws IOException {
1072      throw new NotInMountpointException(path, "getXAttrs");
1073    }
1074
1075    @Override
1076    public List<String> listXAttrs(Path path) throws IOException {
1077      throw new NotInMountpointException(path, "listXAttrs");
1078    }
1079
1080    @Override
1081    public void removeXAttr(Path path, String name) throws IOException {
1082      checkPathIsSlash(path);
1083      throw readOnlyMountTable("removeXAttr", path);
1084    }
1085
1086    @Override
1087    public Path createSnapshot(Path path, String snapshotName)
1088        throws IOException {
1089      checkPathIsSlash(path);
1090      throw readOnlyMountTable("createSnapshot", path);
1091    }
1092
1093    @Override
1094    public void renameSnapshot(Path path, String snapshotOldName,
1095        String snapshotNewName) throws IOException {
1096      checkPathIsSlash(path);
1097      throw readOnlyMountTable("renameSnapshot", path);
1098    }
1099
1100    @Override
1101    public void deleteSnapshot(Path path, String snapshotName)
1102        throws IOException {
1103      checkPathIsSlash(path);
1104      throw readOnlyMountTable("deleteSnapshot", path);
1105    }
1106
1107    @Override
1108    public void setStoragePolicy(Path path, String policyName)
1109        throws IOException {
1110      throw readOnlyMountTable("setStoragePolicy", path);
1111    }
1112  }
1113}