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