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 }