| /** |
| * 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.PrintWriter; |
| import java.io.StringWriter; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.fs.ContentSummary; |
| import org.apache.hadoop.fs.Path; |
| 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.blockmanagement.BlockInfo; |
| import org.apache.hadoop.hdfs.util.LightWeightGSet.LinkedElement; |
| import org.apache.hadoop.util.StringUtils; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.primitives.SignedBytes; |
| |
| /** |
| * 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. |
| */ |
| @InterfaceAudience.Private |
| abstract class INode implements Comparable<byte[]>, LinkedElement { |
| static final List<INode> EMPTY_LIST = Collections.unmodifiableList(new ArrayList<INode>()); |
| |
| /** Wrapper of two counters for namespace consumed and diskspace consumed. */ |
| static class DirCounts { |
| /** namespace count */ |
| long nsCount = 0; |
| /** diskspace count */ |
| long dsCount = 0; |
| |
| /** returns namespace count */ |
| long getNsCount() { |
| return nsCount; |
| } |
| /** returns diskspace count */ |
| long getDsCount() { |
| return dsCount; |
| } |
| } |
| |
| 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); |
| } |
| |
| /** Encode the {@link PermissionStatus} to a long. */ |
| static long toLong(PermissionStatus ps) { |
| long permission = 0L; |
| final int user = SerialNumberManager.INSTANCE.getUserSerialNumber( |
| ps.getUserName()); |
| permission = USER.combine(user, permission); |
| final int group = SerialNumberManager.INSTANCE.getGroupSerialNumber( |
| ps.getGroupName()); |
| permission = GROUP.combine(group, permission); |
| final int mode = ps.getPermission().toShort(); |
| permission = MODE.combine(mode, permission); |
| return permission; |
| } |
| } |
| |
| /** |
| * The inode id |
| */ |
| final private long id; |
| |
| /** |
| * The inode name is in java UTF8 encoding; |
| * The name in HdfsFileStatus should keep the same encoding as this. |
| * if this encoding is changed, implicitly getFileInfo and listStatus in |
| * clientProtocol are changed; The decoding at the client |
| * side should change accordingly. |
| */ |
| private byte[] name = null; |
| /** |
| * Permission encoded using {@link PermissionStatusFormat}. |
| * Codes other than {@link #clonePermissionStatus(INode)} |
| * and {@link #updatePermissionStatus(PermissionStatusFormat, long)} |
| * should not modify it. |
| */ |
| private long permission = 0L; |
| protected INodeDirectory parent = null; |
| protected long modificationTime = 0L; |
| protected long accessTime = 0L; |
| protected LinkedElement next = null; |
| |
| private INode(long id, byte[] name, long permission, INodeDirectory parent, |
| long modificationTime, long accessTime) { |
| this.id = id; |
| this.name = name; |
| this.permission = permission; |
| this.parent = parent; |
| this.modificationTime = modificationTime; |
| this.accessTime = accessTime; |
| } |
| |
| INode(long id, byte[] name, PermissionStatus permissions, |
| INodeDirectory parent, long modificationTime, long accessTime) { |
| this(id, name, PermissionStatusFormat.toLong(permissions), parent, |
| modificationTime, accessTime); |
| } |
| |
| INode(long id, PermissionStatus permissions, long mtime, long atime) { |
| this(id, null, PermissionStatusFormat.toLong(permissions), null, mtime, atime); |
| } |
| |
| protected INode(long id, String name, PermissionStatus permissions) { |
| this(id, DFSUtil.string2Bytes(name), permissions, null, 0L, 0L); |
| } |
| |
| /** @param other Other node to be copied */ |
| INode(INode other) { |
| this(other.getId(), other.getLocalNameBytes(), other.permission, other |
| .getParent(), other.getModificationTime(), other.getAccessTime()); |
| } |
| |
| /** Get inode id */ |
| public long getId() { |
| return this.id; |
| } |
| |
| /** |
| * Check whether this is the root inode. |
| */ |
| boolean isRoot() { |
| return name.length == 0; |
| } |
| |
| /** Clone the {@link PermissionStatus}. */ |
| void clonePermissionStatus(INode that) { |
| this.permission = that.permission; |
| } |
| /** Get the {@link PermissionStatus} */ |
| protected PermissionStatus getPermissionStatus() { |
| return new PermissionStatus(getUserName(),getGroupName(),getFsPermission()); |
| } |
| private void updatePermissionStatus(PermissionStatusFormat f, long n) { |
| permission = f.combine(n, permission); |
| } |
| /** Get user name */ |
| public 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 */ |
| public 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} */ |
| public 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} */ |
| void setPermission(FsPermission permission) { |
| updatePermissionStatus(PermissionStatusFormat.MODE, permission.toShort()); |
| } |
| |
| /** |
| * Check whether it's a file. |
| */ |
| public boolean isFile() { |
| return false; |
| } |
| |
| /** |
| * Check whether it's a directory |
| */ |
| public boolean isDirectory() { |
| return false; |
| } |
| |
| /** |
| * 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. |
| * |
| * @param info |
| * Containing all the blocks collected from the children of this |
| * INode. These blocks later should be removed from the blocksMap. |
| */ |
| abstract int collectSubtreeBlocksAndClear(BlocksMapUpdateInfo info); |
| |
| /** Compute {@link ContentSummary}. */ |
| public final ContentSummary computeContentSummary() { |
| long[] a = computeContentSummary(new long[]{0,0,0,0}); |
| return new ContentSummary(a[0], a[1], a[2], getNsQuota(), |
| a[3], getDsQuota()); |
| } |
| /** |
| * @return an array of three longs. |
| * 0: length, 1: file count, 2: directory count 3: disk space |
| */ |
| abstract long[] computeContentSummary(long[] summary); |
| |
| /** |
| * Get the quota set for this inode |
| * @return the quota if it is set; -1 otherwise |
| */ |
| long getNsQuota() { |
| return -1; |
| } |
| |
| long getDsQuota() { |
| return -1; |
| } |
| |
| boolean isQuotaSet() { |
| return getNsQuota() >= 0 || getDsQuota() >= 0; |
| } |
| |
| /** |
| * Adds total number of names and total disk space taken under |
| * this tree to counts. |
| * Returns updated counts object. |
| */ |
| abstract DirCounts spaceConsumedInTree(DirCounts counts); |
| |
| /** |
| * @return null if the local name is null; otherwise, return the local name. |
| */ |
| String getLocalName() { |
| return name == null? null: DFSUtil.bytes2String(name); |
| } |
| |
| |
| String getLocalParentDir() { |
| INode inode = isRoot() ? this : getParent(); |
| String parentDir = ""; |
| if (inode != null) { |
| parentDir = inode.getFullPathName(); |
| } |
| return (parentDir != null) ? parentDir : ""; |
| } |
| |
| /** |
| * @return null if the local name is null; |
| * otherwise, return the local name byte array. |
| */ |
| byte[] getLocalNameBytes() { |
| return name; |
| } |
| |
| /** |
| * Set local file name |
| */ |
| void setLocalName(String name) { |
| this.name = DFSUtil.string2Bytes(name); |
| } |
| |
| /** |
| * Set local file name |
| */ |
| void setLocalName(byte[] name) { |
| this.name = name; |
| } |
| |
| public String getFullPathName() { |
| // Get the full path name of this inode. |
| return FSDirectory.getFullPathName(this); |
| } |
| |
| @Override |
| public String toString() { |
| return "\"" + getFullPathName() + "\":" |
| + getUserName() + ":" + getGroupName() + ":" |
| + (isDirectory()? "d": "-") + getFsPermission(); |
| } |
| |
| /** |
| * Get parent directory |
| * @return parent INode |
| */ |
| INodeDirectory getParent() { |
| return this.parent; |
| } |
| |
| /** |
| * Get last modification time of inode. |
| * @return access time |
| */ |
| public long getModificationTime() { |
| return this.modificationTime; |
| } |
| |
| /** |
| * Set last modification time of inode. |
| */ |
| void setModificationTime(long modtime) { |
| assert isDirectory(); |
| if (this.modificationTime <= modtime) { |
| this.modificationTime = modtime; |
| } |
| } |
| |
| /** |
| * Always set the last modification time of inode. |
| */ |
| void setModificationTimeForce(long modtime) { |
| this.modificationTime = modtime; |
| } |
| |
| /** |
| * Get access time of inode. |
| * @return access time |
| */ |
| public long getAccessTime() { |
| return accessTime; |
| } |
| |
| /** |
| * Set last access time of inode. |
| */ |
| void setAccessTime(long atime) { |
| accessTime = atime; |
| } |
| |
| /** |
| * Is this inode being constructed? |
| */ |
| public boolean isUnderConstruction() { |
| return false; |
| } |
| |
| /** |
| * Check whether it's a symlink |
| */ |
| public boolean isSymlink() { |
| 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] = DFSUtil.string2Bytes(strings[i]); |
| return bytes; |
| } |
| |
| /** |
| * Splits an absolute path into an array of path components. |
| * @param path |
| * @throws AssertionError if the given path is invalid. |
| * @return array of path components. |
| */ |
| static String[] getPathNames(String path) { |
| if (path == null || !path.startsWith(Path.SEPARATOR)) { |
| throw new AssertionError("Absolute path required"); |
| } |
| return StringUtils.split(path, Path.SEPARATOR_CHAR); |
| } |
| |
| /** |
| * Given some components, create a path name. |
| * @param components The path components |
| * @param start index |
| * @param end index |
| * @return concatenated path |
| */ |
| static String constructPath(byte[][] components, int start, int end) { |
| StringBuilder buf = new StringBuilder(); |
| for (int i = start; i < end; i++) { |
| buf.append(DFSUtil.bytes2String(components[i])); |
| if (i < end - 1) { |
| buf.append(Path.SEPARATOR); |
| } |
| } |
| return buf.toString(); |
| } |
| |
| boolean removeNode() { |
| if (parent == null) { |
| return false; |
| } else { |
| parent.removeChild(this); |
| parent = null; |
| return true; |
| } |
| } |
| |
| private static final byte[] EMPTY_BYTES = {}; |
| |
| @Override |
| public final int compareTo(byte[] bytes) { |
| final byte[] left = name == null? EMPTY_BYTES: name; |
| final byte[] right = bytes == null? EMPTY_BYTES: bytes; |
| return SignedBytes.lexicographicalComparator().compare(left, right); |
| } |
| |
| @Override |
| public final boolean equals(Object that) { |
| if (this == that) { |
| return true; |
| } |
| if (that == null || !(that instanceof INode)) { |
| return false; |
| } |
| return id == ((INode) that).id; |
| } |
| |
| @Override |
| public final int hashCode() { |
| return (int)(id^(id>>>32)); |
| } |
| |
| /** |
| * Create an INode; the inode's name is not set yet |
| * |
| * @param id preassigned inode id |
| * @param permissions permissions |
| * @param blocks blocks if a file |
| * @param symlink symblic link if a symbolic link |
| * @param replication replication factor |
| * @param modificationTime modification time |
| * @param atime access time |
| * @param nsQuota namespace quota |
| * @param dsQuota disk quota |
| * @param preferredBlockSize block size |
| * @return an inode |
| */ |
| static INode newINode(long id, |
| PermissionStatus permissions, |
| BlockInfo[] blocks, |
| String symlink, |
| short replication, |
| long modificationTime, |
| long atime, |
| long nsQuota, |
| long dsQuota, |
| long preferredBlockSize) { |
| if (symlink.length() != 0) { // check if symbolic link |
| return new INodeSymlink(id, symlink, modificationTime, atime, permissions); |
| } else if (blocks == null) { //not sym link and blocks null? directory! |
| if (nsQuota >= 0 || dsQuota >= 0) { |
| return new INodeDirectoryWithQuota( |
| id, permissions, modificationTime, nsQuota, dsQuota); |
| } |
| // regular directory |
| return new INodeDirectory(id, permissions, modificationTime); |
| } |
| // file |
| return new INodeFile(id, permissions, blocks, replication, |
| modificationTime, atime, preferredBlockSize); |
| } |
| |
| /** |
| * Dump the subtree starting from this inode. |
| * @return a text representation of the tree. |
| */ |
| @VisibleForTesting |
| public StringBuffer dumpTreeRecursively() { |
| final StringWriter out = new StringWriter(); |
| dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder()); |
| return out.getBuffer(); |
| } |
| |
| /** |
| * Dump tree recursively. |
| * @param prefix The prefix string that each line should print. |
| */ |
| @VisibleForTesting |
| public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix) { |
| out.print(prefix); |
| out.print(" "); |
| out.print(getLocalName()); |
| out.print(" ("); |
| final String s = super.toString(); |
| out.print(s.substring(s.lastIndexOf(getClass().getSimpleName()))); |
| out.println(")"); |
| } |
| |
| /** |
| * Information used for updating the blocksMap when deleting files. |
| */ |
| public static class BlocksMapUpdateInfo { |
| /** |
| * The list of blocks that need to be removed from blocksMap |
| */ |
| private List<Block> toDeleteList; |
| |
| public BlocksMapUpdateInfo(List<Block> toDeleteList) { |
| this.toDeleteList = toDeleteList == null ? new ArrayList<Block>() |
| : toDeleteList; |
| } |
| |
| public BlocksMapUpdateInfo() { |
| toDeleteList = new ArrayList<Block>(); |
| } |
| |
| /** |
| * @return The list of blocks that need to be removed from blocksMap |
| */ |
| public List<Block> getToDeleteList() { |
| return toDeleteList; |
| } |
| |
| /** |
| * Add a to-be-deleted block into the |
| * {@link BlocksMapUpdateInfo#toDeleteList} |
| * @param toDelete the to-be-deleted block |
| */ |
| public void addDeleteBlock(Block toDelete) { |
| if (toDelete != null) { |
| toDeleteList.add(toDelete); |
| } |
| } |
| |
| /** |
| * Clear {@link BlocksMapUpdateInfo#toDeleteList} |
| */ |
| public void clear() { |
| toDeleteList.clear(); |
| } |
| } |
| |
| @Override |
| public void setNext(LinkedElement next) { |
| this.next = next; |
| } |
| |
| @Override |
| public LinkedElement getNext() { |
| return next; |
| } |
| } |