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.hdfs.server.namenode; 019 020 import java.io.PrintStream; 021 import java.io.PrintWriter; 022 import java.io.StringWriter; 023 import java.util.ArrayList; 024 import java.util.List; 025 026 import org.apache.commons.logging.Log; 027 import org.apache.commons.logging.LogFactory; 028 import org.apache.hadoop.classification.InterfaceAudience; 029 import org.apache.hadoop.fs.ContentSummary; 030 import org.apache.hadoop.fs.Path; 031 import org.apache.hadoop.fs.permission.FsPermission; 032 import org.apache.hadoop.fs.permission.PermissionStatus; 033 import org.apache.hadoop.hdfs.DFSUtil; 034 import org.apache.hadoop.hdfs.protocol.Block; 035 import org.apache.hadoop.hdfs.protocol.QuotaExceededException; 036 import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference; 037 import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName; 038 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 039 import org.apache.hadoop.hdfs.util.ChunkedArrayList; 040 import org.apache.hadoop.hdfs.util.Diff; 041 import org.apache.hadoop.util.StringUtils; 042 043 import com.google.common.annotations.VisibleForTesting; 044 import com.google.common.base.Preconditions; 045 046 /** 047 * We keep an in-memory representation of the file/block hierarchy. 048 * This is a base INode class containing common fields for file and 049 * directory inodes. 050 */ 051 @InterfaceAudience.Private 052 public abstract class INode implements INodeAttributes, Diff.Element<byte[]> { 053 public static final Log LOG = LogFactory.getLog(INode.class); 054 055 /** parent is either an {@link INodeDirectory} or an {@link INodeReference}.*/ 056 private INode parent = null; 057 058 INode(INode parent) { 059 this.parent = parent; 060 } 061 062 /** Get inode id */ 063 public abstract long getId(); 064 065 /** 066 * Check whether this is the root inode. 067 */ 068 final boolean isRoot() { 069 return getLocalNameBytes().length == 0; 070 } 071 072 /** Get the {@link PermissionStatus} */ 073 abstract PermissionStatus getPermissionStatus(int snapshotId); 074 075 /** The same as getPermissionStatus(null). */ 076 final PermissionStatus getPermissionStatus() { 077 return getPermissionStatus(Snapshot.CURRENT_STATE_ID); 078 } 079 080 /** 081 * @param snapshotId 082 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 083 * from the given snapshot; otherwise, get the result from the 084 * current inode. 085 * @return user name 086 */ 087 abstract String getUserName(int snapshotId); 088 089 /** The same as getUserName(Snapshot.CURRENT_STATE_ID). */ 090 @Override 091 public final String getUserName() { 092 return getUserName(Snapshot.CURRENT_STATE_ID); 093 } 094 095 /** Set user */ 096 abstract void setUser(String user); 097 098 /** Set user */ 099 final INode setUser(String user, int latestSnapshotId) 100 throws QuotaExceededException { 101 final INode nodeToUpdate = recordModification(latestSnapshotId); 102 nodeToUpdate.setUser(user); 103 return nodeToUpdate; 104 } 105 /** 106 * @param snapshotId 107 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 108 * from the given snapshot; otherwise, get the result from the 109 * current inode. 110 * @return group name 111 */ 112 abstract String getGroupName(int snapshotId); 113 114 /** The same as getGroupName(Snapshot.CURRENT_STATE_ID). */ 115 @Override 116 public final String getGroupName() { 117 return getGroupName(Snapshot.CURRENT_STATE_ID); 118 } 119 120 /** Set group */ 121 abstract void setGroup(String group); 122 123 /** Set group */ 124 final INode setGroup(String group, int latestSnapshotId) 125 throws QuotaExceededException { 126 final INode nodeToUpdate = recordModification(latestSnapshotId); 127 nodeToUpdate.setGroup(group); 128 return nodeToUpdate; 129 } 130 131 /** 132 * @param snapshotId 133 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 134 * from the given snapshot; otherwise, get the result from the 135 * current inode. 136 * @return permission. 137 */ 138 abstract FsPermission getFsPermission(int snapshotId); 139 140 /** The same as getFsPermission(Snapshot.CURRENT_STATE_ID). */ 141 @Override 142 public final FsPermission getFsPermission() { 143 return getFsPermission(Snapshot.CURRENT_STATE_ID); 144 } 145 146 /** Set the {@link FsPermission} of this {@link INode} */ 147 abstract void setPermission(FsPermission permission); 148 149 /** Set the {@link FsPermission} of this {@link INode} */ 150 INode setPermission(FsPermission permission, int latestSnapshotId) 151 throws QuotaExceededException { 152 final INode nodeToUpdate = recordModification(latestSnapshotId); 153 nodeToUpdate.setPermission(permission); 154 return nodeToUpdate; 155 } 156 157 abstract AclFeature getAclFeature(int snapshotId); 158 159 @Override 160 public final AclFeature getAclFeature() { 161 return getAclFeature(Snapshot.CURRENT_STATE_ID); 162 } 163 164 abstract void addAclFeature(AclFeature aclFeature); 165 166 final INode addAclFeature(AclFeature aclFeature, int latestSnapshotId) 167 throws QuotaExceededException { 168 final INode nodeToUpdate = recordModification(latestSnapshotId); 169 nodeToUpdate.addAclFeature(aclFeature); 170 return nodeToUpdate; 171 } 172 173 abstract void removeAclFeature(); 174 175 final INode removeAclFeature(int latestSnapshotId) 176 throws QuotaExceededException { 177 final INode nodeToUpdate = recordModification(latestSnapshotId); 178 nodeToUpdate.removeAclFeature(); 179 return nodeToUpdate; 180 } 181 182 /** 183 * @return if the given snapshot id is {@link Snapshot#CURRENT_STATE_ID}, 184 * return this; otherwise return the corresponding snapshot inode. 185 */ 186 public INodeAttributes getSnapshotINode(final int snapshotId) { 187 return this; 188 } 189 190 /** Is this inode in the latest snapshot? */ 191 public final boolean isInLatestSnapshot(final int latestSnapshotId) { 192 if (latestSnapshotId == Snapshot.CURRENT_STATE_ID) { 193 return false; 194 } 195 // if parent is a reference node, parent must be a renamed node. We can 196 // stop the check at the reference node. 197 if (parent != null && parent.isReference()) { 198 return true; 199 } 200 final INodeDirectory parentDir = getParent(); 201 if (parentDir == null) { // root 202 return true; 203 } 204 if (!parentDir.isInLatestSnapshot(latestSnapshotId)) { 205 return false; 206 } 207 final INode child = parentDir.getChild(getLocalNameBytes(), 208 latestSnapshotId); 209 if (this == child) { 210 return true; 211 } 212 if (child == null || !(child.isReference())) { 213 return false; 214 } 215 return this == child.asReference().getReferredINode(); 216 } 217 218 /** @return true if the given inode is an ancestor directory of this inode. */ 219 public final boolean isAncestorDirectory(final INodeDirectory dir) { 220 for(INodeDirectory p = getParent(); p != null; p = p.getParent()) { 221 if (p == dir) { 222 return true; 223 } 224 } 225 return false; 226 } 227 228 /** 229 * When {@link #recordModification} is called on a referred node, 230 * this method tells which snapshot the modification should be 231 * associated with: the snapshot that belongs to the SRC tree of the rename 232 * operation, or the snapshot belonging to the DST tree. 233 * 234 * @param latestInDst 235 * id of the latest snapshot in the DST tree above the reference node 236 * @return True: the modification should be recorded in the snapshot that 237 * belongs to the SRC tree. False: the modification should be 238 * recorded in the snapshot that belongs to the DST tree. 239 */ 240 public final boolean shouldRecordInSrcSnapshot(final int latestInDst) { 241 Preconditions.checkState(!isReference()); 242 243 if (latestInDst == Snapshot.CURRENT_STATE_ID) { 244 return true; 245 } 246 INodeReference withCount = getParentReference(); 247 if (withCount != null) { 248 int dstSnapshotId = withCount.getParentReference().getDstSnapshotId(); 249 if (dstSnapshotId != Snapshot.CURRENT_STATE_ID 250 && dstSnapshotId >= latestInDst) { 251 return true; 252 } 253 } 254 return false; 255 } 256 257 /** 258 * This inode is being modified. The previous version of the inode needs to 259 * be recorded in the latest snapshot. 260 * 261 * @param latestSnapshotId The id of the latest snapshot that has been taken. 262 * Note that it is {@link Snapshot#CURRENT_STATE_ID} 263 * if no snapshots have been taken. 264 * @return The current inode, which usually is the same object of this inode. 265 * However, in some cases, this inode may be replaced with a new inode 266 * for maintaining snapshots. The current inode is then the new inode. 267 */ 268 abstract INode recordModification(final int latestSnapshotId) 269 throws QuotaExceededException; 270 271 /** Check whether it's a reference. */ 272 public boolean isReference() { 273 return false; 274 } 275 276 /** Cast this inode to an {@link INodeReference}. */ 277 public INodeReference asReference() { 278 throw new IllegalStateException("Current inode is not a reference: " 279 + this.toDetailString()); 280 } 281 282 /** 283 * Check whether it's a file. 284 */ 285 public boolean isFile() { 286 return false; 287 } 288 289 /** Cast this inode to an {@link INodeFile}. */ 290 public INodeFile asFile() { 291 throw new IllegalStateException("Current inode is not a file: " 292 + this.toDetailString()); 293 } 294 295 /** 296 * Check whether it's a directory 297 */ 298 public boolean isDirectory() { 299 return false; 300 } 301 302 /** Cast this inode to an {@link INodeDirectory}. */ 303 public INodeDirectory asDirectory() { 304 throw new IllegalStateException("Current inode is not a directory: " 305 + this.toDetailString()); 306 } 307 308 /** 309 * Check whether it's a symlink 310 */ 311 public boolean isSymlink() { 312 return false; 313 } 314 315 /** Cast this inode to an {@link INodeSymlink}. */ 316 public INodeSymlink asSymlink() { 317 throw new IllegalStateException("Current inode is not a symlink: " 318 + this.toDetailString()); 319 } 320 321 /** 322 * Clean the subtree under this inode and collect the blocks from the descents 323 * for further block deletion/update. The current inode can either resides in 324 * the current tree or be stored as a snapshot copy. 325 * 326 * <pre> 327 * In general, we have the following rules. 328 * 1. When deleting a file/directory in the current tree, we have different 329 * actions according to the type of the node to delete. 330 * 331 * 1.1 The current inode (this) is an {@link INodeFile}. 332 * 1.1.1 If {@code prior} is null, there is no snapshot taken on ancestors 333 * before. Thus we simply destroy (i.e., to delete completely, no need to save 334 * snapshot copy) the current INode and collect its blocks for further 335 * cleansing. 336 * 1.1.2 Else do nothing since the current INode will be stored as a snapshot 337 * copy. 338 * 339 * 1.2 The current inode is an {@link INodeDirectory}. 340 * 1.2.1 If {@code prior} is null, there is no snapshot taken on ancestors 341 * before. Similarly, we destroy the whole subtree and collect blocks. 342 * 1.2.2 Else do nothing with the current INode. Recursively clean its 343 * children. 344 * 345 * 1.3 The current inode is a file with snapshot. 346 * Call recordModification(..) to capture the current states. 347 * Mark the INode as deleted. 348 * 349 * 1.4 The current inode is an {@link INodeDirectory} with snapshot feature. 350 * Call recordModification(..) to capture the current states. 351 * Destroy files/directories created after the latest snapshot 352 * (i.e., the inodes stored in the created list of the latest snapshot). 353 * Recursively clean remaining children. 354 * 355 * 2. When deleting a snapshot. 356 * 2.1 To clean {@link INodeFile}: do nothing. 357 * 2.2 To clean {@link INodeDirectory}: recursively clean its children. 358 * 2.3 To clean INodeFile with snapshot: delete the corresponding snapshot in 359 * its diff list. 360 * 2.4 To clean {@link INodeDirectory} with snapshot: delete the corresponding 361 * snapshot in its diff list. Recursively clean its children. 362 * </pre> 363 * 364 * @param snapshotId 365 * The id of the snapshot to delete. 366 * {@link Snapshot#CURRENT_STATE_ID} means to delete the current 367 * file/directory. 368 * @param priorSnapshotId 369 * The id of the latest snapshot before the to-be-deleted snapshot. 370 * When deleting a current inode, this parameter captures the latest 371 * snapshot. 372 * @param collectedBlocks 373 * blocks collected from the descents for further block 374 * deletion/update will be added to the given map. 375 * @param removedINodes 376 * INodes collected from the descents for further cleaning up of 377 * inodeMap 378 * @return quota usage delta when deleting a snapshot 379 */ 380 public abstract Quota.Counts cleanSubtree(final int snapshotId, 381 int priorSnapshotId, BlocksMapUpdateInfo collectedBlocks, 382 List<INode> removedINodes, boolean countDiffChange) 383 throws QuotaExceededException; 384 385 /** 386 * Destroy self and clear everything! If the INode is a file, this method 387 * collects its blocks for further block deletion. If the INode is a 388 * directory, the method goes down the subtree and collects blocks from the 389 * descents, and clears its parent/children references as well. The method 390 * also clears the diff list if the INode contains snapshot diff list. 391 * 392 * @param collectedBlocks 393 * blocks collected from the descents for further block 394 * deletion/update will be added to this map. 395 * @param removedINodes 396 * INodes collected from the descents for further cleaning up of 397 * inodeMap 398 */ 399 public abstract void destroyAndCollectBlocks( 400 BlocksMapUpdateInfo collectedBlocks, List<INode> removedINodes); 401 402 /** Compute {@link ContentSummary}. Blocking call */ 403 public final ContentSummary computeContentSummary() { 404 return computeAndConvertContentSummary( 405 new ContentSummaryComputationContext()); 406 } 407 408 /** 409 * Compute {@link ContentSummary}. 410 */ 411 public final ContentSummary computeAndConvertContentSummary( 412 ContentSummaryComputationContext summary) { 413 Content.Counts counts = computeContentSummary(summary).getCounts(); 414 final Quota.Counts q = getQuotaCounts(); 415 return new ContentSummary(counts.get(Content.LENGTH), 416 counts.get(Content.FILE) + counts.get(Content.SYMLINK), 417 counts.get(Content.DIRECTORY), q.get(Quota.NAMESPACE), 418 counts.get(Content.DISKSPACE), q.get(Quota.DISKSPACE)); 419 } 420 421 /** 422 * Count subtree content summary with a {@link Content.Counts}. 423 * 424 * @param summary the context object holding counts for the subtree. 425 * @return The same objects as summary. 426 */ 427 public abstract ContentSummaryComputationContext computeContentSummary( 428 ContentSummaryComputationContext summary); 429 430 431 /** 432 * Check and add namespace/diskspace consumed to itself and the ancestors. 433 * @throws QuotaExceededException if quote is violated. 434 */ 435 public void addSpaceConsumed(long nsDelta, long dsDelta, boolean verify) 436 throws QuotaExceededException { 437 addSpaceConsumed2Parent(nsDelta, dsDelta, verify); 438 } 439 440 /** 441 * Check and add namespace/diskspace consumed to itself and the ancestors. 442 * @throws QuotaExceededException if quote is violated. 443 */ 444 void addSpaceConsumed2Parent(long nsDelta, long dsDelta, boolean verify) 445 throws QuotaExceededException { 446 if (parent != null) { 447 parent.addSpaceConsumed(nsDelta, dsDelta, verify); 448 } 449 } 450 451 /** 452 * Get the quota set for this inode 453 * @return the quota counts. The count is -1 if it is not set. 454 */ 455 public Quota.Counts getQuotaCounts() { 456 return Quota.Counts.newInstance(-1, -1); 457 } 458 459 public final boolean isQuotaSet() { 460 final Quota.Counts q = getQuotaCounts(); 461 return q.get(Quota.NAMESPACE) >= 0 || q.get(Quota.DISKSPACE) >= 0; 462 } 463 464 /** 465 * Count subtree {@link Quota#NAMESPACE} and {@link Quota#DISKSPACE} usages. 466 */ 467 public final Quota.Counts computeQuotaUsage() { 468 return computeQuotaUsage(new Quota.Counts(), true); 469 } 470 471 /** 472 * Count subtree {@link Quota#NAMESPACE} and {@link Quota#DISKSPACE} usages. 473 * 474 * With the existence of {@link INodeReference}, the same inode and its 475 * subtree may be referred by multiple {@link WithName} nodes and a 476 * {@link DstReference} node. To avoid circles while quota usage computation, 477 * we have the following rules: 478 * 479 * <pre> 480 * 1. For a {@link DstReference} node, since the node must be in the current 481 * tree (or has been deleted as the end point of a series of rename 482 * operations), we compute the quota usage of the referred node (and its 483 * subtree) in the regular manner, i.e., including every inode in the current 484 * tree and in snapshot copies, as well as the size of diff list. 485 * 486 * 2. For a {@link WithName} node, since the node must be in a snapshot, we 487 * only count the quota usage for those nodes that still existed at the 488 * creation time of the snapshot associated with the {@link WithName} node. 489 * We do not count in the size of the diff list. 490 * <pre> 491 * 492 * @param counts The subtree counts for returning. 493 * @param useCache Whether to use cached quota usage. Note that 494 * {@link WithName} node never uses cache for its subtree. 495 * @param lastSnapshotId {@link Snapshot#CURRENT_STATE_ID} indicates the 496 * computation is in the current tree. Otherwise the id 497 * indicates the computation range for a 498 * {@link WithName} node. 499 * @return The same objects as the counts parameter. 500 */ 501 public abstract Quota.Counts computeQuotaUsage(Quota.Counts counts, 502 boolean useCache, int lastSnapshotId); 503 504 public final Quota.Counts computeQuotaUsage(Quota.Counts counts, 505 boolean useCache) { 506 return computeQuotaUsage(counts, useCache, Snapshot.CURRENT_STATE_ID); 507 } 508 509 /** 510 * @return null if the local name is null; otherwise, return the local name. 511 */ 512 public final String getLocalName() { 513 final byte[] name = getLocalNameBytes(); 514 return name == null? null: DFSUtil.bytes2String(name); 515 } 516 517 @Override 518 public final byte[] getKey() { 519 return getLocalNameBytes(); 520 } 521 522 /** 523 * Set local file name 524 */ 525 public abstract void setLocalName(byte[] name); 526 527 public String getFullPathName() { 528 // Get the full path name of this inode. 529 return FSDirectory.getFullPathName(this); 530 } 531 532 @Override 533 public String toString() { 534 return getLocalName(); 535 } 536 537 @VisibleForTesting 538 public final String getObjectString() { 539 return getClass().getSimpleName() + "@" 540 + Integer.toHexString(super.hashCode()); 541 } 542 543 /** @return a string description of the parent. */ 544 @VisibleForTesting 545 public final String getParentString() { 546 final INodeReference parentRef = getParentReference(); 547 if (parentRef != null) { 548 return "parentRef=" + parentRef.getLocalName() + "->"; 549 } else { 550 final INodeDirectory parentDir = getParent(); 551 if (parentDir != null) { 552 return "parentDir=" + parentDir.getLocalName() + "/"; 553 } else { 554 return "parent=null"; 555 } 556 } 557 } 558 559 @VisibleForTesting 560 public String toDetailString() { 561 return toString() + "(" + getObjectString() + "), " + getParentString(); 562 } 563 564 /** @return the parent directory */ 565 public final INodeDirectory getParent() { 566 return parent == null? null 567 : parent.isReference()? getParentReference().getParent(): parent.asDirectory(); 568 } 569 570 /** 571 * @return the parent as a reference if this is a referred inode; 572 * otherwise, return null. 573 */ 574 public INodeReference getParentReference() { 575 return parent == null || !parent.isReference()? null: (INodeReference)parent; 576 } 577 578 /** Set parent directory */ 579 public final void setParent(INodeDirectory parent) { 580 this.parent = parent; 581 } 582 583 /** Set container. */ 584 public final void setParentReference(INodeReference parent) { 585 this.parent = parent; 586 } 587 588 /** Clear references to other objects. */ 589 public void clear() { 590 setParent(null); 591 } 592 593 /** 594 * @param snapshotId 595 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 596 * from the given snapshot; otherwise, get the result from the 597 * current inode. 598 * @return modification time. 599 */ 600 abstract long getModificationTime(int snapshotId); 601 602 /** The same as getModificationTime(Snapshot.CURRENT_STATE_ID). */ 603 @Override 604 public final long getModificationTime() { 605 return getModificationTime(Snapshot.CURRENT_STATE_ID); 606 } 607 608 /** Update modification time if it is larger than the current value. */ 609 public abstract INode updateModificationTime(long mtime, int latestSnapshotId) 610 throws QuotaExceededException; 611 612 /** Set the last modification time of inode. */ 613 public abstract void setModificationTime(long modificationTime); 614 615 /** Set the last modification time of inode. */ 616 public final INode setModificationTime(long modificationTime, 617 int latestSnapshotId) throws QuotaExceededException { 618 final INode nodeToUpdate = recordModification(latestSnapshotId); 619 nodeToUpdate.setModificationTime(modificationTime); 620 return nodeToUpdate; 621 } 622 623 /** 624 * @param snapshotId 625 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 626 * from the given snapshot; otherwise, get the result from the 627 * current inode. 628 * @return access time 629 */ 630 abstract long getAccessTime(int snapshotId); 631 632 /** The same as getAccessTime(Snapshot.CURRENT_STATE_ID). */ 633 @Override 634 public final long getAccessTime() { 635 return getAccessTime(Snapshot.CURRENT_STATE_ID); 636 } 637 638 /** 639 * Set last access time of inode. 640 */ 641 public abstract void setAccessTime(long accessTime); 642 643 /** 644 * Set last access time of inode. 645 */ 646 public final INode setAccessTime(long accessTime, int latestSnapshotId) 647 throws QuotaExceededException { 648 final INode nodeToUpdate = recordModification(latestSnapshotId); 649 nodeToUpdate.setAccessTime(accessTime); 650 return nodeToUpdate; 651 } 652 653 654 /** 655 * Breaks file path into components. 656 * @param path 657 * @return array of byte arrays each of which represents 658 * a single path component. 659 */ 660 static byte[][] getPathComponents(String path) { 661 return getPathComponents(getPathNames(path)); 662 } 663 664 /** Convert strings to byte arrays for path components. */ 665 static byte[][] getPathComponents(String[] strings) { 666 if (strings.length == 0) { 667 return new byte[][]{null}; 668 } 669 byte[][] bytes = new byte[strings.length][]; 670 for (int i = 0; i < strings.length; i++) 671 bytes[i] = DFSUtil.string2Bytes(strings[i]); 672 return bytes; 673 } 674 675 /** 676 * Splits an absolute path into an array of path components. 677 * @param path 678 * @throws AssertionError if the given path is invalid. 679 * @return array of path components. 680 */ 681 static String[] getPathNames(String path) { 682 if (path == null || !path.startsWith(Path.SEPARATOR)) { 683 throw new AssertionError("Absolute path required"); 684 } 685 return StringUtils.split(path, Path.SEPARATOR_CHAR); 686 } 687 688 @Override 689 public final int compareTo(byte[] bytes) { 690 return DFSUtil.compareBytes(getLocalNameBytes(), bytes); 691 } 692 693 @Override 694 public final boolean equals(Object that) { 695 if (this == that) { 696 return true; 697 } 698 if (that == null || !(that instanceof INode)) { 699 return false; 700 } 701 return getId() == ((INode) that).getId(); 702 } 703 704 @Override 705 public final int hashCode() { 706 long id = getId(); 707 return (int)(id^(id>>>32)); 708 } 709 710 /** 711 * Dump the subtree starting from this inode. 712 * @return a text representation of the tree. 713 */ 714 @VisibleForTesting 715 public final StringBuffer dumpTreeRecursively() { 716 final StringWriter out = new StringWriter(); 717 dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), 718 Snapshot.CURRENT_STATE_ID); 719 return out.getBuffer(); 720 } 721 722 @VisibleForTesting 723 public final void dumpTreeRecursively(PrintStream out) { 724 dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), 725 Snapshot.CURRENT_STATE_ID); 726 } 727 728 /** 729 * Dump tree recursively. 730 * @param prefix The prefix string that each line should print. 731 */ 732 @VisibleForTesting 733 public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, 734 int snapshotId) { 735 out.print(prefix); 736 out.print(" "); 737 final String name = getLocalName(); 738 out.print(name.isEmpty()? "/": name); 739 out.print(" ("); 740 out.print(getObjectString()); 741 out.print("), "); 742 out.print(getParentString()); 743 out.print(", " + getPermissionStatus(snapshotId)); 744 } 745 746 /** 747 * Information used for updating the blocksMap when deleting files. 748 */ 749 public static class BlocksMapUpdateInfo { 750 /** 751 * The list of blocks that need to be removed from blocksMap 752 */ 753 private final List<Block> toDeleteList; 754 755 public BlocksMapUpdateInfo() { 756 toDeleteList = new ChunkedArrayList<Block>(); 757 } 758 759 /** 760 * @return The list of blocks that need to be removed from blocksMap 761 */ 762 public List<Block> getToDeleteList() { 763 return toDeleteList; 764 } 765 766 /** 767 * Add a to-be-deleted block into the 768 * {@link BlocksMapUpdateInfo#toDeleteList} 769 * @param toDelete the to-be-deleted block 770 */ 771 public void addDeleteBlock(Block toDelete) { 772 if (toDelete != null) { 773 toDeleteList.add(toDelete); 774 } 775 } 776 777 /** 778 * Clear {@link BlocksMapUpdateInfo#toDeleteList} 779 */ 780 public void clear() { 781 toDeleteList.clear(); 782 } 783 } 784 785 /** 786 * INode feature such as {@link FileUnderConstructionFeature} 787 * and {@link DirectoryWithQuotaFeature}. 788 */ 789 public interface Feature { 790 } 791 }