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