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}