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