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.ArrayList;
024    import java.util.Collections;
025    import java.util.Deque;
026    import java.util.HashMap;
027    import java.util.Iterator;
028    import java.util.List;
029    import java.util.Map;
030    
031    import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
032    import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffReportEntry;
033    import org.apache.hadoop.hdfs.protocol.SnapshotDiffReport.DiffType;
034    import org.apache.hadoop.hdfs.server.namenode.Content;
035    import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
036    import org.apache.hadoop.hdfs.server.namenode.INode;
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.INodeDirectoryWithQuota;
040    import org.apache.hadoop.hdfs.server.namenode.INodeMap;
041    import org.apache.hadoop.hdfs.server.namenode.INodeReference;
042    import org.apache.hadoop.hdfs.server.namenode.Quota;
043    import org.apache.hadoop.hdfs.server.namenode.snapshot.SnapshotFSImageFormat.ReferenceMap;
044    import org.apache.hadoop.hdfs.util.Diff;
045    import org.apache.hadoop.hdfs.util.Diff.Container;
046    import org.apache.hadoop.hdfs.util.Diff.ListType;
047    import org.apache.hadoop.hdfs.util.Diff.UndoInfo;
048    import org.apache.hadoop.hdfs.util.ReadOnlyList;
049    
050    import com.google.common.base.Preconditions;
051    
052    /**
053     * The directory with snapshots. It maintains a list of snapshot diffs for
054     * storing snapshot data. When there are modifications to the directory, the old
055     * data is stored in the latest snapshot, if there is any.
056     */
057    public class INodeDirectoryWithSnapshot extends INodeDirectoryWithQuota {
058      /**
059       * The difference between the current state and a previous snapshot
060       * of the children list of an INodeDirectory.
061       */
062      static class ChildrenDiff extends Diff<byte[], INode> {
063        ChildrenDiff() {}
064        
065        private ChildrenDiff(final List<INode> created, final List<INode> deleted) {
066          super(created, deleted);
067        }
068    
069        /**
070         * Replace the given child from the created/deleted list.
071         * @return true if the child is replaced; false if the child is not found.
072         */
073        private final boolean replace(final ListType type,
074            final INode oldChild, final INode newChild) {
075          final List<INode> list = getList(type); 
076          final int i = search(list, oldChild.getLocalNameBytes());
077          if (i < 0) {
078            return false;
079          }
080    
081          final INode removed = list.set(i, newChild);
082          Preconditions.checkState(removed == oldChild);
083          return true;
084        }
085    
086        private final boolean removeChild(ListType type, final INode child) {
087          final List<INode> list = getList(type);
088          final int i = searchIndex(type, child.getLocalNameBytes());
089          if (i >= 0 && list.get(i) == child) {
090            list.remove(i);
091            return true;
092          }
093          return false;
094        }
095        
096        /** clear the created list */
097        private Quota.Counts destroyCreatedList(
098            final INodeDirectoryWithSnapshot currentINode,
099            final BlocksMapUpdateInfo collectedBlocks,
100            final List<INode> removedINodes) {
101          Quota.Counts counts = Quota.Counts.newInstance();
102          final List<INode> createdList = getList(ListType.CREATED);
103          for (INode c : createdList) {
104            c.computeQuotaUsage(counts, true);
105            c.destroyAndCollectBlocks(collectedBlocks, removedINodes);
106            // c should be contained in the children list, remove it
107            currentINode.removeChild(c);
108          }
109          createdList.clear();
110          return counts;
111        }
112        
113        /** clear the deleted list */
114        private Quota.Counts destroyDeletedList(
115            final BlocksMapUpdateInfo collectedBlocks,
116            final List<INode> removedINodes) {
117          Quota.Counts counts = Quota.Counts.newInstance();
118          final List<INode> deletedList = getList(ListType.DELETED);
119          for (INode d : deletedList) {
120            d.computeQuotaUsage(counts, false);
121            d.destroyAndCollectBlocks(collectedBlocks, removedINodes);
122          }
123          deletedList.clear();
124          return counts;
125        }
126        
127        /** Serialize {@link #created} */
128        private void writeCreated(DataOutput out) throws IOException {
129          final List<INode> created = getList(ListType.CREATED);
130          out.writeInt(created.size());
131          for (INode node : created) {
132            // For INode in created list, we only need to record its local name 
133            byte[] name = node.getLocalNameBytes();
134            out.writeShort(name.length);
135            out.write(name);
136          }
137        }
138        
139        /** Serialize {@link #deleted} */
140        private void writeDeleted(DataOutput out,
141            ReferenceMap referenceMap) throws IOException {
142          final List<INode> deleted = getList(ListType.DELETED);
143          out.writeInt(deleted.size());
144          for (INode node : deleted) {
145            FSImageSerialization.saveINode2Image(node, out, true, referenceMap);
146          }
147        }
148        
149        /** Serialize to out */
150        private void write(DataOutput out, ReferenceMap referenceMap
151            ) throws IOException {
152          writeCreated(out);
153          writeDeleted(out, referenceMap);    
154        }
155    
156        /** Get the list of INodeDirectory contained in the deleted list */
157        private void getDirsInDeleted(List<INodeDirectory> dirList) {
158          for (INode node : getList(ListType.DELETED)) {
159            if (node.isDirectory()) {
160              dirList.add(node.asDirectory());
161            }
162          }
163        }
164        
165        /**
166         * Interpret the diff and generate a list of {@link DiffReportEntry}.
167         * @param parentPath The relative path of the parent.
168         * @param parent The directory that the diff belongs to.
169         * @param fromEarlier True indicates {@code diff=later-earlier}, 
170         *                    False indicates {@code diff=earlier-later}
171         * @return A list of {@link DiffReportEntry} as the diff report.
172         */
173        public List<DiffReportEntry> generateReport(byte[][] parentPath,
174            INodeDirectoryWithSnapshot parent, boolean fromEarlier) {
175          List<DiffReportEntry> cList = new ArrayList<DiffReportEntry>();
176          List<DiffReportEntry> dList = new ArrayList<DiffReportEntry>();
177          int c = 0, d = 0;
178          List<INode> created = getList(ListType.CREATED);
179          List<INode> deleted = getList(ListType.DELETED);
180          byte[][] fullPath = new byte[parentPath.length + 1][];
181          System.arraycopy(parentPath, 0, fullPath, 0, parentPath.length);
182          for (; c < created.size() && d < deleted.size(); ) {
183            INode cnode = created.get(c);
184            INode dnode = deleted.get(d);
185            if (cnode.compareTo(dnode.getLocalNameBytes()) == 0) {
186              fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
187              if (cnode.isSymlink() && dnode.isSymlink()) {
188                dList.add(new DiffReportEntry(DiffType.MODIFY, fullPath));
189              } else {
190                // must be the case: delete first and then create an inode with the
191                // same name
192                cList.add(new DiffReportEntry(DiffType.CREATE, fullPath));
193                dList.add(new DiffReportEntry(DiffType.DELETE, fullPath));
194              }
195              c++;
196              d++;
197            } else if (cnode.compareTo(dnode.getLocalNameBytes()) < 0) {
198              fullPath[fullPath.length - 1] = cnode.getLocalNameBytes();
199              cList.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
200                  : DiffType.DELETE, fullPath));
201              c++;
202            } else {
203              fullPath[fullPath.length - 1] = dnode.getLocalNameBytes();
204              dList.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
205                  : DiffType.CREATE, fullPath));
206              d++;
207            }
208          }
209          for (; d < deleted.size(); d++) {
210            fullPath[fullPath.length - 1] = deleted.get(d).getLocalNameBytes();
211            dList.add(new DiffReportEntry(fromEarlier ? DiffType.DELETE
212                : DiffType.CREATE, fullPath));
213          }
214          for (; c < created.size(); c++) {
215            fullPath[fullPath.length - 1] = created.get(c).getLocalNameBytes();
216            cList.add(new DiffReportEntry(fromEarlier ? DiffType.CREATE
217                : DiffType.DELETE, fullPath));
218          }
219          dList.addAll(cList);
220          return dList;
221        }
222      }
223      
224      /**
225       * The difference of an {@link INodeDirectory} between two snapshots.
226       */
227      public static class DirectoryDiff extends
228          AbstractINodeDiff<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> {
229        /** The size of the children list at snapshot creation time. */
230        private final int childrenSize;
231        /** The children list diff. */
232        private final ChildrenDiff diff;
233    
234        private DirectoryDiff(Snapshot snapshot, INodeDirectory dir) {
235          super(snapshot, null, null);
236    
237          this.childrenSize = dir.getChildrenList(null).size();
238          this.diff = new ChildrenDiff();
239        }
240    
241        /** Constructor used by FSImage loading */
242        DirectoryDiff(Snapshot snapshot, INodeDirectoryAttributes snapshotINode,
243            DirectoryDiff posteriorDiff, int childrenSize,
244            List<INode> createdList, List<INode> deletedList) {
245          super(snapshot, snapshotINode, posteriorDiff);
246          this.childrenSize = childrenSize;
247          this.diff = new ChildrenDiff(createdList, deletedList);
248        }
249        
250        ChildrenDiff getChildrenDiff() {
251          return diff;
252        }
253        
254        /** Is the inode the root of the snapshot? */
255        boolean isSnapshotRoot() {
256          return snapshotINode == snapshot.getRoot();
257        }
258        
259        @Override
260        Quota.Counts combinePosteriorAndCollectBlocks(
261            final INodeDirectory currentDir, final DirectoryDiff posterior,
262            final BlocksMapUpdateInfo collectedBlocks,
263            final List<INode> removedINodes) {
264          final Quota.Counts counts = Quota.Counts.newInstance();
265          diff.combinePosterior(posterior.diff, new Diff.Processor<INode>() {
266            /** Collect blocks for deleted files. */
267            @Override
268            public void process(INode inode) {
269              if (inode != null) {
270                inode.computeQuotaUsage(counts, false);
271                inode.destroyAndCollectBlocks(collectedBlocks, removedINodes);
272              }
273            }
274          });
275          return counts;
276        }
277    
278        /**
279         * @return The children list of a directory in a snapshot.
280         *         Since the snapshot is read-only, the logical view of the list is
281         *         never changed although the internal data structure may mutate.
282         */
283        ReadOnlyList<INode> getChildrenList(final INodeDirectory currentDir) {
284          return new ReadOnlyList<INode>() {
285            private List<INode> children = null;
286    
287            private List<INode> initChildren() {
288              if (children == null) {
289                final ChildrenDiff combined = new ChildrenDiff();
290                for(DirectoryDiff d = DirectoryDiff.this; d != null; d = d.getPosterior()) {
291                  combined.combinePosterior(d.diff, null);
292                }
293                children = combined.apply2Current(ReadOnlyList.Util.asList(
294                    currentDir.getChildrenList(null)));
295              }
296              return children;
297            }
298    
299            @Override
300            public Iterator<INode> iterator() {
301              return initChildren().iterator();
302            }
303        
304            @Override
305            public boolean isEmpty() {
306              return childrenSize == 0;
307            }
308        
309            @Override
310            public int size() {
311              return childrenSize;
312            }
313        
314            @Override
315            public INode get(int i) {
316              return initChildren().get(i);
317            }
318          };
319        }
320    
321        /** @return the child with the given name. */
322        INode getChild(byte[] name, boolean checkPosterior,
323            INodeDirectory currentDir) {
324          for(DirectoryDiff d = this; ; d = d.getPosterior()) {
325            final Container<INode> returned = d.diff.accessPrevious(name);
326            if (returned != null) {
327              // the diff is able to determine the inode
328              return returned.getElement(); 
329            } else if (!checkPosterior) {
330              // Since checkPosterior is false, return null, i.e. not found.   
331              return null;
332            } else if (d.getPosterior() == null) {
333              // no more posterior diff, get from current inode.
334              return currentDir.getChild(name, null);
335            }
336          }
337        }
338        
339        @Override
340        public String toString() {
341          return super.toString() + " childrenSize=" + childrenSize + ", " + diff;
342        }
343        
344        @Override
345        void write(DataOutput out, ReferenceMap referenceMap) throws IOException {
346          writeSnapshot(out);
347          out.writeInt(childrenSize);
348    
349          // write snapshotINode
350          if (isSnapshotRoot()) {
351            out.writeBoolean(true);
352          } else {
353            out.writeBoolean(false);
354            if (snapshotINode != null) {
355              out.writeBoolean(true);
356              FSImageSerialization.writeINodeDirectoryAttributes(snapshotINode, out);
357            } else {
358              out.writeBoolean(false);
359            }
360          }
361          // Write diff. Node need to write poseriorDiff, since diffs is a list.
362          diff.write(out, referenceMap);
363        }
364    
365        @Override
366        Quota.Counts destroyDiffAndCollectBlocks(INodeDirectory currentINode,
367            BlocksMapUpdateInfo collectedBlocks, final List<INode> removedINodes) {
368          // this diff has been deleted
369          Quota.Counts counts = Quota.Counts.newInstance();
370          counts.add(diff.destroyDeletedList(collectedBlocks, removedINodes));
371          return counts;
372        }
373      }
374    
375      /** A list of directory diffs. */
376      public static class DirectoryDiffList
377          extends AbstractINodeDiffList<INodeDirectory, INodeDirectoryAttributes, DirectoryDiff> {
378    
379        @Override
380        DirectoryDiff createDiff(Snapshot snapshot, INodeDirectory currentDir) {
381          return new DirectoryDiff(snapshot, currentDir);
382        }
383    
384        @Override
385        INodeDirectoryAttributes createSnapshotCopy(INodeDirectory currentDir) {
386          return currentDir.isQuotaSet()?
387              new INodeDirectoryAttributes.CopyWithQuota(currentDir)
388            : new INodeDirectoryAttributes.SnapshotCopy(currentDir);
389        }
390    
391        /** Replace the given child in the created/deleted list, if there is any. */
392        private boolean replaceChild(final ListType type, final INode oldChild,
393            final INode newChild) {
394          final List<DirectoryDiff> diffList = asList();
395          for(int i = diffList.size() - 1; i >= 0; i--) {
396            final ChildrenDiff diff = diffList.get(i).diff;
397            if (diff.replace(type, oldChild, newChild)) {
398              return true;
399            }
400          }
401          return false;
402        }
403        
404        /** Remove the given child in the created/deleted list, if there is any. */
405        private boolean removeChild(final ListType type, final INode child) {
406          final List<DirectoryDiff> diffList = asList();
407          for(int i = diffList.size() - 1; i >= 0; i--) {
408            final ChildrenDiff diff = diffList.get(i).diff;
409            if (diff.removeChild(type, child)) {
410              return true;
411            }
412          }
413          return false;
414        }
415      }
416    
417      /**
418       * Compute the difference between Snapshots.
419       * 
420       * @param fromSnapshot Start point of the diff computation. Null indicates
421       *          current tree.
422       * @param toSnapshot End point of the diff computation. Null indicates current
423       *          tree.
424       * @param diff Used to capture the changes happening to the children. Note
425       *          that the diff still represents (later_snapshot - earlier_snapshot)
426       *          although toSnapshot can be before fromSnapshot.
427       * @return Whether changes happened between the startSnapshot and endSnaphsot.
428       */
429      boolean computeDiffBetweenSnapshots(Snapshot fromSnapshot,
430          Snapshot toSnapshot, ChildrenDiff diff) {
431        Snapshot earlier = fromSnapshot;
432        Snapshot later = toSnapshot;
433        if (Snapshot.ID_COMPARATOR.compare(fromSnapshot, toSnapshot) > 0) {
434          earlier = toSnapshot;
435          later = fromSnapshot;
436        }
437        
438        boolean modified = diffs.changedBetweenSnapshots(earlier,
439            later);
440        if (!modified) {
441          return false;
442        }
443        
444        final List<DirectoryDiff> difflist = diffs.asList();
445        final int size = difflist.size();
446        int earlierDiffIndex = Collections.binarySearch(difflist, earlier.getId());
447        int laterDiffIndex = later == null ? size : Collections
448            .binarySearch(difflist, later.getId());
449        earlierDiffIndex = earlierDiffIndex < 0 ? (-earlierDiffIndex - 1)
450            : earlierDiffIndex;
451        laterDiffIndex = laterDiffIndex < 0 ? (-laterDiffIndex - 1)
452            : laterDiffIndex;
453        
454        boolean dirMetadataChanged = false;
455        INodeDirectoryAttributes dirCopy = null;
456        for (int i = earlierDiffIndex; i < laterDiffIndex; i++) {
457          DirectoryDiff sdiff = difflist.get(i);
458          diff.combinePosterior(sdiff.diff, null);
459          if (dirMetadataChanged == false && sdiff.snapshotINode != null) {
460            if (dirCopy == null) {
461              dirCopy = sdiff.snapshotINode;
462            } else if (!dirCopy.metadataEquals(sdiff.snapshotINode)) {
463              dirMetadataChanged = true;
464            }
465          }
466        }
467    
468        if (!diff.isEmpty() || dirMetadataChanged) {
469          return true;
470        } else if (dirCopy != null) {
471          for (int i = laterDiffIndex; i < size; i++) {
472            if (!dirCopy.metadataEquals(difflist.get(i).snapshotINode)) {
473              return true;
474            }
475          }
476          return !dirCopy.metadataEquals(this);
477        } else {
478          return false;
479        }
480      }
481    
482      /** Diff list sorted by snapshot IDs, i.e. in chronological order. */
483      private final DirectoryDiffList diffs;
484    
485      public INodeDirectoryWithSnapshot(INodeDirectory that) {
486        this(that, true, that instanceof INodeDirectoryWithSnapshot?
487            ((INodeDirectoryWithSnapshot)that).getDiffs(): null);
488      }
489    
490      INodeDirectoryWithSnapshot(INodeDirectory that, boolean adopt,
491          DirectoryDiffList diffs) {
492        super(that, adopt, that.getNsQuota(), that.getDsQuota());
493        this.diffs = diffs != null? diffs: new DirectoryDiffList();
494      }
495    
496      /** @return the last snapshot. */
497      public Snapshot getLastSnapshot() {
498        return diffs.getLastSnapshot();
499      }
500    
501      /** @return the snapshot diff list. */
502      public DirectoryDiffList getDiffs() {
503        return diffs;
504      }
505    
506      @Override
507      public INodeDirectoryAttributes getSnapshotINode(Snapshot snapshot) {
508        return diffs.getSnapshotINode(snapshot, this);
509      }
510    
511      @Override
512      public INodeDirectoryWithSnapshot recordModification(final Snapshot latest,
513          final INodeMap inodeMap) throws QuotaExceededException {
514        if (isInLatestSnapshot(latest) && !shouldRecordInSrcSnapshot(latest)) {
515          return saveSelf2Snapshot(latest, null);
516        }
517        return this;
518      }
519    
520      /** Save the snapshot copy to the latest snapshot. */
521      public INodeDirectoryWithSnapshot saveSelf2Snapshot(
522          final Snapshot latest, final INodeDirectory snapshotCopy)
523              throws QuotaExceededException {
524        diffs.saveSelf2Snapshot(latest, this, snapshotCopy);
525        return this;
526      }
527    
528      @Override
529      public INode saveChild2Snapshot(final INode child, final Snapshot latest,
530          final INode snapshotCopy, final INodeMap inodeMap)
531          throws QuotaExceededException {
532        Preconditions.checkArgument(!child.isDirectory(),
533            "child is a directory, child=%s", child);
534        if (latest == null) {
535          return child;
536        }
537    
538        final DirectoryDiff diff = diffs.checkAndAddLatestSnapshotDiff(latest, this);
539        if (diff.getChild(child.getLocalNameBytes(), false, this) != null) {
540          // it was already saved in the latest snapshot earlier.  
541          return child;
542        }
543    
544        diff.diff.modify(snapshotCopy, child);
545        return child;
546      }
547    
548      @Override
549      public boolean addChild(INode inode, boolean setModTime, Snapshot latest,
550          final INodeMap inodeMap) throws QuotaExceededException {
551        ChildrenDiff diff = null;
552        Integer undoInfo = null;
553        if (isInLatestSnapshot(latest)) {
554          diff = diffs.checkAndAddLatestSnapshotDiff(latest, this).diff;
555          undoInfo = diff.create(inode);
556        }
557        final boolean added = super.addChild(inode, setModTime, null, inodeMap);
558        if (!added && undoInfo != null) {
559          diff.undoCreate(inode, undoInfo);
560        }
561        return added; 
562      }
563    
564      @Override
565      public boolean removeChild(INode child, Snapshot latest,
566          final INodeMap inodeMap) throws QuotaExceededException {
567        ChildrenDiff diff = null;
568        UndoInfo<INode> undoInfo = null;
569        // For a directory that is not a renamed node, if isInLatestSnapshot returns
570        // false, the directory is not in the latest snapshot, thus we do not need
571        // to record the removed child in any snapshot.
572        // For a directory that was moved/renamed, note that if the directory is in
573        // any of the previous snapshots, we will create a reference node for the 
574        // directory while rename, and isInLatestSnapshot will return true in that
575        // scenario (if all previous snapshots have been deleted, isInLatestSnapshot
576        // still returns false). Thus if isInLatestSnapshot returns false, the 
577        // directory node cannot be in any snapshot (not in current tree, nor in 
578        // previous src tree). Thus we do not need to record the removed child in 
579        // any snapshot.
580        if (isInLatestSnapshot(latest)) {
581          diff = diffs.checkAndAddLatestSnapshotDiff(latest, this).diff;
582          undoInfo = diff.delete(child);
583        }
584        final boolean removed = removeChild(child);
585        if (undoInfo != null) {
586          if (!removed) {
587            //remove failed, undo
588            diff.undoDelete(child, undoInfo);
589          }
590        }
591        return removed;
592      }
593      
594      @Override
595      public void replaceChild(final INode oldChild, final INode newChild,
596          final INodeMap inodeMap) {
597        super.replaceChild(oldChild, newChild, inodeMap);
598        diffs.replaceChild(ListType.CREATED, oldChild, newChild);
599      }
600      
601      /**
602       * This method is usually called by the undo section of rename.
603       * 
604       * Before calling this function, in the rename operation, we replace the
605       * original src node (of the rename operation) with a reference node (WithName
606       * instance) in both the children list and a created list, delete the
607       * reference node from the children list, and add it to the corresponding
608       * deleted list.
609       * 
610       * To undo the above operations, we have the following steps in particular:
611       * 
612       * <pre>
613       * 1) remove the WithName node from the deleted list (if it exists) 
614       * 2) replace the WithName node in the created list with srcChild 
615       * 3) add srcChild back as a child of srcParent. Note that we already add 
616       * the node into the created list of a snapshot diff in step 2, we do not need
617       * to add srcChild to the created list of the latest snapshot.
618       * </pre>
619       * 
620       * We do not need to update quota usage because the old child is in the 
621       * deleted list before. 
622       * 
623       * @param oldChild
624       *          The reference node to be removed/replaced
625       * @param newChild
626       *          The node to be added back
627       * @param latestSnapshot
628       *          The latest snapshot. Note this may not be the last snapshot in the
629       *          {@link #diffs}, since the src tree of the current rename operation
630       *          may be the dst tree of a previous rename.
631       * @throws QuotaExceededException should not throw this exception
632       */
633      public void undoRename4ScrParent(final INodeReference oldChild,
634          final INode newChild, Snapshot latestSnapshot)
635          throws QuotaExceededException {
636        diffs.removeChild(ListType.DELETED, oldChild);
637        diffs.replaceChild(ListType.CREATED, oldChild, newChild);
638        // pass null for inodeMap since the parent node will not get replaced when
639        // undoing rename
640        addChild(newChild, true, null, null);
641      }
642      
643      /**
644       * Undo the rename operation for the dst tree, i.e., if the rename operation
645       * (with OVERWRITE option) removes a file/dir from the dst tree, add it back
646       * and delete possible record in the deleted list.  
647       */
648      public void undoRename4DstParent(final INode deletedChild,
649          Snapshot latestSnapshot) throws QuotaExceededException {
650        boolean removeDeletedChild = diffs.removeChild(ListType.DELETED,
651            deletedChild);
652        // pass null for inodeMap since the parent node will not get replaced when
653        // undoing rename
654        final boolean added = addChild(deletedChild, true, removeDeletedChild ? null
655            : latestSnapshot, null);
656        // update quota usage if adding is successfully and the old child has not
657        // been stored in deleted list before
658        if (added && !removeDeletedChild) {
659          final Quota.Counts counts = deletedChild.computeQuotaUsage();
660          addSpaceConsumed(counts.get(Quota.NAMESPACE),
661              counts.get(Quota.DISKSPACE), false);
662        }
663      }
664    
665      @Override
666      public ReadOnlyList<INode> getChildrenList(Snapshot snapshot) {
667        final DirectoryDiff diff = diffs.getDiff(snapshot);
668        return diff != null? diff.getChildrenList(this): super.getChildrenList(null);
669      }
670    
671      @Override
672      public INode getChild(byte[] name, Snapshot snapshot) {
673        final DirectoryDiff diff = diffs.getDiff(snapshot);
674        return diff != null? diff.getChild(name, true, this): super.getChild(name, null);
675      }
676    
677      @Override
678      public String toDetailString() {
679        return super.toDetailString() + ", " + diffs;
680      }
681      
682      /**
683       * Get all the directories that are stored in some snapshot but not in the
684       * current children list. These directories are equivalent to the directories
685       * stored in the deletes lists.
686       */
687      public void getSnapshotDirectory(List<INodeDirectory> snapshotDir) {
688        for (DirectoryDiff sdiff : diffs) {
689          sdiff.getChildrenDiff().getDirsInDeleted(snapshotDir);
690        }
691      }
692    
693      @Override
694      public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
695          final BlocksMapUpdateInfo collectedBlocks,
696          final List<INode> removedINodes, final boolean countDiffChange)
697          throws QuotaExceededException {
698        Quota.Counts counts = Quota.Counts.newInstance();
699        Map<INode, INode> priorCreated = null;
700        Map<INode, INode> priorDeleted = null;
701        if (snapshot == null) { // delete the current directory
702          recordModification(prior, null);
703          // delete everything in created list
704          DirectoryDiff lastDiff = diffs.getLast();
705          if (lastDiff != null) {
706            counts.add(lastDiff.diff.destroyCreatedList(this, collectedBlocks,
707                removedINodes));
708          }
709        } else {
710          // update prior
711          prior = getDiffs().updatePrior(snapshot, prior);
712          // if there is a snapshot diff associated with prior, we need to record
713          // its original created and deleted list before deleting post
714          if (prior != null) {
715            DirectoryDiff priorDiff = this.getDiffs().getDiff(prior);
716            if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
717              List<INode> cList = priorDiff.diff.getList(ListType.CREATED);
718              List<INode> dList = priorDiff.diff.getList(ListType.DELETED);
719              priorCreated = new HashMap<INode, INode>(cList.size());
720              for (INode cNode : cList) {
721                priorCreated.put(cNode, cNode);
722              }
723              priorDeleted = new HashMap<INode, INode>(dList.size());
724              for (INode dNode : dList) {
725                priorDeleted.put(dNode, dNode);
726              }
727            }
728          }
729          
730          counts.add(getDiffs().deleteSnapshotDiff(snapshot, prior, this, 
731              collectedBlocks, removedINodes, countDiffChange));
732          
733          // check priorDiff again since it may be created during the diff deletion
734          if (prior != null) {
735            DirectoryDiff priorDiff = this.getDiffs().getDiff(prior);
736            if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
737              // For files/directories created between "prior" and "snapshot", 
738              // we need to clear snapshot copies for "snapshot". Note that we must
739              // use null as prior in the cleanSubtree call. Files/directories that
740              // were created before "prior" will be covered by the later 
741              // cleanSubtreeRecursively call.
742              if (priorCreated != null) {
743                // we only check the node originally in prior's created list
744                for (INode cNode : priorDiff.getChildrenDiff().getList(
745                    ListType.CREATED)) {
746                  if (priorCreated.containsKey(cNode)) {
747                    counts.add(cNode.cleanSubtree(snapshot, null, collectedBlocks,
748                        removedINodes, countDiffChange));
749                  }
750                }
751              }
752              
753              // When a directory is moved from the deleted list of the posterior
754              // diff to the deleted list of this diff, we need to destroy its
755              // descendants that were 1) created after taking this diff and 2)
756              // deleted after taking posterior diff.
757    
758              // For files moved from posterior's deleted list, we also need to
759              // delete its snapshot copy associated with the posterior snapshot.
760              
761              for (INode dNode : priorDiff.getChildrenDiff().getList(
762                  ListType.DELETED)) {
763                if (priorDeleted == null || !priorDeleted.containsKey(dNode)) {
764                  counts.add(cleanDeletedINode(dNode, snapshot, prior,
765                      collectedBlocks, removedINodes, countDiffChange));
766                }
767              }
768            }
769          }
770        }
771        counts.add(cleanSubtreeRecursively(snapshot, prior, collectedBlocks,
772            removedINodes, priorDeleted, countDiffChange));
773        
774        if (isQuotaSet()) {
775          this.addSpaceConsumed2Cache(-counts.get(Quota.NAMESPACE),
776              -counts.get(Quota.DISKSPACE));
777        }
778        return counts;
779      }
780      
781      /**
782       * Clean an inode while we move it from the deleted list of post to the
783       * deleted list of prior.
784       * @param inode The inode to clean.
785       * @param post The post snapshot.
786       * @param prior The prior snapshot.
787       * @param collectedBlocks Used to collect blocks for later deletion.
788       * @return Quota usage update.
789       */
790      private static Quota.Counts cleanDeletedINode(INode inode,
791          final Snapshot post, final Snapshot prior,
792          final BlocksMapUpdateInfo collectedBlocks,
793          final List<INode> removedINodes, final boolean countDiffChange) 
794          throws QuotaExceededException {
795        Quota.Counts counts = Quota.Counts.newInstance();
796        Deque<INode> queue = new ArrayDeque<INode>();
797        queue.addLast(inode);
798        while (!queue.isEmpty()) {
799          INode topNode = queue.pollFirst();
800          if (topNode instanceof INodeReference.WithName) {
801            INodeReference.WithName wn = (INodeReference.WithName) topNode;
802            if (wn.getLastSnapshotId() >= post.getId()) {
803              wn.cleanSubtree(post, prior, collectedBlocks, removedINodes,
804                  countDiffChange);
805            }
806            // For DstReference node, since the node is not in the created list of
807            // prior, we should treat it as regular file/dir
808          } else if (topNode.isFile()
809              && topNode.asFile() instanceof FileWithSnapshot) {
810            FileWithSnapshot fs = (FileWithSnapshot) topNode.asFile();
811            counts.add(fs.getDiffs().deleteSnapshotDiff(post, prior,
812                topNode.asFile(), collectedBlocks, removedINodes, countDiffChange));
813          } else if (topNode.isDirectory()) {
814            INodeDirectory dir = topNode.asDirectory();
815            ChildrenDiff priorChildrenDiff = null;
816            if (dir instanceof INodeDirectoryWithSnapshot) {
817              // delete files/dirs created after prior. Note that these
818              // files/dirs, along with inode, were deleted right after post.
819              INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) dir;
820              DirectoryDiff priorDiff = sdir.getDiffs().getDiff(prior);
821              if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
822                priorChildrenDiff = priorDiff.getChildrenDiff();
823                counts.add(priorChildrenDiff.destroyCreatedList(sdir,
824                    collectedBlocks, removedINodes));
825              }
826            }
827            
828            for (INode child : dir.getChildrenList(prior)) {
829              if (priorChildrenDiff != null
830                  && priorChildrenDiff.search(ListType.DELETED,
831                      child.getLocalNameBytes()) != null) {
832                continue;
833              }
834              queue.addLast(child);
835            }
836          }
837        }
838        return counts;
839      }
840    
841      @Override
842      public void destroyAndCollectBlocks(
843          final BlocksMapUpdateInfo collectedBlocks, 
844          final List<INode> removedINodes) {
845        // destroy its diff list
846        for (DirectoryDiff diff : diffs) {
847          diff.destroyDiffAndCollectBlocks(this, collectedBlocks, removedINodes);
848        }
849        diffs.clear();
850        super.destroyAndCollectBlocks(collectedBlocks, removedINodes);
851      }
852    
853      @Override
854      public final Quota.Counts computeQuotaUsage(Quota.Counts counts,
855          boolean useCache, int lastSnapshotId) {
856        if ((useCache && isQuotaSet()) || lastSnapshotId == Snapshot.INVALID_ID) {
857          return super.computeQuotaUsage(counts, useCache, lastSnapshotId);
858        }
859        
860        Snapshot lastSnapshot = diffs.getSnapshotById(lastSnapshotId);
861        
862        ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshot);
863        for (INode child : childrenList) {
864          child.computeQuotaUsage(counts, useCache, lastSnapshotId);
865        }
866        
867        counts.add(Quota.NAMESPACE, 1);
868        return counts;
869      }
870      
871      @Override
872      public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) {
873        super.computeQuotaUsage4CurrentDirectory(counts);
874        for(DirectoryDiff d : diffs) {
875          for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) {
876            deleted.computeQuotaUsage(counts, false, Snapshot.INVALID_ID);
877          }
878        }
879        counts.add(Quota.NAMESPACE, diffs.asList().size());
880        return counts;
881      }
882    
883      @Override
884      public Content.Counts computeContentSummary(final Content.Counts counts) {
885        super.computeContentSummary(counts);
886        computeContentSummary4Snapshot(counts);
887        return counts;
888      }
889    
890      private void computeContentSummary4Snapshot(final Content.Counts counts) {
891        for(DirectoryDiff d : diffs) {
892          for(INode deleted : d.getChildrenDiff().getList(ListType.DELETED)) {
893            deleted.computeContentSummary(counts);
894          }
895        }
896        counts.add(Content.DIRECTORY, diffs.asList().size());
897      }
898      
899      /**
900       * Destroy a subtree under a DstReference node.
901       */
902      public static void destroyDstSubtree(INode inode, final Snapshot snapshot,
903          final Snapshot prior, final BlocksMapUpdateInfo collectedBlocks,
904          final List<INode> removedINodes) throws QuotaExceededException {
905        Preconditions.checkArgument(prior != null);
906        if (inode.isReference()) {
907          if (inode instanceof INodeReference.WithName && snapshot != null) {
908            // this inode has been renamed before the deletion of the DstReference
909            // subtree
910            inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes,
911                true);
912          } else { 
913            // for DstReference node, continue this process to its subtree
914            destroyDstSubtree(inode.asReference().getReferredINode(), snapshot,
915                prior, collectedBlocks, removedINodes);
916          }
917        } else if (inode.isFile() && snapshot != null) {
918          inode.cleanSubtree(snapshot, prior, collectedBlocks, removedINodes, true);
919        } else if (inode.isDirectory()) {
920          Map<INode, INode> excludedNodes = null;
921          if (inode instanceof INodeDirectoryWithSnapshot) {
922            INodeDirectoryWithSnapshot sdir = (INodeDirectoryWithSnapshot) inode;
923            DirectoryDiffList diffList = sdir.getDiffs();
924            if (snapshot != null) {
925              diffList.deleteSnapshotDiff(snapshot, prior, sdir, collectedBlocks,
926                  removedINodes, true);
927            }
928            DirectoryDiff priorDiff = diffList.getDiff(prior);
929            if (priorDiff != null && priorDiff.getSnapshot().equals(prior)) {
930              priorDiff.diff.destroyCreatedList(sdir, collectedBlocks,
931                  removedINodes);
932              List<INode> dList = priorDiff.diff.getList(ListType.DELETED);
933              excludedNodes = new HashMap<INode, INode>(dList.size());
934              for (INode dNode : dList) {
935                excludedNodes.put(dNode, dNode);
936              }
937            }
938          }
939          for (INode child : inode.asDirectory().getChildrenList(prior)) {
940            if (excludedNodes != null && excludedNodes.containsKey(child)) {
941              continue;
942            }
943            destroyDstSubtree(child, snapshot, prior, collectedBlocks,
944                removedINodes);
945          }
946        }
947      }
948    }