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