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