001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hdfs.server.namenode;
019
020import static org.apache.hadoop.hdfs.server.common.Util.now;
021
022import java.io.Closeable;
023import java.io.FileNotFoundException;
024import java.io.IOException;
025import java.util.ArrayList;
026import java.util.List;
027import java.util.concurrent.TimeUnit;
028import java.util.concurrent.locks.Condition;
029import java.util.concurrent.locks.ReentrantReadWriteLock;
030
031import org.apache.hadoop.conf.Configuration;
032import org.apache.hadoop.fs.ContentSummary;
033import org.apache.hadoop.fs.FileAlreadyExistsException;
034import org.apache.hadoop.fs.Options;
035import org.apache.hadoop.fs.Options.Rename;
036import org.apache.hadoop.fs.ParentNotDirectoryException;
037import org.apache.hadoop.fs.Path;
038import org.apache.hadoop.fs.UnresolvedLinkException;
039import org.apache.hadoop.fs.permission.FsAction;
040import org.apache.hadoop.fs.permission.FsPermission;
041import org.apache.hadoop.fs.permission.PermissionStatus;
042import org.apache.hadoop.hdfs.DFSConfigKeys;
043import org.apache.hadoop.hdfs.DistributedFileSystem;
044import org.apache.hadoop.hdfs.protocol.Block;
045import org.apache.hadoop.hdfs.protocol.ClientProtocol;
046import org.apache.hadoop.hdfs.protocol.DirectoryListing;
047import org.apache.hadoop.hdfs.protocol.HdfsConstants;
048import org.apache.hadoop.hdfs.protocol.FSLimitException;
049import org.apache.hadoop.hdfs.protocol.FSLimitException.MaxDirectoryItemsExceededException;
050import org.apache.hadoop.hdfs.protocol.FSLimitException.PathComponentTooLongException;
051import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
052import org.apache.hadoop.hdfs.protocol.HdfsLocatedFileStatus;
053import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
054import org.apache.hadoop.hdfs.protocol.QuotaExceededException;
055import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo;
056import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfoUnderConstruction;
057import org.apache.hadoop.hdfs.server.blockmanagement.BlockManager;
058import org.apache.hadoop.hdfs.server.blockmanagement.DatanodeDescriptor;
059import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.BlockUCState;
060import org.apache.hadoop.hdfs.server.common.HdfsServerConstants.StartupOption;
061import org.apache.hadoop.hdfs.util.ByteArray;
062
063import com.google.common.annotations.VisibleForTesting;
064import com.google.common.base.Preconditions;
065
066/*************************************************
067 * FSDirectory stores the filesystem directory state.
068 * It handles writing/loading values to disk, and logging
069 * changes as we go.
070 *
071 * It keeps the filename->blockset mapping always-current
072 * and logged to disk.
073 * 
074 *************************************************/
075public class FSDirectory implements Closeable {
076
077  INodeDirectoryWithQuota rootDir;
078  FSImage fsImage;  
079  private volatile boolean ready = false;
080  private static final long UNKNOWN_DISK_SPACE = -1;
081  private final int maxComponentLength;
082  private final int maxDirItems;
083  private final int lsLimit;  // max list limit
084  private final int contentCountLimit; // max content summary counts per run
085
086  // lock to protect the directory and BlockMap
087  private ReentrantReadWriteLock dirLock;
088  private Condition cond;
089
090  // utility methods to acquire and release read lock and write lock
091  void readLock() {
092    this.dirLock.readLock().lock();
093  }
094
095  void readUnlock() {
096    this.dirLock.readLock().unlock();
097  }
098
099  void writeLock() {
100    this.dirLock.writeLock().lock();
101  }
102
103  void writeUnlock() {
104    this.dirLock.writeLock().unlock();
105  }
106
107  boolean hasWriteLock() {
108    return this.dirLock.isWriteLockedByCurrentThread();
109  }
110
111  boolean hasReadLock() {
112    return this.dirLock.getReadHoldCount() > 0;
113  }
114
115  public int getReadHoldCount() {
116    return this.dirLock.getReadHoldCount();
117  }
118
119  public int getWriteHoldCount() {
120    return this.dirLock.getWriteHoldCount();
121  }
122
123  /**
124   * Caches frequently used file names used in {@link INode} to reuse 
125   * byte[] objects and reduce heap usage.
126   */
127  private final NameCache<ByteArray> nameCache;
128
129  /** Access an existing dfs name directory. */
130  FSDirectory(FSNamesystem ns, Configuration conf) throws IOException {
131    this(new FSImage(conf), ns, conf);
132  }
133
134  FSDirectory(FSImage fsImage, FSNamesystem ns, Configuration conf) {
135    this.dirLock = new ReentrantReadWriteLock(true); // fair
136    this.cond = dirLock.writeLock().newCondition();
137    fsImage.setFSNamesystem(ns);
138    rootDir = new INodeDirectoryWithQuota(INodeDirectory.ROOT_NAME,
139        ns.createFsOwnerPermissions(new FsPermission((short)0755)),
140        Integer.MAX_VALUE, UNKNOWN_DISK_SPACE);
141    this.fsImage = fsImage;
142    int configuredLimit = conf.getInt(
143        DFSConfigKeys.DFS_LIST_LIMIT, DFSConfigKeys.DFS_LIST_LIMIT_DEFAULT);
144    this.lsLimit = configuredLimit>0 ?
145        configuredLimit : DFSConfigKeys.DFS_LIST_LIMIT_DEFAULT;
146
147    this.contentCountLimit = conf.getInt(
148        DFSConfigKeys.DFS_CONTENT_SUMMARY_LIMIT_KEY,
149        DFSConfigKeys.DFS_CONTENT_SUMMARY_LIMIT_DEFAULT);
150    
151    // filesystem limits
152    this.maxComponentLength = conf.getInt(
153        DFSConfigKeys.DFS_NAMENODE_MAX_COMPONENT_LENGTH_KEY,
154        DFSConfigKeys.DFS_NAMENODE_MAX_COMPONENT_LENGTH_DEFAULT);
155    this.maxDirItems = conf.getInt(
156        DFSConfigKeys.DFS_NAMENODE_MAX_DIRECTORY_ITEMS_KEY,
157        DFSConfigKeys.DFS_NAMENODE_MAX_DIRECTORY_ITEMS_DEFAULT);
158
159    int threshold = conf.getInt(
160        DFSConfigKeys.DFS_NAMENODE_NAME_CACHE_THRESHOLD_KEY,
161        DFSConfigKeys.DFS_NAMENODE_NAME_CACHE_THRESHOLD_DEFAULT);
162    NameNode.LOG.info("Caching file names occuring more than " + threshold
163        + " times ");
164    nameCache = new NameCache<ByteArray>(threshold);
165  }
166    
167  private FSNamesystem getFSNamesystem() {
168    return fsImage.getFSNamesystem();
169  }
170
171  private BlockManager getBlockManager() {
172    return getFSNamesystem().getBlockManager();
173  }
174
175  /**
176   * Load the filesystem image into memory.
177   *
178   * @param startOpt Startup type as specified by the user.
179   * @throws IOException If image or editlog cannot be read.
180   */
181  void loadFSImage(StartupOption startOpt) 
182      throws IOException {
183    // format before starting up if requested
184    if (startOpt == StartupOption.FORMAT) {
185      fsImage.format(fsImage.getStorage().determineClusterId());// reuse current id
186
187      startOpt = StartupOption.REGULAR;
188    }
189    boolean success = false;
190    try {
191      if (fsImage.recoverTransitionRead(startOpt)) {
192        fsImage.saveNamespace();
193      }
194      fsImage.openEditLog();
195      
196      fsImage.setCheckpointDirectories(null, null);
197      success = true;
198    } finally {
199      if (!success) {
200        fsImage.close();
201      }
202    }
203    setReady();
204  }
205  
206
207  /**
208   * Notify that loading of this FSDirectory is complete, and
209   * it is ready for use 
210   */
211  void imageLoadComplete() {
212    Preconditions.checkState(!ready, "FSDirectory already loaded");
213    setReady();
214  }
215
216  void setReady() {
217    if(ready) return;
218    writeLock();
219    try {
220      setReady(true);
221      this.nameCache.initialized();
222      cond.signalAll();
223    } finally {
224      writeUnlock();
225    }
226  }
227  
228  //This is for testing purposes only
229  @VisibleForTesting
230  boolean isReady() {
231    return ready;
232  }
233
234  // exposed for unit tests
235  protected void setReady(boolean flag) {
236    ready = flag;
237  }
238
239  private void incrDeletedFileCount(int count) {
240    if (getFSNamesystem() != null)
241      NameNode.getNameNodeMetrics().incrFilesDeleted(count);
242  }
243    
244  /**
245   * Shutdown the filestore
246   */
247  public void close() throws IOException {
248    fsImage.close();
249  }
250
251  /**
252   * Block until the object is ready to be used.
253   */
254  void waitForReady() {
255    if (!ready) {
256      writeLock();
257      try {
258        while (!ready) {
259          try {
260            cond.await(5000, TimeUnit.MILLISECONDS);
261          } catch (InterruptedException ie) {
262          }
263        }
264      } finally {
265        writeUnlock();
266      }
267    }
268  }
269
270  /**
271   * Add the given filename to the fs.
272   * @throws QuotaExceededException 
273   * @throws FileAlreadyExistsException 
274   */
275  INodeFileUnderConstruction addFile(String path, 
276                PermissionStatus permissions,
277                short replication,
278                long preferredBlockSize,
279                String clientName,
280                String clientMachine,
281                DatanodeDescriptor clientNode,
282                long generationStamp) 
283    throws FileAlreadyExistsException, QuotaExceededException,
284      UnresolvedLinkException {
285    waitForReady();
286
287    // Always do an implicit mkdirs for parent directory tree.
288    long modTime = now();
289    
290    Path parent = new Path(path).getParent();
291    if (parent == null) {
292      // Trying to add "/" as a file - this path has no
293      // parent -- avoids an NPE below.
294      return null;
295    }
296    
297    if (!mkdirs(parent.toString(), permissions, true, modTime)) {
298      return null;
299    }
300    INodeFileUnderConstruction newNode = new INodeFileUnderConstruction(
301                                 permissions,replication,
302                                 preferredBlockSize, modTime, clientName, 
303                                 clientMachine, clientNode);
304    writeLock();
305    try {
306      newNode = addNode(path, newNode, UNKNOWN_DISK_SPACE);
307    } finally {
308      writeUnlock();
309    }
310    if (newNode == null) {
311      NameNode.stateChangeLog.info("DIR* FSDirectory.addFile: "
312                                   +"failed to add "+path
313                                   +" to the file system");
314      return null;
315    }
316
317    if(NameNode.stateChangeLog.isDebugEnabled()) {
318      NameNode.stateChangeLog.debug("DIR* FSDirectory.addFile: "
319          +path+" is added to the file system");
320    }
321    return newNode;
322  }
323
324  /**
325   */
326  INode unprotectedAddFile( String path, 
327                            PermissionStatus permissions,
328                            BlockInfo[] blocks, 
329                            short replication,
330                            long modificationTime,
331                            long atime,
332                            long preferredBlockSize,
333                            String clientName,
334                            String clientMachine)
335      throws UnresolvedLinkException {
336    INode newNode;
337    assert hasWriteLock();
338    if (blocks == null)
339      newNode = new INodeDirectory(permissions, modificationTime);
340    else if(blocks.length == 0 || blocks[blocks.length-1].getBlockUCState()
341        == BlockUCState.UNDER_CONSTRUCTION) {
342      newNode = new INodeFileUnderConstruction(
343          permissions, blocks.length, replication,
344          preferredBlockSize, modificationTime, clientName, 
345          clientMachine, null);
346    } else {
347      newNode = new INodeFile(permissions, blocks.length, replication,
348                              modificationTime, atime, preferredBlockSize);
349    }
350    writeLock();
351    try {
352      try {
353        newNode = addNode(path, newNode, UNKNOWN_DISK_SPACE);
354        if(newNode != null && blocks != null) {
355          int nrBlocks = blocks.length;
356          // Add file->block mapping
357          INodeFile newF = (INodeFile)newNode;
358          for (int i = 0; i < nrBlocks; i++) {
359            newF.setBlock(i, getBlockManager().addINode(blocks[i], newF));
360          }
361        }
362      } catch (IOException e) {
363        return null;
364      }
365      return newNode;
366    } finally {
367      writeUnlock();
368    }
369
370  }
371
372  /**
373   * Update files in-memory data structures with new block information.
374   * @throws IOException 
375   */
376  void updateFile(INodeFile file,
377                  String path,
378                  BlockInfo[] blocks, 
379                  long mtime,
380                  long atime) throws IOException {
381
382    // Update the salient file attributes.
383    file.setAccessTime(atime);
384    file.setModificationTimeForce(mtime);
385
386    // Update its block list
387    BlockInfo[] oldBlocks = file.getBlocks();
388
389    // Are we only updating the last block's gen stamp.
390    boolean isGenStampUpdate = oldBlocks.length == blocks.length;
391
392    // First, update blocks in common
393    BlockInfo oldBlock = null;
394    for (int i = 0; i < oldBlocks.length && i < blocks.length; i++) {
395      oldBlock = oldBlocks[i];
396      Block newBlock = blocks[i];
397
398      boolean isLastBlock = i == oldBlocks.length - 1;
399      if (oldBlock.getBlockId() != newBlock.getBlockId() ||
400          (oldBlock.getGenerationStamp() != newBlock.getGenerationStamp() && 
401              !(isGenStampUpdate && isLastBlock))) {
402        throw new IOException("Mismatched block IDs or generation stamps, " + 
403            "attempting to replace block " + oldBlock + " with " + newBlock +
404            " as block # " + i + "/" + blocks.length + " of " + path);
405      }
406
407      oldBlock.setNumBytes(newBlock.getNumBytes());
408      oldBlock.setGenerationStamp(newBlock.getGenerationStamp());
409    }
410
411    if (blocks.length < oldBlocks.length) {
412      // We're removing a block from the file, e.g. abandonBlock(...)
413      if (!file.isUnderConstruction()) {
414        throw new IOException("Trying to remove a block from file " +
415            path + " which is not under construction.");
416      }
417      if (blocks.length != oldBlocks.length - 1) {
418        throw new IOException("Trying to remove more than one block from file "
419            + path);
420      }
421      unprotectedRemoveBlock(path,
422          (INodeFileUnderConstruction)file, oldBlocks[oldBlocks.length - 1]);
423    } else if (blocks.length > oldBlocks.length) {
424      // We're adding blocks
425      // First complete last old Block
426      getBlockManager().completeBlock(file, oldBlocks.length-1, true);
427      // Add the new blocks
428      for (int i = oldBlocks.length; i < blocks.length; i++) {
429        // addBlock();
430        BlockInfo newBI = blocks[i];
431        getBlockManager().addINode(newBI, file);
432        file.addBlock(newBI);
433      }
434    }
435  }
436
437  INodeDirectory addToParent(byte[] src, INodeDirectory parentINode,
438      INode newNode, boolean propagateModTime) throws UnresolvedLinkException {
439    // NOTE: This does not update space counts for parents
440    INodeDirectory newParent = null;
441    writeLock();
442    try {
443      try {
444        newParent = rootDir.addToParent(src, newNode, parentINode,
445                                        propagateModTime);
446        cacheName(newNode);
447      } catch (FileNotFoundException e) {
448        return null;
449      }
450      if(newParent == null)
451        return null;
452      if(!newNode.isDirectory() && !newNode.isLink()) {
453        // Add file->block mapping
454        INodeFile newF = (INodeFile)newNode;
455        BlockInfo[] blocks = newF.getBlocks();
456        for (int i = 0; i < blocks.length; i++) {
457          newF.setBlock(i, getBlockManager().addINode(blocks[i], newF));
458        }
459      }
460    } finally {
461      writeUnlock();
462    }
463    return newParent;
464  }
465
466  /**
467   * Add a block to the file. Returns a reference to the added block.
468   */
469  BlockInfo addBlock(String path,
470                     INode[] inodes,
471                     Block block,
472                     DatanodeDescriptor targets[]
473  ) throws QuotaExceededException {
474    waitForReady();
475
476    writeLock();
477    try {
478      assert inodes[inodes.length-1].isUnderConstruction() :
479        "INode should correspond to a file under construction";
480      INodeFileUnderConstruction fileINode = 
481        (INodeFileUnderConstruction)inodes[inodes.length-1];
482
483      // check quota limits and updated space consumed
484      updateCount(inodes, inodes.length-1, 0,
485          fileINode.getPreferredBlockSize()*fileINode.getReplication(), true);
486
487      // associate new last block for the file
488      BlockInfoUnderConstruction blockInfo =
489        new BlockInfoUnderConstruction(
490            block,
491            fileINode.getReplication(),
492            BlockUCState.UNDER_CONSTRUCTION,
493            targets);
494      getBlockManager().addINode(blockInfo, fileINode);
495      fileINode.addBlock(blockInfo);
496
497      if(NameNode.stateChangeLog.isDebugEnabled()) {
498        NameNode.stateChangeLog.debug("DIR* FSDirectory.addBlock: "
499            + path + " with " + block
500            + " block is added to the in-memory "
501            + "file system");
502      }
503      return blockInfo;
504    } finally {
505      writeUnlock();
506    }
507  }
508
509  /**
510   * Persist the block list for the inode.
511   */
512  void persistBlocks(String path, INodeFileUnderConstruction file) {
513    waitForReady();
514
515    writeLock();
516    try {
517      fsImage.getEditLog().logOpenFile(path, file);
518      if(NameNode.stateChangeLog.isDebugEnabled()) {
519        NameNode.stateChangeLog.debug("DIR* FSDirectory.persistBlocks: "
520            +path+" with "+ file.getBlocks().length 
521            +" blocks is persisted to the file system");
522      }
523    } finally {
524      writeUnlock();
525    }
526  }
527
528  /**
529   * Close file.
530   */
531  void closeFile(String path, INodeFile file) {
532    waitForReady();
533    long now = now();
534    writeLock();
535    try {
536      // file is closed
537      file.setModificationTimeForce(now);
538      fsImage.getEditLog().logCloseFile(path, file);
539      if (NameNode.stateChangeLog.isDebugEnabled()) {
540        NameNode.stateChangeLog.debug("DIR* FSDirectory.closeFile: "
541            +path+" with "+ file.getBlocks().length 
542            +" blocks is persisted to the file system");
543      }
544    } finally {
545      writeUnlock();
546    }
547  }
548
549  /**
550   * Remove a block to the file.
551   */
552  boolean removeBlock(String path, INodeFileUnderConstruction fileNode, 
553                      Block block) throws IOException {
554    waitForReady();
555
556    writeLock();
557    try {
558      unprotectedRemoveBlock(path, fileNode, block);
559      // write modified block locations to log
560      fsImage.getEditLog().logOpenFile(path, fileNode);
561    } finally {
562      writeUnlock();
563    }
564    return true;
565  }
566
567  void unprotectedRemoveBlock(String path, INodeFileUnderConstruction fileNode, 
568      Block block) throws IOException {
569    // modify file-> block and blocksMap
570    fileNode.removeLastBlock(block);
571    getBlockManager().removeBlockFromMap(block);
572
573    if(NameNode.stateChangeLog.isDebugEnabled()) {
574      NameNode.stateChangeLog.debug("DIR* FSDirectory.removeBlock: "
575          +path+" with "+block
576          +" block is removed from the file system");
577    }
578
579    // update space consumed
580    INode[] pathINodes = getExistingPathINodes(path);
581    updateCount(pathINodes, pathINodes.length-1, 0,
582        -fileNode.getPreferredBlockSize()*fileNode.getReplication(), true);
583  }
584
585  /**
586   * @see #unprotectedRenameTo(String, String, long)
587   * @deprecated Use {@link #renameTo(String, String, Rename...)} instead.
588   */
589  @Deprecated
590  boolean renameTo(String src, String dst) 
591      throws QuotaExceededException, UnresolvedLinkException, 
592      FileAlreadyExistsException {
593    if (NameNode.stateChangeLog.isDebugEnabled()) {
594      NameNode.stateChangeLog.debug("DIR* FSDirectory.renameTo: "
595          +src+" to "+dst);
596    }
597    waitForReady();
598    long now = now();
599    writeLock();
600    try {
601      if (!unprotectedRenameTo(src, dst, now))
602        return false;
603    } finally {
604      writeUnlock();
605    }
606    fsImage.getEditLog().logRename(src, dst, now);
607    return true;
608  }
609
610  /**
611   * @see #unprotectedRenameTo(String, String, long, Options.Rename...)
612   */
613  void renameTo(String src, String dst, Options.Rename... options)
614      throws FileAlreadyExistsException, FileNotFoundException,
615      ParentNotDirectoryException, QuotaExceededException,
616      UnresolvedLinkException, IOException {
617    if (NameNode.stateChangeLog.isDebugEnabled()) {
618      NameNode.stateChangeLog.debug("DIR* FSDirectory.renameTo: " + src
619          + " to " + dst);
620    }
621    waitForReady();
622    long now = now();
623    writeLock();
624    try {
625      if (unprotectedRenameTo(src, dst, now, options)) {
626        incrDeletedFileCount(1);
627      }
628    } finally {
629      writeUnlock();
630    }
631    fsImage.getEditLog().logRename(src, dst, now, options);
632  }
633
634  /**
635   * Change a path name
636   * 
637   * @param src source path
638   * @param dst destination path
639   * @return true if rename succeeds; false otherwise
640   * @throws QuotaExceededException if the operation violates any quota limit
641   * @throws FileAlreadyExistsException if the src is a symlink that points to dst
642   * @deprecated See {@link #renameTo(String, String)}
643   */
644  @Deprecated
645  boolean unprotectedRenameTo(String src, String dst, long timestamp)
646    throws QuotaExceededException, UnresolvedLinkException, 
647    FileAlreadyExistsException {
648    assert hasWriteLock();
649    INode[] srcInodes = rootDir.getExistingPathINodes(src, false);
650    INode srcInode = srcInodes[srcInodes.length-1];
651    
652    // check the validation of the source
653    if (srcInode == null) {
654      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
655          + "failed to rename " + src + " to " + dst
656          + " because source does not exist");
657      return false;
658    } 
659    if (srcInodes.length == 1) {
660      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
661          +"failed to rename "+src+" to "+dst+ " because source is the root");
662      return false;
663    }
664    if (isDir(dst)) {
665      dst += Path.SEPARATOR + new Path(src).getName();
666    }
667    
668    // check the validity of the destination
669    if (dst.equals(src)) {
670      return true;
671    }
672    if (srcInode.isLink() && 
673        dst.equals(((INodeSymlink)srcInode).getLinkValue())) {
674      throw new FileAlreadyExistsException(
675          "Cannot rename symlink "+src+" to its target "+dst);
676    }
677    
678    // dst cannot be directory or a file under src
679    if (dst.startsWith(src) && 
680        dst.charAt(src.length()) == Path.SEPARATOR_CHAR) {
681      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
682          + "failed to rename " + src + " to " + dst
683          + " because destination starts with src");
684      return false;
685    }
686    
687    byte[][] dstComponents = INode.getPathComponents(dst);
688    INode[] dstInodes = new INode[dstComponents.length];
689    rootDir.getExistingPathINodes(dstComponents, dstInodes, false);
690    if (dstInodes[dstInodes.length-1] != null) {
691      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
692                                   +"failed to rename "+src+" to "+dst+ 
693                                   " because destination exists");
694      return false;
695    }
696    if (dstInodes[dstInodes.length-2] == null) {
697      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
698          +"failed to rename "+src+" to "+dst+ 
699          " because destination's parent does not exist");
700      return false;
701    }
702    
703    // Ensure dst has quota to accommodate rename
704    verifyQuotaForRename(srcInodes,dstInodes);
705    
706    INode dstChild = null;
707    INode srcChild = null;
708    String srcChildName = null;
709    try {
710      // remove src
711      srcChild = removeChild(srcInodes, srcInodes.length-1);
712      if (srcChild == null) {
713        NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
714            + "failed to rename " + src + " to " + dst
715            + " because the source can not be removed");
716        return false;
717      }
718      srcChildName = srcChild.getLocalName();
719      srcChild.setLocalName(dstComponents[dstInodes.length-1]);
720      
721      // add src to the destination
722      dstChild = addChildNoQuotaCheck(dstInodes, dstInodes.length - 1,
723          srcChild, UNKNOWN_DISK_SPACE);
724      if (dstChild != null) {
725        srcChild = null;
726        if (NameNode.stateChangeLog.isDebugEnabled()) {
727          NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedRenameTo: " 
728              + src + " is renamed to " + dst);
729        }
730        // update modification time of dst and the parent of src
731        srcInodes[srcInodes.length-2].setModificationTime(timestamp);
732        dstInodes[dstInodes.length-2].setModificationTime(timestamp);
733        // update moved leases with new filename
734        getFSNamesystem().unprotectedChangeLease(src, dst);        
735        return true;
736      }
737    } finally {
738      if (dstChild == null && srcChild != null) {
739        // put it back
740        srcChild.setLocalName(srcChildName);
741        addChildNoQuotaCheck(srcInodes, srcInodes.length - 1, srcChild, 
742            UNKNOWN_DISK_SPACE);
743      }
744    }
745    NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
746        +"failed to rename "+src+" to "+dst);
747    return false;
748  }
749
750  /**
751   * Rename src to dst.
752   * See {@link DistributedFileSystem#rename(Path, Path, Options.Rename...)}
753   * for details related to rename semantics and exceptions.
754   * 
755   * @param src source path
756   * @param dst destination path
757   * @param timestamp modification time
758   * @param options Rename options
759   */
760  boolean unprotectedRenameTo(String src, String dst, long timestamp,
761      Options.Rename... options) throws FileAlreadyExistsException,
762      FileNotFoundException, ParentNotDirectoryException,
763      QuotaExceededException, UnresolvedLinkException, IOException {
764    assert hasWriteLock();
765    boolean overwrite = false;
766    if (null != options) {
767      for (Rename option : options) {
768        if (option == Rename.OVERWRITE) {
769          overwrite = true;
770        }
771      }
772    }
773    String error = null;
774    final INode[] srcInodes = rootDir.getExistingPathINodes(src, false);
775    final INode srcInode = srcInodes[srcInodes.length - 1];
776    // validate source
777    if (srcInode == null) {
778      error = "rename source " + src + " is not found.";
779      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
780          + error);
781      throw new FileNotFoundException(error);
782    }
783    if (srcInodes.length == 1) {
784      error = "rename source cannot be the root";
785      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
786          + error);
787      throw new IOException(error);
788    }
789
790    // validate the destination
791    if (dst.equals(src)) {
792      throw new FileAlreadyExistsException(
793          "The source "+src+" and destination "+dst+" are the same");
794    }
795    if (srcInode.isLink() && 
796        dst.equals(((INodeSymlink)srcInode).getLinkValue())) {
797      throw new FileAlreadyExistsException(
798          "Cannot rename symlink "+src+" to its target "+dst);
799    }
800    // dst cannot be a directory or a file under src
801    if (dst.startsWith(src) && 
802        dst.charAt(src.length()) == Path.SEPARATOR_CHAR) {
803      error = "Rename destination " + dst
804          + " is a directory or file under source " + src;
805      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
806          + error);
807      throw new IOException(error);
808    }
809    final byte[][] dstComponents = INode.getPathComponents(dst);
810    final INode[] dstInodes = new INode[dstComponents.length];
811    rootDir.getExistingPathINodes(dstComponents, dstInodes, false);
812    INode dstInode = dstInodes[dstInodes.length - 1];
813    if (dstInodes.length == 1) {
814      error = "rename destination cannot be the root";
815      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
816          + error);
817      throw new IOException(error);
818    }
819    if (dstInode != null) { // Destination exists
820      // It's OK to rename a file to a symlink and vice versa
821      if (dstInode.isDirectory() != srcInode.isDirectory()) {
822        error = "Source " + src + " and destination " + dst
823            + " must both be directories";
824        NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
825            + error);
826        throw new IOException(error);
827      }
828      if (!overwrite) { // If destination exists, overwrite flag must be true
829        error = "rename destination " + dst + " already exists";
830        NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
831            + error);
832        throw new FileAlreadyExistsException(error);
833      }
834      List<INode> children = dstInode.isDirectory() ? 
835          ((INodeDirectory) dstInode).getChildrenRaw() : null;
836      if (children != null && children.size() != 0) {
837        error = "rename cannot overwrite non empty destination directory "
838            + dst;
839        NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
840            + error);
841        throw new IOException(error);
842      }
843    }
844    if (dstInodes[dstInodes.length - 2] == null) {
845      error = "rename destination parent " + dst + " not found.";
846      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
847          + error);
848      throw new FileNotFoundException(error);
849    }
850    if (!dstInodes[dstInodes.length - 2].isDirectory()) {
851      error = "rename destination parent " + dst + " is a file.";
852      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
853          + error);
854      throw new ParentNotDirectoryException(error);
855    }
856
857    // Ensure dst has quota to accommodate rename
858    verifyQuotaForRename(srcInodes, dstInodes);
859    INode removedSrc = removeChild(srcInodes, srcInodes.length - 1);
860    if (removedSrc == null) {
861      error = "Failed to rename " + src + " to " + dst
862          + " because the source can not be removed";
863      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
864          + error);
865      throw new IOException(error);
866    }
867    final String srcChildName = removedSrc.getLocalName();
868    String dstChildName = null;
869    INode removedDst = null;
870    try {
871      if (dstInode != null) { // dst exists remove it
872        removedDst = removeChild(dstInodes, dstInodes.length - 1);
873        dstChildName = removedDst.getLocalName();
874      }
875
876      INode dstChild = null;
877      removedSrc.setLocalName(dstComponents[dstInodes.length - 1]);
878      // add src as dst to complete rename
879      dstChild = addChildNoQuotaCheck(dstInodes, dstInodes.length - 1,
880          removedSrc, UNKNOWN_DISK_SPACE);
881
882      int filesDeleted = 0;
883      if (dstChild != null) {
884        removedSrc = null;
885        if (NameNode.stateChangeLog.isDebugEnabled()) {
886          NameNode.stateChangeLog.debug(
887              "DIR* FSDirectory.unprotectedRenameTo: " + src
888              + " is renamed to " + dst);
889        }
890        srcInodes[srcInodes.length - 2].setModificationTime(timestamp);
891        dstInodes[dstInodes.length - 2].setModificationTime(timestamp);
892        // update moved lease with new filename
893        getFSNamesystem().unprotectedChangeLease(src, dst);
894
895        // Collect the blocks and remove the lease for previous dst
896        if (removedDst != null) {
897          INode rmdst = removedDst;
898          removedDst = null;
899          List<Block> collectedBlocks = new ArrayList<Block>();
900          filesDeleted = rmdst.collectSubtreeBlocksAndClear(collectedBlocks);
901          getFSNamesystem().removePathAndBlocks(src, collectedBlocks);
902        }
903        return filesDeleted >0;
904      }
905    } finally {
906      if (removedSrc != null) {
907        // Rename failed - restore src
908        removedSrc.setLocalName(srcChildName);
909        addChildNoQuotaCheck(srcInodes, srcInodes.length - 1, removedSrc, 
910            UNKNOWN_DISK_SPACE);
911      }
912      if (removedDst != null) {
913        // Rename failed - restore dst
914        removedDst.setLocalName(dstChildName);
915        addChildNoQuotaCheck(dstInodes, dstInodes.length - 1, removedDst, 
916            UNKNOWN_DISK_SPACE);
917      }
918    }
919    NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedRenameTo: "
920        + "failed to rename " + src + " to " + dst);
921    throw new IOException("rename from " + src + " to " + dst + " failed.");
922  }
923
924  /**
925   * Set file replication
926   * 
927   * @param src file name
928   * @param replication new replication
929   * @param oldReplication old replication - output parameter
930   * @return array of file blocks
931   * @throws QuotaExceededException
932   */
933  Block[] setReplication(String src, short replication, short[] oldReplication)
934      throws QuotaExceededException, UnresolvedLinkException {
935    waitForReady();
936    Block[] fileBlocks = null;
937    writeLock();
938    try {
939      fileBlocks = unprotectedSetReplication(src, replication, oldReplication);
940      if (fileBlocks != null)  // log replication change
941        fsImage.getEditLog().logSetReplication(src, replication);
942      return fileBlocks;
943    } finally {
944      writeUnlock();
945    }
946  }
947
948  Block[] unprotectedSetReplication(String src, 
949                                    short replication,
950                                    short[] oldReplication
951                                    ) throws QuotaExceededException, 
952                                    UnresolvedLinkException {
953    assert hasWriteLock();
954
955    INode[] inodes = rootDir.getExistingPathINodes(src, true);
956    INode inode = inodes[inodes.length - 1];
957    if (inode == null) {
958      return null;
959    }
960    assert !inode.isLink();
961    if (inode.isDirectory()) {
962      return null;
963    }
964    INodeFile fileNode = (INodeFile)inode;
965    final short oldRepl = fileNode.getReplication();
966
967    // check disk quota
968    long dsDelta = (replication - oldRepl) * (fileNode.diskspaceConsumed()/oldRepl);
969    updateCount(inodes, inodes.length-1, 0, dsDelta, true);
970
971    fileNode.setReplication(replication);
972
973    if (oldReplication != null) {
974      oldReplication[0] = oldRepl;
975    }
976    return fileNode.getBlocks();
977  }
978
979  /**
980   * Get the blocksize of a file
981   * @param filename the filename
982   * @return the number of bytes 
983   */
984  long getPreferredBlockSize(String filename) throws UnresolvedLinkException,
985      FileNotFoundException, IOException {
986    readLock();
987    try {
988      INode inode = rootDir.getNode(filename, false);
989      if (inode == null) {
990        throw new FileNotFoundException("File does not exist: " + filename);
991      }
992      if (inode.isDirectory() || inode.isLink()) {
993        throw new IOException("Getting block size of non-file: "+ filename); 
994      }
995      return ((INodeFile)inode).getPreferredBlockSize();
996    } finally {
997      readUnlock();
998    }
999  }
1000
1001  boolean exists(String src) throws UnresolvedLinkException {
1002    src = normalizePath(src);
1003    readLock();
1004    try {
1005      INode inode = rootDir.getNode(src, false);
1006      if (inode == null) {
1007         return false;
1008      }
1009      return inode.isDirectory() || inode.isLink() 
1010        ? true 
1011        : ((INodeFile)inode).getBlocks() != null;
1012    } finally {
1013      readUnlock();
1014    }
1015  }
1016
1017  void setPermission(String src, FsPermission permission)
1018      throws FileNotFoundException, UnresolvedLinkException {
1019    writeLock();
1020    try {
1021      unprotectedSetPermission(src, permission);
1022    } finally {
1023      writeUnlock();
1024    }
1025    fsImage.getEditLog().logSetPermissions(src, permission);
1026  }
1027
1028  void unprotectedSetPermission(String src, FsPermission permissions) 
1029      throws FileNotFoundException, UnresolvedLinkException {
1030    assert hasWriteLock();
1031    INode inode = rootDir.getNode(src, true);
1032    if (inode == null) {
1033      throw new FileNotFoundException("File does not exist: " + src);
1034    }
1035    inode.setPermission(permissions);
1036  }
1037
1038  void setOwner(String src, String username, String groupname)
1039      throws FileNotFoundException, UnresolvedLinkException {
1040    writeLock();
1041    try {
1042      unprotectedSetOwner(src, username, groupname);
1043    } finally {
1044      writeUnlock();
1045    }
1046    fsImage.getEditLog().logSetOwner(src, username, groupname);
1047  }
1048
1049  void unprotectedSetOwner(String src, String username, String groupname) 
1050      throws FileNotFoundException, UnresolvedLinkException {
1051    assert hasWriteLock();
1052    INode inode = rootDir.getNode(src, true);
1053    if (inode == null) {
1054      throw new FileNotFoundException("File does not exist: " + src);
1055    }
1056    if (username != null) {
1057      inode.setUser(username);
1058    }
1059    if (groupname != null) {
1060      inode.setGroup(groupname);
1061    }
1062  }
1063
1064  /**
1065   * Concat all the blocks from srcs to trg and delete the srcs files
1066   */
1067  public void concat(String target, String [] srcs) 
1068      throws UnresolvedLinkException {
1069    writeLock();
1070    try {
1071      // actual move
1072      waitForReady();
1073      long timestamp = now();
1074      unprotectedConcat(target, srcs, timestamp);
1075      // do the commit
1076      fsImage.getEditLog().logConcat(target, srcs, timestamp);
1077    } finally {
1078      writeUnlock();
1079    }
1080  }
1081  
1082
1083  
1084  /**
1085   * Concat all the blocks from srcs to trg and delete the srcs files
1086   * @param target target file to move the blocks to
1087   * @param srcs list of file to move the blocks from
1088   * Must be public because also called from EditLogs
1089   * NOTE: - it does not update quota (not needed for concat)
1090   */
1091  public void unprotectedConcat(String target, String [] srcs, long timestamp) 
1092      throws UnresolvedLinkException {
1093    assert hasWriteLock();
1094    if (NameNode.stateChangeLog.isDebugEnabled()) {
1095      NameNode.stateChangeLog.debug("DIR* FSNamesystem.concat to "+target);
1096    }
1097    // do the move
1098    
1099    INode [] trgINodes =  getExistingPathINodes(target);
1100    INodeFile trgInode = (INodeFile) trgINodes[trgINodes.length-1];
1101    INodeDirectory trgParent = (INodeDirectory)trgINodes[trgINodes.length-2];
1102    
1103    INodeFile [] allSrcInodes = new INodeFile[srcs.length];
1104    int i = 0;
1105    int totalBlocks = 0;
1106    for(String src : srcs) {
1107      INodeFile srcInode = getFileINode(src);
1108      allSrcInodes[i++] = srcInode;
1109      totalBlocks += srcInode.blocks.length;  
1110    }
1111    trgInode.appendBlocks(allSrcInodes, totalBlocks); // copy the blocks
1112    
1113    // since we are in the same dir - we can use same parent to remove files
1114    int count = 0;
1115    for(INodeFile nodeToRemove: allSrcInodes) {
1116      if(nodeToRemove == null) continue;
1117      
1118      nodeToRemove.blocks = null;
1119      trgParent.removeChild(nodeToRemove);
1120      count++;
1121    }
1122    
1123    trgInode.setModificationTimeForce(timestamp);
1124    trgParent.setModificationTime(timestamp);
1125    // update quota on the parent directory ('count' files removed, 0 space)
1126    unprotectedUpdateCount(trgINodes, trgINodes.length-1, - count, 0);
1127  }
1128
1129  /**
1130   * Delete the target directory and collect the blocks under it
1131   * 
1132   * @param src Path of a directory to delete
1133   * @param collectedBlocks Blocks under the deleted directory
1134   * @return true on successful deletion; else false
1135   */
1136  boolean delete(String src, List<Block>collectedBlocks) 
1137    throws UnresolvedLinkException {
1138    if (NameNode.stateChangeLog.isDebugEnabled()) {
1139      NameNode.stateChangeLog.debug("DIR* FSDirectory.delete: " + src);
1140    }
1141    waitForReady();
1142    long now = now();
1143    int filesRemoved;
1144    writeLock();
1145    try {
1146      filesRemoved = unprotectedDelete(src, collectedBlocks, now);
1147    } finally {
1148      writeUnlock();
1149    }
1150    if (filesRemoved <= 0) {
1151      return false;
1152    }
1153    incrDeletedFileCount(filesRemoved);
1154    // Blocks will be deleted later by the caller of this method
1155    getFSNamesystem().removePathAndBlocks(src, null);
1156    fsImage.getEditLog().logDelete(src, now);
1157    return true;
1158  }
1159  
1160  /** Return if a directory is empty or not **/
1161  boolean isDirEmpty(String src) throws UnresolvedLinkException {
1162    boolean dirNotEmpty = true;
1163    if (!isDir(src)) {
1164      return true;
1165    }
1166    readLock();
1167    try {
1168      INode targetNode = rootDir.getNode(src, false);
1169      assert targetNode != null : "should be taken care in isDir() above";
1170      if (((INodeDirectory)targetNode).getChildren().size() != 0) {
1171        dirNotEmpty = false;
1172      }
1173    } finally {
1174      readUnlock();
1175    }
1176    return dirNotEmpty;
1177  }
1178
1179  boolean isEmpty() {
1180    try {
1181      return isDirEmpty("/");
1182    } catch (UnresolvedLinkException e) {
1183      if(NameNode.stateChangeLog.isDebugEnabled()) {
1184        NameNode.stateChangeLog.debug("/ cannot be a symlink");
1185      }
1186      assert false : "/ cannot be a symlink";
1187      return true;
1188    }
1189  }
1190
1191  /**
1192   * Delete a path from the name space
1193   * Update the count at each ancestor directory with quota
1194   * <br>
1195   * Note: This is to be used by {@link FSEditLog} only.
1196   * <br>
1197   * @param src a string representation of a path to an inode
1198   * @param mtime the time the inode is removed
1199   */ 
1200  void unprotectedDelete(String src, long mtime) 
1201    throws UnresolvedLinkException {
1202    assert hasWriteLock();
1203    List<Block> collectedBlocks = new ArrayList<Block>();
1204    int filesRemoved = unprotectedDelete(src, collectedBlocks, mtime);
1205    if (filesRemoved > 0) {
1206      getFSNamesystem().removePathAndBlocks(src, collectedBlocks);
1207    }
1208  }
1209  
1210  /**
1211   * Delete a path from the name space
1212   * Update the count at each ancestor directory with quota
1213   * @param src a string representation of a path to an inode
1214   * @param collectedBlocks blocks collected from the deleted path
1215   * @param mtime the time the inode is removed
1216   * @return the number of inodes deleted; 0 if no inodes are deleted.
1217   */ 
1218  int unprotectedDelete(String src, List<Block> collectedBlocks, 
1219      long mtime) throws UnresolvedLinkException {
1220    assert hasWriteLock();
1221    src = normalizePath(src);
1222
1223    INode[] inodes =  rootDir.getExistingPathINodes(src, false);
1224    INode targetNode = inodes[inodes.length-1];
1225
1226    if (targetNode == null) { // non-existent src
1227      if(NameNode.stateChangeLog.isDebugEnabled()) {
1228        NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: "
1229            +"failed to remove "+src+" because it does not exist");
1230      }
1231      return 0;
1232    }
1233    if (inodes.length == 1) { // src is the root
1234      NameNode.stateChangeLog.warn("DIR* FSDirectory.unprotectedDelete: " +
1235          "failed to remove " + src +
1236          " because the root is not allowed to be deleted");
1237      return 0;
1238    }
1239    int pos = inodes.length - 1;
1240    // Remove the node from the namespace
1241    targetNode = removeChild(inodes, pos);
1242    if (targetNode == null) {
1243      return 0;
1244    }
1245    // set the parent's modification time
1246    inodes[pos-1].setModificationTime(mtime);
1247    int filesRemoved = targetNode.collectSubtreeBlocksAndClear(collectedBlocks);
1248    if (NameNode.stateChangeLog.isDebugEnabled()) {
1249      NameNode.stateChangeLog.debug("DIR* FSDirectory.unprotectedDelete: "
1250          +src+" is removed");
1251    }
1252    return filesRemoved;
1253  }
1254
1255  /**
1256   * Replaces the specified inode with the specified one.
1257   */
1258  public void replaceNode(String path, INodeFile oldnode, INodeFile newnode)
1259      throws IOException, UnresolvedLinkException {    
1260    writeLock();
1261    try {
1262      //
1263      // Remove the node from the namespace 
1264      //
1265      if (!oldnode.removeNode()) {
1266        NameNode.stateChangeLog.warn("DIR* FSDirectory.replaceNode: " +
1267                                     "failed to remove " + path);
1268        throw new IOException("FSDirectory.replaceNode: " +
1269                              "failed to remove " + path);
1270      } 
1271      
1272      /* Currently oldnode and newnode are assumed to contain the same
1273       * blocks. Otherwise, blocks need to be removed from the blocksMap.
1274       */
1275      rootDir.addNode(path, newnode); 
1276
1277      int index = 0;
1278      for (BlockInfo b : newnode.getBlocks()) {
1279        BlockInfo info = getBlockManager().addINode(b, newnode);
1280        newnode.setBlock(index, info); // inode refers to the block in BlocksMap
1281        index++;
1282      }
1283    } finally {
1284      writeUnlock();
1285    }
1286  }
1287
1288  /**
1289   * Get a partial listing of the indicated directory
1290   *
1291   * @param src the directory name
1292   * @param startAfter the name to start listing after
1293   * @param needLocation if block locations are returned
1294   * @return a partial listing starting after startAfter
1295   */
1296  DirectoryListing getListing(String src, byte[] startAfter,
1297      boolean needLocation) throws UnresolvedLinkException, IOException {
1298    String srcs = normalizePath(src);
1299
1300    readLock();
1301    try {
1302      INode targetNode = rootDir.getNode(srcs, true);
1303      if (targetNode == null)
1304        return null;
1305      
1306      if (!targetNode.isDirectory()) {
1307        return new DirectoryListing(
1308            new HdfsFileStatus[]{createFileStatus(HdfsFileStatus.EMPTY_NAME,
1309                targetNode, needLocation)}, 0);
1310      }
1311      INodeDirectory dirInode = (INodeDirectory)targetNode;
1312      List<INode> contents = dirInode.getChildren();
1313      int startChild = dirInode.nextChild(startAfter);
1314      int totalNumChildren = contents.size();
1315      int numOfListing = Math.min(totalNumChildren-startChild, this.lsLimit);
1316      HdfsFileStatus listing[] = new HdfsFileStatus[numOfListing];
1317      for (int i=0; i<numOfListing; i++) {
1318        INode cur = contents.get(startChild+i);
1319        listing[i] = createFileStatus(cur.name, cur, needLocation);
1320      }
1321      return new DirectoryListing(
1322          listing, totalNumChildren-startChild-numOfListing);
1323    } finally {
1324      readUnlock();
1325    }
1326  }
1327
1328  /** Get the file info for a specific file.
1329   * @param src The string representation of the path to the file
1330   * @param resolveLink whether to throw UnresolvedLinkException 
1331   * @return object containing information regarding the file
1332   *         or null if file not found
1333   */
1334  HdfsFileStatus getFileInfo(String src, boolean resolveLink) 
1335      throws UnresolvedLinkException {
1336    String srcs = normalizePath(src);
1337    readLock();
1338    try {
1339      INode targetNode = rootDir.getNode(srcs, resolveLink);
1340      if (targetNode == null) {
1341        return null;
1342      }
1343      else {
1344        return createFileStatus(HdfsFileStatus.EMPTY_NAME, targetNode);
1345      }
1346    } finally {
1347      readUnlock();
1348    }
1349  }
1350
1351  /**
1352   * Get the blocks associated with the file.
1353   */
1354  Block[] getFileBlocks(String src) throws UnresolvedLinkException {
1355    waitForReady();
1356    readLock();
1357    try {
1358      INode targetNode = rootDir.getNode(src, false);
1359      if (targetNode == null)
1360        return null;
1361      if (targetNode.isDirectory())
1362        return null;
1363      if (targetNode.isLink()) 
1364        return null;
1365      return ((INodeFile)targetNode).getBlocks();
1366    } finally {
1367      readUnlock();
1368    }
1369  }
1370
1371  /**
1372   * Get {@link INode} associated with the file.
1373   */
1374  INodeFile getFileINode(String src) throws UnresolvedLinkException {
1375    INode inode = getINode(src);
1376    if (inode == null || inode.isDirectory())
1377      return null;
1378    assert !inode.isLink();
1379    return (INodeFile) inode;
1380  }
1381  
1382  /**
1383   * Get {@link INode} associated with the file / directory.
1384   */
1385  INode getINode(String src) throws UnresolvedLinkException {
1386    readLock();
1387    try {
1388      INode iNode = rootDir.getNode(src, true);
1389      return iNode;
1390    } finally {
1391      readUnlock();
1392    }
1393  }
1394
1395  /**
1396   * Retrieve the existing INodes along the given path.
1397   * 
1398   * @param path the path to explore
1399   * @return INodes array containing the existing INodes in the order they
1400   *         appear when following the path from the root INode to the
1401   *         deepest INodes. The array size will be the number of expected
1402   *         components in the path, and non existing components will be
1403   *         filled with null
1404   *         
1405   * @see INodeDirectory#getExistingPathINodes(byte[][], INode[])
1406   */
1407  INode[] getExistingPathINodes(String path) 
1408    throws UnresolvedLinkException {
1409    readLock();
1410    try {
1411      return rootDir.getExistingPathINodes(path, true);
1412    } finally {
1413      readUnlock();
1414    }
1415  }
1416  
1417  /**
1418   * Get the parent node of path.
1419   * 
1420   * @param path the path to explore
1421   * @return its parent node
1422   */
1423  INodeDirectory getParent(byte[][] path) 
1424    throws FileNotFoundException, UnresolvedLinkException {
1425    readLock();
1426    try {
1427      return rootDir.getParent(path);
1428    } finally {
1429      readUnlock();
1430    }
1431  }
1432  
1433  /** 
1434   * Check whether the filepath could be created
1435   */
1436  boolean isValidToCreate(String src) throws UnresolvedLinkException {
1437    String srcs = normalizePath(src);
1438    readLock();
1439    try {
1440      if (srcs.startsWith("/") && 
1441          !srcs.endsWith("/") && 
1442          rootDir.getNode(srcs, false) == null) {
1443        return true;
1444      } else {
1445        return false;
1446      }
1447    } finally {
1448      readUnlock();
1449    }
1450  }
1451
1452  /**
1453   * Check whether the path specifies a directory
1454   */
1455  boolean isDir(String src) throws UnresolvedLinkException {
1456    src = normalizePath(src);
1457    readLock();
1458    try {
1459      INode node = rootDir.getNode(src, false);
1460      return node != null && node.isDirectory();
1461    } finally {
1462      readUnlock();
1463    }
1464  }
1465
1466  /** Updates namespace and diskspace consumed for all
1467   * directories until the parent directory of file represented by path.
1468   * 
1469   * @param path path for the file.
1470   * @param nsDelta the delta change of namespace
1471   * @param dsDelta the delta change of diskspace
1472   * @throws QuotaExceededException if the new count violates any quota limit
1473   * @throws FileNotFound if path does not exist.
1474   */
1475  void updateSpaceConsumed(String path, long nsDelta, long dsDelta)
1476                                         throws QuotaExceededException,
1477                                                FileNotFoundException,
1478                                                UnresolvedLinkException {
1479    writeLock();
1480    try {
1481      INode[] inodes = rootDir.getExistingPathINodes(path, false);
1482      int len = inodes.length;
1483      if (inodes[len - 1] == null) {
1484        throw new FileNotFoundException(path + 
1485                                        " does not exist under rootDir.");
1486      }
1487      updateCount(inodes, len-1, nsDelta, dsDelta, true);
1488    } finally {
1489      writeUnlock();
1490    }
1491  }
1492  
1493  /** update count of each inode with quota
1494   * 
1495   * @param inodes an array of inodes on a path
1496   * @param numOfINodes the number of inodes to update starting from index 0
1497   * @param nsDelta the delta change of namespace
1498   * @param dsDelta the delta change of diskspace
1499   * @param checkQuota if true then check if quota is exceeded
1500   * @throws QuotaExceededException if the new count violates any quota limit
1501   */
1502  private void updateCount(INode[] inodes, int numOfINodes, 
1503                           long nsDelta, long dsDelta, boolean checkQuota)
1504                           throws QuotaExceededException {
1505    assert hasWriteLock();
1506    if (!ready) {
1507      //still initializing. do not check or update quotas.
1508      return;
1509    }
1510    if (numOfINodes>inodes.length) {
1511      numOfINodes = inodes.length;
1512    }
1513    if (checkQuota) {
1514      verifyQuota(inodes, numOfINodes, nsDelta, dsDelta, null);
1515    }
1516    for(int i = 0; i < numOfINodes; i++) {
1517      if (inodes[i].isQuotaSet()) { // a directory with quota
1518        INodeDirectoryWithQuota node =(INodeDirectoryWithQuota)inodes[i]; 
1519        node.updateNumItemsInTree(nsDelta, dsDelta);
1520      }
1521    }
1522  }
1523  
1524  /** 
1525   * update quota of each inode and check to see if quota is exceeded. 
1526   * See {@link #updateCount(INode[], int, long, long, boolean)}
1527   */ 
1528  private void updateCountNoQuotaCheck(INode[] inodes, int numOfINodes, 
1529                           long nsDelta, long dsDelta) {
1530    assert hasWriteLock();
1531    try {
1532      updateCount(inodes, numOfINodes, nsDelta, dsDelta, false);
1533    } catch (QuotaExceededException e) {
1534      NameNode.LOG.warn("FSDirectory.updateCountNoQuotaCheck - unexpected ", e);
1535    }
1536  }
1537  
1538  /**
1539   * updates quota without verification
1540   * callers responsibility is to make sure quota is not exceeded
1541   * @param inodes
1542   * @param numOfINodes
1543   * @param nsDelta
1544   * @param dsDelta
1545   */
1546   void unprotectedUpdateCount(INode[] inodes, int numOfINodes, 
1547                                      long nsDelta, long dsDelta) {
1548     assert hasWriteLock();
1549    for(int i=0; i < numOfINodes; i++) {
1550      if (inodes[i].isQuotaSet()) { // a directory with quota
1551        INodeDirectoryWithQuota node =(INodeDirectoryWithQuota)inodes[i]; 
1552        node.unprotectedUpdateNumItemsInTree(nsDelta, dsDelta);
1553      }
1554    }
1555  }
1556  
1557  /** Return the name of the path represented by inodes at [0, pos] */
1558  private static String getFullPathName(INode[] inodes, int pos) {
1559    StringBuilder fullPathName = new StringBuilder();
1560    if (inodes[0].isRoot()) {
1561      if (pos == 0) return Path.SEPARATOR;
1562    } else {
1563      fullPathName.append(inodes[0].getLocalName());
1564    }
1565    
1566    for (int i=1; i<=pos; i++) {
1567      fullPathName.append(Path.SEPARATOR_CHAR).append(inodes[i].getLocalName());
1568    }
1569    return fullPathName.toString();
1570  }
1571
1572  /** Return the full path name of the specified inode */
1573  static String getFullPathName(INode inode) {
1574    // calculate the depth of this inode from root
1575    int depth = 0;
1576    for (INode i = inode; i != null; i = i.parent) {
1577      depth++;
1578    }
1579    INode[] inodes = new INode[depth];
1580
1581    // fill up the inodes in the path from this inode to root
1582    for (int i = 0; i < depth; i++) {
1583      if (inode == null) {
1584        NameNode.stateChangeLog.warn("Could not get full path."
1585            + " Corresponding file might have deleted already.");
1586        return null;
1587      }
1588      inodes[depth-i-1] = inode;
1589      inode = inode.parent;
1590    }
1591    return getFullPathName(inodes, depth-1);
1592  }
1593  
1594  /**
1595   * Create a directory 
1596   * If ancestor directories do not exist, automatically create them.
1597
1598   * @param src string representation of the path to the directory
1599   * @param permissions the permission of the directory
1600   * @param isAutocreate if the permission of the directory should inherit
1601   *                          from its parent or not. u+wx is implicitly added to
1602   *                          the automatically created directories, and to the
1603   *                          given directory if inheritPermission is true
1604   * @param now creation time
1605   * @return true if the operation succeeds false otherwise
1606   * @throws FileNotFoundException if an ancestor or itself is a file
1607   * @throws QuotaExceededException if directory creation violates 
1608   *                                any quota limit
1609   * @throws UnresolvedLinkException if a symlink is encountered in src.                      
1610   */
1611  boolean mkdirs(String src, PermissionStatus permissions,
1612      boolean inheritPermission, long now)
1613      throws FileAlreadyExistsException, QuotaExceededException, 
1614             UnresolvedLinkException {
1615    src = normalizePath(src);
1616    String[] names = INode.getPathNames(src);
1617    byte[][] components = INode.getPathComponents(names);
1618    INode[] inodes = new INode[components.length];
1619    final int lastInodeIndex = inodes.length - 1;
1620
1621    writeLock();
1622    try {
1623      rootDir.getExistingPathINodes(components, inodes, false);
1624
1625      // find the index of the first null in inodes[]
1626      StringBuilder pathbuilder = new StringBuilder();
1627      int i = 1;
1628      for(; i < inodes.length && inodes[i] != null; i++) {
1629        pathbuilder.append(Path.SEPARATOR + names[i]);
1630        if (!inodes[i].isDirectory()) {
1631          throw new FileAlreadyExistsException("Parent path is not a directory: "
1632              + pathbuilder+ " "+inodes[i].getLocalName());
1633        }
1634      }
1635
1636      // default to creating parent dirs with the given perms
1637      PermissionStatus parentPermissions = permissions;
1638
1639      // if not inheriting and it's the last inode, there's no use in
1640      // computing perms that won't be used
1641      if (inheritPermission || (i < lastInodeIndex)) {
1642        // if inheriting (ie. creating a file or symlink), use the parent dir,
1643        // else the supplied permissions
1644        // NOTE: the permissions of the auto-created directories violate posix
1645        FsPermission parentFsPerm = inheritPermission
1646            ? inodes[i-1].getFsPermission() : permissions.getPermission();
1647        
1648        // ensure that the permissions allow user write+execute
1649        if (!parentFsPerm.getUserAction().implies(FsAction.WRITE_EXECUTE)) {
1650          parentFsPerm = new FsPermission(
1651              parentFsPerm.getUserAction().or(FsAction.WRITE_EXECUTE),
1652              parentFsPerm.getGroupAction(),
1653              parentFsPerm.getOtherAction()
1654          );
1655        }
1656        
1657        if (!parentPermissions.getPermission().equals(parentFsPerm)) {
1658          parentPermissions = new PermissionStatus(
1659              parentPermissions.getUserName(),
1660              parentPermissions.getGroupName(),
1661              parentFsPerm
1662          );
1663          // when inheriting, use same perms for entire path
1664          if (inheritPermission) permissions = parentPermissions;
1665        }
1666      }
1667      
1668      // create directories beginning from the first null index
1669      for(; i < inodes.length; i++) {
1670        pathbuilder.append(Path.SEPARATOR + names[i]);
1671        String cur = pathbuilder.toString();
1672        unprotectedMkdir(inodes, i, components[i],
1673            (i < lastInodeIndex) ? parentPermissions : permissions, now);
1674        if (inodes[i] == null) {
1675          return false;
1676        }
1677        // Directory creation also count towards FilesCreated
1678        // to match count of FilesDeleted metric.
1679        if (getFSNamesystem() != null)
1680          NameNode.getNameNodeMetrics().incrFilesCreated();
1681        fsImage.getEditLog().logMkDir(cur, inodes[i]);
1682        if(NameNode.stateChangeLog.isDebugEnabled()) {
1683          NameNode.stateChangeLog.debug(
1684              "DIR* FSDirectory.mkdirs: created directory " + cur);
1685        }
1686      }
1687    } finally {
1688      writeUnlock();
1689    }
1690    return true;
1691  }
1692
1693  /**
1694   */
1695  INode unprotectedMkdir(String src, PermissionStatus permissions,
1696                          long timestamp) throws QuotaExceededException,
1697                          UnresolvedLinkException {
1698    assert hasWriteLock();
1699    byte[][] components = INode.getPathComponents(src);
1700    INode[] inodes = new INode[components.length];
1701
1702    rootDir.getExistingPathINodes(components, inodes, false);
1703    unprotectedMkdir(inodes, inodes.length-1, components[inodes.length-1],
1704        permissions, timestamp);
1705    return inodes[inodes.length-1];
1706  }
1707
1708  /** create a directory at index pos.
1709   * The parent path to the directory is at [0, pos-1].
1710   * All ancestors exist. Newly created one stored at index pos.
1711   */
1712  private void unprotectedMkdir(INode[] inodes, int pos,
1713      byte[] name, PermissionStatus permission,
1714      long timestamp) throws QuotaExceededException {
1715    assert hasWriteLock();
1716    inodes[pos] = addChild(inodes, pos, 
1717        new INodeDirectory(name, permission, timestamp),
1718        -1);
1719  }
1720  
1721  /** Add a node child to the namespace. The full path name of the node is src.
1722   * childDiskspace should be -1, if unknown. 
1723   * QuotaExceededException is thrown if it violates quota limit */
1724  private <T extends INode> T addNode(String src, T child, 
1725        long childDiskspace) 
1726  throws QuotaExceededException, UnresolvedLinkException {
1727    byte[][] components = INode.getPathComponents(src);
1728    byte[] path = components[components.length-1];
1729    child.setLocalName(path);
1730    cacheName(child);
1731    INode[] inodes = new INode[components.length];
1732    writeLock();
1733    try {
1734      rootDir.getExistingPathINodes(components, inodes, false);
1735      return addChild(inodes, inodes.length-1, child, childDiskspace);
1736    } finally {
1737      writeUnlock();
1738    }
1739  }
1740
1741  /**
1742   * Verify quota for adding or moving a new INode with required 
1743   * namespace and diskspace to a given position.
1744   *  
1745   * @param inodes INodes corresponding to a path
1746   * @param pos position where a new INode will be added
1747   * @param nsDelta needed namespace
1748   * @param dsDelta needed diskspace
1749   * @param commonAncestor Last node in inodes array that is a common ancestor
1750   *          for a INode that is being moved from one location to the other.
1751   *          Pass null if a node is not being moved.
1752   * @throws QuotaExceededException if quota limit is exceeded.
1753   */
1754  private void verifyQuota(INode[] inodes, int pos, long nsDelta, long dsDelta,
1755      INode commonAncestor) throws QuotaExceededException {
1756    if (!ready) {
1757      // Do not check quota if edits log is still being processed
1758      return;
1759    }
1760    if (nsDelta <= 0 && dsDelta <= 0) {
1761      // if quota is being freed or not being consumed
1762      return;
1763    }
1764    if (pos>inodes.length) {
1765      pos = inodes.length;
1766    }
1767    int i = pos - 1;
1768    try {
1769      // check existing components in the path  
1770      for(; i >= 0; i--) {
1771        if (commonAncestor == inodes[i]) {
1772          // Moving an existing node. Stop checking for quota when common
1773          // ancestor is reached
1774          return;
1775        }
1776        if (inodes[i].isQuotaSet()) { // a directory with quota
1777          INodeDirectoryWithQuota node =(INodeDirectoryWithQuota)inodes[i]; 
1778          node.verifyQuota(nsDelta, dsDelta);
1779        }
1780      }
1781    } catch (QuotaExceededException e) {
1782      e.setPathName(getFullPathName(inodes, i));
1783      throw e;
1784    }
1785  }
1786  
1787  /**
1788   * Verify quota for rename operation where srcInodes[srcInodes.length-1] moves
1789   * dstInodes[dstInodes.length-1]
1790   * 
1791   * @param srcInodes directory from where node is being moved.
1792   * @param dstInodes directory to where node is moved to.
1793   * @throws QuotaExceededException if quota limit is exceeded.
1794   */
1795  private void verifyQuotaForRename(INode[] srcInodes, INode[]dstInodes)
1796      throws QuotaExceededException {
1797    if (!ready) {
1798      // Do not check quota if edits log is still being processed
1799      return;
1800    }
1801    INode srcInode = srcInodes[srcInodes.length - 1];
1802    INode commonAncestor = null;
1803    for(int i =0;srcInodes[i] == dstInodes[i]; i++) {
1804      commonAncestor = srcInodes[i];
1805    }
1806    INode.DirCounts srcCounts = new INode.DirCounts();
1807    srcInode.spaceConsumedInTree(srcCounts);
1808    long nsDelta = srcCounts.getNsCount();
1809    long dsDelta = srcCounts.getDsCount();
1810    
1811    // Reduce the required quota by dst that is being removed
1812    INode dstInode = dstInodes[dstInodes.length - 1];
1813    if (dstInode != null) {
1814      INode.DirCounts dstCounts = new INode.DirCounts();
1815      dstInode.spaceConsumedInTree(dstCounts);
1816      nsDelta -= dstCounts.getNsCount();
1817      dsDelta -= dstCounts.getDsCount();
1818    }
1819    verifyQuota(dstInodes, dstInodes.length - 1, nsDelta, dsDelta,
1820        commonAncestor);
1821  }
1822  
1823  /**
1824   * Verify that filesystem limit constraints are not violated
1825   * @throws PathComponentTooLongException child's name is too long
1826   * @throws MaxDirectoryItemsExceededException items per directory is exceeded
1827   */
1828  protected <T extends INode> void verifyFsLimits(INode[] pathComponents,
1829      int pos, T child) throws FSLimitException {
1830    boolean includeChildName = false;
1831    try {
1832      if (maxComponentLength != 0) {
1833        int length = child.getLocalName().length();
1834        if (length > maxComponentLength) {
1835          includeChildName = true;
1836          throw new PathComponentTooLongException(maxComponentLength, length);
1837        }
1838      }
1839      if (maxDirItems != 0) {
1840        INodeDirectory parent = (INodeDirectory)pathComponents[pos-1];
1841        int count = parent.getChildren().size();
1842        if (count >= maxDirItems) {
1843          throw new MaxDirectoryItemsExceededException(maxDirItems, count);
1844        }
1845      }
1846    } catch (FSLimitException e) {
1847      String badPath = getFullPathName(pathComponents, pos-1);
1848      if (includeChildName) {
1849        badPath += Path.SEPARATOR + child.getLocalName();
1850      }
1851      e.setPathName(badPath);
1852      // Do not throw if edits log is still being processed
1853      if (ready) throw(e);
1854      // log pre-existing paths that exceed limits
1855      NameNode.LOG.error("FSDirectory.verifyFsLimits - " + e.getLocalizedMessage());
1856    }
1857  }
1858  
1859  /** Add a node child to the inodes at index pos. 
1860   * Its ancestors are stored at [0, pos-1]. 
1861   * QuotaExceededException is thrown if it violates quota limit */
1862  private <T extends INode> T addChild(INode[] pathComponents, int pos,
1863      T child, long childDiskspace,
1864      boolean checkQuota) throws QuotaExceededException {
1865        // The filesystem limits are not really quotas, so this check may appear
1866        // odd.  It's because a rename operation deletes the src, tries to add
1867        // to the dest, if that fails, re-adds the src from whence it came.
1868        // The rename code disables the quota when it's restoring to the
1869        // original location becase a quota violation would cause the the item
1870        // to go "poof".  The fs limits must be bypassed for the same reason.
1871    if (checkQuota) {
1872      verifyFsLimits(pathComponents, pos, child);
1873    }
1874    
1875    INode.DirCounts counts = new INode.DirCounts();
1876    child.spaceConsumedInTree(counts);
1877    if (childDiskspace < 0) {
1878      childDiskspace = counts.getDsCount();
1879    }
1880    updateCount(pathComponents, pos, counts.getNsCount(), childDiskspace,
1881        checkQuota);
1882    if (pathComponents[pos-1] == null) {
1883      throw new NullPointerException("Panic: parent does not exist");
1884    }
1885    T addedNode = ((INodeDirectory)pathComponents[pos-1]).addChild(
1886        child, true);
1887    if (addedNode == null) {
1888      updateCount(pathComponents, pos, -counts.getNsCount(), 
1889          -childDiskspace, true);
1890    }
1891    return addedNode;
1892  }
1893
1894  private <T extends INode> T addChild(INode[] pathComponents, int pos,
1895      T child, long childDiskspace)
1896      throws QuotaExceededException {
1897    return addChild(pathComponents, pos, child, childDiskspace, true);
1898  }
1899  
1900  private <T extends INode> T addChildNoQuotaCheck(INode[] pathComponents,
1901      int pos, T child, long childDiskspace) {
1902    T inode = null;
1903    try {
1904      inode = addChild(pathComponents, pos, child, childDiskspace, false);
1905    } catch (QuotaExceededException e) {
1906      NameNode.LOG.warn("FSDirectory.addChildNoQuotaCheck - unexpected", e); 
1907    }
1908    return inode;
1909  }
1910  
1911  /** Remove an inode at index pos from the namespace.
1912   * Its ancestors are stored at [0, pos-1].
1913   * Count of each ancestor with quota is also updated.
1914   * Return the removed node; null if the removal fails.
1915   */
1916  private INode removeChild(INode[] pathComponents, int pos) {
1917    INode removedNode = 
1918      ((INodeDirectory)pathComponents[pos-1]).removeChild(pathComponents[pos]);
1919    if (removedNode != null) {
1920      INode.DirCounts counts = new INode.DirCounts();
1921      removedNode.spaceConsumedInTree(counts);
1922      updateCountNoQuotaCheck(pathComponents, pos,
1923                  -counts.getNsCount(), -counts.getDsCount());
1924    }
1925    return removedNode;
1926  }
1927  
1928  /**
1929   */
1930  String normalizePath(String src) {
1931    if (src.length() > 1 && src.endsWith("/")) {
1932      src = src.substring(0, src.length() - 1);
1933    }
1934    return src;
1935  }
1936
1937  ContentSummary getContentSummary(String src) 
1938    throws FileNotFoundException, UnresolvedLinkException {
1939    String srcs = normalizePath(src);
1940    readLock();
1941    try {
1942      INode targetNode = rootDir.getNode(srcs, false);
1943      if (targetNode == null) {
1944        throw new FileNotFoundException("File does not exist: " + srcs);
1945      }
1946      else {
1947        // Make it relinquish locks everytime contentCountLimit entries are
1948        // processed. 0 means disabled. I.e. blocking for the entire duration.
1949        return targetNode.computeAndConvertContentSummary(
1950            new ContentSummaryComputationContext(this, getFSNamesystem(),
1951            contentCountLimit));
1952      }
1953    } finally {
1954      readUnlock();
1955    }
1956  }
1957
1958  /** Update the count of each directory with quota in the namespace
1959   * A directory's count is defined as the total number inodes in the tree
1960   * rooted at the directory.
1961   * 
1962   * This is an update of existing state of the filesystem and does not
1963   * throw QuotaExceededException.
1964   */
1965  void updateCountForINodeWithQuota() {
1966    updateCountForINodeWithQuota(rootDir, new INode.DirCounts(), 
1967                                 new ArrayList<INode>(50));
1968  }
1969  
1970  /** 
1971   * Update the count of the directory if it has a quota and return the count
1972   * 
1973   * This does not throw a QuotaExceededException. This is just an update
1974   * of of existing state and throwing QuotaExceededException does not help
1975   * with fixing the state, if there is a problem.
1976   * 
1977   * @param dir the root of the tree that represents the directory
1978   * @param counters counters for name space and disk space
1979   * @param nodesInPath INodes for the each of components in the path.
1980   */
1981  private static void updateCountForINodeWithQuota(INodeDirectory dir, 
1982                                               INode.DirCounts counts,
1983                                               ArrayList<INode> nodesInPath) {
1984    long parentNamespace = counts.nsCount;
1985    long parentDiskspace = counts.dsCount;
1986    
1987    counts.nsCount = 1L;//for self. should not call node.spaceConsumedInTree()
1988    counts.dsCount = 0L;
1989    
1990    /* We don't need nodesInPath if we could use 'parent' field in 
1991     * INode. using 'parent' is not currently recommended. */
1992    nodesInPath.add(dir);
1993
1994    for (INode child : dir.getChildren()) {
1995      if (child.isDirectory()) {
1996        updateCountForINodeWithQuota((INodeDirectory)child, 
1997                                     counts, nodesInPath);
1998      } else if (child.isLink()) {
1999        counts.nsCount += 1;
2000      } else { // reduce recursive calls
2001        counts.nsCount += 1;
2002        counts.dsCount += ((INodeFile)child).diskspaceConsumed();
2003      }
2004    }
2005      
2006    if (dir.isQuotaSet()) {
2007      ((INodeDirectoryWithQuota)dir).setSpaceConsumed(counts.nsCount,
2008                                                      counts.dsCount);
2009
2010      // check if quota is violated for some reason.
2011      if ((dir.getNsQuota() >= 0 && counts.nsCount > dir.getNsQuota()) ||
2012          (dir.getDsQuota() >= 0 && counts.dsCount > dir.getDsQuota())) {
2013
2014        // can only happen because of a software bug. the bug should be fixed.
2015        StringBuilder path = new StringBuilder(512);
2016        for (INode n : nodesInPath) {
2017          path.append('/');
2018          path.append(n.getLocalName());
2019        }
2020        
2021        NameNode.LOG.warn("Quota violation in image for " + path + 
2022                          " (Namespace quota : " + dir.getNsQuota() +
2023                          " consumed : " + counts.nsCount + ")" +
2024                          " (Diskspace quota : " + dir.getDsQuota() +
2025                          " consumed : " + counts.dsCount + ").");
2026      }            
2027    }
2028      
2029    // pop 
2030    nodesInPath.remove(nodesInPath.size()-1);
2031    
2032    counts.nsCount += parentNamespace;
2033    counts.dsCount += parentDiskspace;
2034  }
2035  
2036  /**
2037   * See {@link ClientProtocol#setQuota(String, long, long)} for the contract.
2038   * Sets quota for for a directory.
2039   * @returns INodeDirectory if any of the quotas have changed. null other wise.
2040   * @throws FileNotFoundException if the path does not exist or is a file
2041   * @throws QuotaExceededException if the directory tree size is 
2042   *                                greater than the given quota
2043   * @throws UnresolvedLinkException if a symlink is encountered in src.
2044   */
2045  INodeDirectory unprotectedSetQuota(String src, long nsQuota, long dsQuota)
2046    throws FileNotFoundException, QuotaExceededException, 
2047      UnresolvedLinkException {
2048    assert hasWriteLock();
2049    // sanity check
2050    if ((nsQuota < 0 && nsQuota != HdfsConstants.QUOTA_DONT_SET && 
2051         nsQuota < HdfsConstants.QUOTA_RESET) || 
2052        (dsQuota < 0 && dsQuota != HdfsConstants.QUOTA_DONT_SET && 
2053          dsQuota < HdfsConstants.QUOTA_RESET)) {
2054      throw new IllegalArgumentException("Illegal value for nsQuota or " +
2055                                         "dsQuota : " + nsQuota + " and " +
2056                                         dsQuota);
2057    }
2058    
2059    String srcs = normalizePath(src);
2060
2061    INode[] inodes = rootDir.getExistingPathINodes(src, true);
2062    INode targetNode = inodes[inodes.length-1];
2063    if (targetNode == null) {
2064      throw new FileNotFoundException("Directory does not exist: " + srcs);
2065    } else if (!targetNode.isDirectory()) {
2066      throw new FileNotFoundException("Cannot set quota on a file: " + srcs);  
2067    } else if (targetNode.isRoot() && nsQuota == HdfsConstants.QUOTA_RESET) {
2068      throw new IllegalArgumentException("Cannot clear namespace quota on root.");
2069    } else { // a directory inode
2070      INodeDirectory dirNode = (INodeDirectory)targetNode;
2071      long oldNsQuota = dirNode.getNsQuota();
2072      long oldDsQuota = dirNode.getDsQuota();
2073      if (nsQuota == HdfsConstants.QUOTA_DONT_SET) {
2074        nsQuota = oldNsQuota;
2075      }
2076      if (dsQuota == HdfsConstants.QUOTA_DONT_SET) {
2077        dsQuota = oldDsQuota;
2078      }        
2079
2080      if (dirNode instanceof INodeDirectoryWithQuota) { 
2081        // a directory with quota; so set the quota to the new value
2082        ((INodeDirectoryWithQuota)dirNode).setQuota(nsQuota, dsQuota);
2083        if (!dirNode.isQuotaSet()) {
2084          // will not come here for root because root's nsQuota is always set
2085          INodeDirectory newNode = new INodeDirectory(dirNode);
2086          INodeDirectory parent = (INodeDirectory)inodes[inodes.length-2];
2087          dirNode = newNode;
2088          parent.replaceChild(newNode);
2089        }
2090      } else {
2091        // a non-quota directory; so replace it with a directory with quota
2092        INodeDirectoryWithQuota newNode = 
2093          new INodeDirectoryWithQuota(nsQuota, dsQuota, dirNode);
2094        // non-root directory node; parent != null
2095        INodeDirectory parent = (INodeDirectory)inodes[inodes.length-2];
2096        dirNode = newNode;
2097        parent.replaceChild(newNode);
2098      }
2099      return (oldNsQuota != nsQuota || oldDsQuota != dsQuota) ? dirNode : null;
2100    }
2101  }
2102  
2103  /**
2104   * See {@link ClientProtocol#setQuota(String, long, long)} for the 
2105   * contract.
2106   * @see #unprotectedSetQuota(String, long, long)
2107   */
2108  void setQuota(String src, long nsQuota, long dsQuota) 
2109    throws FileNotFoundException, QuotaExceededException,
2110    UnresolvedLinkException { 
2111    writeLock();
2112    try {
2113      INodeDirectory dir = unprotectedSetQuota(src, nsQuota, dsQuota);
2114      if (dir != null) {
2115        fsImage.getEditLog().logSetQuota(src, dir.getNsQuota(), 
2116                                         dir.getDsQuota());
2117      }
2118    } finally {
2119      writeUnlock();
2120    }
2121  }
2122  
2123  long totalInodes() {
2124    readLock();
2125    try {
2126      return rootDir.numItemsInTree();
2127    } finally {
2128      readUnlock();
2129    }
2130  }
2131
2132  /**
2133   * Sets the access time on the file/directory. Logs it in the transaction log.
2134   */
2135  void setTimes(String src, INode inode, long mtime, long atime, boolean force) {
2136    boolean status = false;
2137    writeLock();
2138    try {
2139      status = unprotectedSetTimes(src, inode, mtime, atime, force);
2140    } finally {
2141      writeUnlock();
2142    }
2143    if (status) {
2144      fsImage.getEditLog().logTimes(src, mtime, atime);
2145    }
2146  }
2147
2148  boolean unprotectedSetTimes(String src, long mtime, long atime, boolean force) 
2149      throws UnresolvedLinkException {
2150    assert hasWriteLock();
2151    INode inode = getINode(src);
2152    return unprotectedSetTimes(src, inode, mtime, atime, force);
2153  }
2154
2155  private boolean unprotectedSetTimes(String src, INode inode, long mtime,
2156                                      long atime, boolean force) {
2157    assert hasWriteLock();
2158    boolean status = false;
2159    if (mtime != -1) {
2160      inode.setModificationTimeForce(mtime);
2161      status = true;
2162    }
2163    if (atime != -1) {
2164      long inodeTime = inode.getAccessTime();
2165
2166      // if the last access time update was within the last precision interval, then
2167      // no need to store access time
2168      if (atime <= inodeTime + getFSNamesystem().getAccessTimePrecision() && !force) {
2169        status =  false;
2170      } else {
2171        inode.setAccessTime(atime);
2172        status = true;
2173      }
2174    } 
2175    return status;
2176  }
2177
2178  /**
2179   * Reset the entire namespace tree.
2180   */
2181  void reset() {
2182    writeLock();
2183    try {
2184      setReady(false);
2185      rootDir = new INodeDirectoryWithQuota(INodeDirectory.ROOT_NAME,
2186          getFSNamesystem().createFsOwnerPermissions(new FsPermission((short)0755)),
2187          Integer.MAX_VALUE, -1);
2188      nameCache.reset();
2189    } finally {
2190      writeUnlock();
2191    }
2192  }
2193
2194  /**
2195   * create an hdfs file status from an inode
2196   * 
2197   * @param path the local name
2198   * @param node inode
2199   * @param needLocation if block locations need to be included or not
2200   * @return a file status
2201   * @throws IOException if any error occurs
2202   */
2203  private HdfsFileStatus createFileStatus(byte[] path, INode node,
2204      boolean needLocation) throws IOException {
2205    if (needLocation) {
2206      return createLocatedFileStatus(path, node);
2207    } else {
2208      return createFileStatus(path, node);
2209    }
2210  }
2211  /**
2212   * Create FileStatus by file INode 
2213   */
2214   private HdfsFileStatus createFileStatus(byte[] path, INode node) {
2215     long size = 0;     // length is zero for directories
2216     short replication = 0;
2217     long blocksize = 0;
2218     if (node instanceof INodeFile) {
2219       INodeFile fileNode = (INodeFile)node;
2220       size = fileNode.computeFileSize(true);
2221       replication = fileNode.getReplication();
2222       blocksize = fileNode.getPreferredBlockSize();
2223     }
2224     return new HdfsFileStatus(
2225        size, 
2226        node.isDirectory(), 
2227        replication, 
2228        blocksize,
2229        node.getModificationTime(),
2230        node.getAccessTime(),
2231        node.getFsPermission(),
2232        node.getUserName(),
2233        node.getGroupName(),
2234        node.isLink() ? ((INodeSymlink)node).getSymlink() : null,
2235        path);
2236  }
2237
2238   /**
2239    * Create FileStatus with location info by file INode 
2240    */
2241    private HdfsLocatedFileStatus createLocatedFileStatus(
2242        byte[] path, INode node) throws IOException {
2243      assert hasReadLock();
2244      long size = 0;     // length is zero for directories
2245      short replication = 0;
2246      long blocksize = 0;
2247      LocatedBlocks loc = null;
2248      if (node instanceof INodeFile) {
2249        INodeFile fileNode = (INodeFile)node;
2250        size = fileNode.computeFileSize(true);
2251        replication = fileNode.getReplication();
2252        blocksize = fileNode.getPreferredBlockSize();
2253        loc = getFSNamesystem().getBlockManager().createLocatedBlocks(
2254            fileNode.getBlocks(), fileNode.computeFileSize(false),
2255            fileNode.isUnderConstruction(), 0L, size, false);
2256        if (loc==null) {
2257          loc = new LocatedBlocks();
2258        }
2259      }
2260      return new HdfsLocatedFileStatus(
2261          size, 
2262          node.isDirectory(), 
2263          replication, 
2264          blocksize,
2265          node.getModificationTime(),
2266          node.getAccessTime(),
2267          node.getFsPermission(),
2268          node.getUserName(),
2269          node.getGroupName(),
2270          node.isLink() ? ((INodeSymlink)node).getSymlink() : null,
2271          path,
2272          loc);
2273      }
2274
2275    
2276  /**
2277   * Add the given symbolic link to the fs. Record it in the edits log.
2278   */
2279  INodeSymlink addSymlink(String path, String target,
2280      PermissionStatus dirPerms, boolean createParent)
2281      throws UnresolvedLinkException, FileAlreadyExistsException,
2282      QuotaExceededException, IOException {
2283    waitForReady();
2284
2285    final long modTime = now();
2286    if (createParent) {
2287      final String parent = new Path(path).getParent().toString();
2288      if (!mkdirs(parent, dirPerms, true, modTime)) {
2289        return null;
2290      }
2291    }
2292    final String userName = dirPerms.getUserName();
2293    INodeSymlink newNode  = null;
2294    writeLock();
2295    try {
2296      newNode = unprotectedSymlink(path, target, modTime, modTime,
2297          new PermissionStatus(userName, null, FsPermission.getDefault()));
2298    } finally {
2299      writeUnlock();
2300    }
2301    if (newNode == null) {
2302      NameNode.stateChangeLog.info("DIR* FSDirectory.addSymlink: "
2303                                   +"failed to add "+path
2304                                   +" to the file system");
2305      return null;
2306    }
2307    fsImage.getEditLog().logSymlink(path, target, modTime, modTime, newNode);
2308    
2309    if(NameNode.stateChangeLog.isDebugEnabled()) {
2310      NameNode.stateChangeLog.debug("DIR* FSDirectory.addSymlink: "
2311          +path+" is added to the file system");
2312    }
2313    return newNode;
2314  }
2315
2316  /**
2317   * Add the specified path into the namespace. Invoked from edit log processing.
2318   */
2319  INodeSymlink unprotectedSymlink(String path, String target, long modTime, 
2320                                  long atime, PermissionStatus perm) 
2321      throws UnresolvedLinkException {
2322    assert hasWriteLock();
2323    INodeSymlink newNode = new INodeSymlink(target, modTime, atime, perm);
2324    try {
2325      newNode = addNode(path, newNode, UNKNOWN_DISK_SPACE);
2326    } catch (UnresolvedLinkException e) {
2327      /* All UnresolvedLinkExceptions should have been resolved by now, but we
2328       * should re-throw them in case that changes so they are not swallowed 
2329       * by catching IOException below.
2330       */
2331      throw e;
2332    } catch (IOException e) {
2333      return null;
2334    }
2335    return newNode;
2336  }
2337  
2338  /**
2339   * Caches frequently used file names to reuse file name objects and
2340   * reduce heap size.
2341   */
2342  void cacheName(INode inode) {
2343    // Name is cached only for files
2344    if (inode.isDirectory() || inode.isLink()) {
2345      return;
2346    }
2347    ByteArray name = new ByteArray(inode.getLocalNameBytes());
2348    name = nameCache.put(name);
2349    if (name != null) {
2350      inode.setLocalName(name.getBytes());
2351    }
2352  }
2353
2354}