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