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