| /** |
| * 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.dfs; |
| |
| import java.io.FileNotFoundException; |
| import java.io.UnsupportedEncodingException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.io.IOException; |
| |
| import org.apache.hadoop.fs.Path; |
| import org.apache.hadoop.fs.ContentSummary; |
| import org.apache.hadoop.fs.permission.*; |
| import org.apache.hadoop.dfs.BlocksMap.BlockInfo; |
| |
| /** |
| * We keep an in-memory representation of the file/block hierarchy. |
| * This is a base INode class containing common fields for file and |
| * directory inodes. |
| */ |
| abstract class INode implements Comparable<byte[]> { |
| protected byte[] name; |
| protected INodeDirectory parent; |
| protected long modificationTime; |
| |
| //Only updated by updatePermissionStatus(...). |
| //Other codes should not modify it. |
| private long permission; |
| |
| private static enum PermissionStatusFormat { |
| MODE(0, 16), |
| GROUP(MODE.OFFSET + MODE.LENGTH, 25), |
| USER(GROUP.OFFSET + GROUP.LENGTH, 23); |
| |
| final int OFFSET; |
| final int LENGTH; //bit length |
| final long MASK; |
| |
| PermissionStatusFormat(int offset, int length) { |
| OFFSET = offset; |
| LENGTH = length; |
| MASK = ((-1L) >>> (64 - LENGTH)) << OFFSET; |
| } |
| |
| long retrieve(long record) { |
| return (record & MASK) >>> OFFSET; |
| } |
| |
| long combine(long bits, long record) { |
| return (record & ~MASK) | (bits << OFFSET); |
| } |
| } |
| |
| protected INode() { |
| name = null; |
| parent = null; |
| modificationTime = 0; |
| } |
| |
| INode(PermissionStatus permissions, long mTime) { |
| this.name = null; |
| this.parent = null; |
| this.modificationTime = mTime; |
| setPermissionStatus(permissions); |
| } |
| |
| protected INode(String name, PermissionStatus permissions) { |
| this(permissions, 0L); |
| setLocalName(name); |
| } |
| |
| /** copy constructor |
| * |
| * @param other Other node to be copied |
| */ |
| INode(INode other) { |
| setLocalName(other.getLocalName()); |
| this.parent = other.getParent(); |
| setPermissionStatus(other.getPermissionStatus()); |
| setModificationTime(other.getModificationTime()); |
| } |
| |
| /** |
| * Check whether this is the root inode. |
| */ |
| boolean isRoot() { |
| return name.length == 0; |
| } |
| |
| /** Set the {@link PermissionStatus} */ |
| protected void setPermissionStatus(PermissionStatus ps) { |
| setUser(ps.getUserName()); |
| setGroup(ps.getGroupName()); |
| setPermission(ps.getPermission()); |
| } |
| /** Get the {@link PermissionStatus} */ |
| protected PermissionStatus getPermissionStatus() { |
| return new PermissionStatus(getUserName(),getGroupName(),getFsPermission()); |
| } |
| private synchronized void updatePermissionStatus( |
| PermissionStatusFormat f, long n) { |
| permission = f.combine(n, permission); |
| } |
| /** Get user name */ |
| protected String getUserName() { |
| int n = (int)PermissionStatusFormat.USER.retrieve(permission); |
| return SerialNumberManager.INSTANCE.getUser(n); |
| } |
| /** Set user */ |
| protected void setUser(String user) { |
| int n = SerialNumberManager.INSTANCE.getUserSerialNumber(user); |
| updatePermissionStatus(PermissionStatusFormat.USER, n); |
| } |
| /** Get group name */ |
| protected String getGroupName() { |
| int n = (int)PermissionStatusFormat.GROUP.retrieve(permission); |
| return SerialNumberManager.INSTANCE.getGroup(n); |
| } |
| /** Set group */ |
| protected void setGroup(String group) { |
| int n = SerialNumberManager.INSTANCE.getGroupSerialNumber(group); |
| updatePermissionStatus(PermissionStatusFormat.GROUP, n); |
| } |
| /** Get the {@link FsPermission} */ |
| protected FsPermission getFsPermission() { |
| return new FsPermission( |
| (short)PermissionStatusFormat.MODE.retrieve(permission)); |
| } |
| protected short getFsPermissionShort() { |
| return (short)PermissionStatusFormat.MODE.retrieve(permission); |
| } |
| /** Set the {@link FsPermission} of this {@link INode} */ |
| protected void setPermission(FsPermission permission) { |
| updatePermissionStatus(PermissionStatusFormat.MODE, permission.toShort()); |
| } |
| |
| /** |
| * Check whether it's a directory |
| */ |
| abstract boolean isDirectory(); |
| /** |
| * Collect all the blocks in all children of this INode. |
| * Count and return the number of files in the sub tree. |
| * Also clears references since this INode is deleted. |
| */ |
| abstract int collectSubtreeBlocksAndClear(List<Block> v); |
| |
| /** Compute {@link ContentSummary}. */ |
| final ContentSummary computeContentSummary() { |
| long[] a = computeContentSummary(new long[]{0,0,0}); |
| return new ContentSummary(a[0], a[1], a[2], getQuota()); |
| } |
| /** |
| * @return an array of three longs. |
| * 0: length, 1: file count, 2: directory count |
| */ |
| abstract long[] computeContentSummary(long[] summary); |
| |
| /** |
| * Get the quota set for this inode |
| * @return the quota if it is set; -1 otherwise |
| */ |
| long getQuota() { |
| return -1; |
| } |
| |
| /** |
| * Get the total number of names in the tree |
| * rooted at this inode including the root |
| * @return The total number of names in this tree |
| */ |
| long numItemsInTree() { |
| return 1; |
| } |
| |
| /** |
| * Get local file name |
| * @return local file name |
| */ |
| String getLocalName() { |
| return bytes2String(name); |
| } |
| |
| /** |
| * Get local file name |
| * @return local file name |
| */ |
| byte[] getLocalNameBytes() { |
| return name; |
| } |
| |
| /** |
| * Set local file name |
| */ |
| void setLocalName(String name) { |
| this.name = string2Bytes(name); |
| } |
| |
| /** |
| * Set local file name |
| */ |
| void setLocalName(byte[] name) { |
| this.name = name; |
| } |
| |
| /** {@inheritDoc} */ |
| public String toString() { |
| return "\"" + getLocalName() + "\":" + getPermissionStatus(); |
| } |
| |
| /** |
| * Get parent directory |
| * @return parent INode |
| */ |
| INodeDirectory getParent() { |
| return this.parent; |
| } |
| |
| /** |
| * Get last modification time of inode. |
| * @return access time |
| */ |
| long getModificationTime() { |
| return this.modificationTime; |
| } |
| |
| /** |
| * Set last modification time of inode. |
| */ |
| void setModificationTime(long modtime) { |
| assert isDirectory(); |
| if (this.modificationTime <= modtime) { |
| this.modificationTime = modtime; |
| } |
| } |
| |
| /** |
| * Is this inode being constructed? |
| */ |
| boolean isUnderConstruction() { |
| return false; |
| } |
| |
| /** |
| * Breaks file path into components. |
| * @param path |
| * @return array of byte arrays each of which represents |
| * a single path component. |
| */ |
| static byte[][] getPathComponents(String path) { |
| return getPathComponents(getPathNames(path)); |
| } |
| |
| /** Convert strings to byte arrays for path components. */ |
| static byte[][] getPathComponents(String[] strings) { |
| if (strings.length == 0) { |
| return new byte[][]{null}; |
| } |
| byte[][] bytes = new byte[strings.length][]; |
| for (int i = 0; i < strings.length; i++) |
| bytes[i] = string2Bytes(strings[i]); |
| return bytes; |
| } |
| |
| /** |
| * Breaks file path into names. |
| * @param path |
| * @return array of names |
| */ |
| static String[] getPathNames(String path) { |
| if (path == null || !path.startsWith(Path.SEPARATOR)) { |
| return null; |
| } |
| return path.split(Path.SEPARATOR); |
| } |
| |
| boolean removeNode() { |
| if (parent == null) { |
| return false; |
| } else { |
| |
| parent.removeChild(this); |
| parent = null; |
| return true; |
| } |
| } |
| |
| // |
| // Comparable interface |
| // |
| public int compareTo(byte[] o) { |
| return compareBytes(name, o); |
| } |
| |
| public boolean equals(Object o) { |
| if (!(o instanceof INode)) { |
| return false; |
| } |
| return Arrays.equals(this.name, ((INode)o).name); |
| } |
| |
| public int hashCode() { |
| return Arrays.hashCode(this.name); |
| } |
| |
| // |
| // static methods |
| // |
| /** |
| * Compare two byte arrays. |
| * |
| * @return a negative integer, zero, or a positive integer |
| * as defined by {@link #compareTo(byte[])}. |
| */ |
| static int compareBytes(byte[] a1, byte[] a2) { |
| if (a1==a2) |
| return 0; |
| int len1 = (a1==null ? 0 : a1.length); |
| int len2 = (a2==null ? 0 : a2.length); |
| int n = Math.min(len1, len2); |
| byte b1, b2; |
| for (int i=0; i<n; i++) { |
| b1 = a1[i]; |
| b2 = a2[i]; |
| if (b1 != b2) |
| return b1 - b2; |
| } |
| return len1 - len2; |
| } |
| |
| /** |
| * Converts a byte array to a string using UTF8 encoding. |
| */ |
| static String bytes2String(byte[] bytes) { |
| try { |
| return new String(bytes, "UTF8"); |
| } catch(UnsupportedEncodingException e) { |
| assert false : "UTF8 encoding is not supported "; |
| } |
| return null; |
| } |
| |
| /** |
| * Converts a string to a byte array using UTF8 encoding. |
| */ |
| static byte[] string2Bytes(String str) { |
| try { |
| return str.getBytes("UTF8"); |
| } catch(UnsupportedEncodingException e) { |
| assert false : "UTF8 encoding is not supported "; |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Directory INode class. |
| */ |
| class INodeDirectory extends INode { |
| protected static final int DEFAULT_FILES_PER_DIRECTORY = 5; |
| final static String ROOT_NAME = ""; |
| |
| private List<INode> children; |
| |
| INodeDirectory(String name, PermissionStatus permissions) { |
| super(name, permissions); |
| this.children = null; |
| } |
| |
| INodeDirectory(PermissionStatus permissions, long mTime) { |
| super(permissions, mTime); |
| this.children = null; |
| } |
| |
| /** constructor */ |
| INodeDirectory(byte[] localName, PermissionStatus permissions, long mTime) { |
| this(permissions, mTime); |
| this.name = localName; |
| } |
| |
| /** copy constructor |
| * |
| * @param other |
| */ |
| INodeDirectory(INodeDirectory other) { |
| super(other); |
| this.children = other.getChildren(); |
| } |
| |
| /** |
| * Check whether it's a directory |
| */ |
| boolean isDirectory() { |
| return true; |
| } |
| |
| 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. |
| * |
| * @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 |
| children.set(low, newChild); |
| } else { |
| throw new IllegalArgumentException("No child exists to be replaced"); |
| } |
| } |
| |
| INode getChild(String name) { |
| return getChildINode(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; |
| } |
| |
| /** |
| */ |
| private 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)); |
| } |
| |
| /** |
| * 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 compareBytes(this.name, 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 |
| */ |
| 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; |
| * inserted INode, otherwise |
| */ |
| <T extends INode> T addChild(final T node, boolean inheritPermission) { |
| 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 low = Collections.binarySearch(children, node.name); |
| if(low >= 0) |
| return null; |
| node.parent = this; |
| children.add(-low - 1, node); |
| // update modification time of the parent directory |
| setModificationTime(node.getModificationTime()); |
| if (node.getGroupName() == null) { |
| node.setGroup(getGroupName()); |
| } |
| return node; |
| } |
| |
| /** |
| * 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 { |
| if(addToParent(path, newNode, null, inheritPermission) == 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. |
| */ |
| <T extends INode> INodeDirectory addToParent( |
| String path, |
| T newNode, |
| INodeDirectory parent, |
| boolean inheritPermission |
| ) throws FileNotFoundException { |
| byte[][] pathComponents = getPathComponents(path); |
| assert pathComponents != null : "Incorrect path " + path; |
| int pathLen = pathComponents.length; |
| if (pathLen < 2) // add root |
| return null; |
| if(parent == 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: "+path); |
| } |
| if (!inode.isDirectory()) { |
| throw new FileNotFoundException("Parent path is not a directory: "+path); |
| } |
| parent = (INodeDirectory)inode; |
| } |
| // insert into the parent children list |
| newNode.name = pathComponents[pathLen-1]; |
| if(parent.addChild(newNode, inheritPermission) == null) |
| return null; |
| return parent; |
| } |
| |
| /** |
| */ |
| long numItemsInTree() { |
| long total = 1L; |
| if (children == null) { |
| return total; |
| } |
| for (INode child : children) { |
| total += child.numItemsInTree(); |
| } |
| return total; |
| } |
| |
| /** {@inheritDoc} */ |
| long[] computeContentSummary(long[] summary) { |
| if (children != null) { |
| for (INode child : children) { |
| child.computeContentSummary(summary); |
| } |
| } |
| summary[2]++; |
| return summary; |
| } |
| |
| /** |
| */ |
| List<INode> getChildren() { |
| return children==null ? new ArrayList<INode>() : children; |
| } |
| List<INode> getChildrenRaw() { |
| return children; |
| } |
| |
| int collectSubtreeBlocksAndClear(List<Block> v) { |
| int total = 1; |
| if (children == null) { |
| return total; |
| } |
| for (INode child : children) { |
| total += child.collectSubtreeBlocksAndClear(v); |
| } |
| parent = null; |
| children = null; |
| return total; |
| } |
| } |
| |
| /** |
| * Directory INode class that has a quota restriction |
| */ |
| class INodeDirectoryWithQuota extends INodeDirectory { |
| private long quota; |
| private long count; |
| |
| /** Convert an existing directory inode to one with the given quota |
| * |
| * @param quota Quota to be assigned to this inode |
| * @param other The other inode from which all other properties are copied |
| */ |
| INodeDirectoryWithQuota(long quota, INodeDirectory other) |
| throws QuotaExceededException { |
| super(other); |
| this.count = other.numItemsInTree(); |
| setQuota(quota); |
| } |
| |
| /** constructor with no quota verification */ |
| INodeDirectoryWithQuota( |
| PermissionStatus permissions, long modificationTime, long quota) |
| { |
| super(permissions, modificationTime); |
| this.quota = quota; |
| } |
| |
| /** constructor with no quota verification */ |
| INodeDirectoryWithQuota(String name, PermissionStatus permissions, long quota) |
| { |
| super(name, permissions); |
| this.quota = quota; |
| } |
| |
| /** Get this directory's quota |
| * @return this directory's quota |
| */ |
| long getQuota() { |
| return quota; |
| } |
| |
| /** Set this directory's quota |
| * |
| * @param quota Quota to be set |
| * @throws QuotaExceededException if the given quota is less than |
| * the size of the tree |
| */ |
| void setQuota(long quota) throws QuotaExceededException { |
| verifyQuota(quota, this.count); |
| this.quota = quota; |
| } |
| |
| /** Get the number of names in the subtree rooted at this directory |
| * @return the size of the subtree rooted at this directory |
| */ |
| long numItemsInTree() { |
| return count; |
| } |
| |
| /** Update the size of the tree |
| * |
| * @param delta the change of the tree size |
| * @throws QuotaExceededException if the changed size is greater |
| * than the quota |
| */ |
| void updateNumItemsInTree(long delta) throws QuotaExceededException { |
| long newCount = this.count + delta; |
| if (delta>0) { |
| verifyQuota(this.quota, newCount); |
| } |
| this.count = newCount; |
| } |
| |
| /** Set the size of the tree rooted at this directory |
| * |
| * @param count size of the directory to be set |
| * @throws QuotaExceededException if the given count is greater than quota |
| */ |
| void setCount(long count) throws QuotaExceededException { |
| verifyQuota(this.quota, count); |
| this.count = count; |
| } |
| |
| /** Verify if the count satisfies the quota restriction |
| * @throws QuotaExceededException if the given quota is less than the count |
| */ |
| private static void verifyQuota(long quota, long count) |
| throws QuotaExceededException { |
| if (quota < count) { |
| throw new QuotaExceededException(quota, count); |
| } |
| } |
| } |
| |
| class INodeFile extends INode { |
| static final FsPermission UMASK = FsPermission.createImmutable((short)0111); |
| |
| protected BlockInfo blocks[] = null; |
| protected short blockReplication; |
| protected long preferredBlockSize; |
| |
| INodeFile(PermissionStatus permissions, |
| int nrBlocks, short replication, long modificationTime, |
| long preferredBlockSize) { |
| this(permissions, new BlockInfo[nrBlocks], replication, |
| modificationTime, preferredBlockSize); |
| } |
| |
| protected INodeFile() { |
| blocks = null; |
| blockReplication = 0; |
| preferredBlockSize = 0; |
| } |
| |
| protected INodeFile(PermissionStatus permissions, BlockInfo[] blklist, |
| short replication, long modificationTime, |
| long preferredBlockSize) { |
| super(permissions, modificationTime); |
| this.blockReplication = replication; |
| this.preferredBlockSize = preferredBlockSize; |
| blocks = blklist; |
| } |
| |
| /** |
| * Set the {@link FsPermission} of this {@link INodeFile}. |
| * Since this is a file, |
| * the {@link FsAction#EXECUTE} action, if any, is ignored. |
| */ |
| protected void setPermission(FsPermission permission) { |
| super.setPermission(permission.applyUMask(UMASK)); |
| } |
| |
| boolean isDirectory() { |
| return false; |
| } |
| |
| /** |
| * Get block replication for the file |
| * @return block replication |
| */ |
| short getReplication() { |
| return this.blockReplication; |
| } |
| |
| void setReplication(short replication) { |
| this.blockReplication = replication; |
| } |
| |
| /** |
| * Get file blocks |
| * @return file blocks |
| */ |
| BlockInfo[] getBlocks() { |
| return this.blocks; |
| } |
| |
| /** |
| * add a block to the block list |
| */ |
| void addBlock(BlockInfo newblock) { |
| if (this.blocks == null) { |
| this.blocks = new BlockInfo[1]; |
| this.blocks[0] = newblock; |
| } else { |
| int size = this.blocks.length; |
| BlockInfo[] newlist = new BlockInfo[size + 1]; |
| for (int i = 0; i < size; i++) { |
| newlist[i] = this.blocks[i]; |
| } |
| newlist[size] = newblock; |
| this.blocks = newlist; |
| } |
| } |
| |
| /** |
| * Set file block |
| */ |
| void setBlock(int idx, BlockInfo blk) { |
| this.blocks[idx] = blk; |
| } |
| |
| int collectSubtreeBlocksAndClear(List<Block> v) { |
| parent = null; |
| for (Block blk : blocks) { |
| v.add(blk); |
| } |
| blocks = null; |
| return 1; |
| } |
| |
| /** {@inheritDoc} */ |
| long[] computeContentSummary(long[] summary) { |
| long bytes = 0; |
| for(Block blk : blocks) { |
| bytes += blk.getNumBytes(); |
| } |
| summary[0] += bytes; |
| summary[1]++; |
| return summary; |
| } |
| |
| /** |
| * Get the preferred block size of the file. |
| * @return the number of bytes |
| */ |
| long getPreferredBlockSize() { |
| return preferredBlockSize; |
| } |
| |
| /** |
| * Return the penultimate allocated block for this file. |
| */ |
| Block getPenultimateBlock() { |
| if (blocks == null || blocks.length <= 1) { |
| return null; |
| } |
| return blocks[blocks.length - 2]; |
| } |
| |
| INodeFileUnderConstruction toINodeFileUnderConstruction( |
| String clientName, String clientMachine, DatanodeDescriptor clientNode |
| ) throws IOException { |
| if (isUnderConstruction()) { |
| return (INodeFileUnderConstruction)this; |
| } |
| return new INodeFileUnderConstruction(name, |
| blockReplication, modificationTime, preferredBlockSize, |
| blocks, getPermissionStatus(), |
| clientName, clientMachine, clientNode); |
| } |
| } |
| |
| class INodeFileUnderConstruction extends INodeFile { |
| StringBytesWritable clientName = null; // lease holder |
| StringBytesWritable clientMachine = null; |
| DatanodeDescriptor clientNode = null; // if client is a cluster node too. |
| |
| private int primaryNodeIndex = -1; //the node working on lease recovery |
| private DatanodeDescriptor[] targets = null; //locations for last block |
| private long lastRecoveryTime = 0; |
| |
| INodeFileUnderConstruction() {} |
| |
| INodeFileUnderConstruction(PermissionStatus permissions, |
| short replication, |
| long preferredBlockSize, |
| long modTime, |
| String clientName, |
| String clientMachine, |
| DatanodeDescriptor clientNode) |
| throws IOException { |
| super(permissions.applyUMask(UMASK), 0, replication, modTime, |
| preferredBlockSize); |
| this.clientName = new StringBytesWritable(clientName); |
| this.clientMachine = new StringBytesWritable(clientMachine); |
| this.clientNode = clientNode; |
| } |
| |
| INodeFileUnderConstruction(byte[] name, |
| short blockReplication, |
| long modificationTime, |
| long preferredBlockSize, |
| BlockInfo[] blocks, |
| PermissionStatus perm, |
| String clientName, |
| String clientMachine, |
| DatanodeDescriptor clientNode) |
| throws IOException { |
| super(perm, blocks, blockReplication, modificationTime, |
| preferredBlockSize); |
| setLocalName(name); |
| this.clientName = new StringBytesWritable(clientName); |
| this.clientMachine = new StringBytesWritable(clientMachine); |
| this.clientNode = clientNode; |
| } |
| |
| String getClientName() throws IOException { |
| return clientName.getString(); |
| } |
| |
| String getClientMachine() throws IOException { |
| return clientMachine.getString(); |
| } |
| |
| DatanodeDescriptor getClientNode() { |
| return clientNode; |
| } |
| |
| /** |
| * Is this inode being constructed? |
| */ |
| @Override |
| boolean isUnderConstruction() { |
| return true; |
| } |
| |
| DatanodeDescriptor[] getTargets() { |
| return targets; |
| } |
| |
| void setTargets(DatanodeDescriptor[] targets) { |
| this.targets = targets; |
| this.primaryNodeIndex = -1; |
| } |
| |
| // |
| // converts a INodeFileUnderConstruction into a INodeFile |
| // |
| INodeFile convertToInodeFile() { |
| INodeFile obj = new INodeFile(getPermissionStatus(), |
| getBlocks(), |
| getReplication(), |
| getModificationTime(), |
| getPreferredBlockSize()); |
| return obj; |
| |
| } |
| |
| /** |
| * remove a block from the block list. This block should be |
| * the last one on the list. |
| */ |
| void removeBlock(Block oldblock) throws IOException { |
| if (blocks == null) { |
| throw new IOException("Trying to delete non-existant block " + oldblock); |
| } |
| int size_1 = blocks.length - 1; |
| if (!blocks[size_1].equals(oldblock)) { |
| throw new IOException("Trying to delete non-last block " + oldblock); |
| } |
| |
| //copy to a new list |
| BlockInfo[] newlist = new BlockInfo[size_1]; |
| System.arraycopy(blocks, 0, newlist, 0, size_1); |
| blocks = newlist; |
| |
| // Remove the block locations for the last block. |
| targets = null; |
| } |
| |
| synchronized void setLastBlock(BlockInfo newblock, DatanodeDescriptor[] newtargets |
| ) throws IOException { |
| if (blocks == null) { |
| throw new IOException("Trying to update non-existant block (newblock=" |
| + newblock + ")"); |
| } |
| blocks[blocks.length - 1] = newblock; |
| setTargets(newtargets); |
| lastRecoveryTime = 0; |
| } |
| |
| /** |
| * Initialize lease recovery for this object |
| */ |
| void assignPrimaryDatanode() { |
| //assign the first alive datanode as the primary datanode |
| |
| if (targets.length == 0) { |
| NameNode.stateChangeLog.warn("BLOCK*" |
| + " INodeFileUnderConstruction.initLeaseRecovery:" |
| + " No blocks found, lease removed."); |
| } |
| |
| int previous = primaryNodeIndex; |
| //find an alive datanode beginning from previous |
| for(int i = 1; i <= targets.length; i++) { |
| int j = (previous + i)%targets.length; |
| if (targets[j].isAlive) { |
| DatanodeDescriptor primary = targets[primaryNodeIndex = j]; |
| primary.addBlockToBeRecovered(blocks[blocks.length - 1], targets); |
| NameNode.stateChangeLog.info("BLOCK* " + blocks[blocks.length - 1] |
| + " recovery started, primary=" + primary); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Update lastRecoveryTime if expired. |
| * @return true if lastRecoveryTimeis updated. |
| */ |
| synchronized boolean setLastRecoveryTime(long now) { |
| boolean expired = now - lastRecoveryTime > NameNode.LEASE_RECOVER_PERIOD; |
| if (expired) { |
| lastRecoveryTime = now; |
| } |
| return expired; |
| } |
| } |