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