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