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  
456  @Override
457  public void renameInternal(final Path src, final Path dst,
458      final boolean overwrite) throws IOException, UnresolvedLinkException {
459    // passing resolveLastComponet as false to catch renaming a mount point 
460    // itself we need to catch this as an internal operation and fail.
461    InodeTree.ResolveResult<AbstractFileSystem> resSrc = 
462      fsState.resolve(getUriPath(src), false); 
463  
464    if (resSrc.isInternalDir()) {
465      throw new AccessControlException(
466          "Cannot Rename within internal dirs of mount table: src=" + src
467              + " is readOnly");
468    }
469
470    InodeTree.ResolveResult<AbstractFileSystem> resDst = 
471                                fsState.resolve(getUriPath(dst), false);
472    if (resDst.isInternalDir()) {
473      throw new AccessControlException(
474          "Cannot Rename within internal dirs of mount table: dest=" + dst
475              + " is readOnly");
476    }
477    
478    /**
479    // Alternate 1: renames within same file system - valid but we disallow
480    // Alternate 2: (as described in next para - valid but we have disallowed it
481    //
482    // Note we compare the URIs. the URIs include the link targets. 
483    // hence we allow renames across mount links as long as the mount links
484    // point to the same target.
485    if (!resSrc.targetFileSystem.getUri().equals(
486              resDst.targetFileSystem.getUri())) {
487      throw new IOException("Renames across Mount points not supported");
488    }
489    */
490    
491    //
492    // Alternate 3 : renames ONLY within the the same mount links.
493    //
494
495    if (resSrc.targetFileSystem !=resDst.targetFileSystem) {
496      throw new IOException("Renames across Mount points not supported");
497    }
498    
499    resSrc.targetFileSystem.renameInternal(resSrc.remainingPath,
500      resDst.remainingPath, overwrite);
501  }
502
503  @Override
504  public void renameInternal(final Path src, final Path dst)
505      throws AccessControlException, FileAlreadyExistsException,
506      FileNotFoundException, ParentNotDirectoryException,
507      UnresolvedLinkException, IOException {
508    renameInternal(src, dst, false);
509  }
510  
511  @Override
512  public boolean supportsSymlinks() {
513    return true;
514  }
515  
516  @Override
517  public void createSymlink(final Path target, final Path link,
518      final boolean createParent) throws IOException, UnresolvedLinkException {
519    InodeTree.ResolveResult<AbstractFileSystem> res;
520    try {
521      res = fsState.resolve(getUriPath(link), false);
522    } catch (FileNotFoundException e) {
523      if (createParent) {
524        throw readOnlyMountTable("createSymlink", link);
525      } else {
526        throw e;
527      }
528    }
529    assert(res.remainingPath != null);
530    res.targetFileSystem.createSymlink(target, res.remainingPath,
531        createParent);  
532  }
533
534  @Override
535  public Path getLinkTarget(final Path f) throws IOException {
536    InodeTree.ResolveResult<AbstractFileSystem> res = 
537      fsState.resolve(getUriPath(f), false); // do not follow mount link
538    return res.targetFileSystem.getLinkTarget(res.remainingPath);
539  }
540
541  @Override
542  public void setOwner(final Path f, final String username,
543      final String groupname) throws AccessControlException,
544      FileNotFoundException, UnresolvedLinkException, IOException {
545    InodeTree.ResolveResult<AbstractFileSystem> res = 
546      fsState.resolve(getUriPath(f), true);
547    res.targetFileSystem.setOwner(res.remainingPath, username, groupname); 
548  }
549
550  @Override
551  public void setPermission(final Path f, final FsPermission permission)
552      throws AccessControlException, FileNotFoundException,
553      UnresolvedLinkException, IOException {
554    InodeTree.ResolveResult<AbstractFileSystem> res = 
555      fsState.resolve(getUriPath(f), true);
556    res.targetFileSystem.setPermission(res.remainingPath, permission); 
557    
558  }
559
560  @Override
561  public boolean setReplication(final Path f, final short replication)
562      throws AccessControlException, FileNotFoundException,
563      UnresolvedLinkException, IOException {
564    InodeTree.ResolveResult<AbstractFileSystem> res = 
565      fsState.resolve(getUriPath(f), true);
566    return res.targetFileSystem.setReplication(res.remainingPath, replication);
567  }
568
569  @Override
570  public void setTimes(final Path f, final long mtime, final long atime)
571      throws AccessControlException, FileNotFoundException,
572      UnresolvedLinkException, IOException {
573    InodeTree.ResolveResult<AbstractFileSystem> res = 
574      fsState.resolve(getUriPath(f), true);
575    res.targetFileSystem.setTimes(res.remainingPath, mtime, atime); 
576  }
577
578  @Override
579  public void setVerifyChecksum(final boolean verifyChecksum)
580      throws AccessControlException, IOException {
581    // This is a file system level operations, however ViewFs 
582    // points to many file systems. Noop for ViewFs. 
583  }
584  
585  public MountPoint[] getMountPoints() {
586    List<InodeTree.MountPoint<AbstractFileSystem>> mountPoints = 
587                  fsState.getMountPoints();
588    
589    MountPoint[] result = new MountPoint[mountPoints.size()];
590    for ( int i = 0; i < mountPoints.size(); ++i ) {
591      result[i] = new MountPoint(new Path(mountPoints.get(i).src), 
592                              mountPoints.get(i).target.targetDirLinkList);
593    }
594    return result;
595  }
596  
597  @Override
598  public List<Token<?>> getDelegationTokens(String renewer) throws IOException {
599    List<InodeTree.MountPoint<AbstractFileSystem>> mountPoints = 
600                fsState.getMountPoints();
601    int initialListSize  = 0;
602    for (InodeTree.MountPoint<AbstractFileSystem> im : mountPoints) {
603      initialListSize += im.target.targetDirLinkList.length; 
604    }
605    List<Token<?>> result = new ArrayList<Token<?>>(initialListSize);
606    for ( int i = 0; i < mountPoints.size(); ++i ) {
607      List<Token<?>> tokens = 
608        mountPoints.get(i).target.targetFileSystem.getDelegationTokens(renewer);
609      if (tokens != null) {
610        result.addAll(tokens);
611      }
612    }
613    return result;
614  }
615
616  @Override
617  public boolean isValidName(String src) {
618    // Prefix validated at mount time and rest of path validated by mount target.
619    return true;
620  }
621
622  @Override
623  public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
624      throws IOException {
625    InodeTree.ResolveResult<AbstractFileSystem> res =
626        fsState.resolve(getUriPath(path), true);
627    res.targetFileSystem.modifyAclEntries(res.remainingPath, aclSpec);
628  }
629
630  @Override
631  public void removeAclEntries(Path path, List<AclEntry> aclSpec)
632      throws IOException {
633    InodeTree.ResolveResult<AbstractFileSystem> res =
634        fsState.resolve(getUriPath(path), true);
635    res.targetFileSystem.removeAclEntries(res.remainingPath, aclSpec);
636  }
637
638  @Override
639  public void removeDefaultAcl(Path path)
640      throws IOException {
641    InodeTree.ResolveResult<AbstractFileSystem> res =
642        fsState.resolve(getUriPath(path), true);
643    res.targetFileSystem.removeDefaultAcl(res.remainingPath);
644  }
645
646  @Override
647  public void removeAcl(Path path)
648      throws IOException {
649    InodeTree.ResolveResult<AbstractFileSystem> res =
650        fsState.resolve(getUriPath(path), true);
651    res.targetFileSystem.removeAcl(res.remainingPath);
652  }
653
654  @Override
655  public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
656    InodeTree.ResolveResult<AbstractFileSystem> res =
657        fsState.resolve(getUriPath(path), true);
658    res.targetFileSystem.setAcl(res.remainingPath, aclSpec);
659  }
660
661  @Override
662  public AclStatus getAclStatus(Path path) throws IOException {
663    InodeTree.ResolveResult<AbstractFileSystem> res =
664        fsState.resolve(getUriPath(path), true);
665    return res.targetFileSystem.getAclStatus(res.remainingPath);
666  }
667
668  @Override
669  public void setXAttr(Path path, String name, byte[] value,
670                       EnumSet<XAttrSetFlag> flag) throws IOException {
671    InodeTree.ResolveResult<AbstractFileSystem> res =
672        fsState.resolve(getUriPath(path), true);
673    res.targetFileSystem.setXAttr(res.remainingPath, name, value, flag);
674  }
675
676  @Override
677  public byte[] getXAttr(Path path, String name) throws IOException {
678    InodeTree.ResolveResult<AbstractFileSystem> res =
679        fsState.resolve(getUriPath(path), true);
680    return res.targetFileSystem.getXAttr(res.remainingPath, name);
681  }
682
683  @Override
684  public Map<String, byte[]> getXAttrs(Path path) throws IOException {
685    InodeTree.ResolveResult<AbstractFileSystem> res =
686        fsState.resolve(getUriPath(path), true);
687    return res.targetFileSystem.getXAttrs(res.remainingPath);
688  }
689
690  @Override
691  public Map<String, byte[]> getXAttrs(Path path, List<String> names)
692      throws IOException {
693    InodeTree.ResolveResult<AbstractFileSystem> res =
694        fsState.resolve(getUriPath(path), true);
695    return res.targetFileSystem.getXAttrs(res.remainingPath, names);
696  }
697
698  @Override
699  public List<String> listXAttrs(Path path) throws IOException {
700    InodeTree.ResolveResult<AbstractFileSystem> res =
701        fsState.resolve(getUriPath(path), true);
702    return res.targetFileSystem.listXAttrs(res.remainingPath);
703  }
704
705  @Override
706  public void removeXAttr(Path path, String name) throws IOException {
707    InodeTree.ResolveResult<AbstractFileSystem> res =
708        fsState.resolve(getUriPath(path), true);
709    res.targetFileSystem.removeXAttr(res.remainingPath, name);
710  }
711  
712  
713  /*
714   * An instance of this class represents an internal dir of the viewFs 
715   * ie internal dir of the mount table.
716   * It is a ready only mount tbale and create, mkdir or delete operations
717   * are not allowed.
718   * If called on create or mkdir then this target is the parent of the
719   * directory in which one is trying to create or mkdir; hence
720   * in this case the path name passed in is the last component. 
721   * Otherwise this target is the end point of the path and hence
722   * the path name passed in is null. 
723   */
724  static class InternalDirOfViewFs extends AbstractFileSystem {
725    
726    final InodeTree.INodeDir<AbstractFileSystem>  theInternalDir;
727    final long creationTime; // of the the mount table
728    final UserGroupInformation ugi; // the user/group of user who created mtable
729    final URI myUri; // the URI of the outer ViewFs
730    
731    public InternalDirOfViewFs(final InodeTree.INodeDir<AbstractFileSystem> dir,
732        final long cTime, final UserGroupInformation ugi, final URI uri)
733      throws URISyntaxException {
734      super(FsConstants.VIEWFS_URI, FsConstants.VIEWFS_SCHEME, false, -1);
735      theInternalDir = dir;
736      creationTime = cTime;
737      this.ugi = ugi;
738      myUri = uri;
739    }
740
741    static private void checkPathIsSlash(final Path f) throws IOException {
742      if (f != InodeTree.SlashPath) {
743        throw new IOException (
744        "Internal implementation error: expected file name to be /" );
745      }
746    }
747
748    @Override
749    public FSDataOutputStream createInternal(final Path f,
750        final EnumSet<CreateFlag> flag, final FsPermission absolutePermission,
751        final int bufferSize, final short replication, final long blockSize,
752        final Progressable progress, final ChecksumOpt checksumOpt,
753        final boolean createParent) throws AccessControlException,
754        FileAlreadyExistsException, FileNotFoundException,
755        ParentNotDirectoryException, UnsupportedFileSystemException,
756        UnresolvedLinkException, IOException {
757      throw readOnlyMountTable("create", f);
758    }
759
760    @Override
761    public boolean delete(final Path f, final boolean recursive)
762        throws AccessControlException, IOException {
763      checkPathIsSlash(f);
764      throw readOnlyMountTable("delete", f);
765    }
766
767    @Override
768    public BlockLocation[] getFileBlockLocations(final Path f, final long start,
769        final long len) throws FileNotFoundException, IOException {
770      checkPathIsSlash(f);
771      throw new FileNotFoundException("Path points to dir not a file");
772    }
773
774    @Override
775    public FileChecksum getFileChecksum(final Path f)
776        throws FileNotFoundException, IOException {
777      checkPathIsSlash(f);
778      throw new FileNotFoundException("Path points to dir not a file");
779    }
780
781    @Override
782    public FileStatus getFileStatus(final Path f) throws IOException {
783      checkPathIsSlash(f);
784      return new FileStatus(0, true, 0, 0, creationTime, creationTime,
785          PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
786          new Path(theInternalDir.fullPath).makeQualified(
787              myUri, null));
788    }
789    
790    @Override
791    public FileStatus getFileLinkStatus(final Path f)
792        throws IOException {
793      // look up i internalDirs children - ignore first Slash
794      INode<AbstractFileSystem> inode =
795        theInternalDir.children.get(f.toUri().toString().substring(1)); 
796      if (inode == null) {
797        throw new FileNotFoundException(
798            "viewFs internal mount table - missing entry:" + f);
799      }
800      FileStatus result;
801      if (inode instanceof INodeLink) {
802        INodeLink<AbstractFileSystem> inodelink = 
803          (INodeLink<AbstractFileSystem>) inode;
804        result = new FileStatus(0, false, 0, 0, creationTime, creationTime,
805            PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
806            inodelink.getTargetLink(),
807            new Path(inode.fullPath).makeQualified(
808                myUri, null));
809      } else {
810        result = new FileStatus(0, true, 0, 0, creationTime, creationTime,
811          PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
812          new Path(inode.fullPath).makeQualified(
813              myUri, null));
814      }
815      return result;
816    }
817    
818    @Override
819    public FsStatus getFsStatus() {
820      return new FsStatus(0, 0, 0);
821    }
822
823    @Override
824    public FsServerDefaults getServerDefaults() throws IOException {
825      throw new IOException("FsServerDefaults not implemented yet");
826    }
827
828    @Override
829    public int getUriDefaultPort() {
830      return -1;
831    }
832
833    @Override
834    public FileStatus[] listStatus(final Path f) throws AccessControlException,
835        IOException {
836      checkPathIsSlash(f);
837      FileStatus[] result = new FileStatus[theInternalDir.children.size()];
838      int i = 0;
839      for (Entry<String, INode<AbstractFileSystem>> iEntry : 
840                                          theInternalDir.children.entrySet()) {
841        INode<AbstractFileSystem> inode = iEntry.getValue();
842
843        
844        if (inode instanceof INodeLink ) {
845          INodeLink<AbstractFileSystem> link = 
846            (INodeLink<AbstractFileSystem>) inode;
847
848          result[i++] = new FileStatus(0, false, 0, 0,
849            creationTime, creationTime,
850            PERMISSION_555, ugi.getUserName(), ugi.getPrimaryGroupName(),
851            link.getTargetLink(),
852            new Path(inode.fullPath).makeQualified(
853                myUri, null));
854        } else {
855          result[i++] = new FileStatus(0, true, 0, 0,
856            creationTime, creationTime,
857            PERMISSION_555, ugi.getUserName(), ugi.getGroupNames()[0],
858            new Path(inode.fullPath).makeQualified(
859                myUri, null));
860        }
861      }
862      return result;
863    }
864
865    @Override
866    public void mkdir(final Path dir, final FsPermission permission,
867        final boolean createParent) throws AccessControlException,
868        FileAlreadyExistsException {
869      if (theInternalDir.isRoot && dir == null) {
870        throw new FileAlreadyExistsException("/ already exits");
871      }
872      throw readOnlyMountTable("mkdir", dir);
873    }
874
875    @Override
876    public FSDataInputStream open(final Path f, final int bufferSize)
877        throws FileNotFoundException, IOException {
878      checkPathIsSlash(f);
879      throw new FileNotFoundException("Path points to dir not a file");
880    }
881
882    @Override
883    public void renameInternal(final Path src, final Path dst)
884        throws AccessControlException, IOException {
885      checkPathIsSlash(src);
886      checkPathIsSlash(dst);
887      throw readOnlyMountTable("rename", src);     
888    }
889
890    @Override
891    public boolean supportsSymlinks() {
892      return true;
893    }
894    
895    @Override
896    public void createSymlink(final Path target, final Path link,
897        final boolean createParent) throws AccessControlException {
898      throw readOnlyMountTable("createSymlink", link);    
899    }
900
901    @Override
902    public Path getLinkTarget(final Path f) throws FileNotFoundException,
903        IOException {
904      return getFileLinkStatus(f).getSymlink();
905    }
906
907    @Override
908    public void setOwner(final Path f, final String username,
909        final String groupname) throws AccessControlException, IOException {
910      checkPathIsSlash(f);
911      throw readOnlyMountTable("setOwner", f);
912    }
913
914    @Override
915    public void setPermission(final Path f, final FsPermission permission)
916        throws AccessControlException, IOException {
917      checkPathIsSlash(f);
918      throw readOnlyMountTable("setPermission", f);    
919    }
920
921    @Override
922    public boolean setReplication(final Path f, final short replication)
923        throws AccessControlException, IOException {
924      checkPathIsSlash(f);
925      throw readOnlyMountTable("setReplication", f);
926    }
927
928    @Override
929    public void setTimes(final Path f, final long mtime, final long atime)
930        throws AccessControlException, IOException {
931      checkPathIsSlash(f);
932      throw readOnlyMountTable("setTimes", f);    
933    }
934
935    @Override
936    public void setVerifyChecksum(final boolean verifyChecksum)
937        throws AccessControlException {
938      throw readOnlyMountTable("setVerifyChecksum", "");   
939    }
940
941    @Override
942    public void modifyAclEntries(Path path, List<AclEntry> aclSpec)
943        throws IOException {
944      checkPathIsSlash(path);
945      throw readOnlyMountTable("modifyAclEntries", path);
946    }
947
948    @Override
949    public void removeAclEntries(Path path, List<AclEntry> aclSpec)
950        throws IOException {
951      checkPathIsSlash(path);
952      throw readOnlyMountTable("removeAclEntries", path);
953    }
954
955    @Override
956    public void removeDefaultAcl(Path path) throws IOException {
957      checkPathIsSlash(path);
958      throw readOnlyMountTable("removeDefaultAcl", path);
959    }
960
961    @Override
962    public void removeAcl(Path path) throws IOException {
963      checkPathIsSlash(path);
964      throw readOnlyMountTable("removeAcl", path);
965    }
966
967    @Override
968    public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
969      checkPathIsSlash(path);
970      throw readOnlyMountTable("setAcl", path);
971    }
972
973    @Override
974    public AclStatus getAclStatus(Path path) throws IOException {
975      checkPathIsSlash(path);
976      return new AclStatus.Builder().owner(ugi.getUserName())
977          .group(ugi.getPrimaryGroupName())
978          .addEntries(AclUtil.getMinimalAcl(PERMISSION_555))
979          .stickyBit(false).build();
980    }
981
982    @Override
983    public void setXAttr(Path path, String name, byte[] value,
984                         EnumSet<XAttrSetFlag> flag) throws IOException {
985      checkPathIsSlash(path);
986      throw readOnlyMountTable("setXAttr", path);
987    }
988
989    @Override
990    public byte[] getXAttr(Path path, String name) throws IOException {
991      throw new NotInMountpointException(path, "getXAttr");
992    }
993
994    @Override
995    public Map<String, byte[]> getXAttrs(Path path) throws IOException {
996      throw new NotInMountpointException(path, "getXAttrs");
997    }
998
999    @Override
1000    public Map<String, byte[]> getXAttrs(Path path, List<String> names)
1001        throws IOException {
1002      throw new NotInMountpointException(path, "getXAttrs");
1003    }
1004
1005    @Override
1006    public List<String> listXAttrs(Path path) throws IOException {
1007      throw new NotInMountpointException(path, "listXAttrs");
1008    }
1009
1010    @Override
1011    public void removeXAttr(Path path, String name) throws IOException {
1012      checkPathIsSlash(path);
1013      throw readOnlyMountTable("removeXAttr", path);
1014    }
1015  }
1016}