/** * 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.*; import java.util.*; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.fs.ContentSummary; import org.apache.hadoop.fs.permission.*; import org.apache.hadoop.metrics.MetricsRecord; import org.apache.hadoop.metrics.MetricsUtil; import org.apache.hadoop.metrics.MetricsContext; import org.apache.hadoop.dfs.BlocksMap.BlockInfo; /************************************************* * FSDirectory stores the filesystem directory state. * It handles writing/loading values to disk, and logging * changes as we go. * * It keeps the filename->blockset mapping always-current * and logged to disk. * *************************************************/ class FSDirectory implements FSConstants, Closeable { final FSNamesystem namesystem; final INodeDirectoryWithQuota rootDir; FSImage fsImage; boolean ready = false; // Metrics record private MetricsRecord directoryMetrics = null; /** Access an existing dfs name directory. */ public FSDirectory(FSNamesystem ns, Configuration conf) throws IOException { this(new FSImage(), ns, conf); fsImage.setCheckpointDirectories(FSImage.getCheckpointDirs(conf, null)); } public FSDirectory(FSImage fsImage, FSNamesystem ns, Configuration conf) throws IOException { rootDir = new INodeDirectoryWithQuota(INodeDirectory.ROOT_NAME, ns.createFsOwnerPermissions(new FsPermission((short)0755)), Integer.MAX_VALUE); this.fsImage = fsImage; namesystem = ns; initialize(conf); } private void initialize(Configuration conf) { MetricsContext metricsContext = MetricsUtil.getContext("dfs"); directoryMetrics = MetricsUtil.createRecord(metricsContext, "FSDirectory"); directoryMetrics.setTag("sessionId", conf.get("session.id")); } void loadFSImage(Collection<File> dataDirs, StartupOption startOpt) throws IOException { // format before starting up if requested if (startOpt == StartupOption.FORMAT) { fsImage.setStorageDirectories(dataDirs); fsImage.format(); startOpt = StartupOption.REGULAR; } try { if (fsImage.recoverTransitionRead(dataDirs, startOpt)) { fsImage.saveFSImage(); } FSEditLog editLog = fsImage.getEditLog(); assert editLog != null : "editLog must be initialized"; if (!editLog.isOpen()) editLog.open(); fsImage.setCheckpointDirectories(null); } catch(IOException e) { fsImage.close(); throw e; } synchronized (this) { this.ready = true; this.notifyAll(); } } private void incrDeletedFileCount(int count) { directoryMetrics.incrMetric("files_deleted", count); directoryMetrics.update(); } /** * Shutdown the filestore */ public void close() throws IOException { fsImage.close(); } /** * Block until the object is ready to be used. */ void waitForReady() { if (!ready) { synchronized (this) { while (!ready) { try { this.wait(5000); } catch (InterruptedException ie) { } } } } } /** * Add the given filename to the fs. */ INodeFileUnderConstruction addFile(String path, PermissionStatus permissions, short replication, long preferredBlockSize, String clientName, String clientMachine, DatanodeDescriptor clientNode, long generationStamp) throws IOException { waitForReady(); // Always do an implicit mkdirs for parent directory tree. long modTime = FSNamesystem.now(); if (!mkdirs(new Path(path).getParent().toString(), permissions, true, modTime)) { return null; } INodeFileUnderConstruction newNode = new INodeFileUnderConstruction( permissions,replication, preferredBlockSize, modTime, clientName, clientMachine, clientNode); synchronized (rootDir) { newNode = addNode(path, newNode, false); } if (newNode == null) { NameNode.stateChangeLog.info("DIR* FSDirectory.addFile: " +"failed to add "+path +" to the file system"); return null; } // add create file record to log, record new generation stamp fsImage.getEditLog().logOpenFile(path, newNode); NameNode.stateChangeLog.debug("DIR* FSDirectory.addFile: " +path+" is added to the file system"); return newNode; } /** */ INode unprotectedAddFile( String path, PermissionStatus permissions, Block[] blocks, short replication, long modificationTime, long preferredBlockSize) { INode newNode; if (blocks == null) newNode = new INodeDirectory(permissions, modificationTime); else newNode = new INodeFile(permissions, blocks.length, replication, modificationTime, preferredBlockSize); synchronized (rootDir) { try { newNode = addNode(path, newNode, false); if(newNode != null && blocks != null) { int nrBlocks = blocks.length; // Add file->block mapping INodeFile newF = (INodeFile)newNode; for (int i = 0; i < nrBlocks; i++) { newF.setBlock(i, namesystem.blocksMap.addINode(blocks[i], newF)); } } } catch (IOException e) { return null; } return newNode; } } INodeDirectory addToParent( String src, INodeDirectory parentINode, PermissionStatus permissions, Block[] blocks, short replication, long modificationTime, long quota, long preferredBlockSize) { // create new inode INode newNode; if (blocks == null) { if (quota >= 0) { newNode = new INodeDirectoryWithQuota( permissions, modificationTime, quota); } else { newNode = new INodeDirectory(permissions, modificationTime); } } else newNode = new INodeFile(permissions, blocks.length, replication, modificationTime, preferredBlockSize); // add new node to the parent INodeDirectory newParent = null; synchronized (rootDir) { try { newParent = rootDir.addToParent(src, newNode, parentINode, false); } catch (FileNotFoundException e) { return null; } if(newParent == null) return null; if(blocks != null) { int nrBlocks = blocks.length; // Add file->block mapping INodeFile newF = (INodeFile)newNode; for (int i = 0; i < nrBlocks; i++) { newF.setBlock(i, namesystem.blocksMap.addINode(blocks[i], newF)); } } } return newParent; } /** * Add a block to the file. Returns a reference to the added block. */ Block addBlock(String path, INode file, Block block) throws IOException { waitForReady(); synchronized (rootDir) { INodeFile fileNode = (INodeFile) file; // associate the new list of blocks with this file namesystem.blocksMap.addINode(block, fileNode); BlockInfo blockInfo = namesystem.blocksMap.getStoredBlock(block); fileNode.addBlock(blockInfo); NameNode.stateChangeLog.debug("DIR* FSDirectory.addFile: " + path + " with " + block + " block is added to the in-memory " + "file system"); } return block; } /** * Persist the block list for the inode. */ void persistBlocks(String path, INodeFileUnderConstruction file) throws IOException { waitForReady(); synchronized (rootDir) { fsImage.getEditLog().logOpenFile(path, file); NameNode.stateChangeLog.debug("DIR* FSDirectory.persistBlocks: " +path+" with "+ file.getBlocks().length +" blocks is persisted to the file system"); } } /** * Close file. */ void closeFile(String path, INodeFile file) throws IOException { waitForReady(); synchronized (rootDir) { // file is closed fsImage.getEditLog().logCloseFile(path, file); if (NameNode.stateChangeLog.isDebugEnabled()) { NameNode.stateChangeLog.debug("DIR* FSDirectory.closeFile: " +path+" with "+ file.getBlocks().length +" blocks is persisted to the file system"); } } } /** * Remove a block to the file. */ boolean removeBlock(String path, INodeFileUnderConstruction fileNode, Block block) throws IOException { waitForReady(); synchronized (rootDir) { // modify file-> block and blocksMap fileNode.removeBlock(block); namesystem.blocksMap.removeINode(block); // If block is removed from blocksMap remove it from corruptReplicasMap namesystem.corruptReplicas.removeFromCorruptReplicasMap(block); // write modified block locations to log fsImage.getEditLog().logOpenFile(path, fileNode); NameNode.stateChangeLog.debug("DIR* FSDirectory.addFile: " +path+" with "+block +" block is added to the file system"); } return true; } /** * @see #unprotectedRenameTo(String, String, long) */ boolean renameTo(String src, String dst) throws QuotaExceededException { if (NameNode.stateChangeLog.isDebugEnabled()) { NameNode.stateChangeLog.debug("DIR* FSDirectory.renameTo: " +src+" to "+dst); } waitForReady(); long now = FSNamesystem.now(); if (!unprotectedRenameTo(src, dst, now)) return false; fsImage.getEditLog().logRename(src, dst, now); return true; } /** Change a path name * * @param src source path * @param dst destination path * @return true if rename succeeds; false otherwise * @throws QuotaExceededException if the operation violates any quota limit */ boolean unprotectedRenameTo(String src, String dst, long timestamp) throws QuotaExceededException { byte[][] srcComponents = INode.getPathComponents(src); INode[] srcInodes = new INode[srcComponents.length]; synchronized (rootDir) { rootDir.getExistingPathINodes(srcComponents, srcInodes); // check the validation of the source if (srcInodes[srcInodes.length-1] == null) { NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " +"failed to rename "+src+" to "+dst+ " because source does not exist"); return false; } else if (srcInodes.length == 1) { NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " +"failed to rename "+src+" to "+dst+ " because source is the root"); return false; } if (isDir(dst)) { dst += Path.SEPARATOR + new Path(src).getName(); } // remove source INode srcChild = null; try { srcChild = removeChild(srcInodes, srcInodes.length-1); } catch (IOException e) { // srcChild == null; go to next if statement } if (srcChild == null) { NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " +"failed to rename "+src+" to "+dst+ " because the source can not be removed"); return false; } String srcChildName = srcChild.getLocalName(); // check the validity of the destination INode dstChild = null; QuotaExceededException failureByQuota = null; byte[][] dstComponents = INode.getPathComponents(dst); INode[] dstInodes = new INode[dstComponents.length]; rootDir.getExistingPathINodes(dstComponents, dstInodes); if (dstInodes[dstInodes.length-1] != null) { //check if destination exists NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " +"failed to rename "+src+" to "+dst+ " because destination exists"); } else if (dstInodes[dstInodes.length-2] == null) { // check if its parent exists NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " +"failed to rename "+src+" to "+dst+ " because destination's parent does not exists"); } else { // add to the destination srcChild.setLocalName(dstComponents[dstInodes.length-1]); try { // add it to the namespace dstChild = addChild(dstInodes, dstInodes.length-1, srcChild, false); } catch (QuotaExceededException qe) { failureByQuota = qe; } } if (dstChild != null) { if (NameNode.stateChangeLog.isDebugEnabled()) { NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedRenameTo: " +src+" is renamed to "+dst); } // update modification time of dst and the parent of src srcInodes[srcInodes.length-2].setModificationTime(timestamp); dstInodes[dstInodes.length-2].setModificationTime(timestamp); return true; } else { NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: " +"failed to rename "+src+" to "+dst); try { // put it back srcChild.setLocalName(srcChildName); addChild(srcInodes, srcInodes.length-1, srcChild, false); } catch (IOException ignored) {} if (failureByQuota != null) { throw failureByQuota; } else { return false; } } } } /** * Set file replication * * @param src file name * @param replication new replication * @param oldReplication old replication - output parameter * @return array of file blocks * @throws IOException */ Block[] setReplication(String src, short replication, int[] oldReplication ) throws IOException { waitForReady(); Block[] fileBlocks = unprotectedSetReplication(src, replication, oldReplication); if (fileBlocks != null) // log replication change fsImage.getEditLog().logSetReplication(src, replication); return fileBlocks; } Block[] unprotectedSetReplication( String src, short replication, int[] oldReplication ) throws IOException { if (oldReplication == null) oldReplication = new int[1]; oldReplication[0] = -1; Block[] fileBlocks = null; synchronized(rootDir) { INode inode = rootDir.getNode(src); if (inode == null) return null; if (inode.isDirectory()) return null; INodeFile fileNode = (INodeFile)inode; oldReplication[0] = fileNode.getReplication(); fileNode.setReplication(replication); fileBlocks = fileNode.getBlocks(); } return fileBlocks; } /** * Get the blocksize of a file * @param filename the filename * @return the number of bytes * @throws IOException if it is a directory or does not exist. */ public long getPreferredBlockSize(String filename) throws IOException { synchronized (rootDir) { INode fileNode = rootDir.getNode(filename); if (fileNode == null) { throw new IOException("Unknown file: " + filename); } if (fileNode.isDirectory()) { throw new IOException("Getting block size of a directory: " + filename); } return ((INodeFile)fileNode).getPreferredBlockSize(); } } boolean exists(String src) { src = normalizePath(src); synchronized(rootDir) { INode inode = rootDir.getNode(src); if (inode == null) { return false; } return inode.isDirectory()? true: ((INodeFile)inode).getBlocks() != null; } } void setPermission(String src, FsPermission permission ) throws IOException { unprotectedSetPermission(src, permission); fsImage.getEditLog().logSetPermissions(src, permission); } void unprotectedSetPermission(String src, FsPermission permissions) throws FileNotFoundException { synchronized(rootDir) { INode inode = rootDir.getNode(src); if(inode == null) throw new FileNotFoundException("File does not exist: " + src); inode.setPermission(permissions); } } void setOwner(String src, String username, String groupname ) throws IOException { unprotectedSetOwner(src, username, groupname); fsImage.getEditLog().logSetOwner(src, username, groupname); } void unprotectedSetOwner(String src, String username, String groupname) throws FileNotFoundException { synchronized(rootDir) { INode inode = rootDir.getNode(src); if(inode == null) throw new FileNotFoundException("File does not exist: " + src); if (username != null) { inode.setUser(username); } if (groupname != null) { inode.setGroup(groupname); } } } /** * Remove the file from management, return blocks */ public INode delete(String src) { if (NameNode.stateChangeLog.isDebugEnabled()) { NameNode.stateChangeLog.debug("DIR* FSDirectory.delete: "+src); } waitForReady(); long now = FSNamesystem.now(); INode deletedNode = unprotectedDelete(src, now); if (deletedNode != null) { fsImage.getEditLog().logDelete(src, now); } return deletedNode; } /** Return if a directory is empty or not **/ public boolean isDirEmpty(String src) { boolean dirNotEmpty = true; if (!isDir(src)) { return true; } synchronized(rootDir) { INode targetNode = rootDir.getNode(src); assert targetNode != null : "should be taken care in isDir() above"; if (((INodeDirectory)targetNode).getChildren().size() != 0) { dirNotEmpty = false; } } return dirNotEmpty; } /** * Delete a path from the name space * Update the count at each ancestor directory with quota * @param src a string representation of a path to an inode * @param modificationTime the time the inode is removed * @param deletedBlocks the place holder for the blocks to be removed * @return if the deletion succeeds */ INode unprotectedDelete(String src, long modificationTime) { src = normalizePath(src); String[] names = INode.getPathNames(src); byte[][] components = INode.getPathComponents(names); INode[] inodes = new INode[components.length]; synchronized (rootDir) { rootDir.getExistingPathINodes(components, inodes); INode targetNode = inodes[inodes.length-1]; if (targetNode == null) { // non-existent src NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: " +"failed to remove "+src+" because it does not exist"); return null; } else if (inodes.length == 1) { // src is the root NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedDelete: " + "failed to remove " + src + " because the root is not allowed to be deleted"); return null; } else { try { // Remove the node from the namespace removeChild(inodes, inodes.length-1); // set the parent's modification time inodes[inodes.length-2].setModificationTime(modificationTime); // GC all the blocks underneath the node. ArrayList<Block> v = new ArrayList<Block>(); int filesRemoved = targetNode.collectSubtreeBlocksAndClear(v); incrDeletedFileCount(filesRemoved); namesystem.removePathAndBlocks(src, v); if (NameNode.stateChangeLog.isDebugEnabled()) { NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: " +src+" is removed"); } return targetNode; } catch (IOException e) { NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedDelete: " + "failed to remove " + src + " because " + e.getMessage()); return null; } } } } /** * Replaces the specified inode with the specified one. */ void replaceNode(String path, INodeFile oldnode, INodeFile newnode) throws IOException { synchronized (rootDir) { // // Remove the node from the namespace // if (!oldnode.removeNode()) { NameNode.stateChangeLog.warn("DIR* FSDirectory.replaceNode: " + "failed to remove " + path); throw new IOException("FSDirectory.replaceNode: " + "failed to remove " + path); } rootDir.addNode(path, newnode); int index = 0; for (Block b : newnode.getBlocks()) { BlockInfo info = namesystem.blocksMap.addINode(b, newnode); newnode.setBlock(index, info); // inode refers to the block in BlocksMap index++; } } } /** * Get a listing of files given path 'src' * * This function is admittedly very inefficient right now. We'll * make it better later. */ public DFSFileInfo[] getListing(String src) { String srcs = normalizePath(src); synchronized (rootDir) { INode targetNode = rootDir.getNode(srcs); if (targetNode == null) return null; if (!targetNode.isDirectory()) { return new DFSFileInfo[]{new DFSFileInfo(srcs, targetNode)}; } List<INode> contents = ((INodeDirectory)targetNode).getChildren(); DFSFileInfo listing[] = new DFSFileInfo[contents.size()]; if(! srcs.endsWith(Path.SEPARATOR)) srcs += Path.SEPARATOR; int i = 0; for (INode cur : contents) { listing[i] = new DFSFileInfo(srcs+cur.getLocalName(), cur); i++; } return listing; } } /** Get the file info for a specific file. * @param src The string representation of the path to the file * @return object containing information regarding the file * or null if file not found */ DFSFileInfo getFileInfo(String src) { String srcs = normalizePath(src); synchronized (rootDir) { INode targetNode = rootDir.getNode(srcs); if (targetNode == null) { return null; } else { return new DFSFileInfo(srcs, targetNode); } } } /** * Get the blocks associated with the file. */ Block[] getFileBlocks(String src) { waitForReady(); synchronized (rootDir) { INode targetNode = rootDir.getNode(src); if (targetNode == null) return null; if(targetNode.isDirectory()) return null; return ((INodeFile)targetNode).getBlocks(); } } /** * Get {@link INode} associated with the file. */ INodeFile getFileINode(String src) { synchronized (rootDir) { INode inode = rootDir.getNode(src); if (inode == null || inode.isDirectory()) return null; return (INodeFile)inode; } } /** * Check whether the filepath could be created */ public boolean isValidToCreate(String src) { String srcs = normalizePath(src); synchronized (rootDir) { if (srcs.startsWith("/") && !srcs.endsWith("/") && rootDir.getNode(srcs) == null) { return true; } else { return false; } } } /** * Check whether the path specifies a directory */ public boolean isDir(String src) { synchronized (rootDir) { INode node = rootDir.getNode(normalizePath(src)); return node != null && node.isDirectory(); } } /** update count of each inode with quota * * @param inodes an array of inodes on a path * @param numOfINodes the number of inodes to update starting from index 0 * @param deltaCount the delta change of the count * @throws QuotaExceededException if the new count violates any quota limit */ private static void updateCount( INode[] inodes, int numOfINodes, long deltaCount ) throws QuotaExceededException { if (numOfINodes>inodes.length) { numOfINodes = inodes.length; } // check existing components in the path List<INodeDirectoryWithQuota> inodesWithQuota = new ArrayList<INodeDirectoryWithQuota>(numOfINodes); int i=0; try { for(; i < numOfINodes; i++) { if (inodes[i].getQuota() >= 0) { // a directory with quota INodeDirectoryWithQuota quotaINode =(INodeDirectoryWithQuota)inodes[i]; quotaINode.updateNumItemsInTree(deltaCount); inodesWithQuota.add(quotaINode); } } } catch (QuotaExceededException e) { for (INodeDirectoryWithQuota quotaINode:inodesWithQuota) { try { quotaINode.updateNumItemsInTree(-deltaCount); } catch (IOException ingored) { } } e.setPathName(getFullPathName(inodes, i)); throw e; } } /** Return the name of the path represented by inodes at [0, pos] */ private static String getFullPathName(INode[] inodes, int pos) { StringBuilder fullPathName = new StringBuilder(); for (int i=1; i<=pos; i++) { fullPathName.append(Path.SEPARATOR_CHAR).append(inodes[i].getLocalName()); } return fullPathName.toString(); } /** * Create a directory * If ancestor directories do not exist, automatically create them. * @param src string representation of the path to the directory * @param permissions the permission of the directory * @param inheritPermission if the permission of the directory should inherit * from its parent or not. The automatically created * ones always inherit its permission from its parent * @param now creation time * @return true if the operation succeeds false otherwise * @throws FileNotFoundException if an ancestor or itself is a file * @throws QuotaExceededException if directory creation violates * any quota limit */ boolean mkdirs(String src, PermissionStatus permissions, boolean inheritPermission, long now) throws FileNotFoundException, QuotaExceededException { src = normalizePath(src); String[] names = INode.getPathNames(src); byte[][] components = INode.getPathComponents(names); INode[] inodes = new INode[components.length]; synchronized(rootDir) { rootDir.getExistingPathINodes(components, inodes); // find the index of the first null in inodes[] StringBuilder pathbuilder = new StringBuilder(); int i = 1; for(; i < inodes.length && inodes[i] != null; i++) { pathbuilder.append(Path.SEPARATOR + names[i]); if (!inodes[i].isDirectory()) { throw new FileNotFoundException("Parent path is not a directory: " + pathbuilder); } } // create directories beginning from the first null index for(; i < inodes.length; i++) { pathbuilder.append(Path.SEPARATOR + names[i]); String cur = pathbuilder.toString(); unprotectedMkdir(inodes, i, components[i], permissions, inheritPermission || i != components.length-1, now); if (inodes[i] == null) { return false; } // Directory creation also count towards FilesCreated // to match count of files_deleted metric. if (namesystem != null) NameNode.getNameNodeMetrics().numFilesCreated.inc(); fsImage.getEditLog().logMkDir(cur, inodes[i]); NameNode.stateChangeLog.debug( "DIR* FSDirectory.mkdirs: created directory " + cur); } } return true; } /** */ INode unprotectedMkdir(String src, PermissionStatus permissions, long timestamp) throws QuotaExceededException { byte[][] components = INode.getPathComponents(src); INode[] inodes = new INode[components.length]; synchronized (rootDir) { rootDir.getExistingPathINodes(components, inodes); unprotectedMkdir(inodes, inodes.length-1, components[inodes.length-1], permissions, false, timestamp); return inodes[inodes.length-1]; } } /** create a directory at index pos. * The parent path to the directory is at [0, pos-1]. * All ancestors exist. Newly created one stored at index pos. */ private void unprotectedMkdir(INode[] inodes, int pos, byte[] name, PermissionStatus permission, boolean inheritPermission, long timestamp) throws QuotaExceededException { inodes[pos] = addChild(inodes, pos, new INodeDirectory(name, permission, timestamp), inheritPermission ); } /** Add a node child to the namespace. The full path name of the node is src. * QuotaExceededException is thrown if it violates quota limit */ private <T extends INode> T addNode(String src, T child, boolean inheritPermission) throws QuotaExceededException { byte[][] components = INode.getPathComponents(src); child.setLocalName(components[components.length-1]); INode[] inodes = new INode[components.length]; synchronized (rootDir) { rootDir.getExistingPathINodes(components, inodes); return addChild(inodes, inodes.length-1, child, inheritPermission); } } /** Add a node child to the inodes at index pos. * Its ancestors are stored at [0, pos-1]. * QuotaExceededException is thrown if it violates quota limit */ private <T extends INode> T addChild(INode[] pathComponents, int pos, T child, boolean inheritPermission) throws QuotaExceededException { long childSize = child.numItemsInTree(); updateCount(pathComponents, pos, childSize); T addedNode = ((INodeDirectory)pathComponents[pos-1]).addChild( child, inheritPermission); if (addedNode == null) { updateCount(pathComponents, pos, -childSize); } return addedNode; } /** Remove an inode at index pos from the namespace. * Its ancestors are stored at [0, pos-1]. * Count of each ancestor with quota is also updated. * Return the removed node; null if the removal fails. */ private INode removeChild(INode[] pathComponents, int pos) throws QuotaExceededException { INode removedNode = ((INodeDirectory)pathComponents[pos-1]).removeChild(pathComponents[pos]); if (removedNode != null) { updateCount(pathComponents, pos, -removedNode.numItemsInTree()); } return removedNode; } /** */ String normalizePath(String src) { if (src.length() > 1 && src.endsWith("/")) { src = src.substring(0, src.length() - 1); } return src; } ContentSummary getContentSummary(String src) throws IOException { String srcs = normalizePath(src); synchronized (rootDir) { INode targetNode = rootDir.getNode(srcs); if (targetNode == null) { throw new FileNotFoundException("File does not exist: " + srcs); } else { return targetNode.computeContentSummary(); } } } /** Update the count of each directory with quota in the namespace * A directory's count is defined as the total number inodes in the tree * rooted at the directory. * * @throws QuotaExceededException if the count update violates * any quota limitation */ void updateCountForINodeWithQuota() throws QuotaExceededException { updateCountForINodeWithQuota(rootDir); } /** Update the count of the directory if it has a quota and return the count * * @param node the root of the tree that represents the directory * @return the size of the tree * @throws QuotaExceededException if the count is greater than its quota */ private static long updateCountForINodeWithQuota(INode node) throws QuotaExceededException { long count = 1L; if (node.isDirectory()) { INodeDirectory dNode = (INodeDirectory)node; for (INode child : dNode.getChildren()) { count += updateCountForINodeWithQuota(child); } if (dNode.getQuota()>=0) { ((INodeDirectoryWithQuota)dNode).setCount(count); } } return count; } /** * Set the quota for a directory. * @param path The string representation of the path to the directory * @param quota The limit of the number of names in or below the directory * @throws FileNotFoundException if the path does not exist or is a file * @throws QuotaExceededException if the directory tree size is * greater than the given quota */ void unprotectedSetQuota(String src, long quota) throws FileNotFoundException, QuotaExceededException { String srcs = normalizePath(src); byte[][] components = INode.getPathComponents(src); INode[] inodes = new INode[components.length==1?1:2]; synchronized (rootDir) { rootDir.getExistingPathINodes(components, inodes); INode targetNode = inodes[inodes.length-1]; if (targetNode == null) { throw new FileNotFoundException("Directory does not exist: " + srcs); } else if (!targetNode.isDirectory()) { throw new FileNotFoundException("Cannot set quota on a file: " + srcs); } else { // a directory inode INodeDirectory dirNode = (INodeDirectory)targetNode; if (dirNode instanceof INodeDirectoryWithQuota) { // a directory with quota; so set the quota to the new value ((INodeDirectoryWithQuota)dirNode).setQuota(quota); } else { // a non-quota directory; so replace it with a directory with quota INodeDirectoryWithQuota newNode = new INodeDirectoryWithQuota(quota, dirNode); // non-root directory node; parent != null assert inodes.length==2; INodeDirectory parent = (INodeDirectory)inodes[0]; parent.replaceChild(newNode); } } } } /** * @see #unprotectedSetQuota(String, long) */ void setQuota(String src, long quota) throws FileNotFoundException, QuotaExceededException { unprotectedSetQuota(src, quota); fsImage.getEditLog().logSetQuota(src, quota); } /** * Remove the quota for a directory * @param src The string representation of the path to the directory * @throws FileNotFoundException if the path does not exist or it is a file */ void unprotectedClearQuota(String src) throws IOException { String srcs = normalizePath(src); byte[][] components = INode.getPathComponents(src); INode[] inodes = new INode[components.length==1?1:2]; synchronized (rootDir) { rootDir.getExistingPathINodes(components, inodes); INode targetNode = inodes[inodes.length-1]; if (targetNode == null || !targetNode.isDirectory()) { throw new FileNotFoundException("Directory does not exist: " + srcs); } else if (targetNode instanceof INodeDirectoryWithQuota) { // a directory inode with quota // replace the directory with quota with a non-quota one INodeDirectoryWithQuota dirNode = (INodeDirectoryWithQuota)targetNode; INodeDirectory newNode = new INodeDirectory(dirNode); if (dirNode == rootDir) { // root throw new IOException("Can't clear the root's quota"); } else { // non-root directory node; parent != null INodeDirectory parent = (INodeDirectory)inodes[0]; parent.replaceChild(newNode); } } } } /** * @see #unprotectedClearQuota(String) */ void clearQuota(String src) throws IOException { unprotectedClearQuota(src); fsImage.getEditLog().logClearQuota(src); } long totalInodes() { synchronized (rootDir) { return rootDir.numItemsInTree(); } } }