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