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