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;
019    
020    import java.io.FileNotFoundException;
021    import java.io.PrintWriter;
022    import java.util.ArrayList;
023    import java.util.Collections;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.apache.hadoop.fs.PathIsNotDirectoryException;
029    import org.apache.hadoop.fs.UnresolvedLinkException;
030    import org.apache.hadoop.fs.permission.PermissionStatus;
031    import org.apache.hadoop.hdfs.DFSUtil;
032    import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
033    import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException;
034    import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithCount;
035    import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature;
036    import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature.DirectoryDiffList;
037    import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectorySnapshottable;
038    import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
039    import org.apache.hadoop.hdfs.util.Diff.ListType;
040    import org.apache.hadoop.hdfs.util.ReadOnlyList;
041    
042    import com.google.common.annotations.VisibleForTesting;
043    import com.google.common.base.Preconditions;
044    
045    /**
046     * Directory INode class.
047     */
048    public class INodeDirectory extends INodeWithAdditionalFields
049        implements INodeDirectoryAttributes {
050    
051      /** Cast INode to INodeDirectory. */
052      public static INodeDirectory valueOf(INode inode, Object path
053          ) throws FileNotFoundException, PathIsNotDirectoryException {
054        if (inode == null) {
055          throw new FileNotFoundException("Directory does not exist: "
056              + DFSUtil.path2String(path));
057        }
058        if (!inode.isDirectory()) {
059          throw new PathIsNotDirectoryException(DFSUtil.path2String(path));
060        }
061        return inode.asDirectory(); 
062      }
063    
064      protected static final int DEFAULT_FILES_PER_DIRECTORY = 5;
065      final static byte[] ROOT_NAME = DFSUtil.string2Bytes("");
066    
067      private List<INode> children = null;
068      
069      /** constructor */
070      public INodeDirectory(long id, byte[] name, PermissionStatus permissions,
071          long mtime) {
072        super(id, name, permissions, mtime, 0L);
073      }
074      
075      /**
076       * Copy constructor
077       * @param other The INodeDirectory to be copied
078       * @param adopt Indicate whether or not need to set the parent field of child
079       *              INodes to the new node
080       * @param featuresToCopy any number of features to copy to the new node.
081       *              The method will do a reference copy, not a deep copy.
082       */
083      public INodeDirectory(INodeDirectory other, boolean adopt,
084          Feature... featuresToCopy) {
085        super(other);
086        this.children = other.children;
087        if (adopt && this.children != null) {
088          for (INode child : children) {
089            child.setParent(this);
090          }
091        }
092        this.features = featuresToCopy;
093      }
094    
095      /** @return true unconditionally. */
096      @Override
097      public final boolean isDirectory() {
098        return true;
099      }
100    
101      /** @return this object. */
102      @Override
103      public final INodeDirectory asDirectory() {
104        return this;
105      }
106    
107      /** Is this a snapshottable directory? */
108      public boolean isSnapshottable() {
109        return false;
110      }
111    
112      void setQuota(long nsQuota, long dsQuota) {
113        DirectoryWithQuotaFeature quota = getDirectoryWithQuotaFeature();
114        if (quota != null) {
115          // already has quota; so set the quota to the new values
116          quota.setQuota(nsQuota, dsQuota);
117          if (!isQuotaSet() && !isRoot()) {
118            removeFeature(quota);
119          }
120        } else {
121          final Quota.Counts c = computeQuotaUsage();
122          quota = addDirectoryWithQuotaFeature(nsQuota, dsQuota);
123          quota.setSpaceConsumed(c.get(Quota.NAMESPACE), c.get(Quota.DISKSPACE));
124        }
125      }
126    
127      @Override
128      public Quota.Counts getQuotaCounts() {
129        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
130        return q != null? q.getQuota(): super.getQuotaCounts();
131      }
132    
133      @Override
134      public void addSpaceConsumed(long nsDelta, long dsDelta, boolean verify) 
135          throws QuotaExceededException {
136        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
137        if (q != null) {
138          q.addSpaceConsumed(this, nsDelta, dsDelta, verify);
139        } else {
140          addSpaceConsumed2Parent(nsDelta, dsDelta, verify);
141        }
142      }
143    
144      /**
145       * If the directory contains a {@link DirectoryWithQuotaFeature}, return it;
146       * otherwise, return null.
147       */
148      public final DirectoryWithQuotaFeature getDirectoryWithQuotaFeature() {
149        return getFeature(DirectoryWithQuotaFeature.class);
150      }
151    
152      /** Is this directory with quota? */
153      final boolean isWithQuota() {
154        return getDirectoryWithQuotaFeature() != null;
155      }
156    
157      DirectoryWithQuotaFeature addDirectoryWithQuotaFeature(
158          long nsQuota, long dsQuota) {
159        Preconditions.checkState(!isWithQuota(), "Directory is already with quota");
160        final DirectoryWithQuotaFeature quota = new DirectoryWithQuotaFeature(
161            nsQuota, dsQuota);
162        addFeature(quota);
163        return quota;
164      }
165    
166      private int searchChildren(byte[] name) {
167        return children == null? -1: Collections.binarySearch(children, name);
168      }
169      
170      public DirectoryWithSnapshotFeature addSnapshotFeature(
171          DirectoryDiffList diffs) {
172        Preconditions.checkState(!isWithSnapshot(), 
173            "Directory is already with snapshot");
174        DirectoryWithSnapshotFeature sf = new DirectoryWithSnapshotFeature(diffs);
175        addFeature(sf);
176        return sf;
177      }
178      
179      /**
180       * If feature list contains a {@link DirectoryWithSnapshotFeature}, return it;
181       * otherwise, return null.
182       */
183      public final DirectoryWithSnapshotFeature getDirectoryWithSnapshotFeature() {
184        return getFeature(DirectoryWithSnapshotFeature.class);
185      }
186    
187      /** Is this file has the snapshot feature? */
188      public final boolean isWithSnapshot() {
189        return getDirectoryWithSnapshotFeature() != null;
190      }
191      
192      public DirectoryDiffList getDiffs() {
193        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
194        return sf != null ? sf.getDiffs() : null;
195      }
196      
197      @Override
198      public INodeDirectoryAttributes getSnapshotINode(int snapshotId) {
199        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
200        return sf == null ? this : sf.getDiffs().getSnapshotINode(snapshotId, this);
201      }
202      
203      @Override
204      public String toDetailString() {
205        DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
206        return super.toDetailString() + (sf == null ? "" : ", " + sf.getDiffs()); 
207      }
208    
209      /** Replace itself with an {@link INodeDirectorySnapshottable}. */
210      public INodeDirectorySnapshottable replaceSelf4INodeDirectorySnapshottable(
211          int latestSnapshotId, final INodeMap inodeMap)
212          throws QuotaExceededException {
213        Preconditions.checkState(!(this instanceof INodeDirectorySnapshottable),
214            "this is already an INodeDirectorySnapshottable, this=%s", this);
215        final INodeDirectorySnapshottable s = new INodeDirectorySnapshottable(this);
216        replaceSelf(s, inodeMap).getDirectoryWithSnapshotFeature().getDiffs()
217            .saveSelf2Snapshot(latestSnapshotId, s, this);
218        return s;
219      }
220    
221      /** Replace itself with {@link INodeDirectory}. */
222      public INodeDirectory replaceSelf4INodeDirectory(final INodeMap inodeMap) {
223        Preconditions.checkState(getClass() != INodeDirectory.class,
224            "the class is already INodeDirectory, this=%s", this);
225        return replaceSelf(new INodeDirectory(this, true, this.getFeatures()),
226          inodeMap);
227      }
228    
229      /** Replace itself with the given directory. */
230      private final <N extends INodeDirectory> N replaceSelf(final N newDir,
231          final INodeMap inodeMap) {
232        final INodeReference ref = getParentReference();
233        if (ref != null) {
234          ref.setReferredINode(newDir);
235          if (inodeMap != null) {
236            inodeMap.put(newDir);
237          }
238        } else {
239          final INodeDirectory parent = getParent();
240          Preconditions.checkArgument(parent != null, "parent is null, this=%s", this);
241          parent.replaceChild(this, newDir, inodeMap);
242        }
243        clear();
244        return newDir;
245      }
246      
247      /** 
248       * Replace the given child with a new child. Note that we no longer need to
249       * replace an normal INodeDirectory or INodeFile into an
250       * INodeDirectoryWithSnapshot or INodeFileUnderConstruction. The only cases
251       * for child replacement is for {@link INodeDirectorySnapshottable} and 
252       * reference nodes.
253       */
254      public void replaceChild(INode oldChild, final INode newChild,
255          final INodeMap inodeMap) {
256        Preconditions.checkNotNull(children);
257        final int i = searchChildren(newChild.getLocalNameBytes());
258        Preconditions.checkState(i >= 0);
259        Preconditions.checkState(oldChild == children.get(i)
260            || oldChild == children.get(i).asReference().getReferredINode()
261                .asReference().getReferredINode());
262        oldChild = children.get(i);
263        
264        if (oldChild.isReference() && newChild.isReference()) {
265          // both are reference nodes, e.g., DstReference -> WithName
266          final INodeReference.WithCount withCount = 
267              (WithCount) oldChild.asReference().getReferredINode();
268          withCount.removeReference(oldChild.asReference());
269        }
270        children.set(i, newChild);
271        
272        // replace the instance in the created list of the diff list
273        DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
274        if (sf != null) {
275          sf.getDiffs().replaceChild(ListType.CREATED, oldChild, newChild);
276        }
277        
278        // update the inodeMap
279        if (inodeMap != null) {
280          inodeMap.put(newChild);
281        }    
282      }
283    
284      INodeReference.WithName replaceChild4ReferenceWithName(INode oldChild,
285          int latestSnapshotId) {
286        Preconditions.checkArgument(latestSnapshotId != Snapshot.CURRENT_STATE_ID);
287        if (oldChild instanceof INodeReference.WithName) {
288          return (INodeReference.WithName)oldChild;
289        }
290    
291        final INodeReference.WithCount withCount;
292        if (oldChild.isReference()) {
293          Preconditions.checkState(oldChild instanceof INodeReference.DstReference);
294          withCount = (INodeReference.WithCount) oldChild.asReference()
295              .getReferredINode();
296        } else {
297          withCount = new INodeReference.WithCount(null, oldChild);
298        }
299        final INodeReference.WithName ref = new INodeReference.WithName(this,
300            withCount, oldChild.getLocalNameBytes(), latestSnapshotId);
301        replaceChild(oldChild, ref, null);
302        return ref;
303      }
304    
305      @Override
306      public INodeDirectory recordModification(int latestSnapshotId) 
307          throws QuotaExceededException {
308        if (isInLatestSnapshot(latestSnapshotId)
309            && !shouldRecordInSrcSnapshot(latestSnapshotId)) {
310          // add snapshot feature if necessary
311          DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
312          if (sf == null) {
313            sf = addSnapshotFeature(null);
314          }
315          // record self in the diff list if necessary
316          sf.getDiffs().saveSelf2Snapshot(latestSnapshotId, this, null);
317        }
318        return this;
319      }
320    
321      /**
322       * Save the child to the latest snapshot.
323       * 
324       * @return the child inode, which may be replaced.
325       */
326      public INode saveChild2Snapshot(final INode child, final int latestSnapshotId,
327          final INode snapshotCopy) throws QuotaExceededException {
328        if (latestSnapshotId == Snapshot.CURRENT_STATE_ID) {
329          return child;
330        }
331        
332        // add snapshot feature if necessary
333        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
334        if (sf == null) {
335          sf = this.addSnapshotFeature(null);
336        }
337        return sf.saveChild2Snapshot(this, child, latestSnapshotId, snapshotCopy);
338      }
339    
340      /**
341       * @param name the name of the child
342       * @param snapshotId
343       *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
344       *          from the corresponding snapshot; otherwise, get the result from
345       *          the current directory.
346       * @return the child inode.
347       */
348      public INode getChild(byte[] name, int snapshotId) {
349        DirectoryWithSnapshotFeature sf;
350        if (snapshotId == Snapshot.CURRENT_STATE_ID || 
351            (sf = getDirectoryWithSnapshotFeature()) == null) {
352          ReadOnlyList<INode> c = getCurrentChildrenList();
353          final int i = ReadOnlyList.Util.binarySearch(c, name);
354          return i < 0 ? null : c.get(i);
355        }
356        
357        return sf.getChild(this, name, snapshotId);
358      }
359      
360      /**
361       * @param snapshotId
362       *          if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result
363       *          from the corresponding snapshot; otherwise, get the result from
364       *          the current directory.
365       * @return the current children list if the specified snapshot is null;
366       *         otherwise, return the children list corresponding to the snapshot.
367       *         Note that the returned list is never null.
368       */
369      public ReadOnlyList<INode> getChildrenList(final int snapshotId) {
370        DirectoryWithSnapshotFeature sf;
371        if (snapshotId == Snapshot.CURRENT_STATE_ID
372            || (sf = this.getDirectoryWithSnapshotFeature()) == null) {
373          return getCurrentChildrenList();
374        }
375        return sf.getChildrenList(this, snapshotId);
376      }
377      
378      private ReadOnlyList<INode> getCurrentChildrenList() {
379        return children == null ? ReadOnlyList.Util.<INode> emptyList()
380            : ReadOnlyList.Util.asReadOnlyList(children);
381      }
382    
383      /** @return the {@link INodesInPath} containing only the last inode. */
384      INodesInPath getLastINodeInPath(String path, boolean resolveLink
385          ) throws UnresolvedLinkException {
386        return INodesInPath.resolve(this, getPathComponents(path), 1, resolveLink);
387      }
388    
389      /** @return the {@link INodesInPath} containing all inodes in the path. */
390      INodesInPath getINodesInPath(String path, boolean resolveLink
391          ) throws UnresolvedLinkException {
392        final byte[][] components = getPathComponents(path);
393        return INodesInPath.resolve(this, components, components.length, resolveLink);
394      }
395    
396      /** @return the last inode in the path. */
397      INode getNode(String path, boolean resolveLink) 
398        throws UnresolvedLinkException {
399        return getLastINodeInPath(path, resolveLink).getINode(0);
400      }
401    
402      /**
403       * @return the INode of the last component in src, or null if the last
404       * component does not exist.
405       * @throws UnresolvedLinkException if symlink can't be resolved
406       * @throws SnapshotAccessControlException if path is in RO snapshot
407       */
408      INode getINode4Write(String src, boolean resolveLink)
409          throws UnresolvedLinkException, SnapshotAccessControlException {
410        return getINodesInPath4Write(src, resolveLink).getLastINode();
411      }
412    
413      /**
414       * @return the INodesInPath of the components in src
415       * @throws UnresolvedLinkException if symlink can't be resolved
416       * @throws SnapshotAccessControlException if path is in RO snapshot
417       */
418      INodesInPath getINodesInPath4Write(String src, boolean resolveLink)
419          throws UnresolvedLinkException, SnapshotAccessControlException {
420        final byte[][] components = INode.getPathComponents(src);
421        INodesInPath inodesInPath = INodesInPath.resolve(this, components,
422            components.length, resolveLink);
423        if (inodesInPath.isSnapshot()) {
424          throw new SnapshotAccessControlException(
425              "Modification on a read-only snapshot is disallowed");
426        }
427        return inodesInPath;
428      }
429    
430      /**
431       * Given a child's name, return the index of the next child
432       *
433       * @param name a child's name
434       * @return the index of the next child
435       */
436      static int nextChild(ReadOnlyList<INode> children, byte[] name) {
437        if (name.length == 0) { // empty name
438          return 0;
439        }
440        int nextPos = ReadOnlyList.Util.binarySearch(children, name) + 1;
441        if (nextPos >= 0) {
442          return nextPos;
443        }
444        return -nextPos;
445      }
446      
447      /**
448       * Remove the specified child from this directory.
449       */
450      public boolean removeChild(INode child, int latestSnapshotId)
451          throws QuotaExceededException {
452        if (isInLatestSnapshot(latestSnapshotId)) {
453          // create snapshot feature if necessary
454          DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
455          if (sf == null) {
456            sf = this.addSnapshotFeature(null);
457          }
458          return sf.removeChild(this, child, latestSnapshotId);
459        }
460        return removeChild(child);
461      }
462      
463      /** 
464       * Remove the specified child from this directory.
465       * The basic remove method which actually calls children.remove(..).
466       *
467       * @param child the child inode to be removed
468       * 
469       * @return true if the child is removed; false if the child is not found.
470       */
471      public boolean removeChild(final INode child) {
472        final int i = searchChildren(child.getLocalNameBytes());
473        if (i < 0) {
474          return false;
475        }
476    
477        final INode removed = children.remove(i);
478        Preconditions.checkState(removed == child);
479        return true;
480      }
481    
482      /**
483       * Add a child inode to the directory.
484       * 
485       * @param node INode to insert
486       * @param setModTime set modification time for the parent node
487       *                   not needed when replaying the addition and 
488       *                   the parent already has the proper mod time
489       * @return false if the child with this name already exists; 
490       *         otherwise, return true;
491       */
492      public boolean addChild(INode node, final boolean setModTime,
493          final int latestSnapshotId) throws QuotaExceededException {
494        final int low = searchChildren(node.getLocalNameBytes());
495        if (low >= 0) {
496          return false;
497        }
498    
499        if (isInLatestSnapshot(latestSnapshotId)) {
500          // create snapshot feature if necessary
501          DirectoryWithSnapshotFeature sf = this.getDirectoryWithSnapshotFeature();
502          if (sf == null) {
503            sf = this.addSnapshotFeature(null);
504          }
505          return sf.addChild(this, node, setModTime, latestSnapshotId);
506        }
507        addChild(node, low);
508        if (setModTime) {
509          // update modification time of the parent directory
510          updateModificationTime(node.getModificationTime(), latestSnapshotId);
511        }
512        return true;
513      }
514    
515      public boolean addChild(INode node) {
516        final int low = searchChildren(node.getLocalNameBytes());
517        if (low >= 0) {
518          return false;
519        }
520        addChild(node, low);
521        return true;
522      }
523    
524      /**
525       * Add the node to the children list at the given insertion point.
526       * The basic add method which actually calls children.add(..).
527       */
528      private void addChild(final INode node, final int insertionPoint) {
529        if (children == null) {
530          children = new ArrayList<INode>(DEFAULT_FILES_PER_DIRECTORY);
531        }
532        node.setParent(this);
533        children.add(-insertionPoint - 1, node);
534    
535        if (node.getGroupName() == null) {
536          node.setGroup(getGroupName());
537        }
538      }
539    
540      @Override
541      public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
542          int lastSnapshotId) {
543        final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
544        
545        // we are computing the quota usage for a specific snapshot here, i.e., the
546        // computation only includes files/directories that exist at the time of the
547        // given snapshot
548        if (sf != null && lastSnapshotId != Snapshot.CURRENT_STATE_ID
549            && !(useCache && isQuotaSet())) {
550          ReadOnlyList<INode> childrenList = getChildrenList(lastSnapshotId);
551          for (INode child : childrenList) {
552            child.computeQuotaUsage(counts, useCache, lastSnapshotId);
553          }
554          counts.add(Quota.NAMESPACE, 1);
555          return counts;
556        }
557        
558        // compute the quota usage in the scope of the current directory tree
559        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
560        if (useCache && q != null && q.isQuotaSet()) { // use the cached quota
561          return q.addNamespaceDiskspace(counts);
562        } else {
563          useCache = q != null && !q.isQuotaSet() ? false : useCache;
564          return computeDirectoryQuotaUsage(counts, useCache, lastSnapshotId);
565        }
566      }
567    
568      private Quota.Counts computeDirectoryQuotaUsage(Quota.Counts counts,
569          boolean useCache, int lastSnapshotId) {
570        if (children != null) {
571          for (INode child : children) {
572            child.computeQuotaUsage(counts, useCache, lastSnapshotId);
573          }
574        }
575        return computeQuotaUsage4CurrentDirectory(counts);
576      }
577      
578      /** Add quota usage for this inode excluding children. */
579      public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) {
580        counts.add(Quota.NAMESPACE, 1);
581        // include the diff list
582        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
583        if (sf != null) {
584          sf.computeQuotaUsage4CurrentDirectory(counts);
585        }
586        return counts;
587      }
588    
589      @Override
590      public ContentSummaryComputationContext computeContentSummary(
591          ContentSummaryComputationContext summary) {
592        final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
593        if (sf != null) {
594          sf.computeContentSummary4Snapshot(summary.getCounts());
595        }
596        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
597        if (q != null) {
598          return q.computeContentSummary(this, summary);
599        } else {
600          return computeDirectoryContentSummary(summary);
601        }
602      }
603    
604      ContentSummaryComputationContext computeDirectoryContentSummary(
605          ContentSummaryComputationContext summary) {
606        ReadOnlyList<INode> childrenList = getChildrenList(Snapshot.CURRENT_STATE_ID);
607        // Explicit traversing is done to enable repositioning after relinquishing
608        // and reacquiring locks.
609        for (int i = 0;  i < childrenList.size(); i++) {
610          INode child = childrenList.get(i);
611          byte[] childName = child.getLocalNameBytes();
612    
613          long lastYieldCount = summary.getYieldCount();
614          child.computeContentSummary(summary);
615    
616          // Check whether the computation was paused in the subtree.
617          // The counts may be off, but traversing the rest of children
618          // should be made safe.
619          if (lastYieldCount == summary.getYieldCount()) {
620            continue;
621          }
622          // The locks were released and reacquired. Check parent first.
623          if (getParent() == null) {
624            // Stop further counting and return whatever we have so far.
625            break;
626          }
627          // Obtain the children list again since it may have been modified.
628          childrenList = getChildrenList(Snapshot.CURRENT_STATE_ID);
629          // Reposition in case the children list is changed. Decrement by 1
630          // since it will be incremented when loops.
631          i = nextChild(childrenList, childName) - 1;
632        }
633    
634        // Increment the directory count for this directory.
635        summary.getCounts().add(Content.DIRECTORY, 1);
636        // Relinquish and reacquire locks if necessary.
637        summary.yield();
638        return summary;
639      }
640      
641      /**
642       * This method is usually called by the undo section of rename.
643       * 
644       * Before calling this function, in the rename operation, we replace the
645       * original src node (of the rename operation) with a reference node (WithName
646       * instance) in both the children list and a created list, delete the
647       * reference node from the children list, and add it to the corresponding
648       * deleted list.
649       * 
650       * To undo the above operations, we have the following steps in particular:
651       * 
652       * <pre>
653       * 1) remove the WithName node from the deleted list (if it exists) 
654       * 2) replace the WithName node in the created list with srcChild 
655       * 3) add srcChild back as a child of srcParent. Note that we already add 
656       * the node into the created list of a snapshot diff in step 2, we do not need
657       * to add srcChild to the created list of the latest snapshot.
658       * </pre>
659       * 
660       * We do not need to update quota usage because the old child is in the 
661       * deleted list before. 
662       * 
663       * @param oldChild
664       *          The reference node to be removed/replaced
665       * @param newChild
666       *          The node to be added back
667       * @throws QuotaExceededException should not throw this exception
668       */
669      public void undoRename4ScrParent(final INodeReference oldChild,
670          final INode newChild) throws QuotaExceededException {
671        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
672        Preconditions.checkState(sf != null,
673            "Directory does not have snapshot feature");
674        sf.getDiffs().removeChild(ListType.DELETED, oldChild);
675        sf.getDiffs().replaceChild(ListType.CREATED, oldChild, newChild);
676        addChild(newChild, true, Snapshot.CURRENT_STATE_ID);
677      }
678      
679      /**
680       * Undo the rename operation for the dst tree, i.e., if the rename operation
681       * (with OVERWRITE option) removes a file/dir from the dst tree, add it back
682       * and delete possible record in the deleted list.  
683       */
684      public void undoRename4DstParent(final INode deletedChild,
685          int latestSnapshotId) throws QuotaExceededException {
686        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
687        Preconditions.checkState(sf != null,
688            "Directory does not have snapshot feature");
689        boolean removeDeletedChild = sf.getDiffs().removeChild(ListType.DELETED,
690            deletedChild);
691        int sid = removeDeletedChild ? Snapshot.CURRENT_STATE_ID : latestSnapshotId;
692        final boolean added = addChild(deletedChild, true, sid);
693        // update quota usage if adding is successfully and the old child has not
694        // been stored in deleted list before
695        if (added && !removeDeletedChild) {
696          final Quota.Counts counts = deletedChild.computeQuotaUsage();
697          addSpaceConsumed(counts.get(Quota.NAMESPACE),
698              counts.get(Quota.DISKSPACE), false);
699        }
700      }
701    
702      /** Set the children list to null. */
703      public void clearChildren() {
704        this.children = null;
705      }
706    
707      @Override
708      public void clear() {
709        super.clear();
710        clearChildren();
711      }
712    
713      /** Call cleanSubtree(..) recursively down the subtree. */
714      public Quota.Counts cleanSubtreeRecursively(final int snapshot,
715          int prior, final BlocksMapUpdateInfo collectedBlocks,
716          final List<INode> removedINodes, final Map<INode, INode> excludedNodes, 
717          final boolean countDiffChange) throws QuotaExceededException {
718        Quota.Counts counts = Quota.Counts.newInstance();
719        // in case of deletion snapshot, since this call happens after we modify
720        // the diff list, the snapshot to be deleted has been combined or renamed
721        // to its latest previous snapshot. (besides, we also need to consider nodes
722        // created after prior but before snapshot. this will be done in 
723        // DirectoryWithSnapshotFeature)
724        int s = snapshot != Snapshot.CURRENT_STATE_ID
725            && prior != Snapshot.NO_SNAPSHOT_ID ? prior : snapshot;
726        for (INode child : getChildrenList(s)) {
727          if (snapshot != Snapshot.CURRENT_STATE_ID && excludedNodes != null
728              && excludedNodes.containsKey(child)) {
729            continue;
730          } else {
731            Quota.Counts childCounts = child.cleanSubtree(snapshot, prior,
732                collectedBlocks, removedINodes, countDiffChange);
733            counts.add(childCounts);
734          }
735        }
736        return counts;
737      }
738    
739      @Override
740      public void destroyAndCollectBlocks(final BlocksMapUpdateInfo collectedBlocks,
741          final List<INode> removedINodes) {
742        final DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
743        if (sf != null) {
744          sf.clear(this, collectedBlocks, removedINodes);
745        }
746        for (INode child : getChildrenList(Snapshot.CURRENT_STATE_ID)) {
747          child.destroyAndCollectBlocks(collectedBlocks, removedINodes);
748        }
749        clear();
750        removedINodes.add(this);
751      }
752      
753      @Override
754      public Quota.Counts cleanSubtree(final int snapshotId, int priorSnapshotId,
755          final BlocksMapUpdateInfo collectedBlocks,
756          final List<INode> removedINodes, final boolean countDiffChange)
757          throws QuotaExceededException {
758        DirectoryWithSnapshotFeature sf = getDirectoryWithSnapshotFeature();
759        // there is snapshot data
760        if (sf != null) {
761          return sf.cleanDirectory(this, snapshotId, priorSnapshotId,
762              collectedBlocks, removedINodes, countDiffChange);
763        }
764        // there is no snapshot data
765        if (priorSnapshotId == Snapshot.NO_SNAPSHOT_ID
766            && snapshotId == Snapshot.CURRENT_STATE_ID) {
767          // destroy the whole subtree and collect blocks that should be deleted
768          Quota.Counts counts = Quota.Counts.newInstance();
769          this.computeQuotaUsage(counts, true);
770          destroyAndCollectBlocks(collectedBlocks, removedINodes);
771          return counts; 
772        } else {
773          // process recursively down the subtree
774          Quota.Counts counts = cleanSubtreeRecursively(snapshotId, priorSnapshotId,
775              collectedBlocks, removedINodes, null, countDiffChange);
776          if (isQuotaSet()) {
777            getDirectoryWithQuotaFeature().addSpaceConsumed2Cache(
778                -counts.get(Quota.NAMESPACE), -counts.get(Quota.DISKSPACE));
779          }
780          return counts;
781        }
782      }
783      
784      /**
785       * Compare the metadata with another INodeDirectory
786       */
787      @Override
788      public boolean metadataEquals(INodeDirectoryAttributes other) {
789        return other != null
790            && getQuotaCounts().equals(other.getQuotaCounts())
791            && getPermissionLong() == other.getPermissionLong();
792      }
793      
794      /*
795       * The following code is to dump the tree recursively for testing.
796       * 
797       *      \- foo   (INodeDirectory@33dd2717)
798       *        \- sub1   (INodeDirectory@442172)
799       *          +- file1   (INodeFile@78392d4)
800       *          +- file2   (INodeFile@78392d5)
801       *          +- sub11   (INodeDirectory@8400cff)
802       *            \- file3   (INodeFile@78392d6)
803       *          \- z_file4   (INodeFile@45848712)
804       */
805      static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-"; 
806      static final String DUMPTREE_LAST_ITEM = "\\-";
807      @VisibleForTesting
808      @Override
809      public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
810          final int snapshot) {
811        super.dumpTreeRecursively(out, prefix, snapshot);
812        out.print(", childrenSize=" + getChildrenList(snapshot).size());
813        final DirectoryWithQuotaFeature q = getDirectoryWithQuotaFeature();
814        if (q != null) {
815          out.print(", " + q);
816        }
817        if (this instanceof Snapshot.Root) {
818          out.print(", snapshotId=" + snapshot);
819        }
820        out.println();
821    
822        if (prefix.length() >= 2) {
823          prefix.setLength(prefix.length() - 2);
824          prefix.append("  ");
825        }
826        dumpTreeRecursively(out, prefix, new Iterable<SnapshotAndINode>() {
827          final Iterator<INode> i = getChildrenList(snapshot).iterator();
828          
829          @Override
830          public Iterator<SnapshotAndINode> iterator() {
831            return new Iterator<SnapshotAndINode>() {
832              @Override
833              public boolean hasNext() {
834                return i.hasNext();
835              }
836    
837              @Override
838              public SnapshotAndINode next() {
839                return new SnapshotAndINode(snapshot, i.next());
840              }
841    
842              @Override
843              public void remove() {
844                throw new UnsupportedOperationException();
845              }
846            };
847          }
848        });
849      }
850    
851      /**
852       * Dump the given subtrees.
853       * @param prefix The prefix string that each line should print.
854       * @param subs The subtrees.
855       */
856      @VisibleForTesting
857      protected static void dumpTreeRecursively(PrintWriter out,
858          StringBuilder prefix, Iterable<SnapshotAndINode> subs) {
859        if (subs != null) {
860          for(final Iterator<SnapshotAndINode> i = subs.iterator(); i.hasNext();) {
861            final SnapshotAndINode pair = i.next();
862            prefix.append(i.hasNext()? DUMPTREE_EXCEPT_LAST_ITEM: DUMPTREE_LAST_ITEM);
863            pair.inode.dumpTreeRecursively(out, prefix, pair.snapshotId);
864            prefix.setLength(prefix.length() - 2);
865          }
866        }
867      }
868    
869      /** A pair of Snapshot and INode objects. */
870      protected static class SnapshotAndINode {
871        public final int snapshotId;
872        public final INode inode;
873    
874        public SnapshotAndINode(int snapshot, INode inode) {
875          this.snapshotId = snapshot;
876          this.inode = inode;
877        }
878      }
879    
880      public final int getChildrenNum(final int snapshotId) {
881        return getChildrenList(snapshotId).size();
882      }
883    }