001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hdfs.server.namenode; 019 020import java.io.FileNotFoundException; 021import java.io.PrintWriter; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Iterator; 025import java.util.List; 026import java.util.Map; 027 028import org.apache.hadoop.fs.PathIsNotDirectoryException; 029import org.apache.hadoop.fs.permission.PermissionStatus; 030import org.apache.hadoop.fs.StorageType; 031import org.apache.hadoop.fs.XAttr; 032import org.apache.hadoop.hdfs.DFSUtil; 033import org.apache.hadoop.hdfs.protocol.QuotaExceededException; 034import org.apache.hadoop.hdfs.protocol.SnapshotException; 035import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite; 036import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount; 037import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectorySnapshottableFeature; 038import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature; 039import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList; 040import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 041import org.apache.hadoop.hdfs.util.Diff.ListType; 042import org.apache.hadoop.hdfs.util.ReadOnlyList; 043 044import com.google.common.annotations.VisibleForTesting; 045import com.google.common.base.Preconditions; 046 047import static org.apache.hadoop.hdfs.protocol.HdfsConstants.BLOCK_STORAGE_POLICY_ID_UNSPECIFIED; 048 049/** 050 * Directory INode class. 051 */ 052public class INodeDirectory extends INodeWithAdditionalFields 053 implements INodeDirectoryAttributes { 054 055 /** Cast INode to INodeDirectory. */ 056 public static INodeDirectory valueOf(INode inode, Object path 057 ) throws FileNotFoundException, PathIsNotDirectoryException { 058 if (inode == null) { 059 throw new FileNotFoundException("Directory does not exist: " 060 + DFSUtil.path2String(path)); 061 } 062 if (!inode.isDirectory()) { 063 throw new PathIsNotDirectoryException(DFSUtil.path2String(path)); 064 } 065 return inode.asDirectory(); 066 } 067 068 protected static final int DEFAULT_FILES_PER_DIRECTORY = 5; 069 final static byte[] ROOT_NAME = DFSUtil.string2Bytes(""); 070 071 private List<INode> children = null; 072 073 /** constructor */ 074 public INodeDirectory(long id, byte[] name, PermissionStatus permissions, 075 long mtime) { 076 super(id, name, permissions, mtime, 0L); 077 } 078 079 /** 080 * Copy constructor 081 * @param other The INodeDirectory to be copied 082 * @param adopt Indicate whether or not need to set the parent field of child 083 * INodes to the new node 084 * @param featuresToCopy any number of features to copy to the new node. 085 * The method will do a reference copy, not a deep copy. 086 */ 087 public INodeDirectory(INodeDirectory other, boolean adopt, 088 Feature... featuresToCopy) { 089 super(other); 090 this.children = other.children; 091 if (adopt && this.children != null) { 092 for (INode child : children) { 093 child.setParent(this); 094 } 095 } 096 this.features = featuresToCopy; 097 AclFeature aclFeature = getFeature(AclFeature.class); 098 if (aclFeature != null) { 099 // for the de-duplication of AclFeature 100 removeFeature(aclFeature); 101 addFeature(AclStorage.addAclFeature(aclFeature)); 102 } 103 } 104 105 /** @return true unconditionally. */ 106 @Override 107 public final boolean isDirectory() { 108 return true; 109 } 110 111 /** @return this object. */ 112 @Override 113 public final INodeDirectory asDirectory() { 114 return this; 115 } 116 117 @Override 118 public byte getLocalStoragePolicyID() { 119 XAttrFeature f = getXAttrFeature(); 120 XAttr xattr = f == null ? null : f.getXAttr( 121 BlockStoragePolicySuite.getStoragePolicyXAttrPrefixedName()); 122 if (xattr != null) { 123 return (xattr.getValue())[0]; 124 } 125 return BLOCK_STORAGE_POLICY_ID_UNSPECIFIED; 126 } 127 128 @Override 129 public byte getStoragePolicyID() { 130 byte id = getLocalStoragePolicyID(); 131 if (id != BLOCK_STORAGE_POLICY_ID_UNSPECIFIED) { 132 return id; 133 } 134 // if it is unspecified, check its parent 135 return getParent() != null ? getParent().getStoragePolicyID() : BLOCK_STORAGE_POLICY_ID_UNSPECIFIED; 136 } 137 138 void setQuota(BlockStoragePolicySuite bsps, long nsQuota, long ssQuota, StorageType type) { 139 DirectoryWithQuotaFeature quota = getDirectoryWithQuotaFeature(); 140 if (quota != null) { 141 // already has quota; so set the quota to the new values 142 if (type != null) { 143 quota.setQuota(ssQuota, type); 144 } else { 145 quota.setQuota(nsQuota, ssQuota); 146 } 147 if (!isQuotaSet() && !isRoot()) { 148 removeFeature(quota); 149 } 150 } else { 151 final QuotaCounts c = computeQuotaUsage(bsps); 152 DirectoryWithQuotaFeature.Builder builder = 153 new DirectoryWithQuotaFeature.Builder().nameSpaceQuota(nsQuota); 154 if (type != null) { 155 builder.typeQuota(type, ssQuota); 156 } else { 157 builder.storageSpaceQuota(ssQuota); 158 } 159 addDirectoryWithQuotaFeature(builder.build()).setSpaceConsumed(c); 160 } 161 } 162 163 @Override 164 public QuotaCounts getQuotaCounts() { 165 final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature(); 166 return q != null? q.getQuota(): super.getQuotaCounts(); 167 } 168 169 @Override 170 public void addSpaceConsumed(QuotaCounts counts, boolean verify) 171 throws QuotaExceededException { 172 final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature(); 173 if (q != null) { 174 q.addSpaceConsumed(this, counts, verify); 175 } else { 176 addSpaceConsumed2Parent(counts, verify); 177 } 178 } 179 180 /** 181 * If the directory contains a {@link DirectoryWithQuotaFeature}, return it; 182 * otherwise, return null. 183 */ 184 public final DirectoryWithQuotaFeature getDirectoryWithQuotaFeature() { 185 return getFeature(DirectoryWithQuotaFeature.class); 186 } 187 188 /** Is this directory with quota? */ 189 final boolean isWithQuota() { 190 return getDirectoryWithQuotaFeature() != null; 191 } 192 193 DirectoryWithQuotaFeature addDirectoryWithQuotaFeature( 194 DirectoryWithQuotaFeature q) { 195 Preconditions.checkState(!isWithQuota(), "Directory is already with quota"); 196 addFeature(q); 197 return q; 198 } 199 200 int searchChildren(byte[] name) { 201 return children == null? -1: Collections.binarySearch(children, name); 202 } 203 204 public DirectoryWithSnapshotFeature addSnapshotFeature( 205 DirectoryDiffList diffs) { 206 Preconditions.checkState(!isWithSnapshot(), 207 "Directory is already with snapshot"); 208 DirectoryWithSnapshotFeature sf = new DirectoryWithSnapshotFeature(diffs); 209 addFeature(sf); 210 return sf; 211 } 212 213 /** 214 * If feature list contains a {@link DirectoryWithSnapshotFeature}, return it; 215 * otherwise, return null. 216 */ 217 public final DirectoryWithSnapshotFeature getDirectoryWithSnapshotFeature() { 218 return getFeature(DirectoryWithSnapshotFeature.class); 219 } 220 221 /** Is this file has the snapshot feature? */ 222 public final boolean isWithSnapshot() { 223 return getDirectoryWithSnapshotFeature() != null; 224 } 225 226 public DirectoryDiffList getDiffs() { 227 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 228 return sf != null ? sf.getDiffs() : null; 229 } 230 231 @Override 232 public INodeDirectoryAttributes getSnapshotINode(int snapshotId) { 233 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 234 return sf == null ? this : sf.getDiffs().getSnapshotINode(snapshotId, this); 235 } 236 237 @Override 238 public String toDetailString() { 239 DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature(); 240 return super.toDetailString() + (sf == null ? "" : ", " + sf.getDiffs()); 241 } 242 243 public DirectorySnapshottableFeature getDirectorySnapshottableFeature() { 244 return getFeature(DirectorySnapshottableFeature.class); 245 } 246 247 public boolean isSnapshottable() { 248 return getDirectorySnapshottableFeature() != null; 249 } 250 251 public Snapshot getSnapshot(byte[] snapshotName) { 252 return getDirectorySnapshottableFeature().getSnapshot(snapshotName); 253 } 254 255 public void setSnapshotQuota(int snapshotQuota) { 256 getDirectorySnapshottableFeature().setSnapshotQuota(snapshotQuota); 257 } 258 259 public Snapshot addSnapshot(int id, String name) throws SnapshotException, 260 QuotaExceededException { 261 return getDirectorySnapshottableFeature().addSnapshot(this, id, name); 262 } 263 264 public Snapshot removeSnapshot( 265 ReclaimContext reclaimContext, String snapshotName) 266 throws SnapshotException { 267 return getDirectorySnapshottableFeature().removeSnapshot( 268 reclaimContext, this, snapshotName); 269 } 270 271 public void renameSnapshot(String path, String oldName, String newName) 272 throws SnapshotException { 273 getDirectorySnapshottableFeature().renameSnapshot(path, oldName, newName); 274 } 275 276 /** add DirectorySnapshottableFeature */ 277 public void addSnapshottableFeature() { 278 Preconditions.checkState(!isSnapshottable(), 279 "this is already snapshottable, this=%s", this); 280 DirectoryWithSnapshotFeature s = this.getDirectoryWithSnapshotFeature(); 281 final DirectorySnapshottableFeature snapshottable = 282 new DirectorySnapshottableFeature(s); 283 if (s != null) { 284 this.removeFeature(s); 285 } 286 this.addFeature(snapshottable); 287 } 288 289 /** remove DirectorySnapshottableFeature */ 290 public void removeSnapshottableFeature() { 291 DirectorySnapshottableFeature s = getDirectorySnapshottableFeature(); 292 Preconditions.checkState(s != null, 293 "The dir does not have snapshottable feature: this=%s", this); 294 this.removeFeature(s); 295 if (s.getDiffs().asList().size() > 0) { 296 // add a DirectoryWithSnapshotFeature back 297 DirectoryWithSnapshotFeature sf = new DirectoryWithSnapshotFeature( 298 s.getDiffs()); 299 addFeature(sf); 300 } 301 } 302 303 /** 304 * Replace the given child with a new child. Note that we no longer need to 305 * replace an normal INodeDirectory or INodeFile into an 306 * INodeDirectoryWithSnapshot or INodeFileUnderConstruction. The only cases 307 * for child replacement is for reference nodes. 308 */ 309 public void replaceChild(INode oldChild, final INode newChild, 310 final INodeMap inodeMap) { 311 Preconditions.checkNotNull(children); 312 final int i = searchChildren(newChild.getLocalNameBytes()); 313 Preconditions.checkState(i >= 0); 314 Preconditions.checkState(oldChild == children.get(i) 315 || oldChild == children.get(i).asReference().getReferredINode() 316 .asReference().getReferredINode()); 317 oldChild = children.get(i); 318 319 if (oldChild.isReference() && newChild.isReference()) { 320 // both are reference nodes, e.g., DstReference -> WithName 321 final INodeReference.WithCount withCount = 322 (WithCount) oldChild.asReference().getReferredINode(); 323 withCount.removeReference(oldChild.asReference()); 324 } 325 children.set(i, newChild); 326 327 // replace the instance in the created list of the diff list 328 DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature(); 329 if (sf != null) { 330 sf.getDiffs().replaceChild(ListType.CREATED, oldChild, newChild); 331 } 332 333 // update the inodeMap 334 if (inodeMap != null) { 335 inodeMap.put(newChild); 336 } 337 } 338 339 INodeReference.WithName replaceChild4ReferenceWithName(INode oldChild, 340 int latestSnapshotId) { 341 Preconditions.checkArgument(latestSnapshotId != Snapshot.CURRENT_STATE_ID); 342 if (oldChild instanceof INodeReference.WithName) { 343 return (INodeReference.WithName)oldChild; 344 } 345 346 final INodeReference.WithCount withCount; 347 if (oldChild.isReference()) { 348 Preconditions.checkState(oldChild instanceof INodeReference.DstReference); 349 withCount = (INodeReference.WithCount) oldChild.asReference() 350 .getReferredINode(); 351 } else { 352 withCount = new INodeReference.WithCount(null, oldChild); 353 } 354 final INodeReference.WithName ref = new INodeReference.WithName(this, 355 withCount, oldChild.getLocalNameBytes(), latestSnapshotId); 356 replaceChild(oldChild, ref, null); 357 return ref; 358 } 359 360 @Override 361 public void recordModification(int latestSnapshotId) { 362 if (isInLatestSnapshot(latestSnapshotId) 363 && !shouldRecordInSrcSnapshot(latestSnapshotId)) { 364 // add snapshot feature if necessary 365 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 366 if (sf == null) { 367 sf = addSnapshotFeature(null); 368 } 369 // record self in the diff list if necessary 370 sf.getDiffs().saveSelf2Snapshot(latestSnapshotId, this, null); 371 } 372 } 373 374 /** 375 * Save the child to the latest snapshot. 376 * 377 * @return the child inode, which may be replaced. 378 */ 379 public INode saveChild2Snapshot(final INode child, final int latestSnapshotId, 380 final INode snapshotCopy) { 381 if (latestSnapshotId == Snapshot.CURRENT_STATE_ID) { 382 return child; 383 } 384 385 // add snapshot feature if necessary 386 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 387 if (sf == null) { 388 sf = this.addSnapshotFeature(null); 389 } 390 return sf.saveChild2Snapshot(this, child, latestSnapshotId, snapshotCopy); 391 } 392 393 /** 394 * @param name the name of the child 395 * @param snapshotId 396 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 397 * from the corresponding snapshot; otherwise, get the result from 398 * the current directory. 399 * @return the child inode. 400 */ 401 public INode getChild(byte[] name, int snapshotId) { 402 DirectoryWithSnapshotFeature sf; 403 if (snapshotId == Snapshot.CURRENT_STATE_ID || 404 (sf = getDirectoryWithSnapshotFeature()) == null) { 405 ReadOnlyList<INode> c = getCurrentChildrenList(); 406 final int i = ReadOnlyList.Util.binarySearch(c, name); 407 return i < 0 ? null : c.get(i); 408 } 409 410 return sf.getChild(this, name, snapshotId); 411 } 412 413 /** 414 * Search for the given INode in the children list and the deleted lists of 415 * snapshots. 416 * @return {@link Snapshot#CURRENT_STATE_ID} if the inode is in the children 417 * list; {@link Snapshot#NO_SNAPSHOT_ID} if the inode is neither in the 418 * children list nor in any snapshot; otherwise the snapshot id of the 419 * corresponding snapshot diff list. 420 */ 421 public int searchChild(INode inode) { 422 INode child = getChild(inode.getLocalNameBytes(), Snapshot.CURRENT_STATE_ID); 423 if (child != inode) { 424 // inode is not in parent's children list, thus inode must be in 425 // snapshot. identify the snapshot id and later add it into the path 426 DirectoryDiffList diffs = getDiffs(); 427 if (diffs == null) { 428 return Snapshot.NO_SNAPSHOT_ID; 429 } 430 return diffs.findSnapshotDeleted(inode); 431 } else { 432 return Snapshot.CURRENT_STATE_ID; 433 } 434 } 435 436 /** 437 * @param snapshotId 438 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 439 * from the corresponding snapshot; otherwise, get the result from 440 * the current directory. 441 * @return the current children list if the specified snapshot is null; 442 * otherwise, return the children list corresponding to the snapshot. 443 * Note that the returned list is never null. 444 */ 445 public ReadOnlyList<INode> getChildrenList(final int snapshotId) { 446 DirectoryWithSnapshotFeature sf; 447 if (snapshotId == Snapshot.CURRENT_STATE_ID 448 || (sf = this.getDirectoryWithSnapshotFeature()) == null) { 449 return getCurrentChildrenList(); 450 } 451 return sf.getChildrenList(this, snapshotId); 452 } 453 454 private ReadOnlyList<INode> getCurrentChildrenList() { 455 return children == null ? ReadOnlyList.Util.<INode> emptyList() 456 : ReadOnlyList.Util.asReadOnlyList(children); 457 } 458 459 /** 460 * Given a child's name, return the index of the next child 461 * 462 * @param name a child's name 463 * @return the index of the next child 464 */ 465 static int nextChild(ReadOnlyList<INode> children, byte[] name) { 466 if (name.length == 0) { // empty name 467 return 0; 468 } 469 int nextPos = ReadOnlyList.Util.binarySearch(children, name) + 1; 470 if (nextPos >= 0) { 471 return nextPos; 472 } 473 return -nextPos; 474 } 475 476 /** 477 * Remove the specified child from this directory. 478 */ 479 public boolean removeChild(INode child, int latestSnapshotId) { 480 if (isInLatestSnapshot(latestSnapshotId)) { 481 // create snapshot feature if necessary 482 DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature(); 483 if (sf == null) { 484 sf = this.addSnapshotFeature(null); 485 } 486 return sf.removeChild(this, child, latestSnapshotId); 487 } 488 return removeChild(child); 489 } 490 491 /** 492 * Remove the specified child from this directory. 493 * The basic remove method which actually calls children.remove(..). 494 * 495 * @param child the child inode to be removed 496 * 497 * @return true if the child is removed; false if the child is not found. 498 */ 499 public boolean removeChild(final INode child) { 500 final int i = searchChildren(child.getLocalNameBytes()); 501 if (i < 0) { 502 return false; 503 } 504 505 final INode removed = children.remove(i); 506 Preconditions.checkState(removed == child); 507 return true; 508 } 509 510 /** 511 * Add a child inode to the directory. 512 * 513 * @param node INode to insert 514 * @param setModTime set modification time for the parent node 515 * not needed when replaying the addition and 516 * the parent already has the proper mod time 517 * @return false if the child with this name already exists; 518 * otherwise, return true; 519 */ 520 public boolean addChild(INode node, final boolean setModTime, 521 final int latestSnapshotId) throws QuotaExceededException { 522 final int low = searchChildren(node.getLocalNameBytes()); 523 if (low >= 0) { 524 return false; 525 } 526 527 if (isInLatestSnapshot(latestSnapshotId)) { 528 // create snapshot feature if necessary 529 DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature(); 530 if (sf == null) { 531 sf = this.addSnapshotFeature(null); 532 } 533 return sf.addChild(this, node, setModTime, latestSnapshotId); 534 } 535 addChild(node, low); 536 if (setModTime) { 537 // update modification time of the parent directory 538 updateModificationTime(node.getModificationTime(), latestSnapshotId); 539 } 540 return true; 541 } 542 543 public boolean addChild(INode node) { 544 final int low = searchChildren(node.getLocalNameBytes()); 545 if (low >= 0) { 546 return false; 547 } 548 addChild(node, low); 549 return true; 550 } 551 552 /** 553 * Add the node to the children list at the given insertion point. 554 * The basic add method which actually calls children.add(..). 555 */ 556 private void addChild(final INode node, final int insertionPoint) { 557 if (children == null) { 558 children = new ArrayList<>(DEFAULT_FILES_PER_DIRECTORY); 559 } 560 node.setParent(this); 561 children.add(-insertionPoint - 1, node); 562 563 if (node.getGroupName() == null) { 564 node.setGroup(getGroupName()); 565 } 566 } 567 568 @Override 569 public QuotaCounts computeQuotaUsage(BlockStoragePolicySuite bsps, 570 byte blockStoragePolicyId, boolean useCache, int lastSnapshotId) { 571 final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 572 573 QuotaCounts counts = new QuotaCounts.Builder().build(); 574 // we are computing the quota usage for a specific snapshot here, i.e., the 575 // computation only includes files/directories that exist at the time of the 576 // given snapshot 577 if (sf != null && lastSnapshotId != Snapshot.CURRENT_STATE_ID 578 && !(useCache && isQuotaSet())) { 579 ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshotId); 580 for (INode child : childrenList) { 581 final byte childPolicyId = child.getStoragePolicyIDForQuota( 582 blockStoragePolicyId); 583 counts.add(child.computeQuotaUsage(bsps, childPolicyId, useCache, 584 lastSnapshotId)); 585 } 586 counts.addNameSpace(1); 587 return counts; 588 } 589 590 // compute the quota usage in the scope of the current directory tree 591 final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature(); 592 if (useCache && q != null && q.isQuotaSet()) { // use the cached quota 593 return q.AddCurrentSpaceUsage(counts); 594 } else { 595 useCache = q != null && !q.isQuotaSet() ? false : useCache; 596 return computeDirectoryQuotaUsage(bsps, blockStoragePolicyId, counts, 597 useCache, lastSnapshotId); 598 } 599 } 600 601 private QuotaCounts computeDirectoryQuotaUsage(BlockStoragePolicySuite bsps, 602 byte blockStoragePolicyId, QuotaCounts counts, boolean useCache, 603 int lastSnapshotId) { 604 if (children != null) { 605 for (INode child : children) { 606 final byte childPolicyId = child.getStoragePolicyIDForQuota( 607 blockStoragePolicyId); 608 counts.add(child.computeQuotaUsage(bsps, childPolicyId, useCache, 609 lastSnapshotId)); 610 } 611 } 612 return computeQuotaUsage4CurrentDirectory(bsps, blockStoragePolicyId, 613 counts); 614 } 615 616 /** Add quota usage for this inode excluding children. */ 617 public QuotaCounts computeQuotaUsage4CurrentDirectory( 618 BlockStoragePolicySuite bsps, byte storagePolicyId, QuotaCounts counts) { 619 counts.addNameSpace(1); 620 // include the diff list 621 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 622 if (sf != null) { 623 counts.add(sf.computeQuotaUsage4CurrentDirectory(bsps, storagePolicyId)); 624 } 625 return counts; 626 } 627 628 @Override 629 public ContentSummaryComputationContext computeContentSummary(int snapshotId, 630 ContentSummaryComputationContext summary) { 631 summary.nodeIncluded(this); 632 final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 633 if (sf != null && snapshotId == Snapshot.CURRENT_STATE_ID) { 634 sf.computeContentSummary4Snapshot(summary); 635 } 636 final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature(); 637 if (q != null && snapshotId == Snapshot.CURRENT_STATE_ID) { 638 return q.computeContentSummary(this, summary); 639 } else { 640 return computeDirectoryContentSummary(summary, snapshotId); 641 } 642 } 643 644 protected ContentSummaryComputationContext computeDirectoryContentSummary( 645 ContentSummaryComputationContext summary, int snapshotId) { 646 ReadOnlyList<INode> childrenList = getChildrenList(snapshotId); 647 // Explicit traversing is done to enable repositioning after relinquishing 648 // and reacquiring locks. 649 for (int i = 0; i < childrenList.size(); i++) { 650 INode child = childrenList.get(i); 651 byte[] childName = child.getLocalNameBytes(); 652 653 long lastYieldCount = summary.getYieldCount(); 654 child.computeContentSummary(snapshotId, summary); 655 656 // Check whether the computation was paused in the subtree. 657 // The counts may be off, but traversing the rest of children 658 // should be made safe. 659 if (lastYieldCount == summary.getYieldCount()) { 660 continue; 661 } 662 // The locks were released and reacquired. Check parent first. 663 if (!isRoot() && getParent() == null) { 664 // Stop further counting and return whatever we have so far. 665 break; 666 } 667 // Obtain the children list again since it may have been modified. 668 childrenList = getChildrenList(snapshotId); 669 // Reposition in case the children list is changed. Decrement by 1 670 // since it will be incremented when loops. 671 i = nextChild(childrenList, childName) - 1; 672 } 673 674 // Increment the directory count for this directory. 675 summary.getCounts().addContent(Content.DIRECTORY, 1); 676 // Relinquish and reacquire locks if necessary. 677 summary.yield(); 678 return summary; 679 } 680 681 /** 682 * This method is usually called by the undo section of rename. 683 * 684 * Before calling this function, in the rename operation, we replace the 685 * original src node (of the rename operation) with a reference node (WithName 686 * instance) in both the children list and a created list, delete the 687 * reference node from the children list, and add it to the corresponding 688 * deleted list. 689 * 690 * To undo the above operations, we have the following steps in particular: 691 * 692 * <pre> 693 * 1) remove the WithName node from the deleted list (if it exists) 694 * 2) replace the WithName node in the created list with srcChild 695 * 3) add srcChild back as a child of srcParent. Note that we already add 696 * the node into the created list of a snapshot diff in step 2, we do not need 697 * to add srcChild to the created list of the latest snapshot. 698 * </pre> 699 * 700 * We do not need to update quota usage because the old child is in the 701 * deleted list before. 702 * 703 * @param oldChild 704 * The reference node to be removed/replaced 705 * @param newChild 706 * The node to be added back 707 * @throws QuotaExceededException should not throw this exception 708 */ 709 public void undoRename4ScrParent(final INodeReference oldChild, 710 final INode newChild) throws QuotaExceededException { 711 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 712 assert sf != null : "Directory does not have snapshot feature"; 713 sf.getDiffs().removeChild(ListType.DELETED, oldChild); 714 sf.getDiffs().replaceChild(ListType.CREATED, oldChild, newChild); 715 addChild(newChild, true, Snapshot.CURRENT_STATE_ID); 716 } 717 718 /** 719 * Undo the rename operation for the dst tree, i.e., if the rename operation 720 * (with OVERWRITE option) removes a file/dir from the dst tree, add it back 721 * and delete possible record in the deleted list. 722 */ 723 public void undoRename4DstParent(final BlockStoragePolicySuite bsps, 724 final INode deletedChild, 725 int latestSnapshotId) throws QuotaExceededException { 726 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 727 assert sf != null : "Directory does not have snapshot feature"; 728 boolean removeDeletedChild = sf.getDiffs().removeChild(ListType.DELETED, 729 deletedChild); 730 int sid = removeDeletedChild ? Snapshot.CURRENT_STATE_ID : latestSnapshotId; 731 final boolean added = addChild(deletedChild, true, sid); 732 // update quota usage if adding is successfully and the old child has not 733 // been stored in deleted list before 734 if (added && !removeDeletedChild) { 735 final QuotaCounts counts = deletedChild.computeQuotaUsage(bsps); 736 addSpaceConsumed(counts, false); 737 738 } 739 } 740 741 /** Set the children list to null. */ 742 public void clearChildren() { 743 this.children = null; 744 } 745 746 @Override 747 public void clear() { 748 super.clear(); 749 clearChildren(); 750 } 751 752 /** Call cleanSubtree(..) recursively down the subtree. */ 753 public void cleanSubtreeRecursively( 754 ReclaimContext reclaimContext, final int snapshot, int prior, 755 final Map<INode, INode> excludedNodes) { 756 // in case of deletion snapshot, since this call happens after we modify 757 // the diff list, the snapshot to be deleted has been combined or renamed 758 // to its latest previous snapshot. (besides, we also need to consider nodes 759 // created after prior but before snapshot. this will be done in 760 // DirectoryWithSnapshotFeature) 761 int s = snapshot != Snapshot.CURRENT_STATE_ID 762 && prior != Snapshot.NO_SNAPSHOT_ID ? prior : snapshot; 763 for (INode child : getChildrenList(s)) { 764 if (snapshot == Snapshot.CURRENT_STATE_ID || excludedNodes == null || 765 !excludedNodes.containsKey(child)) { 766 child.cleanSubtree(reclaimContext, snapshot, prior); 767 } 768 } 769 } 770 771 @Override 772 public void destroyAndCollectBlocks(ReclaimContext reclaimContext) { 773 reclaimContext.quotaDelta().add( 774 new QuotaCounts.Builder().nameSpace(1).build()); 775 final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 776 if (sf != null) { 777 sf.clear(reclaimContext, this); 778 } 779 for (INode child : getChildrenList(Snapshot.CURRENT_STATE_ID)) { 780 child.destroyAndCollectBlocks(reclaimContext); 781 } 782 if (getAclFeature() != null) { 783 AclStorage.removeAclFeature(getAclFeature()); 784 } 785 clear(); 786 reclaimContext.removedINodes.add(this); 787 } 788 789 @Override 790 public void cleanSubtree(ReclaimContext reclaimContext, final int snapshotId, 791 int priorSnapshotId) { 792 DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature(); 793 // there is snapshot data 794 if (sf != null) { 795 sf.cleanDirectory(reclaimContext, this, snapshotId, priorSnapshotId); 796 } else { 797 // there is no snapshot data 798 if (priorSnapshotId == Snapshot.NO_SNAPSHOT_ID && 799 snapshotId == Snapshot.CURRENT_STATE_ID) { 800 // destroy the whole subtree and collect blocks that should be deleted 801 destroyAndCollectBlocks(reclaimContext); 802 } else { 803 // make a copy the quota delta 804 QuotaCounts old = reclaimContext.quotaDelta().getCountsCopy(); 805 // process recursively down the subtree 806 cleanSubtreeRecursively(reclaimContext, snapshotId, priorSnapshotId, 807 null); 808 QuotaCounts current = reclaimContext.quotaDelta().getCountsCopy(); 809 current.subtract(old); 810 if (isQuotaSet()) { 811 reclaimContext.quotaDelta().addQuotaDirUpdate(this, current); 812 } 813 } 814 } 815 } 816 817 /** 818 * Compare the metadata with another INodeDirectory 819 */ 820 @Override 821 public boolean metadataEquals(INodeDirectoryAttributes other) { 822 return other != null 823 && getQuotaCounts().equals(other.getQuotaCounts()) 824 && getPermissionLong() == other.getPermissionLong() 825 && getAclFeature() == other.getAclFeature() 826 && getXAttrFeature() == other.getXAttrFeature(); 827 } 828 829 /* 830 * The following code is to dump the tree recursively for testing. 831 * 832 * \- foo (INodeDirectory@33dd2717) 833 * \- sub1 (INodeDirectory@442172) 834 * +- file1 (INodeFile@78392d4) 835 * +- file2 (INodeFile@78392d5) 836 * +- sub11 (INodeDirectory@8400cff) 837 * \- file3 (INodeFile@78392d6) 838 * \- z_file4 (INodeFile@45848712) 839 */ 840 static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-"; 841 static final String DUMPTREE_LAST_ITEM = "\\-"; 842 @VisibleForTesting 843 @Override 844 public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, 845 final int snapshot) { 846 super.dumpTreeRecursively(out, prefix, snapshot); 847 out.print(", childrenSize=" + getChildrenList(snapshot).size()); 848 final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature(); 849 if (q != null) { 850 out.print(", " + q); 851 } 852 if (this instanceof Snapshot.Root) { 853 out.print(", snapshotId=" + snapshot); 854 } 855 out.println(); 856 857 if (prefix.length() >= 2) { 858 prefix.setLength(prefix.length() - 2); 859 prefix.append(" "); 860 } 861 dumpTreeRecursively(out, prefix, new Iterable<SnapshotAndINode>() { 862 final Iterator<INode> i = getChildrenList(snapshot).iterator(); 863 864 @Override 865 public Iterator<SnapshotAndINode> iterator() { 866 return new Iterator<SnapshotAndINode>() { 867 @Override 868 public boolean hasNext() { 869 return i.hasNext(); 870 } 871 872 @Override 873 public SnapshotAndINode next() { 874 return new SnapshotAndINode(snapshot, i.next()); 875 } 876 877 @Override 878 public void remove() { 879 throw new UnsupportedOperationException(); 880 } 881 }; 882 } 883 }); 884 885 final DirectorySnapshottableFeature s = getDirectorySnapshottableFeature(); 886 if (s != null) { 887 s.dumpTreeRecursively(this, out, prefix, snapshot); 888 } 889 } 890 891 /** 892 * Dump the given subtrees. 893 * @param prefix The prefix string that each line should print. 894 * @param subs The subtrees. 895 */ 896 @VisibleForTesting 897 public static void dumpTreeRecursively(PrintWriter out, 898 StringBuilder prefix, Iterable<SnapshotAndINode> subs) { 899 if (subs != null) { 900 for(final Iterator<SnapshotAndINode> i = subs.iterator(); i.hasNext();) { 901 final SnapshotAndINode pair = i.next(); 902 prefix.append(i.hasNext()? DUMPTREE_EXCEPT_LAST_ITEM: DUMPTREE_LAST_ITEM); 903 pair.inode.dumpTreeRecursively(out, prefix, pair.snapshotId); 904 prefix.setLength(prefix.length() - 2); 905 } 906 } 907 } 908 909 /** A pair of Snapshot and INode objects. */ 910 public static class SnapshotAndINode { 911 public final int snapshotId; 912 public final INode inode; 913 914 public SnapshotAndINode(int snapshot, INode inode) { 915 this.snapshotId = snapshot; 916 this.inode = inode; 917 } 918 } 919 920 public final int getChildrenNum(final int snapshotId) { 921 return getChildrenList(snapshotId).size(); 922 } 923}