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 }