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     */
018    package org.apache.hadoop.fs.viewfs;
019    
020    import static org.apache.hadoop.fs.viewfs.Constants.PERMISSION_RRR;
021    
022    import java.io.FileNotFoundException;
023    import java.io.IOException;
024    import java.net.URI;
025    import java.net.URISyntaxException;
026    import java.util.Arrays;
027    import java.util.EnumSet;
028    import java.util.HashSet;
029    import java.util.List;
030    import java.util.Set;
031    import java.util.StringTokenizer;
032    import java.util.Map.Entry;
033    
034    import org.apache.hadoop.classification.InterfaceAudience;
035    import org.apache.hadoop.classification.InterfaceStability;
036    import org.apache.hadoop.conf.Configuration;
037    import org.apache.hadoop.fs.BlockLocation;
038    import org.apache.hadoop.fs.ContentSummary;
039    import org.apache.hadoop.fs.CreateFlag;
040    import org.apache.hadoop.fs.FSDataInputStream;
041    import org.apache.hadoop.fs.FSDataOutputStream;
042    import org.apache.hadoop.fs.FileAlreadyExistsException;
043    import org.apache.hadoop.fs.FileChecksum;
044    import org.apache.hadoop.fs.FileStatus;
045    import org.apache.hadoop.fs.FileSystem;
046    import org.apache.hadoop.fs.FsConstants;
047    import org.apache.hadoop.fs.FsServerDefaults;
048    import org.apache.hadoop.fs.InvalidPathException;
049    import org.apache.hadoop.fs.Path;
050    import org.apache.hadoop.fs.UnsupportedFileSystemException;
051    import org.apache.hadoop.fs.permission.FsPermission;
052    import org.apache.hadoop.fs.viewfs.InodeTree.INode;
053    import org.apache.hadoop.fs.viewfs.InodeTree.INodeLink;
054    import org.apache.hadoop.security.AccessControlException;
055    import org.apache.hadoop.security.UserGroupInformation;
056    import org.apache.hadoop.util.Progressable;
057    import org.apache.hadoop.util.Time;
058    
059    /**
060     * ViewFileSystem (extends the FileSystem interface) implements a client-side
061     * mount table. Its spec and implementation is identical to {@link ViewFs}.
062     */
063    
064    @InterfaceAudience.Public
065    @InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */
066    public class ViewFileSystem extends FileSystem {
067      static AccessControlException readOnlyMountTable(final String operation,
068          final String p) {
069        return new AccessControlException( 
070            "InternalDir of ViewFileSystem is readonly; operation=" + operation + 
071            "Path=" + p);
072      }
073      static AccessControlException readOnlyMountTable(final String operation,
074          final Path p) {
075        return readOnlyMountTable(operation, p.toString());
076      }
077      
078      static public class MountPoint {
079        private Path src;       // the src of the mount
080        private URI[] targets; //  target of the mount; Multiple targets imply mergeMount
081        MountPoint(Path srcPath, URI[] targetURIs) {
082          src = srcPath;
083          targets = targetURIs;
084        }
085        Path getSrc() {
086          return src;
087        }
088        URI[] getTargets() {
089          return targets;
090        }
091      }
092      
093      final long creationTime; // of the the mount table
094      final UserGroupInformation ugi; // the user/group of user who created mtable
095      URI myUri;
096      private Path workingDir;
097      Configuration config;
098      InodeTree<FileSystem> fsState;  // the fs state; ie the mount table
099      Path homeDir = null;
100      
101      /**
102       * Prohibits names which contain a ".", "..", ":" or "/" 
103       */
104      private static boolean isValidName(final String src) {
105        // Check for ".." "." ":" "/"
106        final StringTokenizer tokens = new StringTokenizer(src, Path.SEPARATOR);
107        while(tokens.hasMoreTokens()) {
108          String element = tokens.nextToken();
109          if (element.equals("..") ||
110              element.equals(".")  ||
111              (element.indexOf(":") >= 0)) {
112            return false;
113          }
114        }
115        return true;
116      }
117      
118      /**
119       * Make the path Absolute and get the path-part of a pathname.
120       * Checks that URI matches this file system 
121       * and that the path-part is a valid name.
122       * 
123       * @param p path
124       * @return path-part of the Path p
125       */
126      private String getUriPath(final Path p) {
127        checkPath(p);
128        String s = makeAbsolute(p).toUri().getPath();
129        if (!isValidName(s)) {
130          throw new InvalidPathException("Path part " + s + " from URI" + p
131              + " is not a valid filename.");
132        }
133        return s;
134      }
135      
136      private Path makeAbsolute(final Path f) {
137        return f.isAbsolute() ? f : new Path(workingDir, f);
138      }
139      
140      /**
141       * This is the  constructor with the signature needed by
142       * {@link FileSystem#createFileSystem(URI, Configuration)}
143       * 
144       * After this constructor is called initialize() is called.
145       * @throws IOException 
146       */
147      public ViewFileSystem() throws IOException {
148        ugi = UserGroupInformation.getCurrentUser();
149        creationTime = Time.now();
150      }
151    
152      /**
153       * Return the protocol scheme for the FileSystem.
154       * <p/>
155       *
156       * @return <code>viewfs</code>
157       */
158      @Override
159      public String getScheme() {
160        return "viewfs";
161      }
162    
163      /**
164       * Called after a new FileSystem instance is constructed.
165       * @param theUri a uri whose authority section names the host, port, etc. for
166       *          this FileSystem
167       * @param conf the configuration
168       */
169      @Override
170      public void initialize(final URI theUri, final Configuration conf)
171          throws IOException {
172        super.initialize(theUri, conf);
173        setConf(conf);
174        config = conf;
175        // Now build  client side view (i.e. client side mount table) from config.
176        final String authority = theUri.getAuthority();
177        try {
178          myUri = new URI(FsConstants.VIEWFS_SCHEME, authority, "/", null, null);
179          fsState = new InodeTree<FileSystem>(conf, authority) {
180    
181            @Override
182            protected
183            FileSystem getTargetFileSystem(final URI uri)
184              throws URISyntaxException, IOException {
185                return new ChRootedFileSystem(uri, config);
186            }
187    
188            @Override
189            protected
190            FileSystem getTargetFileSystem(final INodeDir<FileSystem> dir)
191              throws URISyntaxException {
192              return new InternalDirOfViewFs(dir, creationTime, ugi, myUri);
193            }
194    
195            @Override
196            protected
197            FileSystem getTargetFileSystem(URI[] mergeFsURIList)
198                throws URISyntaxException, UnsupportedFileSystemException {
199              throw new UnsupportedFileSystemException("mergefs not implemented");
200              // return MergeFs.createMergeFs(mergeFsURIList, config);
201            }
202          };
203          workingDir = this.getHomeDirectory();
204        } catch (URISyntaxException e) {
205          throw new IOException("URISyntax exception: " + theUri);
206        }
207    
208      }
209      
210      
211      /**
212       * Convenience Constructor for apps to call directly
213       * @param theUri which must be that of ViewFileSystem
214       * @param conf
215       * @throws IOException
216       */
217      ViewFileSystem(final URI theUri, final Configuration conf)
218        throws IOException {
219        this();
220        initialize(theUri, conf);
221      }
222      
223      /**
224       * Convenience Constructor for apps to call directly
225       * @param conf
226       * @throws IOException
227       */
228      public ViewFileSystem(final Configuration conf) throws IOException {
229        this(FsConstants.VIEWFS_URI, conf);
230      }
231      
232      public Path getTrashCanLocation(final Path f) throws FileNotFoundException {
233        final InodeTree.ResolveResult<FileSystem> res = 
234          fsState.resolve(getUriPath(f), true);
235        return res.isInternalDir() ? null : res.targetFileSystem.getHomeDirectory();
236      }
237      
238      @Override
239      public URI getUri() {
240        return myUri;
241      }
242      
243      @Override
244      public Path resolvePath(final Path f)
245          throws IOException {
246        final InodeTree.ResolveResult<FileSystem> res;
247          res = fsState.resolve(getUriPath(f), true);
248        if (res.isInternalDir()) {
249          return f;
250        }
251        return res.targetFileSystem.resolvePath(res.remainingPath);
252      }
253      
254      @Override
255      public Path getHomeDirectory() {
256        if (homeDir == null) {
257          String base = fsState.getHomeDirPrefixValue();
258          if (base == null) {
259            base = "/user";
260          }
261          homeDir = (base.equals("/") ? 
262              this.makeQualified(new Path(base + ugi.getShortUserName())):
263              this.makeQualified(new Path(base + "/" + ugi.getShortUserName())));
264        }
265        return homeDir;
266      }
267      
268      @Override
269      public Path getWorkingDirectory() {
270        return workingDir;
271      }
272    
273      @Override
274      public void setWorkingDirectory(final Path new_dir) {
275        getUriPath(new_dir); // this validates the path
276        workingDir = makeAbsolute(new_dir);
277      }
278      
279      @Override
280      public FSDataOutputStream append(final Path f, final int bufferSize,
281          final Progressable progress) throws IOException {
282        InodeTree.ResolveResult<FileSystem> res = 
283          fsState.resolve(getUriPath(f), true);
284        return res.targetFileSystem.append(res.remainingPath, bufferSize, progress);
285      }
286      
287      @Override
288      public FSDataOutputStream createNonRecursive(Path f, FsPermission permission,
289          EnumSet<CreateFlag> flags, int bufferSize, short replication, long blockSize,
290          Progressable progress) throws IOException {
291        InodeTree.ResolveResult<FileSystem> res;
292        try {
293          res = fsState.resolve(getUriPath(f), false);
294        } catch (FileNotFoundException e) {
295            throw readOnlyMountTable("create", f);
296        }
297        assert(res.remainingPath != null);
298        return res.targetFileSystem.createNonRecursive(res.remainingPath, permission,
299             flags, bufferSize, replication, blockSize, progress);
300      }
301      
302      @Override
303      public FSDataOutputStream create(final Path f, final FsPermission permission,
304          final boolean overwrite, final int bufferSize, final short replication,
305          final long blockSize, final Progressable progress) throws IOException {
306        InodeTree.ResolveResult<FileSystem> res;
307        try {
308          res = fsState.resolve(getUriPath(f), false);
309        } catch (FileNotFoundException e) {
310            throw readOnlyMountTable("create", f);
311        }
312        assert(res.remainingPath != null);
313        return res.targetFileSystem.create(res.remainingPath, permission,
314             overwrite, bufferSize, replication, blockSize, progress);
315      }
316    
317      
318      @Override
319      public boolean delete(final Path f, final boolean recursive)
320          throws AccessControlException, FileNotFoundException,
321          IOException {
322        InodeTree.ResolveResult<FileSystem> res = 
323          fsState.resolve(getUriPath(f), true);
324        // If internal dir or target is a mount link (ie remainingPath is Slash)
325        if (res.isInternalDir() || res.remainingPath == InodeTree.SlashPath) {
326          throw readOnlyMountTable("delete", f);
327        }
328        return res.targetFileSystem.delete(res.remainingPath, recursive);
329      }
330      
331      @Override
332      @SuppressWarnings("deprecation")
333      public boolean delete(final Path f)
334          throws AccessControlException, FileNotFoundException,
335          IOException {
336          return delete(f, true);
337      }
338      
339      @Override
340      public BlockLocation[] getFileBlockLocations(FileStatus fs, 
341          long start, long len) throws IOException {
342        final InodeTree.ResolveResult<FileSystem> res = 
343          fsState.resolve(getUriPath(fs.getPath()), true);
344        return res.targetFileSystem.getFileBlockLocations(
345              new ViewFsFileStatus(fs, res.remainingPath), start, len);
346      }
347    
348      @Override
349      public FileChecksum getFileChecksum(final Path f)
350          throws AccessControlException, FileNotFoundException,
351          IOException {
352        InodeTree.ResolveResult<FileSystem> res = 
353          fsState.resolve(getUriPath(f), true);
354        return res.targetFileSystem.getFileChecksum(res.remainingPath);
355      }
356    
357      @Override
358      public FileStatus getFileStatus(final Path f) throws AccessControlException,
359          FileNotFoundException, IOException {
360        InodeTree.ResolveResult<FileSystem> res = 
361          fsState.resolve(getUriPath(f), true);
362        
363        // FileStatus#getPath is a fully qualified path relative to the root of 
364        // target file system.
365        // We need to change it to viewfs URI - relative to root of mount table.
366        
367        // The implementors of RawLocalFileSystem were trying to be very smart.
368        // They implement FileStatus#getOwener lazily -- the object
369        // returned is really a RawLocalFileSystem that expect the
370        // FileStatus#getPath to be unchanged so that it can get owner when needed.
371        // Hence we need to interpose a new ViewFileSystemFileStatus that 
372        // works around.
373        FileStatus status =  res.targetFileSystem.getFileStatus(res.remainingPath);
374        return new ViewFsFileStatus(status, this.makeQualified(f));
375      }
376      
377      
378      @Override
379      public FileStatus[] listStatus(final Path f) throws AccessControlException,
380          FileNotFoundException, IOException {
381        InodeTree.ResolveResult<FileSystem> res =
382          fsState.resolve(getUriPath(f), true);
383        
384        FileStatus[] statusLst = res.targetFileSystem.listStatus(res.remainingPath);
385        if (!res.isInternalDir()) {
386          // We need to change the name in the FileStatus as described in
387          // {@link #getFileStatus }
388          ChRootedFileSystem targetFs;
389          targetFs = (ChRootedFileSystem) res.targetFileSystem;
390          int i = 0;
391          for (FileStatus status : statusLst) {
392              String suffix = targetFs.stripOutRoot(status.getPath());
393              statusLst[i++] = new ViewFsFileStatus(status, this.makeQualified(
394                  suffix.length() == 0 ? f : new Path(res.resolvedPath, suffix)));
395          }
396        }
397        return statusLst;
398      }
399    
400      @Override
401      public boolean mkdirs(final Path dir, final FsPermission permission)
402          throws IOException {
403        InodeTree.ResolveResult<FileSystem> res = 
404          fsState.resolve(getUriPath(dir), false);
405       return  res.targetFileSystem.mkdirs(res.remainingPath, permission);
406      }
407    
408      @Override
409      public FSDataInputStream open(final Path f, final int bufferSize)
410          throws AccessControlException, FileNotFoundException,
411          IOException {
412        InodeTree.ResolveResult<FileSystem> res = 
413            fsState.resolve(getUriPath(f), true);
414        return res.targetFileSystem.open(res.remainingPath, bufferSize);
415      }
416    
417      
418      @Override
419      public boolean rename(final Path src, final Path dst) throws IOException {
420        // passing resolveLastComponet as false to catch renaming a mount point to 
421        // itself. We need to catch this as an internal operation and fail.
422        InodeTree.ResolveResult<FileSystem> resSrc = 
423          fsState.resolve(getUriPath(src), false); 
424      
425        if (resSrc.isInternalDir()) {
426          throw readOnlyMountTable("rename", src);
427        }
428          
429        InodeTree.ResolveResult<FileSystem> resDst = 
430          fsState.resolve(getUriPath(dst), false);
431        if (resDst.isInternalDir()) {
432              throw readOnlyMountTable("rename", dst);
433        }
434        /**
435        // Alternate 1: renames within same file system - valid but we disallow
436        // Alternate 2: (as described in next para - valid but we have disallowed it
437        //
438        // Note we compare the URIs. the URIs include the link targets. 
439        // hence we allow renames across mount links as long as the mount links
440        // point to the same target.
441        if (!resSrc.targetFileSystem.getUri().equals(
442                  resDst.targetFileSystem.getUri())) {
443          throw new IOException("Renames across Mount points not supported");
444        }
445        */
446        
447        //
448        // Alternate 3 : renames ONLY within the the same mount links.
449        //
450        if (resSrc.targetFileSystem !=resDst.targetFileSystem) {
451          throw new IOException("Renames across Mount points not supported");
452        }
453        return resSrc.targetFileSystem.rename(resSrc.remainingPath,
454            resDst.remainingPath);
455      }
456      
457      @Override
458      public void setOwner(final Path f, final String username,
459          final String groupname) throws AccessControlException,
460          FileNotFoundException,
461          IOException {
462        InodeTree.ResolveResult<FileSystem> res = 
463          fsState.resolve(getUriPath(f), true);
464        res.targetFileSystem.setOwner(res.remainingPath, username, groupname); 
465      }
466    
467      @Override
468      public void setPermission(final Path f, final FsPermission permission)
469          throws AccessControlException, FileNotFoundException,
470          IOException {
471        InodeTree.ResolveResult<FileSystem> res = 
472          fsState.resolve(getUriPath(f), true);
473        res.targetFileSystem.setPermission(res.remainingPath, permission); 
474      }
475    
476      @Override
477      public boolean setReplication(final Path f, final short replication)
478          throws AccessControlException, FileNotFoundException,
479          IOException {
480        InodeTree.ResolveResult<FileSystem> res = 
481          fsState.resolve(getUriPath(f), true);
482        return res.targetFileSystem.setReplication(res.remainingPath, replication);
483      }
484    
485      @Override
486      public void setTimes(final Path f, final long mtime, final long atime)
487          throws AccessControlException, FileNotFoundException,
488          IOException {
489        InodeTree.ResolveResult<FileSystem> res = 
490          fsState.resolve(getUriPath(f), true);
491        res.targetFileSystem.setTimes(res.remainingPath, mtime, atime); 
492      }
493    
494      @Override
495      public void setVerifyChecksum(final boolean verifyChecksum) { 
496        List<InodeTree.MountPoint<FileSystem>> mountPoints = 
497            fsState.getMountPoints();
498        for (InodeTree.MountPoint<FileSystem> mount : mountPoints) {
499          mount.target.targetFileSystem.setVerifyChecksum(verifyChecksum);
500        }
501      }
502      
503      @Override
504      public long getDefaultBlockSize() {
505        throw new NotInMountpointException("getDefaultBlockSize");
506      }
507    
508      @Override
509      public short getDefaultReplication() {
510        throw new NotInMountpointException("getDefaultReplication");
511      }
512    
513      @Override
514      public FsServerDefaults getServerDefaults() throws IOException {
515        throw new NotInMountpointException("getServerDefaults");
516      }
517    
518      @Override
519      public long getDefaultBlockSize(Path f) {
520        try {
521          InodeTree.ResolveResult<FileSystem> res =
522            fsState.resolve(getUriPath(f), true);
523          return res.targetFileSystem.getDefaultBlockSize(res.remainingPath);
524        } catch (FileNotFoundException e) {
525          throw new NotInMountpointException(f, "getDefaultBlockSize"); 
526        }
527      }
528    
529      @Override
530      public short getDefaultReplication(Path f) {
531        try {
532          InodeTree.ResolveResult<FileSystem> res =
533            fsState.resolve(getUriPath(f), true);
534          return res.targetFileSystem.getDefaultReplication(res.remainingPath);
535        } catch (FileNotFoundException e) {
536          throw new NotInMountpointException(f, "getDefaultReplication"); 
537        }
538      }
539    
540      @Override
541      public FsServerDefaults getServerDefaults(Path f) throws IOException {
542        InodeTree.ResolveResult<FileSystem> res =
543          fsState.resolve(getUriPath(f), true);
544        return res.targetFileSystem.getServerDefaults(res.remainingPath);    
545      }
546    
547      @Override
548      public ContentSummary getContentSummary(Path f) throws IOException {
549        InodeTree.ResolveResult<FileSystem> res = 
550          fsState.resolve(getUriPath(f), true);
551        return res.targetFileSystem.getContentSummary(res.remainingPath);
552      }
553    
554      @Override
555      public void setWriteChecksum(final boolean writeChecksum) { 
556        List<InodeTree.MountPoint<FileSystem>> mountPoints = 
557            fsState.getMountPoints();
558        for (InodeTree.MountPoint<FileSystem> mount : mountPoints) {
559          mount.target.targetFileSystem.setWriteChecksum(writeChecksum);
560        }
561      }
562    
563      @Override
564      public FileSystem[] getChildFileSystems() {
565        List<InodeTree.MountPoint<FileSystem>> mountPoints =
566            fsState.getMountPoints();
567        Set<FileSystem> children = new HashSet<FileSystem>();
568        for (InodeTree.MountPoint<FileSystem> mountPoint : mountPoints) {
569          FileSystem targetFs = mountPoint.target.targetFileSystem;
570          children.addAll(Arrays.asList(targetFs.getChildFileSystems()));
571        }
572        return children.toArray(new FileSystem[]{});
573      }
574      
575      public MountPoint[] getMountPoints() {
576        List<InodeTree.MountPoint<FileSystem>> mountPoints = 
577                      fsState.getMountPoints();
578        
579        MountPoint[] result = new MountPoint[mountPoints.size()];
580        for ( int i = 0; i < mountPoints.size(); ++i ) {
581          result[i] = new MountPoint(new Path(mountPoints.get(i).src), 
582                                  mountPoints.get(i).target.targetDirLinkList);
583        }
584        return result;
585      }
586      
587      /*
588       * An instance of this class represents an internal dir of the viewFs 
589       * that is internal dir of the mount table.
590       * It is a read only mount tables and create, mkdir or delete operations
591       * are not allowed.
592       * If called on create or mkdir then this target is the parent of the
593       * directory in which one is trying to create or mkdir; hence
594       * in this case the path name passed in is the last component. 
595       * Otherwise this target is the end point of the path and hence
596       * the path name passed in is null. 
597       */
598      static class InternalDirOfViewFs extends FileSystem {
599        final InodeTree.INodeDir<FileSystem>  theInternalDir;
600        final long creationTime; // of the the mount table
601        final UserGroupInformation ugi; // the user/group of user who created mtable
602        final URI myUri;
603        
604        public InternalDirOfViewFs(final InodeTree.INodeDir<FileSystem> dir,
605            final long cTime, final UserGroupInformation ugi, URI uri)
606          throws URISyntaxException {
607          myUri = uri;
608          try {
609            initialize(myUri, new Configuration());
610          } catch (IOException e) {
611            throw new RuntimeException("Cannot occur");
612          }
613          theInternalDir = dir;
614          creationTime = cTime;
615          this.ugi = ugi;
616        }
617    
618        static private void checkPathIsSlash(final Path f) throws IOException {
619          if (f != InodeTree.SlashPath) {
620            throw new IOException (
621            "Internal implementation error: expected file name to be /" );
622          }
623        }
624        
625        @Override
626        public URI getUri() {
627          return myUri;
628        }
629    
630        @Override
631        public Path getWorkingDirectory() {
632          throw new RuntimeException (
633          "Internal impl error: getWorkingDir should not have been called" );
634        }
635    
636        @Override
637        public void setWorkingDirectory(final Path new_dir) {
638          throw new RuntimeException (
639          "Internal impl error: getWorkingDir should not have been called" ); 
640        }
641    
642        @Override
643        public FSDataOutputStream append(final Path f, final int bufferSize,
644            final Progressable progress) throws IOException {
645          throw readOnlyMountTable("append", f);
646        }
647    
648        @Override
649        public FSDataOutputStream create(final Path f,
650            final FsPermission permission, final boolean overwrite,
651            final int bufferSize, final short replication, final long blockSize,
652            final Progressable progress) throws AccessControlException {
653          throw readOnlyMountTable("create", f);
654        }
655    
656        @Override
657        public boolean delete(final Path f, final boolean recursive)
658            throws AccessControlException, IOException {
659          checkPathIsSlash(f);
660          throw readOnlyMountTable("delete", f);
661        }
662        
663        @Override
664        @SuppressWarnings("deprecation")
665        public boolean delete(final Path f)
666            throws AccessControlException, IOException {
667          return delete(f, true);
668        }
669    
670        @Override
671        public BlockLocation[] getFileBlockLocations(final FileStatus fs,
672            final long start, final long len) throws 
673            FileNotFoundException, IOException {
674          checkPathIsSlash(fs.getPath());
675          throw new FileNotFoundException("Path points to dir not a file");
676        }
677    
678        @Override
679        public FileChecksum getFileChecksum(final Path f)
680            throws FileNotFoundException, IOException {
681          checkPathIsSlash(f);
682          throw new FileNotFoundException("Path points to dir not a file");
683        }
684    
685        @Override
686        public FileStatus getFileStatus(Path f) throws IOException {
687          checkPathIsSlash(f);
688          return new FileStatus(0, true, 0, 0, creationTime, creationTime,
689              PERMISSION_RRR, ugi.getUserName(), ugi.getGroupNames()[0],
690    
691              new Path(theInternalDir.fullPath).makeQualified(
692                  myUri, null));
693        }
694        
695    
696        @Override
697        public FileStatus[] listStatus(Path f) throws AccessControlException,
698            FileNotFoundException, IOException {
699          checkPathIsSlash(f);
700          FileStatus[] result = new FileStatus[theInternalDir.children.size()];
701          int i = 0;
702          for (Entry<String, INode<FileSystem>> iEntry : 
703                                              theInternalDir.children.entrySet()) {
704            INode<FileSystem> inode = iEntry.getValue();
705            if (inode instanceof INodeLink ) {
706              INodeLink<FileSystem> link = (INodeLink<FileSystem>) inode;
707    
708              result[i++] = new FileStatus(0, false, 0, 0,
709                creationTime, creationTime, PERMISSION_RRR,
710                ugi.getUserName(), ugi.getGroupNames()[0],
711                link.getTargetLink(),
712                new Path(inode.fullPath).makeQualified(
713                    myUri, null));
714            } else {
715              result[i++] = new FileStatus(0, true, 0, 0,
716                creationTime, creationTime, PERMISSION_RRR,
717                ugi.getUserName(), ugi.getGroupNames()[0],
718                new Path(inode.fullPath).makeQualified(
719                    myUri, null));
720            }
721          }
722          return result;
723        }
724    
725        @Override
726        public boolean mkdirs(Path dir, FsPermission permission)
727            throws AccessControlException, FileAlreadyExistsException {
728          if (theInternalDir.isRoot && dir == null) {
729            throw new FileAlreadyExistsException("/ already exits");
730          }
731          // Note dir starts with /
732          if (theInternalDir.children.containsKey(dir.toString().substring(1))) {
733            return true; // this is the stupid semantics of FileSystem
734          }
735          throw readOnlyMountTable("mkdirs",  dir);
736        }
737    
738        @Override
739        public FSDataInputStream open(Path f, int bufferSize)
740            throws AccessControlException, FileNotFoundException, IOException {
741          checkPathIsSlash(f);
742          throw new FileNotFoundException("Path points to dir not a file");
743        }
744    
745        @Override
746        public boolean rename(Path src, Path dst) throws AccessControlException,
747            IOException {
748          checkPathIsSlash(src);
749          checkPathIsSlash(dst);
750          throw readOnlyMountTable("rename", src);     
751        }
752    
753        @Override
754        public void setOwner(Path f, String username, String groupname)
755            throws AccessControlException, IOException {
756          checkPathIsSlash(f);
757          throw readOnlyMountTable("setOwner", f);
758        }
759    
760        @Override
761        public void setPermission(Path f, FsPermission permission)
762            throws AccessControlException, IOException {
763          checkPathIsSlash(f);
764          throw readOnlyMountTable("setPermission", f);    
765        }
766    
767        @Override
768        public boolean setReplication(Path f, short replication)
769            throws AccessControlException, IOException {
770          checkPathIsSlash(f);
771          throw readOnlyMountTable("setReplication", f);
772        }
773    
774        @Override
775        public void setTimes(Path f, long mtime, long atime)
776            throws AccessControlException, IOException {
777          checkPathIsSlash(f);
778          throw readOnlyMountTable("setTimes", f);    
779        }
780    
781        @Override
782        public void setVerifyChecksum(boolean verifyChecksum) {
783          // Noop for viewfs
784        }
785    
786        @Override
787        public FsServerDefaults getServerDefaults(Path f) throws IOException {
788          throw new NotInMountpointException(f, "getServerDefaults");
789        }
790        
791        @Override
792        public long getDefaultBlockSize(Path f) {
793          throw new NotInMountpointException(f, "getDefaultBlockSize");
794        }
795    
796        @Override
797        public short getDefaultReplication(Path f) {
798          throw new NotInMountpointException(f, "getDefaultReplication");
799        }
800      }
801    }