/**
* 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 io.hops.common.IDsGeneratorFactory;
import io.hops.exception.StorageException;
import io.hops.exception.TransactionContextException;
import io.hops.metadata.hdfs.entity.INodeIdentifier;
import io.hops.metadata.hdfs.entity.MetadataLogEntry;
import io.hops.transaction.EntityManager;
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.Block;
import org.apache.hadoop.hdfs.protocol.UnresolvedPathException;
import org.tukaani.xz.UnsupportedOptionsException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
/**
* Directory INode class.
*/
public class INodeDirectory extends INode {
/**
* Cast INode to INodeDirectory.
*/
public static INodeDirectory valueOf(INode inode, String path)
throws IOException {
if (inode == null) {
throw new IOException("Directory does not exist: " + path);
}
if (!inode.isDirectory()) {
throw new IOException("Path is not a directory: " + path);
}
return (INodeDirectory) inode;
}
protected static final int DEFAULT_FILES_PER_DIRECTORY = 5;
public final static String ROOT_NAME = "";
public final static int ROOT_ID = 1;
public final static int ROOT_PARENT_ID = NON_EXISTING_ID;
public static final int ROOT_DIR_PARTITION_KEY = ROOT_PARENT_ID;
public static final short ROOT_DIR_DEPTH =0;
private boolean metaEnabled;
public INodeDirectory(String name, PermissionStatus permissions)
throws IOException {
super(name, permissions);
}
public INodeDirectory(PermissionStatus permissions, long mTime)
throws IOException {
super(permissions, mTime, 0);
}
/**
* constructor
*/
INodeDirectory(byte[] localName, PermissionStatus permissions, long mTime)
throws IOException {
this(permissions, mTime);
this.setLocalNameNoPersistance(localName);
}
/**
* copy constructor
*
* @param other
*/
INodeDirectory(INodeDirectory other)
throws IOException {
super(other);
//HOP: FIXME: Mahmoud: the new directory has the same id as the "other"
// directory so we don't need to notify the children of the directory change
}
/**
* @return true unconditionally.
*/
@Override
public final boolean isDirectory() {
return true;
}
public boolean isMetaEnabled() {
return metaEnabled;
}
public void setMetaEnabled(boolean metaEnabled) {
this.metaEnabled = metaEnabled;
}
INode removeChild(INode node)
throws StorageException, TransactionContextException {
INode existingInode = getChildINode(node.getLocalNameBytes());
if (existingInode != null) {
remove(existingInode);
return existingInode;
}
return null;
}
/**
* Replace a child that has the same name as newChild by newChild.
*
* @param newChild
* Child node to be added
*/
void replaceChild(INode newChild)
throws StorageException, TransactionContextException {
//HOP: Mahmoud: equals based on the inode name
INode existingINode = getChildINode(newChild.getLocalNameBytes());
if (existingINode == null) {
throw new IllegalArgumentException("No child exists to be replaced");
} else {
//[M] make sure that the newChild has the same parentid
if (existingINode.getParentId() != newChild.getParentId()) {
throw new IllegalArgumentException("Invalid parentid");
}
short depth = myDepth();
int childPartitionKey = INode.calculatePartitionId(getId(), newChild.getLocalName(), (short) (myDepth()+1));
newChild.setPartitionId(childPartitionKey);
EntityManager.update(newChild);
}
}
INode getChild(String name)
throws StorageException, TransactionContextException {
return getChildINode(DFSUtil.string2Bytes(name));
}
private INode getChildINode(byte[] name)
throws StorageException, TransactionContextException {
short myDepth = myDepth();
int childPartitionId = INode.calculatePartitionId(getId(), DFSUtil.bytes2String(name), (short)(myDepth+1));
INode existingInode = EntityManager
.find(Finder.ByNameParentIdAndPartitionId, DFSUtil.bytes2String(name),
getId(), childPartitionId);
if (existingInode != null && existingInode.exists()) {
return existingInode;
}
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, StorageException,
TransactionContextException {
INode[] inode = new INode[1];
getExistingPathINodes(components, inode, resolveLink);
return inode[0];
}
/**
* This is the external interface
*/
INode getNode(String path, boolean resolveLink)
throws UnresolvedLinkException, StorageException,
TransactionContextException {
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.
* <p/>
* 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/>
* <p/>
* Example: <br>
* Given the path /c1/c2/c3 where only /c1/c2 exists, resulting in the
* following path components: ["","c1","c2","c3"],
* <p/>
* <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/>
* <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/>
* <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 existing
* array to fill with existing INodes
* @param resolveLink
* indicates whether UnresolvedLinkException should
* be thrown when the path refers to a symbolic link.
* @return number of existing INodes in the path
*/
int getExistingPathINodes(byte[][] components, INode[] existing,
boolean resolveLink) throws UnresolvedLinkException, StorageException,
TransactionContextException {
assert
this.compareTo(components[0]) == 0 :
"Incorrect name " + getLocalName() + " expected " +
(components[0] == null ? null :
DFSUtil.bytes2String(components[0]));
INode curNode = this;
int count = 0;
int index = existing.length - components.length;
if (index > 0) {
index = 0;
}
while (count < components.length && curNode != null) {
final boolean lastComp = (count == components.length - 1);
if (index >= 0) {
existing[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).getLinkValue();
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);
}
count++;
index++;
if (lastComp || !curNode.isDirectory()) {
break;
}
INodeDirectory parentDir = (INodeDirectory) curNode;
curNode = parentDir.getChildINode(components[count]);
}
return count;
}
/**
* 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
*/
INode[] getExistingPathINodes(String path, boolean resolveLink)
throws UnresolvedLinkException, StorageException,
TransactionContextException {
byte[][] components = getPathComponents(path);
INode[] inodes = new INode[components.length];
this.getExistingPathINodes(components, inodes, resolveLink);
return inodes;
}
/**
* 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)
throws StorageException, TransactionContextException {
if (name.length == 0) { // empty name
return 0;
}
int nextPos = Collections.binarySearch(getChildrenList(), 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 null if the child with this name already exists;
* node, otherwise
*/
<T extends INode> T addChild(final T node, boolean setModTime)
throws IOException {
INode existingInode = getChildINode(node.name);
if (existingInode != null) {
return null;
}
if (!node.exists()) {
Integer inodeID = IDsGeneratorFactory.getInstance().getUniqueINodeID();
node.setIdNoPersistance(inodeID);
node.setParentNoPersistance(this);
short childDepth = (short)(myDepth()+1);
node.setPartitionIdNoPersistance(INode.calculatePartitionId(node.getParentId(), node.getLocalName(), childDepth));
EntityManager.add(node);
//add the INodeAttributes if it is Directory with Quota
// if (this instanceof INodeDirectoryWithQuota) { // [S] I think this is not necessary now. Quota update manager will take care of this
// ((INodeDirectoryWithQuota) this).persistAttributes();
// }
} else {
node.setParent(this);
}
// update modification time of the parent directory
if (setModTime) {
setModificationTime(node.getModificationTime());
}
if (node.getGroupName() == null) {
node.setGroup(getGroupName());
}
node.logMetadataEvent(MetadataLogEntry.Operation.ADD);
return node;
}
/**
* Add new INode to the file tree.
* Find the parent and insert
*
* @param path
* file path
* @param newNode
* INode to be added
* @return null if the node already exists; inserted INode, otherwise
* @throws FileNotFoundException
* if parent does not exist or
* @throws UnresolvedLinkException
* if any path component is a symbolic link
* is not a directory.
*/
<T extends INode> T addNode(String path, T newNode)
throws IOException {
byte[][] pathComponents = getPathComponents(path);
return addToParent(pathComponents, newNode, true) == null ? null : newNode;
}
/**
* Add new inode to the parent if specified.
* Optimized version of addNode() if parent is not null.
*
* @return parent INode if new inode is inserted
* or null if it already exists.
* @throws FileNotFoundException
* if parent does not exist or
* is not a directory.
*/
INodeDirectory addToParent(byte[] localname, INode newNode,
INodeDirectory parent, boolean propagateModTime)
throws IOException {
// insert into the parent children list
newNode.setLocalNameNoPersistance(localname);
if (parent.addChild(newNode, propagateModTime) == null) {
return null;
}
return parent;
}
INodeDirectory getParent(byte[][] pathComponents)
throws FileNotFoundException, UnresolvedLinkException, StorageException,
TransactionContextException {
if (pathComponents.length < 2) // add root
{
return null;
}
// Gets the parent INode
INode[] inodes = new INode[2];
getExistingPathINodes(pathComponents, inodes, false);
INode inode = inodes[0];
if (inode == null) {
throw new FileNotFoundException("Parent path does not exist: " +
DFSUtil.byteArray2String(pathComponents));
}
if (!inode.isDirectory()) {
throw new FileNotFoundException("Parent path is not a directory: " +
DFSUtil.byteArray2String(pathComponents));
}
return (INodeDirectory) inode;
}
/**
* Add new inode
* Optimized version of addNode()
*
* @return parent INode if new inode is inserted
* or null if it already exists.
* @throws FileNotFoundException
* if parent does not exist or
* is not a directory.
*/
INodeDirectory addToParent(byte[][] pathComponents, INode newNode,
boolean propagateModTime)
throws IOException {
if (pathComponents.length < 2) { // add root
return null;
}
newNode
.setLocalNameNoPersistance(pathComponents[pathComponents.length - 1]);
// insert into the parent children list
INodeDirectory parent = getParent(pathComponents);
return parent.addChild(newNode, propagateModTime) == null ? null : parent;
}
@Override
DirCounts spaceConsumedInTree(DirCounts counts)
throws StorageException, TransactionContextException {
counts.nsCount += 1;
if (getId() != INode.NON_EXISTING_ID) {
List<INode> children = getChildren();
if (children != null) {
for (INode child : children) {
child.spaceConsumedInTree(counts);
}
}
}
return counts;
}
@Override
long[] computeContentSummary(long[] summary)
throws StorageException, TransactionContextException {
// 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};
List<INode> children = getChildren();
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();
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()
throws StorageException, TransactionContextException {
List<INode> children = getChildren();
return children == null ? EMPTY_LIST : children;
}
/**
* @return the children list which is possibly null.
*/
public List<INode> getChildren()
throws StorageException, TransactionContextException {
if (getId() == INode.NON_EXISTING_ID) {
return null;
}
short childrenDepth = ((short)(myDepth()+1));
if(INode.isTreeLevelRandomPartitioned(childrenDepth)){
return (List<INode>) EntityManager
.findList(INode.Finder.ByParentIdFTIS, getId());
}else{
return (List<INode>) EntityManager
.findList(Finder.ByParentIdAndPartitionId, getId(), getId()/*partition id for all the childred is the parent id*/);
}
}
@Override
int collectSubtreeBlocksAndClear(List<Block> v)
throws StorageException, TransactionContextException {
int total = 1;
List<INode> children = getChildren();
if (children == null) {
return total;
}
for (INode child : children) {
total += child.collectSubtreeBlocksAndClear(v);
}
parent = null;
for (INode child : children) {
remove(child);
}
remove(this);
return total;
}
public static int getRootDirPartitionKey(){
return INode.calculatePartitionId(ROOT_PARENT_ID,ROOT_NAME,ROOT_DIR_DEPTH);
}
public static INodeIdentifier getRootIdentifier(){
INodeIdentifier rootINodeIdentifier = new INodeIdentifier(INodeDirectory.ROOT_ID,INodeDirectory.ROOT_PARENT_ID, INodeDirectory.ROOT_NAME,
INodeDirectory.getRootDirPartitionKey());
rootINodeIdentifier.setDepth(INodeDirectory.ROOT_DIR_DEPTH);
return rootINodeIdentifier;
}
}