blob: 2bd33e942a77696d57a8b1f80b6fc58922a96722 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.server.namenode;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.hadoop.fs.PathIsNotDirectoryException;
import org.apache.hadoop.fs.UnresolvedLinkException;
import org.apache.hadoop.fs.permission.PermissionStatus;
import org.apache.hadoop.hdfs.DFSUtil;
import org.apache.hadoop.hdfs.protocol.UnresolvedPathException;
import com.google.common.annotations.VisibleForTesting;
/**
* Directory INode class.
*/
class INodeDirectory extends INode {
/** Cast INode to INodeDirectory. */
public static INodeDirectory valueOf(INode inode, Object path
) throws FileNotFoundException, PathIsNotDirectoryException {
if (inode == null) {
throw new FileNotFoundException("Directory does not exist: "
+ DFSUtil.path2String(path));
}
if (!inode.isDirectory()) {
throw new PathIsNotDirectoryException(DFSUtil.path2String(path));
}
return (INodeDirectory)inode;
}
protected static final int DEFAULT_FILES_PER_DIRECTORY = 5;
final static String ROOT_NAME = "";
private List<INode> children = null;
INodeDirectory(long id, String name, PermissionStatus permissions) {
super(id, name, permissions);
}
public INodeDirectory(long id, PermissionStatus permissions, long mTime) {
super(id, permissions, mTime, 0);
}
/** constructor */
INodeDirectory(long id, byte[] name, PermissionStatus permissions, long mtime) {
super(id, name, permissions, null, mtime, 0L);
}
/** copy constructor
*
* @param other
*/
INodeDirectory(INodeDirectory other) {
super(other);
this.children = other.children;
if (this.children != null) {
for (INode child : children) {
child.parent = this;
}
}
}
/** @return true unconditionally. */
@Override
public final boolean isDirectory() {
return true;
}
private void assertChildrenNonNull() {
if (children == null) {
throw new AssertionError("children is null: " + this);
}
}
private int searchChildren(INode inode) {
return Collections.binarySearch(children, inode.getLocalNameBytes());
}
INode removeChild(INode node) {
assertChildrenNonNull();
final int i = searchChildren(node);
return i >= 0? children.remove(i): null;
}
/** Replace a child that has the same name as newChild by newChild.
*
* @param newChild Child node to be added
*/
void replaceChild(INode newChild) {
assertChildrenNonNull();
final int low = searchChildren(newChild);
if (low>=0) { // an old child exists so replace by the newChild
children.get(low).parent = null;
children.set(low, newChild);
} else {
throw new IllegalArgumentException("No child exists to be replaced");
}
}
INode getChild(String name) {
return getChildINode(DFSUtil.string2Bytes(name));
}
private INode getChildINode(byte[] name) {
if (children == null) {
return null;
}
int low = Collections.binarySearch(children, name);
if (low >= 0) {
return children.get(low);
}
return null;
}
/**
* @return the INode of the last component in components, or null if the last
* component does not exist.
*/
private INode getNode(byte[][] components, boolean resolveLink
) throws UnresolvedLinkException {
INodesInPath inodesInPath = getExistingPathINodes(components, 1,
resolveLink);
return inodesInPath.inodes[0];
}
/**
* This is the external interface
*/
INode getNode(String path, boolean resolveLink)
throws UnresolvedLinkException {
return getNode(getPathComponents(path), resolveLink);
}
/**
* Retrieve existing INodes from a path. If existing is big enough to store
* all path components (existing and non-existing), then existing INodes
* will be stored starting from the root INode into existing[0]; if
* existing is not big enough to store all path components, then only the
* last existing and non existing INodes will be stored so that
* existing[existing.length-1] refers to the INode of the final component.
*
* An UnresolvedPathException is always thrown when an intermediate path
* component refers to a symbolic link. If the final path component refers
* to a symbolic link then an UnresolvedPathException is only thrown if
* resolveLink is true.
*
* <p>
* Example: <br>
* Given the path /c1/c2/c3 where only /c1/c2 exists, resulting in the
* following path components: ["","c1","c2","c3"],
*
* <p>
* <code>getExistingPathINodes(["","c1","c2"], [?])</code> should fill the
* array with [c2] <br>
* <code>getExistingPathINodes(["","c1","c2","c3"], [?])</code> should fill the
* array with [null]
*
* <p>
* <code>getExistingPathINodes(["","c1","c2"], [?,?])</code> should fill the
* array with [c1,c2] <br>
* <code>getExistingPathINodes(["","c1","c2","c3"], [?,?])</code> should fill
* the array with [c2,null]
*
* <p>
* <code>getExistingPathINodes(["","c1","c2"], [?,?,?,?])</code> should fill
* the array with [rootINode,c1,c2,null], <br>
* <code>getExistingPathINodes(["","c1","c2","c3"], [?,?,?,?])</code> should
* fill the array with [rootINode,c1,c2,null]
*
* @param components array of path component name
* @param numOfINodes number of INodes to return
* @param resolveLink indicates whether UnresolvedLinkException should
* be thrown when the path refers to a symbolic link.
* @return the specified number of existing INodes in the path
*/
INodesInPath getExistingPathINodes(byte[][] components, int numOfINodes,
boolean resolveLink)
throws UnresolvedLinkException {
assert this.compareTo(components[0]) == 0 :
"Incorrect name " + getLocalName() + " expected "
+ (components[0] == null? null: DFSUtil.bytes2String(components[0]));
INodesInPath existing = new INodesInPath(numOfINodes);
INode curNode = this;
int count = 0;
int index = numOfINodes - components.length;
if (index > 0) {
index = 0;
}
while (count < components.length && curNode != null) {
final boolean lastComp = (count == components.length - 1);
if (index >= 0) {
existing.inodes[index] = curNode;
}
if (curNode.isSymlink() && (!lastComp || (lastComp && resolveLink))) {
final String path = constructPath(components, 0, components.length);
final String preceding = constructPath(components, 0, count);
final String remainder =
constructPath(components, count + 1, components.length);
final String link = DFSUtil.bytes2String(components[count]);
final String target = ((INodeSymlink)curNode).getSymlinkString();
if (NameNode.stateChangeLog.isDebugEnabled()) {
NameNode.stateChangeLog.debug("UnresolvedPathException " +
" path: " + path + " preceding: " + preceding +
" count: " + count + " link: " + link + " target: " + target +
" remainder: " + remainder);
}
throw new UnresolvedPathException(path, preceding, remainder, target);
}
if (lastComp || !curNode.isDirectory()) {
break;
}
INodeDirectory parentDir = (INodeDirectory)curNode;
curNode = parentDir.getChildINode(components[count + 1]);
count++;
index++;
}
return existing;
}
/**
* Retrieve the existing INodes along the given path. The first INode
* always exist and is this INode.
*
* @param path the path to explore
* @param resolveLink indicates whether UnresolvedLinkException should
* be thrown when the path refers to a symbolic link.
* @return INodes array containing the existing INodes in the order they
* appear when following the path from the root INode to the
* deepest INodes. The array size will be the number of expected
* components in the path, and non existing components will be
* filled with null
*
* @see #getExistingPathINodes(byte[][], int, boolean)
*/
INodesInPath getExistingPathINodes(String path, boolean resolveLink)
throws UnresolvedLinkException {
byte[][] components = getPathComponents(path);
return getExistingPathINodes(components, components.length, resolveLink);
}
/**
* Given a child's name, return the index of the next child
*
* @param name a child's name
* @return the index of the next child
*/
int nextChild(byte[] name) {
if (name.length == 0) { // empty name
return 0;
}
int nextPos = Collections.binarySearch(children, name) + 1;
if (nextPos >= 0) {
return nextPos;
}
return -nextPos;
}
/**
* Add a child inode to the directory.
*
* @param node INode to insert
* @param setModTime set modification time for the parent node
* not needed when replaying the addition and
* the parent already has the proper mod time
* @return false if the child with this name already exists;
* otherwise, return true;
*/
boolean addChild(final INode node, final boolean setModTime) {
if (children == null) {
children = new ArrayList<INode>(DEFAULT_FILES_PER_DIRECTORY);
}
final int low = searchChildren(node);
if (low >= 0) {
return false;
}
node.parent = this;
children.add(-low - 1, node);
// update modification time of the parent directory
if (setModTime)
setModificationTime(node.getModificationTime());
if (node.getGroupName() == null) {
node.setGroup(getGroupName());
}
return true;
}
/**
* Add new INode to the file tree.
* Find the parent and insert
*
* @param path file path
* @param newNode INode to be added
* @return false if the node already exists; otherwise, return true;
* @throws FileNotFoundException if parent does not exist or
* @throws UnresolvedLinkException if any path component is a symbolic link
* is not a directory.
*/
boolean addINode(String path, INode newNode
) throws FileNotFoundException, PathIsNotDirectoryException,
UnresolvedLinkException {
byte[][] pathComponents = getPathComponents(path);
if (pathComponents.length < 2) { // add root
return false;
}
newNode.setLocalName(pathComponents[pathComponents.length - 1]);
// insert into the parent children list
INodeDirectory parent = getParent(pathComponents);
return parent.addChild(newNode, true);
}
INodeDirectory getParent(byte[][] pathComponents
) throws FileNotFoundException, PathIsNotDirectoryException,
UnresolvedLinkException {
if (pathComponents.length < 2) // add root
return null;
// Gets the parent INode
INodesInPath inodes = getExistingPathINodes(pathComponents, 2, false);
return INodeDirectory.valueOf(inodes.inodes[0], pathComponents);
}
@Override
DirCounts spaceConsumedInTree(DirCounts counts) {
counts.nsCount += 1;
if (children != null) {
for (INode child : children) {
child.spaceConsumedInTree(counts);
}
}
return counts;
}
@Override
long[] computeContentSummary(long[] summary) {
// Walk through the children of this node, using a new summary array
// for the (sub)tree rooted at this node
assert 4 == summary.length;
long[] subtreeSummary = new long[]{0,0,0,0};
if (children != null) {
for (INode child : children) {
child.computeContentSummary(subtreeSummary);
}
}
if (this instanceof INodeDirectoryWithQuota) {
// Warn if the cached and computed diskspace values differ
INodeDirectoryWithQuota node = (INodeDirectoryWithQuota)this;
long space = node.diskspaceConsumed();
assert -1 == node.getDsQuota() || space == subtreeSummary[3];
if (-1 != node.getDsQuota() && space != subtreeSummary[3]) {
NameNode.LOG.warn("Inconsistent diskspace for directory "
+getLocalName()+". Cached: "+space+" Computed: "+subtreeSummary[3]);
}
}
// update the passed summary array with the values for this node's subtree
for (int i = 0; i < summary.length; i++) {
summary[i] += subtreeSummary[i];
}
summary[2]++;
return summary;
}
/**
* @return an empty list if the children list is null;
* otherwise, return the children list.
* The returned list should not be modified.
*/
public List<INode> getChildrenList() {
return children==null ? EMPTY_LIST : children;
}
@Override
int collectSubtreeBlocksAndClear(BlocksMapUpdateInfo info) {
int total = 1;
if (children == null) {
return total;
}
for (INode child : children) {
total += child.collectSubtreeBlocksAndClear(info);
}
parent = null;
return total;
}
/**
* Used by
* {@link INodeDirectory#getExistingPathINodes(byte[][], int, boolean)}.
* Containing INodes information resolved from a given path.
*/
static class INodesInPath {
private INode[] inodes;
public INodesInPath(int number) {
assert (number >= 0);
this.inodes = new INode[number];
}
INode[] getINodes() {
return inodes;
}
void setINode(int i, INode inode) {
inodes[i] = inode;
}
}
/*
* The following code is to dump the tree recursively for testing.
*
* \- foo (INodeDirectory@33dd2717)
* \- sub1 (INodeDirectory@442172)
* +- file1 (INodeFile@78392d4)
* +- file2 (INodeFile@78392d5)
* +- sub11 (INodeDirectory@8400cff)
* \- file3 (INodeFile@78392d6)
* \- z_file4 (INodeFile@45848712)
*/
static final String DUMPTREE_EXCEPT_LAST_ITEM = "+-";
static final String DUMPTREE_LAST_ITEM = "\\-";
@VisibleForTesting
@Override
public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix) {
super.dumpTreeRecursively(out, prefix);
if (prefix.length() >= 2) {
prefix.setLength(prefix.length() - 2);
prefix.append(" ");
}
dumpTreeRecursively(out, prefix, children);
}
/**
* Dump the given subtrees.
* @param prefix The prefix string that each line should print.
* @param subs The subtrees.
*/
@VisibleForTesting
protected static void dumpTreeRecursively(PrintWriter out,
StringBuilder prefix, List<? extends INode> subs) {
prefix.append(DUMPTREE_EXCEPT_LAST_ITEM);
if (subs != null && subs.size() != 0) {
int i = 0;
for(; i < subs.size() - 1; i++) {
subs.get(i).dumpTreeRecursively(out, prefix);
prefix.setLength(prefix.length() - 2);
prefix.append(DUMPTREE_EXCEPT_LAST_ITEM);
}
prefix.setLength(prefix.length() - 2);
prefix.append(DUMPTREE_LAST_ITEM);
subs.get(i).dumpTreeRecursively(out, prefix);
}
prefix.setLength(prefix.length() - 2);
}
void clearChildren() {
if (children != null) {
this.children.clear();
this.children = null;
}
}
}