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