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