/** * 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.util.Arrays; import java.util.List; 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.util.StringUtils; /** * 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. */ public abstract class INode implements Comparable<byte[]>, FSInodeInfo { /* * 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. */ protected byte[] name; protected INodeDirectory parent; protected long modificationTime; protected long accessTime; /** Simple wrapper for two counters : * nsCount (namespace consumed) and dsCount (diskspace consumed). */ static class DirCounts { long nsCount = 0; long dsCount = 0; /** returns namespace count */ long getNsCount() { return nsCount; } /** returns diskspace count */ long getDsCount() { return dsCount; } } //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; accessTime = 0; } INode(PermissionStatus permissions, long mTime, long atime) { this.name = null; this.parent = null; this.modificationTime = mTime; setAccessTime(atime); setPermissionStatus(permissions); } protected INode(String name, PermissionStatus permissions) { this(permissions, 0L, 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()); setAccessTime(other.getAccessTime()); } /** * 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 */ 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} */ protected void setPermission(FsPermission permission) { updatePermissionStatus(PermissionStatusFormat.MODE, permission.toShort()); } /** * Check whether it's a directory */ public 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}. */ 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); /** * Get local file name * @return local file name */ String getLocalName() { return DFSUtil.bytes2String(name); } String getLocalParentDir() { INode inode = isRoot() ? this : getParent(); return (inode != null) ? inode.getFullPathName() : ""; } /** * Get local file name * @return local file name */ 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; } /** {@inheritDoc} */ public String getFullPathName() { // Get the full path name of this inode. return FSDirectory.getFullPathName(this); } /** {@inheritDoc} */ 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 isLink() { 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; } } // // 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; } /** * Create an INode; the inode's name is not set yet * * @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(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(symlink, modificationTime, atime, permissions); } else if (blocks == null) { //not sym link and blocks null? directory! if (nsQuota >= 0 || dsQuota >= 0) { return new INodeDirectoryWithQuota( permissions, modificationTime, nsQuota, dsQuota); } // regular directory return new INodeDirectory(permissions, modificationTime); } // file return new INodeFile(permissions, blocks, replication, modificationTime, atime, preferredBlockSize); } }