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.io.PrintStream; 021import java.io.PrintWriter; 022import java.io.StringWriter; 023import java.util.List; 024 025import org.apache.commons.logging.Log; 026import org.apache.commons.logging.LogFactory; 027import org.apache.hadoop.classification.InterfaceAudience; 028import org.apache.hadoop.fs.ContentSummary; 029import org.apache.hadoop.fs.Path; 030import org.apache.hadoop.fs.permission.FsPermission; 031import org.apache.hadoop.fs.permission.PermissionStatus; 032import org.apache.hadoop.hdfs.protocol.HdfsConstants; 033import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite; 034import org.apache.hadoop.hdfs.DFSUtil; 035import org.apache.hadoop.hdfs.protocol.Block; 036import org.apache.hadoop.hdfs.protocol.QuotaExceededException; 037import org.apache.hadoop.hdfs.server.namenode.INodeReference.DstReference; 038import org.apache.hadoop.hdfs.server.namenode.INodeReference.WithName; 039import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 040import org.apache.hadoop.hdfs.util.Diff; 041import org.apache.hadoop.util.ChunkedArrayList; 042import org.apache.hadoop.util.StringUtils; 043 044import com.google.common.annotations.VisibleForTesting; 045import com.google.common.base.Preconditions; 046 047/** 048 * We keep an in-memory representation of the file/block hierarchy. 049 * This is a base INode class containing common fields for file and 050 * directory inodes. 051 */ 052@InterfaceAudience.Private 053public abstract class INode implements INodeAttributes, Diff.Element<byte[]> { 054 public static final Log LOG = LogFactory.getLog(INode.class); 055 056 /** parent is either an {@link INodeDirectory} or an {@link INodeReference}.*/ 057 private INode parent = null; 058 059 INode(INode parent) { 060 this.parent = parent; 061 } 062 063 /** Get inode id */ 064 public abstract long getId(); 065 066 /** 067 * Check whether this is the root inode. 068 */ 069 final boolean isRoot() { 070 return getLocalNameBytes().length == 0; 071 } 072 073 /** Get the {@link PermissionStatus} */ 074 abstract PermissionStatus getPermissionStatus(int snapshotId); 075 076 /** The same as getPermissionStatus(null). */ 077 final PermissionStatus getPermissionStatus() { 078 return getPermissionStatus(Snapshot.CURRENT_STATE_ID); 079 } 080 081 /** 082 * @param snapshotId 083 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 084 * from the given snapshot; otherwise, get the result from the 085 * current inode. 086 * @return user name 087 */ 088 abstract String getUserName(int snapshotId); 089 090 /** The same as getUserName(Snapshot.CURRENT_STATE_ID). */ 091 @Override 092 public final String getUserName() { 093 return getUserName(Snapshot.CURRENT_STATE_ID); 094 } 095 096 /** Set user */ 097 abstract void setUser(String user); 098 099 /** Set user */ 100 final INode setUser(String user, int latestSnapshotId) { 101 recordModification(latestSnapshotId); 102 setUser(user); 103 return this; 104 } 105 /** 106 * @param snapshotId 107 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 108 * from the given snapshot; otherwise, get the result from the 109 * current inode. 110 * @return group name 111 */ 112 abstract String getGroupName(int snapshotId); 113 114 /** The same as getGroupName(Snapshot.CURRENT_STATE_ID). */ 115 @Override 116 public final String getGroupName() { 117 return getGroupName(Snapshot.CURRENT_STATE_ID); 118 } 119 120 /** Set group */ 121 abstract void setGroup(String group); 122 123 /** Set group */ 124 final INode setGroup(String group, int latestSnapshotId) { 125 recordModification(latestSnapshotId); 126 setGroup(group); 127 return this; 128 } 129 130 /** 131 * @param snapshotId 132 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 133 * from the given snapshot; otherwise, get the result from the 134 * current inode. 135 * @return permission. 136 */ 137 abstract FsPermission getFsPermission(int snapshotId); 138 139 /** The same as getFsPermission(Snapshot.CURRENT_STATE_ID). */ 140 @Override 141 public final FsPermission getFsPermission() { 142 return getFsPermission(Snapshot.CURRENT_STATE_ID); 143 } 144 145 /** Set the {@link FsPermission} of this {@link INode} */ 146 abstract void setPermission(FsPermission permission); 147 148 /** Set the {@link FsPermission} of this {@link INode} */ 149 INode setPermission(FsPermission permission, int latestSnapshotId) { 150 recordModification(latestSnapshotId); 151 setPermission(permission); 152 return this; 153 } 154 155 abstract AclFeature getAclFeature(int snapshotId); 156 157 @Override 158 public final AclFeature getAclFeature() { 159 return getAclFeature(Snapshot.CURRENT_STATE_ID); 160 } 161 162 abstract void addAclFeature(AclFeature aclFeature); 163 164 final INode addAclFeature(AclFeature aclFeature, int latestSnapshotId) { 165 recordModification(latestSnapshotId); 166 addAclFeature(aclFeature); 167 return this; 168 } 169 170 abstract void removeAclFeature(); 171 172 final INode removeAclFeature(int latestSnapshotId) { 173 recordModification(latestSnapshotId); 174 removeAclFeature(); 175 return this; 176 } 177 178 /** 179 * @param snapshotId 180 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 181 * from the given snapshot; otherwise, get the result from the 182 * current inode. 183 * @return XAttrFeature 184 */ 185 abstract XAttrFeature getXAttrFeature(int snapshotId); 186 187 @Override 188 public final XAttrFeature getXAttrFeature() { 189 return getXAttrFeature(Snapshot.CURRENT_STATE_ID); 190 } 191 192 /** 193 * Set <code>XAttrFeature</code> 194 */ 195 abstract void addXAttrFeature(XAttrFeature xAttrFeature); 196 197 final INode addXAttrFeature(XAttrFeature xAttrFeature, int latestSnapshotId) { 198 recordModification(latestSnapshotId); 199 addXAttrFeature(xAttrFeature); 200 return this; 201 } 202 203 /** 204 * Remove <code>XAttrFeature</code> 205 */ 206 abstract void removeXAttrFeature(); 207 208 final INode removeXAttrFeature(int lastestSnapshotId) { 209 recordModification(lastestSnapshotId); 210 removeXAttrFeature(); 211 return this; 212 } 213 214 /** 215 * @return if the given snapshot id is {@link Snapshot#CURRENT_STATE_ID}, 216 * return this; otherwise return the corresponding snapshot inode. 217 */ 218 public INodeAttributes getSnapshotINode(final int snapshotId) { 219 return this; 220 } 221 222 /** Is this inode in the latest snapshot? */ 223 public final boolean isInLatestSnapshot(final int latestSnapshotId) { 224 if (latestSnapshotId == Snapshot.CURRENT_STATE_ID || latestSnapshotId == Snapshot.NO_SNAPSHOT_ID) { 225 return false; 226 } 227 // if parent is a reference node, parent must be a renamed node. We can 228 // stop the check at the reference node. 229 if (parent != null && parent.isReference()) { 230 return true; 231 } 232 final INodeDirectory parentDir = getParent(); 233 if (parentDir == null) { // root 234 return true; 235 } 236 if (!parentDir.isInLatestSnapshot(latestSnapshotId)) { 237 return false; 238 } 239 final INode child = parentDir.getChild(getLocalNameBytes(), latestSnapshotId); 240 if (this == child) { 241 return true; 242 } 243 return child != null && child.isReference() && 244 this == child.asReference().getReferredINode(); 245 } 246 247 /** @return true if the given inode is an ancestor directory of this inode. */ 248 public final boolean isAncestorDirectory(final INodeDirectory dir) { 249 for(INodeDirectory p = getParent(); p != null; p = p.getParent()) { 250 if (p == dir) { 251 return true; 252 } 253 } 254 return false; 255 } 256 257 /** 258 * When {@link #recordModification} is called on a referred node, 259 * this method tells which snapshot the modification should be 260 * associated with: the snapshot that belongs to the SRC tree of the rename 261 * operation, or the snapshot belonging to the DST tree. 262 * 263 * @param latestInDst 264 * id of the latest snapshot in the DST tree above the reference node 265 * @return True: the modification should be recorded in the snapshot that 266 * belongs to the SRC tree. False: the modification should be 267 * recorded in the snapshot that belongs to the DST tree. 268 */ 269 public final boolean shouldRecordInSrcSnapshot(final int latestInDst) { 270 Preconditions.checkState(!isReference()); 271 272 if (latestInDst == Snapshot.CURRENT_STATE_ID) { 273 return true; 274 } 275 INodeReference withCount = getParentReference(); 276 if (withCount != null) { 277 int dstSnapshotId = withCount.getParentReference().getDstSnapshotId(); 278 if (dstSnapshotId != Snapshot.CURRENT_STATE_ID 279 && dstSnapshotId >= latestInDst) { 280 return true; 281 } 282 } 283 return false; 284 } 285 286 /** 287 * This inode is being modified. The previous version of the inode needs to 288 * be recorded in the latest snapshot. 289 * 290 * @param latestSnapshotId The id of the latest snapshot that has been taken. 291 * Note that it is {@link Snapshot#CURRENT_STATE_ID} 292 * if no snapshots have been taken. 293 */ 294 abstract void recordModification(final int latestSnapshotId); 295 296 /** Check whether it's a reference. */ 297 public boolean isReference() { 298 return false; 299 } 300 301 /** Cast this inode to an {@link INodeReference}. */ 302 public INodeReference asReference() { 303 throw new IllegalStateException("Current inode is not a reference: " 304 + this.toDetailString()); 305 } 306 307 /** 308 * Check whether it's a file. 309 */ 310 public boolean isFile() { 311 return false; 312 } 313 314 /** Cast this inode to an {@link INodeFile}. */ 315 public INodeFile asFile() { 316 throw new IllegalStateException("Current inode is not a file: " 317 + this.toDetailString()); 318 } 319 320 /** 321 * Check whether it's a directory 322 */ 323 public boolean isDirectory() { 324 return false; 325 } 326 327 /** Cast this inode to an {@link INodeDirectory}. */ 328 public INodeDirectory asDirectory() { 329 throw new IllegalStateException("Current inode is not a directory: " 330 + this.toDetailString()); 331 } 332 333 /** 334 * Check whether it's a symlink 335 */ 336 public boolean isSymlink() { 337 return false; 338 } 339 340 /** Cast this inode to an {@link INodeSymlink}. */ 341 public INodeSymlink asSymlink() { 342 throw new IllegalStateException("Current inode is not a symlink: " 343 + this.toDetailString()); 344 } 345 346 /** 347 * Clean the subtree under this inode and collect the blocks from the descents 348 * for further block deletion/update. The current inode can either resides in 349 * the current tree or be stored as a snapshot copy. 350 * 351 * <pre> 352 * In general, we have the following rules. 353 * 1. When deleting a file/directory in the current tree, we have different 354 * actions according to the type of the node to delete. 355 * 356 * 1.1 The current inode (this) is an {@link INodeFile}. 357 * 1.1.1 If {@code prior} is null, there is no snapshot taken on ancestors 358 * before. Thus we simply destroy (i.e., to delete completely, no need to save 359 * snapshot copy) the current INode and collect its blocks for further 360 * cleansing. 361 * 1.1.2 Else do nothing since the current INode will be stored as a snapshot 362 * copy. 363 * 364 * 1.2 The current inode is an {@link INodeDirectory}. 365 * 1.2.1 If {@code prior} is null, there is no snapshot taken on ancestors 366 * before. Similarly, we destroy the whole subtree and collect blocks. 367 * 1.2.2 Else do nothing with the current INode. Recursively clean its 368 * children. 369 * 370 * 1.3 The current inode is a file with snapshot. 371 * Call recordModification(..) to capture the current states. 372 * Mark the INode as deleted. 373 * 374 * 1.4 The current inode is an {@link INodeDirectory} with snapshot feature. 375 * Call recordModification(..) to capture the current states. 376 * Destroy files/directories created after the latest snapshot 377 * (i.e., the inodes stored in the created list of the latest snapshot). 378 * Recursively clean remaining children. 379 * 380 * 2. When deleting a snapshot. 381 * 2.1 To clean {@link INodeFile}: do nothing. 382 * 2.2 To clean {@link INodeDirectory}: recursively clean its children. 383 * 2.3 To clean INodeFile with snapshot: delete the corresponding snapshot in 384 * its diff list. 385 * 2.4 To clean {@link INodeDirectory} with snapshot: delete the corresponding 386 * snapshot in its diff list. Recursively clean its children. 387 * </pre> 388 * 389 * @param bsps 390 * block storage policy suite to calculate intended storage type usage 391 * @param snapshotId 392 * The id of the snapshot to delete. 393 * {@link Snapshot#CURRENT_STATE_ID} means to delete the current 394 * file/directory. 395 * @param priorSnapshotId 396 * The id of the latest snapshot before the to-be-deleted snapshot. 397 * When deleting a current inode, this parameter captures the latest 398 * snapshot. 399 * @param collectedBlocks 400 * blocks collected from the descents for further block 401 * deletion/update will be added to the given map. 402 * @param removedINodes 403 * INodes collected from the descents for further cleaning up of 404 * inodeMap 405 * @return quota usage delta when deleting a snapshot 406 */ 407 public abstract QuotaCounts cleanSubtree(final BlockStoragePolicySuite bsps, 408 final int snapshotId, 409 int priorSnapshotId, BlocksMapUpdateInfo collectedBlocks, 410 List<INode> removedINodes); 411 412 /** 413 * Destroy self and clear everything! If the INode is a file, this method 414 * collects its blocks for further block deletion. If the INode is a 415 * directory, the method goes down the subtree and collects blocks from the 416 * descents, and clears its parent/children references as well. The method 417 * also clears the diff list if the INode contains snapshot diff list. 418 * 419 * @param bsps 420 * block storage policy suite to calculate intended storage type usage 421 * This is needed because INodeReference#destroyAndCollectBlocks() needs 422 * to call INode#cleanSubtree(), which calls INode#computeQuotaUsage(). 423 * @param collectedBlocks 424 * blocks collected from the descents for further block 425 * deletion/update will be added to this map. 426 * @param removedINodes 427 * INodes collected from the descents for further cleaning up of 428 * inodeMap 429 */ 430 public abstract void destroyAndCollectBlocks( 431 BlockStoragePolicySuite bsps, 432 BlocksMapUpdateInfo collectedBlocks, List<INode> removedINodes); 433 434 /** Compute {@link ContentSummary}. Blocking call */ 435 public final ContentSummary computeContentSummary(BlockStoragePolicySuite bsps) { 436 return computeAndConvertContentSummary( 437 new ContentSummaryComputationContext(bsps)); 438 } 439 440 /** 441 * Compute {@link ContentSummary}. 442 */ 443 public final ContentSummary computeAndConvertContentSummary( 444 ContentSummaryComputationContext summary) { 445 ContentCounts counts = computeContentSummary(summary).getCounts(); 446 final QuotaCounts q = getQuotaCounts(); 447 return new ContentSummary.Builder(). 448 length(counts.getLength()). 449 fileCount(counts.getFileCount() + counts.getSymlinkCount()). 450 directoryCount(counts.getDirectoryCount()). 451 quota(q.getNameSpace()). 452 spaceConsumed(counts.getStoragespace()). 453 spaceQuota(q.getStorageSpace()). 454 typeConsumed(counts.getTypeSpaces()). 455 typeQuota(q.getTypeSpaces().asArray()). 456 build(); 457 } 458 459 /** 460 * Count subtree content summary with a {@link ContentCounts}. 461 * 462 * @param summary the context object holding counts for the subtree. 463 * @return The same objects as summary. 464 */ 465 public abstract ContentSummaryComputationContext computeContentSummary( 466 ContentSummaryComputationContext summary); 467 468 469 /** 470 * Check and add namespace/storagespace/storagetype consumed to itself and the ancestors. 471 * @throws QuotaExceededException if quote is violated. 472 */ 473 public void addSpaceConsumed(QuotaCounts counts, boolean verify) 474 throws QuotaExceededException { 475 addSpaceConsumed2Parent(counts, verify); 476 } 477 478 /** 479 * Check and add namespace/storagespace/storagetype consumed to itself and the ancestors. 480 * @throws QuotaExceededException if quote is violated. 481 */ 482 void addSpaceConsumed2Parent(QuotaCounts counts, boolean verify) 483 throws QuotaExceededException { 484 if (parent != null) { 485 parent.addSpaceConsumed(counts, verify); 486 } 487 } 488 489 /** 490 * Get the quota set for this inode 491 * @return the quota counts. The count is -1 if it is not set. 492 */ 493 public QuotaCounts getQuotaCounts() { 494 return new QuotaCounts.Builder(). 495 nameSpace(HdfsConstants.QUOTA_RESET). 496 storageSpace(HdfsConstants.QUOTA_RESET). 497 typeSpaces(HdfsConstants.QUOTA_RESET). 498 build(); 499 } 500 501 public final boolean isQuotaSet() { 502 final QuotaCounts qc = getQuotaCounts(); 503 return qc.anyNsSsCountGreaterOrEqual(0) || qc.anyTypeSpaceCountGreaterOrEqual(0); 504 } 505 506 /** 507 * Count subtree {@link Quota#NAMESPACE} and {@link Quota#STORAGESPACE} usages. 508 * Entry point for FSDirectory where blockStoragePolicyId is given its initial 509 * value. 510 */ 511 public final QuotaCounts computeQuotaUsage(BlockStoragePolicySuite bsps) { 512 final byte storagePolicyId = isSymlink() ? 513 BlockStoragePolicySuite.ID_UNSPECIFIED : getStoragePolicyID(); 514 return computeQuotaUsage(bsps, storagePolicyId, 515 new QuotaCounts.Builder().build(), true, Snapshot.CURRENT_STATE_ID); 516 } 517 518 /** 519 * Count subtree {@link Quota#NAMESPACE} and {@link Quota#STORAGESPACE} usages. 520 * 521 * With the existence of {@link INodeReference}, the same inode and its 522 * subtree may be referred by multiple {@link WithName} nodes and a 523 * {@link DstReference} node. To avoid circles while quota usage computation, 524 * we have the following rules: 525 * 526 * <pre> 527 * 1. For a {@link DstReference} node, since the node must be in the current 528 * tree (or has been deleted as the end point of a series of rename 529 * operations), we compute the quota usage of the referred node (and its 530 * subtree) in the regular manner, i.e., including every inode in the current 531 * tree and in snapshot copies, as well as the size of diff list. 532 * 533 * 2. For a {@link WithName} node, since the node must be in a snapshot, we 534 * only count the quota usage for those nodes that still existed at the 535 * creation time of the snapshot associated with the {@link WithName} node. 536 * We do not count in the size of the diff list. 537 * <pre> 538 * 539 * @param bsps Block storage policy suite to calculate intended storage type usage 540 * @param blockStoragePolicyId block storage policy id of the current INode 541 * @param counts The subtree counts for returning. 542 * @param useCache Whether to use cached quota usage. Note that 543 * {@link WithName} node never uses cache for its subtree. 544 * @param lastSnapshotId {@link Snapshot#CURRENT_STATE_ID} indicates the 545 * computation is in the current tree. Otherwise the id 546 * indicates the computation range for a 547 * {@link WithName} node. 548 * @return The same objects as the counts parameter. 549 */ 550 public abstract QuotaCounts computeQuotaUsage( 551 BlockStoragePolicySuite bsps, byte blockStoragePolicyId, 552 QuotaCounts counts, boolean useCache, int lastSnapshotId); 553 554 public final QuotaCounts computeQuotaUsage( 555 BlockStoragePolicySuite bsps, QuotaCounts counts, boolean useCache) { 556 final byte storagePolicyId = isSymlink() ? 557 BlockStoragePolicySuite.ID_UNSPECIFIED : getStoragePolicyID(); 558 return computeQuotaUsage(bsps, storagePolicyId, counts, 559 useCache, Snapshot.CURRENT_STATE_ID); 560 } 561 562 /** 563 * @return null if the local name is null; otherwise, return the local name. 564 */ 565 public final String getLocalName() { 566 final byte[] name = getLocalNameBytes(); 567 return name == null? null: DFSUtil.bytes2String(name); 568 } 569 570 @Override 571 public final byte[] getKey() { 572 return getLocalNameBytes(); 573 } 574 575 /** 576 * Set local file name 577 */ 578 public abstract void setLocalName(byte[] name); 579 580 public String getFullPathName() { 581 // Get the full path name of this inode. 582 if (isRoot()) { 583 return Path.SEPARATOR; 584 } 585 // compute size of needed bytes for the path 586 int idx = 0; 587 for (INode inode = this; inode != null; inode = inode.getParent()) { 588 // add component + delimiter (if not tail component) 589 idx += inode.getLocalNameBytes().length + (inode != this ? 1 : 0); 590 } 591 byte[] path = new byte[idx]; 592 for (INode inode = this; inode != null; inode = inode.getParent()) { 593 if (inode != this) { 594 path[--idx] = Path.SEPARATOR_CHAR; 595 } 596 byte[] name = inode.getLocalNameBytes(); 597 idx -= name.length; 598 System.arraycopy(name, 0, path, idx, name.length); 599 } 600 return DFSUtil.bytes2String(path); 601 } 602 603 public byte[][] getPathComponents() { 604 int n = 0; 605 for (INode inode = this; inode != null; inode = inode.getParent()) { 606 n++; 607 } 608 byte[][] components = new byte[n][]; 609 for (INode inode = this; inode != null; inode = inode.getParent()) { 610 components[--n] = inode.getLocalNameBytes(); 611 } 612 return components; 613 } 614 615 @Override 616 public String toString() { 617 return getLocalName(); 618 } 619 620 @VisibleForTesting 621 public final String getObjectString() { 622 return getClass().getSimpleName() + "@" 623 + Integer.toHexString(super.hashCode()); 624 } 625 626 /** @return a string description of the parent. */ 627 @VisibleForTesting 628 public final String getParentString() { 629 final INodeReference parentRef = getParentReference(); 630 if (parentRef != null) { 631 return "parentRef=" + parentRef.getLocalName() + "->"; 632 } else { 633 final INodeDirectory parentDir = getParent(); 634 if (parentDir != null) { 635 return "parentDir=" + parentDir.getLocalName() + "/"; 636 } else { 637 return "parent=null"; 638 } 639 } 640 } 641 642 @VisibleForTesting 643 public String toDetailString() { 644 return toString() + "(" + getObjectString() + "), " + getParentString(); 645 } 646 647 /** @return the parent directory */ 648 public final INodeDirectory getParent() { 649 return parent == null? null 650 : parent.isReference()? getParentReference().getParent(): parent.asDirectory(); 651 } 652 653 /** 654 * @return the parent as a reference if this is a referred inode; 655 * otherwise, return null. 656 */ 657 public INodeReference getParentReference() { 658 return parent == null || !parent.isReference()? null: (INodeReference)parent; 659 } 660 661 /** Set parent directory */ 662 public final void setParent(INodeDirectory parent) { 663 this.parent = parent; 664 } 665 666 /** Set container. */ 667 public final void setParentReference(INodeReference parent) { 668 this.parent = parent; 669 } 670 671 /** Clear references to other objects. */ 672 public void clear() { 673 setParent(null); 674 } 675 676 /** 677 * @param snapshotId 678 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 679 * from the given snapshot; otherwise, get the result from the 680 * current inode. 681 * @return modification time. 682 */ 683 abstract long getModificationTime(int snapshotId); 684 685 /** The same as getModificationTime(Snapshot.CURRENT_STATE_ID). */ 686 @Override 687 public final long getModificationTime() { 688 return getModificationTime(Snapshot.CURRENT_STATE_ID); 689 } 690 691 /** Update modification time if it is larger than the current value. */ 692 public abstract INode updateModificationTime(long mtime, int latestSnapshotId); 693 694 /** Set the last modification time of inode. */ 695 public abstract void setModificationTime(long modificationTime); 696 697 /** Set the last modification time of inode. */ 698 public final INode setModificationTime(long modificationTime, 699 int latestSnapshotId) { 700 recordModification(latestSnapshotId); 701 setModificationTime(modificationTime); 702 return this; 703 } 704 705 /** 706 * @param snapshotId 707 * if it is not {@link Snapshot#CURRENT_STATE_ID}, get the result 708 * from the given snapshot; otherwise, get the result from the 709 * current inode. 710 * @return access time 711 */ 712 abstract long getAccessTime(int snapshotId); 713 714 /** The same as getAccessTime(Snapshot.CURRENT_STATE_ID). */ 715 @Override 716 public final long getAccessTime() { 717 return getAccessTime(Snapshot.CURRENT_STATE_ID); 718 } 719 720 /** 721 * Set last access time of inode. 722 */ 723 public abstract void setAccessTime(long accessTime); 724 725 /** 726 * Set last access time of inode. 727 */ 728 public final INode setAccessTime(long accessTime, int latestSnapshotId) { 729 recordModification(latestSnapshotId); 730 setAccessTime(accessTime); 731 return this; 732 } 733 734 /** 735 * @return the latest block storage policy id of the INode. Specifically, 736 * if a storage policy is directly specified on the INode then return the ID 737 * of that policy. Otherwise follow the latest parental path and return the 738 * ID of the first specified storage policy. 739 */ 740 public abstract byte getStoragePolicyID(); 741 742 /** 743 * @return the storage policy directly specified on the INode. Return 744 * {@link BlockStoragePolicySuite#ID_UNSPECIFIED} if no policy has 745 * been specified. 746 */ 747 public abstract byte getLocalStoragePolicyID(); 748 749 /** 750 * Get the storage policy ID while computing quota usage 751 * @param parentStoragePolicyId the storage policy ID of the parent directory 752 * @return the storage policy ID of this INode. Note that for an 753 * {@link INodeSymlink} we return {@link BlockStoragePolicySuite#ID_UNSPECIFIED} 754 * instead of throwing Exception 755 */ 756 public byte getStoragePolicyIDForQuota(byte parentStoragePolicyId) { 757 byte localId = isSymlink() ? 758 BlockStoragePolicySuite.ID_UNSPECIFIED : getLocalStoragePolicyID(); 759 return localId != BlockStoragePolicySuite.ID_UNSPECIFIED ? 760 localId : parentStoragePolicyId; 761 } 762 763 /** 764 * Breaks {@code path} into components. 765 * @return array of byte arrays each of which represents 766 * a single path component. 767 */ 768 @VisibleForTesting 769 public static byte[][] getPathComponents(String path) { 770 checkAbsolutePath(path); 771 return DFSUtil.getPathComponents(path); 772 } 773 774 /** 775 * Splits an absolute {@code path} into an array of path components. 776 * @throws AssertionError if the given path is invalid. 777 * @return array of path components. 778 */ 779 public static String[] getPathNames(String path) { 780 checkAbsolutePath(path); 781 return StringUtils.split(path, Path.SEPARATOR_CHAR); 782 } 783 784 private static void checkAbsolutePath(final String path) { 785 if (path == null || !path.startsWith(Path.SEPARATOR)) { 786 throw new AssertionError("Absolute path required"); 787 } 788 } 789 790 @Override 791 public final int compareTo(byte[] bytes) { 792 return DFSUtil.compareBytes(getLocalNameBytes(), bytes); 793 } 794 795 @Override 796 public final boolean equals(Object that) { 797 if (this == that) { 798 return true; 799 } 800 if (that == null || !(that instanceof INode)) { 801 return false; 802 } 803 return getId() == ((INode) that).getId(); 804 } 805 806 @Override 807 public final int hashCode() { 808 long id = getId(); 809 return (int)(id^(id>>>32)); 810 } 811 812 /** 813 * Dump the subtree starting from this inode. 814 * @return a text representation of the tree. 815 */ 816 @VisibleForTesting 817 public final StringBuffer dumpTreeRecursively() { 818 final StringWriter out = new StringWriter(); 819 dumpTreeRecursively(new PrintWriter(out, true), new StringBuilder(), 820 Snapshot.CURRENT_STATE_ID); 821 return out.getBuffer(); 822 } 823 824 @VisibleForTesting 825 public final void dumpTreeRecursively(PrintStream out) { 826 out.println(dumpTreeRecursively().toString()); 827 } 828 829 /** 830 * Dump tree recursively. 831 * @param prefix The prefix string that each line should print. 832 */ 833 @VisibleForTesting 834 public void dumpTreeRecursively(PrintWriter out, StringBuilder prefix, 835 int snapshotId) { 836 out.print(prefix); 837 out.print(" "); 838 final String name = getLocalName(); 839 out.print(name.isEmpty()? "/": name); 840 out.print(" ("); 841 out.print(getObjectString()); 842 out.print("), "); 843 out.print(getParentString()); 844 out.print(", " + getPermissionStatus(snapshotId)); 845 } 846 847 /** 848 * Information used for updating the blocksMap when deleting files. 849 */ 850 public static class BlocksMapUpdateInfo { 851 /** 852 * The list of blocks that need to be removed from blocksMap 853 */ 854 private final List<Block> toDeleteList; 855 856 public BlocksMapUpdateInfo() { 857 toDeleteList = new ChunkedArrayList<Block>(); 858 } 859 860 /** 861 * @return The list of blocks that need to be removed from blocksMap 862 */ 863 public List<Block> getToDeleteList() { 864 return toDeleteList; 865 } 866 867 /** 868 * Add a to-be-deleted block into the 869 * {@link BlocksMapUpdateInfo#toDeleteList} 870 * @param toDelete the to-be-deleted block 871 */ 872 public void addDeleteBlock(Block toDelete) { 873 assert toDelete != null : "toDelete is null"; 874 toDeleteList.add(toDelete); 875 } 876 877 public void removeDeleteBlock(Block block) { 878 assert block != null : "block is null"; 879 toDeleteList.remove(block); 880 } 881 882 /** 883 * Clear {@link BlocksMapUpdateInfo#toDeleteList} 884 */ 885 public void clear() { 886 toDeleteList.clear(); 887 } 888 } 889 890 /** 891 * INode feature such as {@link FileUnderConstructionFeature} 892 * and {@link DirectoryWithQuotaFeature}. 893 */ 894 public interface Feature { 895 } 896}