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