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