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