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.INodeDirectorySnapshottable;
036 import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot;
037 import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileUnderConstructionWithSnapshot;
038 import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeFileWithSnapshot;
039 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
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 /** Cast INode to INodeDirectory. */
051 public static INodeDirectory valueOf(INode inode, Object path
052 ) throws FileNotFoundException, PathIsNotDirectoryException {
053 if (inode == null) {
054 throw new FileNotFoundException("Directory does not exist: "
055 + DFSUtil.path2String(path));
056 }
057 if (!inode.isDirectory()) {
058 throw new PathIsNotDirectoryException(DFSUtil.path2String(path));
059 }
060 return inode.asDirectory();
061 }
062
063 protected static final int DEFAULT_FILES_PER_DIRECTORY = 5;
064 final static byte[] ROOT_NAME = DFSUtil.string2Bytes("");
065
066 private List<INode> children = null;
067
068 /** constructor */
069 public INodeDirectory(long id, byte[] name, PermissionStatus permissions,
070 long mtime) {
071 super(id, name, permissions, mtime, 0L);
072 }
073
074 /**
075 * Copy constructor
076 * @param other The INodeDirectory to be copied
077 * @param adopt Indicate whether or not need to set the parent field of child
078 * INodes to the new node
079 */
080 public INodeDirectory(INodeDirectory other, boolean adopt) {
081 super(other);
082 this.children = other.children;
083 if (adopt && this.children != null) {
084 for (INode child : children) {
085 child.setParent(this);
086 }
087 }
088 }
089
090 /** @return true unconditionally. */
091 @Override
092 public final boolean isDirectory() {
093 return true;
094 }
095
096 /** @return this object. */
097 @Override
098 public final INodeDirectory asDirectory() {
099 return this;
100 }
101
102 /** Is this a snapshottable directory? */
103 public boolean isSnapshottable() {
104 return false;
105 }
106
107 private int searchChildren(byte[] name) {
108 return children == null? -1: Collections.binarySearch(children, name);
109 }
110
111 /**
112 * Remove the specified child from this directory.
113 *
114 * @param child the child inode to be removed
115 * @param latest See {@link INode#recordModification(Snapshot, INodeMap)}.
116 */
117 public boolean removeChild(INode child, Snapshot latest,
118 final INodeMap inodeMap) throws QuotaExceededException {
119 if (isInLatestSnapshot(latest)) {
120 return replaceSelf4INodeDirectoryWithSnapshot(inodeMap)
121 .removeChild(child, latest, inodeMap);
122 }
123
124 return removeChild(child);
125 }
126
127 /**
128 * Remove the specified child from this directory.
129 * The basic remove method which actually calls children.remove(..).
130 *
131 * @param child the child inode to be removed
132 *
133 * @return true if the child is removed; false if the child is not found.
134 */
135 protected final boolean removeChild(final INode child) {
136 final int i = searchChildren(child.getLocalNameBytes());
137 if (i < 0) {
138 return false;
139 }
140
141 final INode removed = children.remove(i);
142 Preconditions.checkState(removed == child);
143 return true;
144 }
145
146 /**
147 * Replace itself with {@link INodeDirectoryWithQuota} or
148 * {@link INodeDirectoryWithSnapshot} depending on the latest snapshot.
149 */
150 INodeDirectoryWithQuota replaceSelf4Quota(final Snapshot latest,
151 final long nsQuota, final long dsQuota, final INodeMap inodeMap)
152 throws QuotaExceededException {
153 Preconditions.checkState(!(this instanceof INodeDirectoryWithQuota),
154 "this is already an INodeDirectoryWithQuota, this=%s", this);
155
156 if (!this.isInLatestSnapshot(latest)) {
157 final INodeDirectoryWithQuota q = new INodeDirectoryWithQuota(
158 this, true, nsQuota, dsQuota);
159 replaceSelf(q, inodeMap);
160 return q;
161 } else {
162 final INodeDirectoryWithSnapshot s = new INodeDirectoryWithSnapshot(this);
163 s.setQuota(nsQuota, dsQuota);
164 return replaceSelf(s, inodeMap).saveSelf2Snapshot(latest, this);
165 }
166 }
167 /** Replace itself with an {@link INodeDirectorySnapshottable}. */
168 public INodeDirectorySnapshottable replaceSelf4INodeDirectorySnapshottable(
169 Snapshot latest, final INodeMap inodeMap) throws QuotaExceededException {
170 Preconditions.checkState(!(this instanceof INodeDirectorySnapshottable),
171 "this is already an INodeDirectorySnapshottable, this=%s", this);
172 final INodeDirectorySnapshottable s = new INodeDirectorySnapshottable(this);
173 replaceSelf(s, inodeMap).saveSelf2Snapshot(latest, this);
174 return s;
175 }
176
177 /** Replace itself with an {@link INodeDirectoryWithSnapshot}. */
178 public INodeDirectoryWithSnapshot replaceSelf4INodeDirectoryWithSnapshot(
179 final INodeMap inodeMap) {
180 return replaceSelf(new INodeDirectoryWithSnapshot(this), inodeMap);
181 }
182
183 /** Replace itself with {@link INodeDirectory}. */
184 public INodeDirectory replaceSelf4INodeDirectory(final INodeMap inodeMap) {
185 Preconditions.checkState(getClass() != INodeDirectory.class,
186 "the class is already INodeDirectory, this=%s", this);
187 return replaceSelf(new INodeDirectory(this, true), inodeMap);
188 }
189
190 /** Replace itself with the given directory. */
191 private final <N extends INodeDirectory> N replaceSelf(final N newDir,
192 final INodeMap inodeMap) {
193 final INodeReference ref = getParentReference();
194 if (ref != null) {
195 ref.setReferredINode(newDir);
196 if (inodeMap != null) {
197 inodeMap.put(newDir);
198 }
199 } else {
200 final INodeDirectory parent = getParent();
201 Preconditions.checkArgument(parent != null, "parent is null, this=%s", this);
202 parent.replaceChild(this, newDir, inodeMap);
203 }
204 clear();
205 return newDir;
206 }
207
208 /** Replace the given child with a new child. */
209 public void replaceChild(INode oldChild, final INode newChild,
210 final INodeMap inodeMap) {
211 Preconditions.checkNotNull(children);
212 final int i = searchChildren(newChild.getLocalNameBytes());
213 Preconditions.checkState(i >= 0);
214 Preconditions.checkState(oldChild == children.get(i)
215 || oldChild == children.get(i).asReference().getReferredINode()
216 .asReference().getReferredINode());
217 oldChild = children.get(i);
218
219 if (oldChild.isReference() && !newChild.isReference()) {
220 // replace the referred inode, e.g.,
221 // INodeFileWithSnapshot -> INodeFileUnderConstructionWithSnapshot
222 final INode withCount = oldChild.asReference().getReferredINode();
223 withCount.asReference().setReferredINode(newChild);
224 } else {
225 if (oldChild.isReference()) {
226 // both are reference nodes, e.g., DstReference -> WithName
227 final INodeReference.WithCount withCount =
228 (WithCount) oldChild.asReference().getReferredINode();
229 withCount.removeReference(oldChild.asReference());
230 }
231 children.set(i, newChild);
232 }
233 // update the inodeMap
234 if (inodeMap != null) {
235 inodeMap.put(newChild);
236 }
237 }
238
239 INodeReference.WithName replaceChild4ReferenceWithName(INode oldChild,
240 Snapshot latest) {
241 Preconditions.checkArgument(latest != null);
242 if (oldChild instanceof INodeReference.WithName) {
243 return (INodeReference.WithName)oldChild;
244 }
245
246 final INodeReference.WithCount withCount;
247 if (oldChild.isReference()) {
248 Preconditions.checkState(oldChild instanceof INodeReference.DstReference);
249 withCount = (INodeReference.WithCount) oldChild.asReference()
250 .getReferredINode();
251 } else {
252 withCount = new INodeReference.WithCount(null, oldChild);
253 }
254 final INodeReference.WithName ref = new INodeReference.WithName(this,
255 withCount, oldChild.getLocalNameBytes(), latest.getId());
256 replaceChild(oldChild, ref, null);
257 return ref;
258 }
259
260 private void replaceChildFile(final INodeFile oldChild,
261 final INodeFile newChild, final INodeMap inodeMap) {
262 replaceChild(oldChild, newChild, inodeMap);
263 oldChild.clear();
264 newChild.updateBlockCollection();
265 }
266
267 /** Replace a child {@link INodeFile} with an {@link INodeFileWithSnapshot}. */
268 INodeFileWithSnapshot replaceChild4INodeFileWithSnapshot(
269 final INodeFile child, final INodeMap inodeMap) {
270 Preconditions.checkArgument(!(child instanceof INodeFileWithSnapshot),
271 "Child file is already an INodeFileWithSnapshot, child=" + child);
272 final INodeFileWithSnapshot newChild = new INodeFileWithSnapshot(child);
273 replaceChildFile(child, newChild, inodeMap);
274 return newChild;
275 }
276
277 /** Replace a child {@link INodeFile} with an {@link INodeFileUnderConstructionWithSnapshot}. */
278 INodeFileUnderConstructionWithSnapshot replaceChild4INodeFileUcWithSnapshot(
279 final INodeFileUnderConstruction child, final INodeMap inodeMap) {
280 Preconditions.checkArgument(!(child instanceof INodeFileUnderConstructionWithSnapshot),
281 "Child file is already an INodeFileUnderConstructionWithSnapshot, child=" + child);
282 final INodeFileUnderConstructionWithSnapshot newChild
283 = new INodeFileUnderConstructionWithSnapshot(child, null);
284 replaceChildFile(child, newChild, inodeMap);
285 return newChild;
286 }
287
288 @Override
289 public INodeDirectory recordModification(Snapshot latest,
290 final INodeMap inodeMap) throws QuotaExceededException {
291 if (isInLatestSnapshot(latest)) {
292 return replaceSelf4INodeDirectoryWithSnapshot(inodeMap)
293 .recordModification(latest, inodeMap);
294 } else {
295 return this;
296 }
297 }
298
299 /**
300 * Save the child to the latest snapshot.
301 *
302 * @return the child inode, which may be replaced.
303 */
304 public INode saveChild2Snapshot(final INode child, final Snapshot latest,
305 final INode snapshotCopy, final INodeMap inodeMap)
306 throws QuotaExceededException {
307 if (latest == null) {
308 return child;
309 }
310 return replaceSelf4INodeDirectoryWithSnapshot(inodeMap)
311 .saveChild2Snapshot(child, latest, snapshotCopy, inodeMap);
312 }
313
314 /**
315 * @param name the name of the child
316 * @param snapshot
317 * if it is not null, get the result from the given snapshot;
318 * otherwise, get the result from the current directory.
319 * @return the child inode.
320 */
321 public INode getChild(byte[] name, Snapshot snapshot) {
322 final ReadOnlyList<INode> c = getChildrenList(snapshot);
323 final int i = ReadOnlyList.Util.binarySearch(c, name);
324 return i < 0? null: c.get(i);
325 }
326
327 /** @return the {@link INodesInPath} containing only the last inode. */
328 INodesInPath getLastINodeInPath(String path, boolean resolveLink
329 ) throws UnresolvedLinkException {
330 return INodesInPath.resolve(this, getPathComponents(path), 1, resolveLink);
331 }
332
333 /** @return the {@link INodesInPath} containing all inodes in the path. */
334 INodesInPath getINodesInPath(String path, boolean resolveLink
335 ) throws UnresolvedLinkException {
336 final byte[][] components = getPathComponents(path);
337 return INodesInPath.resolve(this, components, components.length, resolveLink);
338 }
339
340 /** @return the last inode in the path. */
341 INode getNode(String path, boolean resolveLink)
342 throws UnresolvedLinkException {
343 return getLastINodeInPath(path, resolveLink).getINode(0);
344 }
345
346 /**
347 * @return the INode of the last component in src, or null if the last
348 * component does not exist.
349 * @throws UnresolvedLinkException if symlink can't be resolved
350 * @throws SnapshotAccessControlException if path is in RO snapshot
351 */
352 INode getINode4Write(String src, boolean resolveLink)
353 throws UnresolvedLinkException, SnapshotAccessControlException {
354 return getINodesInPath4Write(src, resolveLink).getLastINode();
355 }
356
357 /**
358 * @return the INodesInPath of the components in src
359 * @throws UnresolvedLinkException if symlink can't be resolved
360 * @throws SnapshotAccessControlException if path is in RO snapshot
361 */
362 INodesInPath getINodesInPath4Write(String src, boolean resolveLink)
363 throws UnresolvedLinkException, SnapshotAccessControlException {
364 final byte[][] components = INode.getPathComponents(src);
365 INodesInPath inodesInPath = INodesInPath.resolve(this, components,
366 components.length, resolveLink);
367 if (inodesInPath.isSnapshot()) {
368 throw new SnapshotAccessControlException(
369 "Modification on a read-only snapshot is disallowed");
370 }
371 return inodesInPath;
372 }
373
374 /**
375 * Given a child's name, return the index of the next child
376 *
377 * @param name a child's name
378 * @return the index of the next child
379 */
380 static int nextChild(ReadOnlyList<INode> children, byte[] name) {
381 if (name.length == 0) { // empty name
382 return 0;
383 }
384 int nextPos = ReadOnlyList.Util.binarySearch(children, name) + 1;
385 if (nextPos >= 0) {
386 return nextPos;
387 }
388 return -nextPos;
389 }
390
391 /**
392 * Add a child inode to the directory.
393 *
394 * @param node INode to insert
395 * @param setModTime set modification time for the parent node
396 * not needed when replaying the addition and
397 * the parent already has the proper mod time
398 * @param inodeMap update the inodeMap if the directory node gets replaced
399 * @return false if the child with this name already exists;
400 * otherwise, return true;
401 */
402 public boolean addChild(INode node, final boolean setModTime,
403 final Snapshot latest, final INodeMap inodeMap)
404 throws QuotaExceededException {
405 final int low = searchChildren(node.getLocalNameBytes());
406 if (low >= 0) {
407 return false;
408 }
409
410 if (isInLatestSnapshot(latest)) {
411 INodeDirectoryWithSnapshot sdir =
412 replaceSelf4INodeDirectoryWithSnapshot(inodeMap);
413 boolean added = sdir.addChild(node, setModTime, latest, inodeMap);
414 return added;
415 }
416 addChild(node, low);
417 if (setModTime) {
418 // update modification time of the parent directory
419 updateModificationTime(node.getModificationTime(), latest, inodeMap);
420 }
421 return true;
422 }
423
424
425 /** The same as addChild(node, false, null, false) */
426 public boolean addChild(INode node) {
427 final int low = searchChildren(node.getLocalNameBytes());
428 if (low >= 0) {
429 return false;
430 }
431 addChild(node, low);
432 return true;
433 }
434
435 /**
436 * Add the node to the children list at the given insertion point.
437 * The basic add method which actually calls children.add(..).
438 */
439 private void addChild(final INode node, final int insertionPoint) {
440 if (children == null) {
441 children = new ArrayList<INode>(DEFAULT_FILES_PER_DIRECTORY);
442 }
443 node.setParent(this);
444 children.add(-insertionPoint - 1, node);
445
446 if (node.getGroupName() == null) {
447 node.setGroup(getGroupName());
448 }
449 }
450
451 @Override
452 public Quota.Counts computeQuotaUsage(Quota.Counts counts, boolean useCache,
453 int lastSnapshotId) {
454 if (children != null) {
455 for (INode child : children) {
456 child.computeQuotaUsage(counts, useCache, lastSnapshotId);
457 }
458 }
459 return computeQuotaUsage4CurrentDirectory(counts);
460 }
461
462 /** Add quota usage for this inode excluding children. */
463 public Quota.Counts computeQuotaUsage4CurrentDirectory(Quota.Counts counts) {
464 counts.add(Quota.NAMESPACE, 1);
465 return counts;
466 }
467
468 @Override
469 public Content.Counts computeContentSummary(final Content.Counts counts) {
470 for (INode child : getChildrenList(null)) {
471 child.computeContentSummary(counts);
472 }
473 counts.add(Content.DIRECTORY, 1);
474 return counts;
475 }
476
477 /**
478 * @param snapshot
479 * if it is not null, get the result from the given snapshot;
480 * otherwise, get the result from the current directory.
481 * @return the current children list if the specified snapshot is null;
482 * otherwise, return the children list corresponding to the snapshot.
483 * Note that the returned list is never null.
484 */
485 public ReadOnlyList<INode> getChildrenList(final Snapshot snapshot) {
486 return children == null ? ReadOnlyList.Util.<INode>emptyList()
487 : ReadOnlyList.Util.asReadOnlyList(children);
488 }
489
490 /** Set the children list to null. */
491 public void clearChildren() {
492 this.children = null;
493 }
494
495 @Override
496 public void clear() {
497 super.clear();
498 clearChildren();
499 }
500
501 /** Call cleanSubtree(..) recursively down the subtree. */
502 public Quota.Counts cleanSubtreeRecursively(final Snapshot snapshot,
503 Snapshot prior, final BlocksMapUpdateInfo collectedBlocks,
504 final List<INode> removedINodes, final Map<INode, INode> excludedNodes,
505 final boolean countDiffChange) throws QuotaExceededException {
506 Quota.Counts counts = Quota.Counts.newInstance();
507 // in case of deletion snapshot, since this call happens after we modify
508 // the diff list, the snapshot to be deleted has been combined or renamed
509 // to its latest previous snapshot. (besides, we also need to consider nodes
510 // created after prior but before snapshot. this will be done in
511 // INodeDirectoryWithSnapshot#cleanSubtree)
512 Snapshot s = snapshot != null && prior != null ? prior : snapshot;
513 for (INode child : getChildrenList(s)) {
514 if (snapshot != null && excludedNodes != null
515 && excludedNodes.containsKey(child)) {
516 continue;
517 } else {
518 Quota.Counts childCounts = child.cleanSubtree(snapshot, prior,
519 collectedBlocks, removedINodes, countDiffChange);
520 counts.add(childCounts);
521 }
522 }
523 return counts;
524 }
525
526 @Override
527 public void destroyAndCollectBlocks(final BlocksMapUpdateInfo collectedBlocks,
528 final List<INode> removedINodes) {
529 for (INode child : getChildrenList(null)) {
530 child.destroyAndCollectBlocks(collectedBlocks, removedINodes);
531 }
532 clear();
533 removedINodes.add(this);
534 }
535
536 @Override
537 public Quota.Counts cleanSubtree(final Snapshot snapshot, Snapshot prior,
538 final BlocksMapUpdateInfo collectedBlocks,
539 final List<INode> removedINodes, final boolean countDiffChange)
540 throws QuotaExceededException {
541 if (prior == null && snapshot == null) {
542 // destroy the whole subtree and collect blocks that should be deleted
543 Quota.Counts counts = Quota.Counts.newInstance();
544 this.computeQuotaUsage(counts, true);
545 destroyAndCollectBlocks(collectedBlocks, removedINodes);
546 return counts;
547 } else {
548 // process recursively down the subtree
549 Quota.Counts counts = cleanSubtreeRecursively(snapshot, prior,
550 collectedBlocks, removedINodes, null, countDiffChange);
551 if (isQuotaSet()) {
552 ((INodeDirectoryWithQuota) this).addSpaceConsumed2Cache(
553 -counts.get(Quota.NAMESPACE), -counts.get(Quota.DISKSPACE));
554 }
555 return counts;
556 }
557 }
558
559 /**
560 * Compare the metadata with another INodeDirectory
561 */
562 @Override
563 public boolean metadataEquals(INodeDirectoryAttributes other) {
564 return other != null
565 && getNsQuota() == other.getNsQuota()
566 && getDsQuota() == other.getDsQuota()
567 && getPermissionLong() == other.getPermissionLong();
568 }
569
570 /*
571 * The following code is to dump the tree recursively for testing.
572 *
573 * \- foo (INodeDirectory@33dd2717)
574 * \- sub1 (INodeDirectory@442172)
575 * +- file1 (INodeFile@78392d4)
576 * +- file2 (INodeFile@78392d5)
577 * +- sub11 (INodeDirectory@8400cff)
578 * \- file3 (INodeFile@78392d6)
579 * \- z_file4 (INodeFile@45848712)
580 */
581 static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-";
582 static final String DUMPTREE_LAST_ITEM = "\\-";
583 @VisibleForTesting
584 @Override
585 public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix,
586 final Snapshot snapshot) {
587 super.dumpTreeRecursively(out, prefix, snapshot);
588 out.print(", childrenSize=" + getChildrenList(snapshot).size());
589 if (this instanceof INodeDirectoryWithQuota) {
590 out.print(((INodeDirectoryWithQuota)this).quotaString());
591 }
592 if (this instanceof Snapshot.Root) {
593 out.print(", snapshotId=" + snapshot.getId());
594 }
595 out.println();
596
597 if (prefix.length() >= 2) {
598 prefix.setLength(prefix.length() - 2);
599 prefix.append(" ");
600 }
601 dumpTreeRecursively(out, prefix, new Iterable<SnapshotAndINode>() {
602 final Iterator<INode> i = getChildrenList(snapshot).iterator();
603
604 @Override
605 public Iterator<SnapshotAndINode> iterator() {
606 return new Iterator<SnapshotAndINode>() {
607 @Override
608 public boolean hasNext() {
609 return i.hasNext();
610 }
611
612 @Override
613 public SnapshotAndINode next() {
614 return new SnapshotAndINode(snapshot, i.next());
615 }
616
617 @Override
618 public void remove() {
619 throw new UnsupportedOperationException();
620 }
621 };
622 }
623 });
624 }
625
626 /**
627 * Dump the given subtrees.
628 * @param prefix The prefix string that each line should print.
629 * @param subs The subtrees.
630 */
631 @VisibleForTesting
632 protected static void dumpTreeRecursively(PrintWriter out,
633 StringBuilder prefix, Iterable<SnapshotAndINode> subs) {
634 if (subs != null) {
635 for(final Iterator<SnapshotAndINode> i = subs.iterator(); i.hasNext();) {
636 final SnapshotAndINode pair = i.next();
637 prefix.append(i.hasNext()? DUMPTREE_EXCEPT_LAST_ITEM: DUMPTREE_LAST_ITEM);
638 pair.inode.dumpTreeRecursively(out, prefix, pair.snapshot);
639 prefix.setLength(prefix.length() - 2);
640 }
641 }
642 }
643
644 /** A pair of Snapshot and INode objects. */
645 protected static class SnapshotAndINode {
646 public final Snapshot snapshot;
647 public final INode inode;
648
649 public SnapshotAndINode(Snapshot snapshot, INode inode) {
650 this.snapshot = snapshot;
651 this.inode = inode;
652 }
653
654 public SnapshotAndINode(Snapshot snapshot) {
655 this(snapshot, snapshot.getRoot());
656 }
657 }
658
659 public final int getChildrenNum(final Snapshot snapshot) {
660 return getChildrenList(snapshot).size();
661 }
662 }