/** * 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.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.hadoop.fs.permission.FsAction; import org.apache.hadoop.fs.permission.FsPermission; 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.server.namenode.BlocksMap.BlockInfo; /** * Directory INode class. */ class INodeDirectory extends INode { protected static final int DEFAULT_FILES_PER_DIRECTORY = 5; protected static final int UNKNOWN_INDEX = -1; final static String ROOT_NAME = ""; private List<INode> children; INodeDirectory(long id, String name, PermissionStatus permissions) { super(id, name, permissions); this.children = null; } public INodeDirectory(long id, PermissionStatus permissions, long mTime) { super(id, permissions, mTime, 0); this.children = null; } /** constructor */ INodeDirectory(long id, byte[] localName, PermissionStatus permissions, long mTime) { this(id, permissions, mTime); this.name = localName; } /** copy constructor * * @param other */ INodeDirectory(INodeDirectory other) { super(other); this.children = other.getChildren(); } /** * Check whether it's a directory */ @Override public boolean isDirectory() { return true; } public void setChildrenCapacity(int size){ this.children = new ArrayList<INode>(size); } INode removeChild(INode node) { assert children != null; int low = Collections.binarySearch(children, node.name); if (low >= 0) { return children.remove(low); } else { return null; } } /** * Replace a child that has the same name as newChild by newChild. This is only working on one * child case * * @param newChild Child node to be added */ void replaceChild(INode newChild) { if ( children == null ) { throw new IllegalArgumentException("The directory is empty"); } int low = Collections.binarySearch(children, newChild.name); if (low>=0) { // an old child exists so replace by the newChild INode oldChild = children.get(low); // Need to make sure we are replacing the oldChild with newChild that is the same as oldChild // which means they reference to the same children or they are null or empty array children.set(low, newChild); // newChild should point to current instance (parent of oldChild) newChild.parent = this; // if both are directory, all the children from oldChild should point to newChild if (newChild.isDirectory() && oldChild.isDirectory()) { if (((INodeDirectory)oldChild).getChildren() != null) { for (INode oldGrandChild : ((INodeDirectory)oldChild).getChildren()) { oldGrandChild.parent = (INodeDirectory)newChild; } } } } else { throw new IllegalArgumentException("No child exists to be replaced"); } } INode getChild(String name) { return getChildINode(DFSUtil.string2Bytes(name)); } INode getChildINode(byte[] name) { if (children == null) { return null; } int low = Collections.binarySearch(children, name); if (low >= 0) { return children.get(low); } return null; } /** */ INode getNode(byte[][] components) { INode[] inode = new INode[1]; getExistingPathINodes(components, inode); return inode[0]; } /** * This is the external interface */ INode getNode(String path) { return getNode(getPathComponents(path)); } INode getNode(byte[] path) { return getNode(getPathComponents(path)); } /** * 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 target INode. * * <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 existing INode array to fill with existing INodes * @return number of existing INodes in the path */ int getExistingPathINodes(byte[][] components, INode[] existing) { assert compareTo(components[0]) == 0 : "Incorrect name " + getLocalName() + " expected " + 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)) { if (index >= 0) existing[index] = curNode; if (!curNode.isDirectory() || (count == components.length - 1)) break; // no more child, stop here INodeDirectory parentDir = (INodeDirectory)curNode; curNode = parentDir.getChildINode(components[count + 1]); count += 1; index += 1; } 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 * @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[][], INode[]) */ public INode[] getExistingPathINodes(String path) { byte[][] components = getPathComponents(path); INode[] inodes = new INode[components.length]; this.getExistingPathINodes(components, inodes); return inodes; } /** * Add a child inode to the directory. * * @param node INode to insert * @param inheritPermission inherit permission from parent? * @return null if the child with this name already exists; * node, otherwise */ <T extends INode> T addChild(final T node, boolean inheritPermission) { return addChild(node, inheritPermission, true, UNKNOWN_INDEX); } /** * Add a child inode to the directory. * * @param node INode to insert * @param inheritPermission inherit permission from parent? * @param propagateModTime set parent's mod time to that of a child? * @param childIndex index of the inserted child if known * @return null if the child with this name already exists; * node, otherwise */ <T extends INode> T addChild(final T node, boolean inheritPermission, boolean propagateModTime, int childIndex) { if (inheritPermission) { FsPermission p = getFsPermission(); //make sure the permission has wx for the user if (!p.getUserAction().implies(FsAction.WRITE_EXECUTE)) { p = new FsPermission(p.getUserAction().or(FsAction.WRITE_EXECUTE), p.getGroupAction(), p.getOtherAction()); } node.setPermission(p); } if (children == null) { children = new ArrayList<INode>(DEFAULT_FILES_PER_DIRECTORY); } int index; if (childIndex >= 0) { index = childIndex; } else { int low = Collections.binarySearch(children, node.name); if(low >= 0) return null; index = -low - 1; } node.parent = this; children.add(index, node); if (propagateModTime) { // update modification time of the parent directory setModificationTime(node.getModificationTime()); } if (childIndex < 0) { // if child Index is provided (>=0), this is a result of // loading the image, and the group name is set, no need // to check if (node.getGroupName() == null) { node.setGroup(getGroupName()); } } return node; } /** * Search all children for the first child whose name is greater than * the given name. * * If the given name is one of children's name, the next child's index * is returned; Otherwise, return the insertion point: the index of the * first child whose name's greater than the given name. * * @param name a 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) { // the name is in the list of children return nextPos; } return -nextPos; // insert point } /** * Equivalent to addNode(path, newNode, false). * @see #addNode(String, INode, boolean) */ <T extends INode> T addNode(String path, T newNode) throws FileNotFoundException { return addNode(path, newNode, false); } /** * Add new INode to the file tree. * Find the parent and insert * * @param path file path * @param newNode INode to be added * @param inheritPermission If true, copy the parent's permission to newNode. * @return null if the node already exists; inserted INode, otherwise * @throws FileNotFoundException if parent does not exist or * is not a directory. */ <T extends INode> T addNode(String path, T newNode, boolean inheritPermission ) throws FileNotFoundException { byte[][] pathComponents = getPathComponents(path); if(addToParent(pathComponents, newNode, inheritPermission, true) == null) return null; return 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 inheritPermission, boolean propagateModTime, int childIndex ) throws FileNotFoundException { // insert into the parent children list newNode.name = localname; if(parent.addChild(newNode, inheritPermission, propagateModTime, childIndex) == null) return null; return parent; } INodeDirectory getParent(byte[][] pathComponents) throws FileNotFoundException { int pathLen = pathComponents.length; if (pathLen < 2) // add root return null; // Gets the parent INode INode[] inodes = new INode[2]; getExistingPathINodes(pathComponents, inodes); 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; } <T extends INode> INodeDirectory addToParent( byte[][] pathComponents, T newNode, boolean inheritPermission, boolean propagateModTime ) throws FileNotFoundException { int pathLen = pathComponents.length; if (pathLen < 2) // add root return null; newNode.name = pathComponents[pathLen-1]; // insert into the parent children list INodeDirectory parent = getParent(pathComponents); if(parent.addChild(newNode, inheritPermission, propagateModTime, UNKNOWN_INDEX) == null) return null; return parent; } /** {@inheritDoc} */ @Override DirCounts spaceConsumedInTree(DirCounts counts) { Set<Long> visitedCtx = new HashSet<Long>(); return spaceConsumedInTree(counts, visitedCtx); } DirCounts spaceConsumedInTree(DirCounts counts, Set<Long> visitedCtx) { counts.nsCount += 1; if (children != null) { for (INode child : children) { if (child.isDirectory()) { // Process the directory with the visited hard link context ((INodeDirectory) child).spaceConsumedInTree(counts, visitedCtx); } else { // Process the file if (child instanceof INodeHardLinkFile) { // Get the current hard link ID long hardLinkID = ((INodeHardLinkFile) child).getHardLinkID(); if (visitedCtx.contains(hardLinkID)) { // The current hard link file has been visited, so skip processing // But update the nsCount counts.nsCount++; continue; } else { // Add the current hard link file to the visited set visitedCtx.add(hardLinkID); } } // compute the current child child.spaceConsumedInTree(counts); } } } return counts; } /** {@inheritDoc} */ @Override long[] computeContentSummary(long[] summary) { Set<Long> visitedCtx = new HashSet<Long>(); return this.computeContentSummary(summary, visitedCtx); } /** * Compute the content summary and skip calculating the visited hard link file. */ private long[] computeContentSummary(long[] summary, Set<Long> visitedCtx) { if (children != null) { for (INode child : children) { if (child.isDirectory()) { // Process the directory with the visited hard link context ((INodeDirectory)child).computeContentSummary(summary, visitedCtx); } else { // Process the file if (child instanceof INodeHardLinkFile) { // Get the current hard link ID long hardLinkID = ((INodeHardLinkFile) child).getHardLinkID(); if (visitedCtx.contains(hardLinkID)) { // The current hard link file has been visited, so only increase the file count. summary[1] ++; continue; } else { // Add the current hard link file to the visited set visitedCtx.add(hardLinkID); // Compute the current hardlink file child.computeContentSummary(summary); } } else { // compute the current child for non hard linked files child.computeContentSummary(summary); } } } } summary[2]++; return summary; } /** */ List<INode> getChildren() { return children==null ? new ArrayList<INode>() : children; } List<INode> getChildrenRaw() { return children; } private static boolean isBlocksLimitReached(List<BlockInfo> v, int blocksLimit) { return blocksLimit != FSDirectory.BLOCK_DELETION_NO_LIMIT && blocksLimit <= v.size(); } @Override int collectSubtreeBlocksAndClear(List<BlockInfo> v, int blocksLimit, List<INode> removedINodes) { if (isBlocksLimitReached(v, blocksLimit)) { return 0; } int total = 0; if (children == null) { parent = null; name = null; removedINodes.add(this); return ++total; } int i; for (i=0; i<children.size(); i++) { INode child = children.get(i); total += child.collectSubtreeBlocksAndClear(v, blocksLimit, removedINodes); if (isBlocksLimitReached(v, blocksLimit)) { // reached blocks limit if (child.parent != null) { i--; // this child has not finished yet } break; } } if (i<children.size()-1) { // partial children are processed // Remove children [0,i] children = children.subList(i+1, children.size()); return total; } // all the children are processed parent = null; name = null; children = null; removedINodes.add(this); return ++total; } /** * Numbers of all blocks, files and directories under this directory. * Current directory will be counted as well. */ public static class ItemCounts { int numBlocks; int numDirectories; int numFiles; long startTime; // time stamp when counting started long finishTime; // time stamp when counting finished } private ItemCounts itemCounts = null; /** * Get item counts of the current directory. Need to do countItems() first * if you need updated item counts. * @return numbers of blocks, files and directories */ public ItemCounts getItemCounts() { return itemCounts; } /** * Count items under the current directory */ public void countItems() { itemCounts = new ItemCounts(); itemCounts.startTime = System.currentTimeMillis(); itemCounts.numDirectories = 1; // count the current directory itemCounts.numFiles = 0; itemCounts.numBlocks = 0; if (children != null) { for (INode child : children) { countItemsRecursively(child); } } itemCounts.finishTime = System.currentTimeMillis(); } private void countItemsRecursively(INode curr) { if (curr == null) { return; } itemCounts.numDirectories++; if (curr instanceof INodeDirectory) { itemCounts.numDirectories++; if (((INodeDirectory) curr).children != null) { for (INode child : ((INodeDirectory) curr).children) { countItemsRecursively(child); } } } else { itemCounts.numFiles++; if (((INodeFile) curr).getBlocks() != null) { itemCounts.numBlocks += ((INodeFile) curr).getBlocks().length; } } } }