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