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 java.util.Arrays;
021import java.util.List;
022
023import org.apache.hadoop.fs.ContentSummary;
024import org.apache.hadoop.fs.Path;
025import org.apache.hadoop.fs.permission.FsPermission;
026import org.apache.hadoop.fs.permission.PermissionStatus;
027import org.apache.hadoop.hdfs.DFSUtil;
028import org.apache.hadoop.hdfs.protocol.Block;
029import org.apache.hadoop.hdfs.server.blockmanagement.BlockInfo;
030import org.apache.hadoop.util.StringUtils;
031
032/**
033 * We keep an in-memory representation of the file/block hierarchy.
034 * This is a base INode class containing common fields for file and 
035 * directory inodes.
036 */
037public abstract class INode implements Comparable<byte[]>, FSInodeInfo {
038  /*
039   *  The inode name is in java UTF8 encoding; 
040   *  The name in HdfsFileStatus should keep the same encoding as this.
041   *  if this encoding is changed, implicitly getFileInfo and listStatus in
042   *  clientProtocol are changed; The decoding at the client
043   *  side should change accordingly.
044   */
045  protected byte[] name;
046  protected INodeDirectory parent;
047  protected long modificationTime;
048  protected long accessTime;
049
050  /** Simple wrapper for two counters : 
051   *  nsCount (namespace consumed) and dsCount (diskspace consumed).
052   */
053  static class DirCounts {
054    long nsCount = 0;
055    long dsCount = 0;
056    
057    /** returns namespace count */
058    long getNsCount() {
059      return nsCount;
060    }
061    /** returns diskspace count */
062    long getDsCount() {
063      return dsCount;
064    }
065  }
066  
067  //Only updated by updatePermissionStatus(...).
068  //Other codes should not modify it.
069  private long permission;
070
071  private static enum PermissionStatusFormat {
072    MODE(0, 16),
073    GROUP(MODE.OFFSET + MODE.LENGTH, 25),
074    USER(GROUP.OFFSET + GROUP.LENGTH, 23);
075
076    final int OFFSET;
077    final int LENGTH; //bit length
078    final long MASK;
079
080    PermissionStatusFormat(int offset, int length) {
081      OFFSET = offset;
082      LENGTH = length;
083      MASK = ((-1L) >>> (64 - LENGTH)) << OFFSET;
084    }
085
086    long retrieve(long record) {
087      return (record & MASK) >>> OFFSET;
088    }
089
090    long combine(long bits, long record) {
091      return (record & ~MASK) | (bits << OFFSET);
092    }
093  }
094
095  protected INode() {
096    name = null;
097    parent = null;
098    modificationTime = 0;
099    accessTime = 0;
100  }
101
102  INode(PermissionStatus permissions, long mTime, long atime) {
103    this.name = null;
104    this.parent = null;
105    this.modificationTime = mTime;
106    setAccessTime(atime);
107    setPermissionStatus(permissions);
108  }
109
110  protected INode(String name, PermissionStatus permissions) {
111    this(permissions, 0L, 0L);
112    setLocalName(name);
113  }
114  
115  /** copy constructor
116   * 
117   * @param other Other node to be copied
118   */
119  INode(INode other) {
120    setLocalName(other.getLocalName());
121    this.parent = other.getParent();
122    setPermissionStatus(other.getPermissionStatus());
123    setModificationTime(other.getModificationTime());
124    setAccessTime(other.getAccessTime());
125  }
126
127  /**
128   * Check whether this is the root inode.
129   */
130  boolean isRoot() {
131    return name.length == 0;
132  }
133
134  /** Set the {@link PermissionStatus} */
135  protected void setPermissionStatus(PermissionStatus ps) {
136    setUser(ps.getUserName());
137    setGroup(ps.getGroupName());
138    setPermission(ps.getPermission());
139  }
140  /** Get the {@link PermissionStatus} */
141  protected PermissionStatus getPermissionStatus() {
142    return new PermissionStatus(getUserName(),getGroupName(),getFsPermission());
143  }
144  private synchronized void updatePermissionStatus(
145      PermissionStatusFormat f, long n) {
146    permission = f.combine(n, permission);
147  }
148  /** Get user name */
149  public String getUserName() {
150    int n = (int)PermissionStatusFormat.USER.retrieve(permission);
151    return SerialNumberManager.INSTANCE.getUser(n);
152  }
153  /** Set user */
154  protected void setUser(String user) {
155    int n = SerialNumberManager.INSTANCE.getUserSerialNumber(user);
156    updatePermissionStatus(PermissionStatusFormat.USER, n);
157  }
158  /** Get group name */
159  public String getGroupName() {
160    int n = (int)PermissionStatusFormat.GROUP.retrieve(permission);
161    return SerialNumberManager.INSTANCE.getGroup(n);
162  }
163  /** Set group */
164  protected void setGroup(String group) {
165    int n = SerialNumberManager.INSTANCE.getGroupSerialNumber(group);
166    updatePermissionStatus(PermissionStatusFormat.GROUP, n);
167  }
168  /** Get the {@link FsPermission} */
169  public FsPermission getFsPermission() {
170    return new FsPermission(
171        (short)PermissionStatusFormat.MODE.retrieve(permission));
172  }
173  protected short getFsPermissionShort() {
174    return (short)PermissionStatusFormat.MODE.retrieve(permission);
175  }
176  /** Set the {@link FsPermission} of this {@link INode} */
177  protected void setPermission(FsPermission permission) {
178    updatePermissionStatus(PermissionStatusFormat.MODE, permission.toShort());
179  }
180
181  /**
182   * Check whether it's a directory
183   */
184  public abstract boolean isDirectory();
185
186  /**
187   * Collect all the blocks in all children of this INode.
188   * Count and return the number of files in the sub tree.
189   * Also clears references since this INode is deleted.
190   */
191  abstract int collectSubtreeBlocksAndClear(List<Block> v);
192
193  /** Compute {@link ContentSummary}. Blocking computation. */
194  public final ContentSummary computeContentSummary() {
195    return computeAndConvertContentSummary(
196        new ContentSummaryComputationContext());
197  }
198
199  /** Compute {@link ContentSummary} */
200  public final ContentSummary computeAndConvertContentSummary(
201      ContentSummaryComputationContext summary) {
202    long[] a = computeContentSummary(summary).getCounts();
203    return new ContentSummary(a[0], a[1], a[2], getNsQuota(), 
204                              a[3], getDsQuota());
205  }
206  /**
207   * @return ContentSummaryComputationContext containing
208   * content counts.
209   * 0: length, 1: file count, 2: directory count 3: disk space
210   */
211  abstract ContentSummaryComputationContext computeContentSummary(
212      ContentSummaryComputationContext summary);
213  
214  /**
215   * Get the quota set for this inode
216   * @return the quota if it is set; -1 otherwise
217   */
218  long getNsQuota() {
219    return -1;
220  }
221
222  long getDsQuota() {
223    return -1;
224  }
225  
226  boolean isQuotaSet() {
227    return getNsQuota() >= 0 || getDsQuota() >= 0;
228  }
229  
230  /**
231   * Adds total number of names and total disk space taken under 
232   * this tree to counts.
233   * Returns updated counts object.
234   */
235  abstract DirCounts spaceConsumedInTree(DirCounts counts);
236  
237  /**
238   * Get local file name
239   * @return local file name
240   */
241  String getLocalName() {
242    return DFSUtil.bytes2String(name);
243  }
244
245
246  String getLocalParentDir() {
247    INode inode = isRoot() ? this : getParent();
248    String parentDir = "";
249    if (inode != null) {
250      parentDir = inode.getFullPathName();
251    }
252    return (parentDir != null) ? parentDir : "";
253  }
254
255  /**
256   * Get local file name
257   * @return local file name
258   */
259  byte[] getLocalNameBytes() {
260    return name;
261  }
262
263  /**
264   * Set local file name
265   */
266  void setLocalName(String name) {
267    this.name = DFSUtil.string2Bytes(name);
268  }
269
270  /**
271   * Set local file name
272   */
273  void setLocalName(byte[] name) {
274    this.name = name;
275  }
276
277  /** {@inheritDoc} */
278  public String getFullPathName() {
279    // Get the full path name of this inode.
280    return FSDirectory.getFullPathName(this);
281  }
282
283  /** {@inheritDoc} */
284  public String toString() {
285    return "\"" + getFullPathName() + "\":"
286    + getUserName() + ":" + getGroupName() + ":"
287    + (isDirectory()? "d": "-") + getFsPermission();
288  }
289
290  /**
291   * Get parent directory 
292   * @return parent INode
293   */
294  INodeDirectory getParent() {
295    return this.parent;
296  }
297
298  /** 
299   * Get last modification time of inode.
300   * @return access time
301   */
302  public long getModificationTime() {
303    return this.modificationTime;
304  }
305
306  /**
307   * Set last modification time of inode.
308   */
309  void setModificationTime(long modtime) {
310    assert isDirectory();
311    if (this.modificationTime <= modtime) {
312      this.modificationTime = modtime;
313    }
314  }
315
316  /**
317   * Always set the last modification time of inode.
318   */
319  void setModificationTimeForce(long modtime) {
320    this.modificationTime = modtime;
321  }
322
323  /**
324   * Get access time of inode.
325   * @return access time
326   */
327  public long getAccessTime() {
328    return accessTime;
329  }
330
331  /**
332   * Set last access time of inode.
333   */
334  void setAccessTime(long atime) {
335    accessTime = atime;
336  }
337
338  /**
339   * Is this inode being constructed?
340   */
341  public boolean isUnderConstruction() {
342    return false;
343  }
344
345  /**
346   * Check whether it's a symlink
347   */
348  public boolean isLink() {
349    return false;
350  }
351
352  /**
353   * Breaks file path into components.
354   * @param path
355   * @return array of byte arrays each of which represents 
356   * a single path component.
357   */
358  static byte[][] getPathComponents(String path) {
359    return getPathComponents(getPathNames(path));
360  }
361
362  /** Convert strings to byte arrays for path components. */
363  static byte[][] getPathComponents(String[] strings) {
364    if (strings.length == 0) {
365      return new byte[][]{null};
366    }
367    byte[][] bytes = new byte[strings.length][];
368    for (int i = 0; i < strings.length; i++)
369      bytes[i] = DFSUtil.string2Bytes(strings[i]);
370    return bytes;
371  }
372
373  /**
374   * Splits an absolute path into an array of path components.
375   * @param path
376   * @throws AssertionError if the given path is invalid.
377   * @return array of path components.
378   */
379  static String[] getPathNames(String path) {
380    if (path == null || !path.startsWith(Path.SEPARATOR)) {
381      throw new AssertionError("Absolute path required");
382    }
383    return StringUtils.split(path, Path.SEPARATOR_CHAR);
384  }
385
386  /**
387   * Given some components, create a path name.
388   * @param components The path components
389   * @param start index
390   * @param end index
391   * @return concatenated path
392   */
393  static String constructPath(byte[][] components, int start, int end) {
394    StringBuilder buf = new StringBuilder();
395    for (int i = start; i < end; i++) {
396      buf.append(DFSUtil.bytes2String(components[i]));
397      if (i < end - 1) {
398        buf.append(Path.SEPARATOR);
399      }
400    }
401    return buf.toString();
402  }
403
404  boolean removeNode() {
405    if (parent == null) {
406      return false;
407    } else {
408      parent.removeChild(this);
409      parent = null;
410      return true;
411    }
412  }
413
414  //
415  // Comparable interface
416  //
417  public int compareTo(byte[] o) {
418    return compareBytes(name, o);
419  }
420
421  public boolean equals(Object o) {
422    if (!(o instanceof INode)) {
423      return false;
424    }
425    return Arrays.equals(this.name, ((INode)o).name);
426  }
427
428  public int hashCode() {
429    return Arrays.hashCode(this.name);
430  }
431
432  //
433  // static methods
434  //
435  /**
436   * Compare two byte arrays.
437   * 
438   * @return a negative integer, zero, or a positive integer 
439   * as defined by {@link #compareTo(byte[])}.
440   */
441  static int compareBytes(byte[] a1, byte[] a2) {
442    if (a1==a2)
443        return 0;
444    int len1 = (a1==null ? 0 : a1.length);
445    int len2 = (a2==null ? 0 : a2.length);
446    int n = Math.min(len1, len2);
447    byte b1, b2;
448    for (int i=0; i<n; i++) {
449      b1 = a1[i];
450      b2 = a2[i];
451      if (b1 != b2)
452        return b1 - b2;
453    }
454    return len1 - len2;
455  }
456  
457  /**
458   * Create an INode; the inode's name is not set yet
459   * 
460   * @param permissions permissions
461   * @param blocks blocks if a file
462   * @param symlink symblic link if a symbolic link
463   * @param replication replication factor
464   * @param modificationTime modification time
465   * @param atime access time
466   * @param nsQuota namespace quota
467   * @param dsQuota disk quota
468   * @param preferredBlockSize block size
469   * @return an inode
470   */
471  static INode newINode(PermissionStatus permissions,
472                        BlockInfo[] blocks,
473                        String symlink,
474                        short replication,
475                        long modificationTime,
476                        long atime,
477                        long nsQuota,
478                        long dsQuota,
479                        long preferredBlockSize) {
480    if (symlink.length() != 0) { // check if symbolic link
481      return new INodeSymlink(symlink, modificationTime, atime, permissions);
482    }  else if (blocks == null) { //not sym link and blocks null? directory!
483      if (nsQuota >= 0 || dsQuota >= 0) {
484        return new INodeDirectoryWithQuota(
485            permissions, modificationTime, nsQuota, dsQuota);
486      } 
487      // regular directory
488      return new INodeDirectory(permissions, modificationTime);
489    }
490    // file
491    return new INodeFile(permissions, blocks, replication,
492        modificationTime, atime, preferredBlockSize);
493  }
494}