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.snapshot; 019 020import java.io.DataOutput; 021import java.io.IOException; 022import java.util.ArrayDeque; 023import java.util.Deque; 024import java.util.HashMap; 025import java.util.Iterator; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.hadoop.classification.InterfaceAudience; 030import org.apache.hadoop.hdfs.protocol.QuotaExceededException; 031import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite; 032import org.apache.hadoop.hdfs.server.namenode.AclStorage; 033import org.apache.hadoop.hdfs.server.namenode.ContentSummaryComputationContext; 034import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization; 035import org.apache.hadoop.hdfs.server.namenode.INode; 036import org.apache.hadoop.hdfs.server.namenode.INodeDirectory; 037import org.apache.hadoop.hdfs.server.namenode.INodeDirectoryAttributes; 038import org.apache.hadoop.hdfs.server.namenode.INodeFile; 039import org.apache.hadoop.hdfs.server.namenode.INodeReference; 040import org.apache.hadoop.hdfs.server.namenode.QuotaCounts; 041import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap; 042import org.apache.hadoop.hdfs.util.Diff; 043import org.apache.hadoop.hdfs.util.Diff.Container; 044import org.apache.hadoop.hdfs.util.Diff.ListType; 045import org.apache.hadoop.hdfs.util.Diff.UndoInfo; 046import org.apache.hadoop.hdfs.util.ReadOnlyList; 047 048import com.google.common.base.Preconditions; 049 050import static org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot.NO_SNAPSHOT_ID; 051 052/** 053 * Feature used to store and process the snapshot diff information for a 054 * directory. In particular, it contains a directory diff list recording changes 055 * made to the directory and its children for each snapshot. 056 */ 057@InterfaceAudience.Private 058public class DirectoryWithSnapshotFeature implements INode.Feature { 059 /** 060 * The difference between the current state and a previous snapshot 061 * of the children list of an INodeDirectory. 062 */ 063 static class ChildrenDiff extends Diff<byte[], INode> { 064 ChildrenDiff() {} 065 066 private ChildrenDiff(final List<INode> created, final List<INode> deleted) { 067 super(created, deleted); 068 } 069 070 /** 071 * Replace the given child from the created/deleted list. 072 * @return true if the child is replaced; false if the child is not found. 073 */ 074 private boolean replace(final ListType type, 075 final INode oldChild, final INode newChild) { 076 final List<INode> list = getList(type); 077 final int i = search(list, oldChild.getLocalNameBytes()); 078 if (i < 0 || list.get(i).getId() != oldChild.getId()) { 079 return false; 080 } 081 082 final INode removed = list.set(i, newChild); 083 Preconditions.checkState(removed == oldChild); 084 return true; 085 } 086 087 private boolean removeChild(ListType type, final INode child) { 088 final List<INode> list = getList(type); 089 final int i = searchIndex(type, child.getLocalNameBytes()); 090 if (i >= 0 && list.get(i) == child) { 091 list.remove(i); 092 return true; 093 } 094 return false; 095 } 096 097 /** clear the created list */ 098 private void destroyCreatedList(INode.ReclaimContext reclaimContext, 099 final INodeDirectory currentINode) { 100 final List<INode> createdList = getList(ListType.CREATED); 101 for (INode c : createdList) { 102 c.destroyAndCollectBlocks(reclaimContext); 103 // c should be contained in the children list, remove it 104 currentINode.removeChild(c); 105 } 106 createdList.clear(); 107 } 108 109 /** clear the deleted list */ 110 private void destroyDeletedList(INode.ReclaimContext reclaimContext) { 111 final List<INode> deletedList = getList(ListType.DELETED); 112 for (INode d : deletedList) { 113 d.destroyAndCollectBlocks(reclaimContext); 114 } 115 deletedList.clear(); 116 } 117 118 /** Serialize {@link #created} */ 119 private void writeCreated(DataOutput out) throws IOException { 120 final List<INode> created = getList(ListType.CREATED); 121 out.writeInt(created.size()); 122 for (INode node : created) { 123 // For INode in created list, we only need to record its local name 124 byte[] name = node.getLocalNameBytes(); 125 out.writeShort(name.length); 126 out.write(name); 127 } 128 } 129 130 /** Serialize {@link #deleted} */ 131 private void writeDeleted(DataOutput out, 132 ReferenceMap referenceMap) throws IOException { 133 final List<INode> deleted = getList(ListType.DELETED); 134 out.writeInt(deleted.size()); 135 for (INode node : deleted) { 136 FSImageSerialization.saveINode2Image(node, out, true, referenceMap); 137 } 138 } 139 140 /** Serialize to out */ 141 private void write(DataOutput out, ReferenceMap referenceMap 142 ) throws IOException { 143 writeCreated(out); 144 writeDeleted(out, referenceMap); 145 } 146 147 /** Get the list of INodeDirectory contained in the deleted list */ 148 private void getDirsInDeleted(List<INodeDirectory> dirList) { 149 for (INode node : getList(ListType.DELETED)) { 150 if (node.isDirectory()) { 151 dirList.add(node.asDirectory()); 152 } 153 } 154 } 155 } 156 157 /** 158 * The difference of an {@link INodeDirectory} between two snapshots. 159 */ 160 public static class DirectoryDiff extends 161 AbstractINodeDiff<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> { 162 /** The size of the children list at snapshot creation time. */ 163 private final int childrenSize; 164 /** The children list diff. */ 165 private final ChildrenDiff diff; 166 private boolean isSnapshotRoot = false; 167 168 private DirectoryDiff(int snapshotId, INodeDirectory dir) { 169 super(snapshotId, null, null); 170 171 this.childrenSize = dir.getChildrenList(Snapshot.CURRENT_STATE_ID).size(); 172 this.diff = new ChildrenDiff(); 173 } 174 175 /** Constructor used by FSImage loading */ 176 DirectoryDiff(int snapshotId, INodeDirectoryAttributes snapshotINode, 177 DirectoryDiff posteriorDiff, int childrenSize, List<INode> createdList, 178 List<INode> deletedList, boolean isSnapshotRoot) { 179 super(snapshotId, snapshotINode, posteriorDiff); 180 this.childrenSize = childrenSize; 181 this.diff = new ChildrenDiff(createdList, deletedList); 182 this.isSnapshotRoot = isSnapshotRoot; 183 } 184 185 public ChildrenDiff getChildrenDiff() { 186 return diff; 187 } 188 189 void setSnapshotRoot(INodeDirectoryAttributes root) { 190 this.snapshotINode = root; 191 this.isSnapshotRoot = true; 192 } 193 194 boolean isSnapshotRoot() { 195 return isSnapshotRoot; 196 } 197 198 @Override 199 void combinePosteriorAndCollectBlocks( 200 final INode.ReclaimContext reclaimContext, 201 final INodeDirectory currentDir, 202 final DirectoryDiff posterior) { 203 diff.combinePosterior(posterior.diff, new Diff.Processor<INode>() { 204 /** Collect blocks for deleted files. */ 205 @Override 206 public void process(INode inode) { 207 if (inode != null) { 208 inode.destroyAndCollectBlocks(reclaimContext); 209 } 210 } 211 }); 212 } 213 214 /** 215 * @return The children list of a directory in a snapshot. 216 * Since the snapshot is read-only, the logical view of the list is 217 * never changed although the internal data structure may mutate. 218 */ 219 private ReadOnlyList<INode> getChildrenList(final INodeDirectory currentDir) { 220 return new ReadOnlyList<INode>() { 221 private List<INode> children = null; 222 223 private List<INode> initChildren() { 224 if (children == null) { 225 final ChildrenDiff combined = new ChildrenDiff(); 226 for (DirectoryDiff d = DirectoryDiff.this; d != null; 227 d = d.getPosterior()) { 228 combined.combinePosterior(d.diff, null); 229 } 230 children = combined.apply2Current(ReadOnlyList.Util.asList( 231 currentDir.getChildrenList(Snapshot.CURRENT_STATE_ID))); 232 } 233 return children; 234 } 235 236 @Override 237 public Iterator<INode> iterator() { 238 return initChildren().iterator(); 239 } 240 241 @Override 242 public boolean isEmpty() { 243 return childrenSize == 0; 244 } 245 246 @Override 247 public int size() { 248 return childrenSize; 249 } 250 251 @Override 252 public INode get(int i) { 253 return initChildren().get(i); 254 } 255 }; 256 } 257 258 /** @return the child with the given name. */ 259 INode getChild(byte[] name, boolean checkPosterior, 260 INodeDirectory currentDir) { 261 for(DirectoryDiff d = this; ; d = d.getPosterior()) { 262 final Container<INode> returned = d.diff.accessPrevious(name); 263 if (returned != null) { 264 // the diff is able to determine the inode 265 return returned.getElement(); 266 } else if (!checkPosterior) { 267 // Since checkPosterior is false, return null, i.e. not found. 268 return null; 269 } else if (d.getPosterior() == null) { 270 // no more posterior diff, get from current inode. 271 return currentDir.getChild(name, Snapshot.CURRENT_STATE_ID); 272 } 273 } 274 } 275 276 @Override 277 public String toString() { 278 return super.toString() + " childrenSize=" + childrenSize + ", " + diff; 279 } 280 281 int getChildrenSize() { 282 return childrenSize; 283 } 284 285 @Override 286 void write(DataOutput out, ReferenceMap referenceMap) throws IOException { 287 writeSnapshot(out); 288 out.writeInt(childrenSize); 289 290 // Write snapshotINode 291 out.writeBoolean(isSnapshotRoot); 292 if (!isSnapshotRoot) { 293 if (snapshotINode != null) { 294 out.writeBoolean(true); 295 FSImageSerialization.writeINodeDirectoryAttributes(snapshotINode, out); 296 } else { 297 out.writeBoolean(false); 298 } 299 } 300 // Write diff. Node need to write poseriorDiff, since diffs is a list. 301 diff.write(out, referenceMap); 302 } 303 304 @Override 305 void destroyDiffAndCollectBlocks( 306 INode.ReclaimContext reclaimContext, INodeDirectory currentINode) { 307 // this diff has been deleted 308 diff.destroyDeletedList(reclaimContext); 309 INodeDirectoryAttributes snapshotINode = getSnapshotINode(); 310 if (snapshotINode != null && snapshotINode.getAclFeature() != null) { 311 AclStorage.removeAclFeature(snapshotINode.getAclFeature()); 312 } 313 } 314 } 315 316 /** A list of directory diffs. */ 317 public static class DirectoryDiffList 318 extends AbstractINodeDiffList<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> { 319 320 @Override 321 DirectoryDiff createDiff(int snapshot, INodeDirectory currentDir) { 322 return new DirectoryDiff(snapshot, currentDir); 323 } 324 325 @Override 326 INodeDirectoryAttributes createSnapshotCopy(INodeDirectory currentDir) { 327 return currentDir.isQuotaSet()? 328 new INodeDirectoryAttributes.CopyWithQuota(currentDir) 329 : new INodeDirectoryAttributes.SnapshotCopy(currentDir); 330 } 331 332 /** Replace the given child in the created/deleted list, if there is any. */ 333 public boolean replaceChild(final ListType type, final INode oldChild, 334 final INode newChild) { 335 final List<DirectoryDiff> diffList = asList(); 336 for(int i = diffList.size() - 1; i >= 0; i--) { 337 final ChildrenDiff diff = diffList.get(i).diff; 338 if (diff.replace(type, oldChild, newChild)) { 339 return true; 340 } 341 } 342 return false; 343 } 344 345 /** Remove the given child in the created/deleted list, if there is any. */ 346 public boolean removeChild(final ListType type, final INode child) { 347 final List<DirectoryDiff> diffList = asList(); 348 for(int i = diffList.size() - 1; i >= 0; i--) { 349 final ChildrenDiff diff = diffList.get(i).diff; 350 if (diff.removeChild(type, child)) { 351 return true; 352 } 353 } 354 return false; 355 } 356 357 /** 358 * Find the corresponding snapshot whose deleted list contains the given 359 * inode. 360 * @return the id of the snapshot. {@link Snapshot#NO_SNAPSHOT_ID} if the 361 * given inode is not in any of the snapshot. 362 */ 363 public int findSnapshotDeleted(final INode child) { 364 final List<DirectoryDiff> diffList = asList(); 365 for(int i = diffList.size() - 1; i >= 0; i--) { 366 final ChildrenDiff diff = diffList.get(i).diff; 367 final int d = diff.searchIndex(ListType.DELETED, 368 child.getLocalNameBytes()); 369 if (d >= 0 && diff.getList(ListType.DELETED).get(d) == child) { 370 return diffList.get(i).getSnapshotId(); 371 } 372 } 373 return NO_SNAPSHOT_ID; 374 } 375 } 376 377 private static Map<INode, INode> cloneDiffList(List<INode> diffList) { 378 if (diffList == null || diffList.size() == 0) { 379 return null; 380 } 381 Map<INode, INode> map = new HashMap<>(diffList.size()); 382 for (INode node : diffList) { 383 map.put(node, node); 384 } 385 return map; 386 } 387 388 /** 389 * Destroy a subtree under a DstReference node. 390 */ 391 public static void destroyDstSubtree(INode.ReclaimContext reclaimContext, 392 INode inode, final int snapshot, final int prior) { 393 Preconditions.checkArgument(prior != NO_SNAPSHOT_ID); 394 if (inode.isReference()) { 395 if (inode instanceof INodeReference.WithName 396 && snapshot != Snapshot.CURRENT_STATE_ID) { 397 // this inode has been renamed before the deletion of the DstReference 398 // subtree 399 inode.cleanSubtree(reclaimContext, snapshot, prior); 400 } else { 401 // for DstReference node, continue this process to its subtree 402 destroyDstSubtree(reclaimContext, 403 inode.asReference().getReferredINode(), snapshot, prior); 404 } 405 } else if (inode.isFile()) { 406 inode.cleanSubtree(reclaimContext, snapshot, prior); 407 } else if (inode.isDirectory()) { 408 Map<INode, INode> excludedNodes = null; 409 INodeDirectory dir = inode.asDirectory(); 410 DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature(); 411 if (sf != null) { 412 DirectoryDiffList diffList = sf.getDiffs(); 413 DirectoryDiff priorDiff = diffList.getDiffById(prior); 414 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 415 List<INode> dList = priorDiff.diff.getList(ListType.DELETED); 416 excludedNodes = cloneDiffList(dList); 417 } 418 419 if (snapshot != Snapshot.CURRENT_STATE_ID) { 420 diffList.deleteSnapshotDiff(reclaimContext, 421 snapshot, prior, dir); 422 } 423 priorDiff = diffList.getDiffById(prior); 424 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 425 priorDiff.diff.destroyCreatedList(reclaimContext, dir); 426 } 427 } 428 for (INode child : inode.asDirectory().getChildrenList(prior)) { 429 if (excludedNodes != null && excludedNodes.containsKey(child)) { 430 continue; 431 } 432 destroyDstSubtree(reclaimContext, child, snapshot, prior); 433 } 434 } 435 } 436 437 /** 438 * Clean an inode while we move it from the deleted list of post to the 439 * deleted list of prior. 440 * @param reclaimContext blocks and inodes that need to be reclaimed 441 * @param inode The inode to clean. 442 * @param post The post snapshot. 443 * @param prior The id of the prior snapshot. 444 */ 445 private static void cleanDeletedINode(INode.ReclaimContext reclaimContext, 446 INode inode, final int post, final int prior) { 447 Deque<INode> queue = new ArrayDeque<>(); 448 queue.addLast(inode); 449 while (!queue.isEmpty()) { 450 INode topNode = queue.pollFirst(); 451 if (topNode instanceof INodeReference.WithName) { 452 INodeReference.WithName wn = (INodeReference.WithName) topNode; 453 if (wn.getLastSnapshotId() >= post) { 454 INodeReference.WithCount wc = 455 (INodeReference.WithCount) wn.getReferredINode(); 456 if (wc.getLastWithName() == wn && wc.getParentReference() == null) { 457 // this wn is the last wn inside of the wc, also the dstRef node has 458 // been deleted. In this case, we should treat the referred file/dir 459 // as normal case 460 queue.add(wc.getReferredINode()); 461 } else { 462 wn.cleanSubtree(reclaimContext, post, prior); 463 } 464 } 465 // For DstReference node, since the node is not in the created list of 466 // prior, we should treat it as regular file/dir 467 } else if (topNode.isFile() && topNode.asFile().isWithSnapshot()) { 468 INodeFile file = topNode.asFile(); 469 file.getDiffs().deleteSnapshotDiff(reclaimContext, post, prior, file); 470 } else if (topNode.isDirectory()) { 471 INodeDirectory dir = topNode.asDirectory(); 472 ChildrenDiff priorChildrenDiff = null; 473 DirectoryWithSnapshotFeature sf = dir.getDirectoryWithSnapshotFeature(); 474 if (sf != null) { 475 // delete files/dirs created after prior. Note that these 476 // files/dirs, along with inode, were deleted right after post. 477 DirectoryDiff priorDiff = sf.getDiffs().getDiffById(prior); 478 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 479 priorChildrenDiff = priorDiff.getChildrenDiff(); 480 priorChildrenDiff.destroyCreatedList(reclaimContext, dir); 481 } 482 } 483 484 for (INode child : dir.getChildrenList(prior)) { 485 if (priorChildrenDiff != null && priorChildrenDiff.search( 486 ListType.DELETED, child.getLocalNameBytes()) != null) { 487 continue; 488 } 489 queue.addLast(child); 490 } 491 } 492 } 493 } 494 495 /** Diff list sorted by snapshot IDs, i.e. in chronological order. */ 496 private final DirectoryDiffList diffs; 497 498 public DirectoryWithSnapshotFeature(DirectoryDiffList diffs) { 499 this.diffs = diffs != null ? diffs : new DirectoryDiffList(); 500 } 501 502 /** @return the last snapshot. */ 503 public int getLastSnapshotId() { 504 return diffs.getLastSnapshotId(); 505 } 506 507 /** @return the snapshot diff list. */ 508 public DirectoryDiffList getDiffs() { 509 return diffs; 510 } 511 512 /** 513 * Get all the directories that are stored in some snapshot but not in the 514 * current children list. These directories are equivalent to the directories 515 * stored in the deletes lists. 516 */ 517 public void getSnapshotDirectory(List<INodeDirectory> snapshotDir) { 518 for (DirectoryDiff sdiff : diffs) { 519 sdiff.getChildrenDiff().getDirsInDeleted(snapshotDir); 520 } 521 } 522 523 /** 524 * Add an inode into parent's children list. The caller of this method needs 525 * to make sure that parent is in the given snapshot "latest". 526 */ 527 public boolean addChild(INodeDirectory parent, INode inode, 528 boolean setModTime, int latestSnapshotId) throws QuotaExceededException { 529 ChildrenDiff diff = diffs.checkAndAddLatestSnapshotDiff(latestSnapshotId, 530 parent).diff; 531 int undoInfo = diff.create(inode); 532 533 final boolean added = parent.addChild(inode, setModTime, 534 Snapshot.CURRENT_STATE_ID); 535 if (!added) { 536 diff.undoCreate(inode, undoInfo); 537 } 538 return added; 539 } 540 541 /** 542 * Remove an inode from parent's children list. The caller of this method 543 * needs to make sure that parent is in the given snapshot "latest". 544 */ 545 public boolean removeChild(INodeDirectory parent, INode child, 546 int latestSnapshotId) { 547 // For a directory that is not a renamed node, if isInLatestSnapshot returns 548 // false, the directory is not in the latest snapshot, thus we do not need 549 // to record the removed child in any snapshot. 550 // For a directory that was moved/renamed, note that if the directory is in 551 // any of the previous snapshots, we will create a reference node for the 552 // directory while rename, and isInLatestSnapshot will return true in that 553 // scenario (if all previous snapshots have been deleted, isInLatestSnapshot 554 // still returns false). Thus if isInLatestSnapshot returns false, the 555 // directory node cannot be in any snapshot (not in current tree, nor in 556 // previous src tree). Thus we do not need to record the removed child in 557 // any snapshot. 558 ChildrenDiff diff = diffs.checkAndAddLatestSnapshotDiff(latestSnapshotId, 559 parent).diff; 560 UndoInfo<INode> undoInfo = diff.delete(child); 561 562 final boolean removed = parent.removeChild(child); 563 if (!removed && undoInfo != null) { 564 // remove failed, undo 565 diff.undoDelete(child, undoInfo); 566 } 567 return removed; 568 } 569 570 /** 571 * @return If there is no corresponding directory diff for the given 572 * snapshot, this means that the current children list should be 573 * returned for the snapshot. Otherwise we calculate the children list 574 * for the snapshot and return it. 575 */ 576 public ReadOnlyList<INode> getChildrenList(INodeDirectory currentINode, 577 final int snapshotId) { 578 final DirectoryDiff diff = diffs.getDiffById(snapshotId); 579 return diff != null ? diff.getChildrenList(currentINode) : currentINode 580 .getChildrenList(Snapshot.CURRENT_STATE_ID); 581 } 582 583 public INode getChild(INodeDirectory currentINode, byte[] name, 584 int snapshotId) { 585 final DirectoryDiff diff = diffs.getDiffById(snapshotId); 586 return diff != null ? diff.getChild(name, true, currentINode) 587 : currentINode.getChild(name, Snapshot.CURRENT_STATE_ID); 588 } 589 590 /** Used to record the modification of a symlink node */ 591 public INode saveChild2Snapshot(INodeDirectory currentINode, 592 final INode child, final int latestSnapshotId, final INode snapshotCopy) { 593 Preconditions.checkArgument(!child.isDirectory(), 594 "child is a directory, child=%s", child); 595 Preconditions.checkArgument(latestSnapshotId != Snapshot.CURRENT_STATE_ID); 596 597 final DirectoryDiff diff = diffs.checkAndAddLatestSnapshotDiff( 598 latestSnapshotId, currentINode); 599 if (diff.getChild(child.getLocalNameBytes(), false, currentINode) != null) { 600 // it was already saved in the latest snapshot earlier. 601 return child; 602 } 603 604 diff.diff.modify(snapshotCopy, child); 605 return child; 606 } 607 608 public void clear( 609 INode.ReclaimContext reclaimContext, INodeDirectory currentINode) { 610 // destroy its diff list 611 for (DirectoryDiff diff : diffs) { 612 diff.destroyDiffAndCollectBlocks(reclaimContext, currentINode); 613 } 614 diffs.clear(); 615 } 616 617 public QuotaCounts computeQuotaUsage4CurrentDirectory( 618 BlockStoragePolicySuite bsps, byte storagePolicyId) { 619 final QuotaCounts counts = new QuotaCounts.Builder().build(); 620 for(DirectoryDiff d : diffs) { 621 for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) { 622 final byte childPolicyId = deleted.getStoragePolicyIDForQuota( 623 storagePolicyId); 624 counts.add(deleted.computeQuotaUsage(bsps, childPolicyId, false, 625 Snapshot.CURRENT_STATE_ID)); 626 } 627 } 628 return counts; 629 } 630 631 public void computeContentSummary4Snapshot( 632 ContentSummaryComputationContext context) { 633 for(DirectoryDiff d : diffs) { 634 for(INode deletedNode : d.getChildrenDiff().getList(ListType.DELETED)) { 635 context.reportDeletedSnapshottedNode(deletedNode); 636 } 637 } 638 } 639 640 /** 641 * Compute the difference between Snapshots. 642 * 643 * @param fromSnapshot Start point of the diff computation. Null indicates 644 * current tree. 645 * @param toSnapshot End point of the diff computation. Null indicates current 646 * tree. 647 * @param diff Used to capture the changes happening to the children. Note 648 * that the diff still represents (later_snapshot - earlier_snapshot) 649 * although toSnapshot can be before fromSnapshot. 650 * @param currentINode The {@link INodeDirectory} this feature belongs to. 651 * @return Whether changes happened between the startSnapshot and endSnaphsot. 652 */ 653 boolean computeDiffBetweenSnapshots(Snapshot fromSnapshot, 654 Snapshot toSnapshot, ChildrenDiff diff, INodeDirectory currentINode) { 655 int[] diffIndexPair = diffs.changedBetweenSnapshots(fromSnapshot, 656 toSnapshot); 657 if (diffIndexPair == null) { 658 return false; 659 } 660 int earlierDiffIndex = diffIndexPair[0]; 661 int laterDiffIndex = diffIndexPair[1]; 662 663 boolean dirMetadataChanged = false; 664 INodeDirectoryAttributes dirCopy = null; 665 List<DirectoryDiff> difflist = diffs.asList(); 666 for (int i = earlierDiffIndex; i < laterDiffIndex; i++) { 667 DirectoryDiff sdiff = difflist.get(i); 668 diff.combinePosterior(sdiff.diff, null); 669 if (!dirMetadataChanged && sdiff.snapshotINode != null) { 670 if (dirCopy == null) { 671 dirCopy = sdiff.snapshotINode; 672 } else if (!dirCopy.metadataEquals(sdiff.snapshotINode)) { 673 dirMetadataChanged = true; 674 } 675 } 676 } 677 678 if (!diff.isEmpty() || dirMetadataChanged) { 679 return true; 680 } else if (dirCopy != null) { 681 for (int i = laterDiffIndex; i < difflist.size(); i++) { 682 if (!dirCopy.metadataEquals(difflist.get(i).snapshotINode)) { 683 return true; 684 } 685 } 686 return !dirCopy.metadataEquals(currentINode); 687 } else { 688 return false; 689 } 690 } 691 692 public void cleanDirectory(INode.ReclaimContext reclaimContext, 693 final INodeDirectory currentINode, final int snapshot, int prior) { 694 Map<INode, INode> priorCreated = null; 695 Map<INode, INode> priorDeleted = null; 696 QuotaCounts old = reclaimContext.quotaDelta().getCountsCopy(); 697 if (snapshot == Snapshot.CURRENT_STATE_ID) { // delete the current directory 698 currentINode.recordModification(prior); 699 // delete everything in created list 700 DirectoryDiff lastDiff = diffs.getLast(); 701 if (lastDiff != null) { 702 lastDiff.diff.destroyCreatedList(reclaimContext, currentINode); 703 } 704 currentINode.cleanSubtreeRecursively(reclaimContext, snapshot, prior, 705 null); 706 } else { 707 // update prior 708 prior = getDiffs().updatePrior(snapshot, prior); 709 // if there is a snapshot diff associated with prior, we need to record 710 // its original created and deleted list before deleting post 711 if (prior != NO_SNAPSHOT_ID) { 712 DirectoryDiff priorDiff = this.getDiffs().getDiffById(prior); 713 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 714 List<INode> cList = priorDiff.diff.getList(ListType.CREATED); 715 List<INode> dList = priorDiff.diff.getList(ListType.DELETED); 716 priorCreated = cloneDiffList(cList); 717 priorDeleted = cloneDiffList(dList); 718 } 719 } 720 721 getDiffs().deleteSnapshotDiff(reclaimContext, snapshot, prior, 722 currentINode); 723 currentINode.cleanSubtreeRecursively(reclaimContext, snapshot, prior, 724 priorDeleted); 725 726 // check priorDiff again since it may be created during the diff deletion 727 if (prior != NO_SNAPSHOT_ID) { 728 DirectoryDiff priorDiff = this.getDiffs().getDiffById(prior); 729 if (priorDiff != null && priorDiff.getSnapshotId() == prior) { 730 // For files/directories created between "prior" and "snapshot", 731 // we need to clear snapshot copies for "snapshot". Note that we must 732 // use null as prior in the cleanSubtree call. Files/directories that 733 // were created before "prior" will be covered by the later 734 // cleanSubtreeRecursively call. 735 if (priorCreated != null) { 736 // we only check the node originally in prior's created list 737 for (INode cNode : priorDiff.getChildrenDiff().getList( 738 ListType.CREATED)) { 739 if (priorCreated.containsKey(cNode)) { 740 cNode.cleanSubtree(reclaimContext, snapshot, NO_SNAPSHOT_ID); 741 } 742 } 743 } 744 745 // When a directory is moved from the deleted list of the posterior 746 // diff to the deleted list of this diff, we need to destroy its 747 // descendants that were 1) created after taking this diff and 2) 748 // deleted after taking posterior diff. 749 750 // For files moved from posterior's deleted list, we also need to 751 // delete its snapshot copy associated with the posterior snapshot. 752 753 for (INode dNode : priorDiff.getChildrenDiff().getList( 754 ListType.DELETED)) { 755 if (priorDeleted == null || !priorDeleted.containsKey(dNode)) { 756 cleanDeletedINode(reclaimContext, dNode, snapshot, prior); 757 } 758 } 759 } 760 } 761 } 762 763 QuotaCounts current = reclaimContext.quotaDelta().getCountsCopy(); 764 current.subtract(old); 765 if (currentINode.isQuotaSet()) { 766 reclaimContext.quotaDelta().addQuotaDirUpdate(currentINode, current); 767 } 768 } 769}