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 */ 018 package org.apache.hadoop.hdfs.server.namenode; 019 020 import java.util.Arrays; 021 022 import org.apache.commons.logging.Log; 023 import org.apache.commons.logging.LogFactory; 024 import org.apache.hadoop.fs.Path; 025 import org.apache.hadoop.fs.UnresolvedLinkException; 026 import org.apache.hadoop.hdfs.DFSUtil; 027 import org.apache.hadoop.hdfs.protocol.HdfsConstants; 028 import org.apache.hadoop.hdfs.protocol.UnresolvedPathException; 029 import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectoryWithSnapshotFeature; 030 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; 031 032 import com.google.common.base.Preconditions; 033 034 /** 035 * Contains INodes information resolved from a given path. 036 */ 037 public class INodesInPath { 038 public static final Log LOG = LogFactory.getLog(INodesInPath.class); 039 040 /** 041 * @return true if path component is {@link HdfsConstants#DOT_SNAPSHOT_DIR} 042 */ 043 private static boolean isDotSnapshotDir(byte[] pathComponent) { 044 return pathComponent == null ? false 045 : Arrays.equals(HdfsConstants.DOT_SNAPSHOT_DIR_BYTES, pathComponent); 046 } 047 048 static INodesInPath fromINode(INode inode) { 049 int depth = 0, index; 050 INode tmp = inode; 051 while (tmp != null) { 052 depth++; 053 tmp = tmp.getParent(); 054 } 055 final byte[][] path = new byte[depth][]; 056 final INode[] inodes = new INode[depth]; 057 final INodesInPath iip = new INodesInPath(path, depth); 058 tmp = inode; 059 index = depth; 060 while (tmp != null) { 061 index--; 062 path[index] = tmp.getKey(); 063 inodes[index] = tmp; 064 tmp = tmp.getParent(); 065 } 066 iip.setINodes(inodes); 067 return iip; 068 } 069 070 /** 071 * Given some components, create a path name. 072 * @param components The path components 073 * @param start index 074 * @param end index 075 * @return concatenated path 076 */ 077 private static String constructPath(byte[][] components, int start, int end) { 078 StringBuilder buf = new StringBuilder(); 079 for (int i = start; i < end; i++) { 080 buf.append(DFSUtil.bytes2String(components[i])); 081 if (i < end - 1) { 082 buf.append(Path.SEPARATOR); 083 } 084 } 085 return buf.toString(); 086 } 087 088 static INodesInPath resolve(final INodeDirectory startingDir, 089 final byte[][] components) throws UnresolvedLinkException { 090 return resolve(startingDir, components, components.length, false); 091 } 092 093 /** 094 * Retrieve existing INodes from a path. If existing is big enough to store 095 * all path components (existing and non-existing), then existing INodes 096 * will be stored starting from the root INode into existing[0]; if 097 * existing is not big enough to store all path components, then only the 098 * last existing and non existing INodes will be stored so that 099 * existing[existing.length-1] refers to the INode of the final component. 100 * 101 * An UnresolvedPathException is always thrown when an intermediate path 102 * component refers to a symbolic link. If the final path component refers 103 * to a symbolic link then an UnresolvedPathException is only thrown if 104 * resolveLink is true. 105 * 106 * <p> 107 * Example: <br> 108 * Given the path /c1/c2/c3 where only /c1/c2 exists, resulting in the 109 * following path components: ["","c1","c2","c3"], 110 * 111 * <p> 112 * <code>getExistingPathINodes(["","c1","c2"], [?])</code> should fill the 113 * array with [c2] <br> 114 * <code>getExistingPathINodes(["","c1","c2","c3"], [?])</code> should fill the 115 * array with [null] 116 * 117 * <p> 118 * <code>getExistingPathINodes(["","c1","c2"], [?,?])</code> should fill the 119 * array with [c1,c2] <br> 120 * <code>getExistingPathINodes(["","c1","c2","c3"], [?,?])</code> should fill 121 * the array with [c2,null] 122 * 123 * <p> 124 * <code>getExistingPathINodes(["","c1","c2"], [?,?,?,?])</code> should fill 125 * the array with [rootINode,c1,c2,null], <br> 126 * <code>getExistingPathINodes(["","c1","c2","c3"], [?,?,?,?])</code> should 127 * fill the array with [rootINode,c1,c2,null] 128 * 129 * @param startingDir the starting directory 130 * @param components array of path component name 131 * @param numOfINodes number of INodes to return 132 * @param resolveLink indicates whether UnresolvedLinkException should 133 * be thrown when the path refers to a symbolic link. 134 * @return the specified number of existing INodes in the path 135 */ 136 static INodesInPath resolve(final INodeDirectory startingDir, 137 final byte[][] components, final int numOfINodes, 138 final boolean resolveLink) throws UnresolvedLinkException { 139 Preconditions.checkArgument(startingDir.compareTo(components[0]) == 0); 140 141 INode curNode = startingDir; 142 final INodesInPath existing = new INodesInPath(components, numOfINodes); 143 int count = 0; 144 int index = numOfINodes - components.length; 145 if (index > 0) { 146 index = 0; 147 } 148 while (count < components.length && curNode != null) { 149 final boolean lastComp = (count == components.length - 1); 150 if (index >= 0) { 151 existing.addNode(curNode); 152 } 153 final boolean isRef = curNode.isReference(); 154 final boolean isDir = curNode.isDirectory(); 155 final INodeDirectory dir = isDir? curNode.asDirectory(): null; 156 if (!isRef && isDir && dir.isWithSnapshot()) { 157 //if the path is a non-snapshot path, update the latest snapshot. 158 if (!existing.isSnapshot()) { 159 existing.updateLatestSnapshotId(dir.getDirectoryWithSnapshotFeature() 160 .getLastSnapshotId()); 161 } 162 } else if (isRef && isDir && !lastComp) { 163 // If the curNode is a reference node, need to check its dstSnapshot: 164 // 1. if the existing snapshot is no later than the dstSnapshot (which 165 // is the latest snapshot in dst before the rename), the changes 166 // should be recorded in previous snapshots (belonging to src). 167 // 2. however, if the ref node is already the last component, we still 168 // need to know the latest snapshot among the ref node's ancestors, 169 // in case of processing a deletion operation. Thus we do not overwrite 170 // the latest snapshot if lastComp is true. In case of the operation is 171 // a modification operation, we do a similar check in corresponding 172 // recordModification method. 173 if (!existing.isSnapshot()) { 174 int dstSnapshotId = curNode.asReference().getDstSnapshotId(); 175 int latest = existing.getLatestSnapshotId(); 176 if (latest == Snapshot.CURRENT_STATE_ID || // no snapshot in dst tree of rename 177 (dstSnapshotId != Snapshot.CURRENT_STATE_ID && 178 dstSnapshotId >= latest)) { // the above scenario 179 int lastSnapshot = Snapshot.CURRENT_STATE_ID; 180 DirectoryWithSnapshotFeature sf = null; 181 if (curNode.isDirectory() && 182 (sf = curNode.asDirectory().getDirectoryWithSnapshotFeature()) != null) { 183 lastSnapshot = sf.getLastSnapshotId(); 184 } 185 existing.setSnapshotId(lastSnapshot); 186 } 187 } 188 } 189 if (curNode.isSymlink() && (!lastComp || (lastComp && resolveLink))) { 190 final String path = constructPath(components, 0, components.length); 191 final String preceding = constructPath(components, 0, count); 192 final String remainder = 193 constructPath(components, count + 1, components.length); 194 final String link = DFSUtil.bytes2String(components[count]); 195 final String target = curNode.asSymlink().getSymlinkString(); 196 if (LOG.isDebugEnabled()) { 197 LOG.debug("UnresolvedPathException " + 198 " path: " + path + " preceding: " + preceding + 199 " count: " + count + " link: " + link + " target: " + target + 200 " remainder: " + remainder); 201 } 202 throw new UnresolvedPathException(path, preceding, remainder, target); 203 } 204 if (lastComp || !isDir) { 205 break; 206 } 207 final byte[] childName = components[count + 1]; 208 209 // check if the next byte[] in components is for ".snapshot" 210 if (isDotSnapshotDir(childName) && isDir && dir.isSnapshottable()) { 211 // skip the ".snapshot" in components 212 count++; 213 index++; 214 existing.isSnapshot = true; 215 if (index >= 0) { // decrease the capacity by 1 to account for .snapshot 216 existing.capacity--; 217 } 218 // check if ".snapshot" is the last element of components 219 if (count == components.length - 1) { 220 break; 221 } 222 // Resolve snapshot root 223 final Snapshot s = dir.getSnapshot(components[count + 1]); 224 if (s == null) { 225 //snapshot not found 226 curNode = null; 227 } else { 228 curNode = s.getRoot(); 229 existing.setSnapshotId(s.getId()); 230 } 231 if (index >= -1) { 232 existing.snapshotRootIndex = existing.numNonNull; 233 } 234 } else { 235 // normal case, and also for resolving file/dir under snapshot root 236 curNode = dir.getChild(childName, existing.getPathSnapshotId()); 237 } 238 count++; 239 index++; 240 } 241 return existing; 242 } 243 244 private final byte[][] path; 245 /** 246 * Array with the specified number of INodes resolved for a given path. 247 */ 248 private INode[] inodes; 249 /** 250 * Indicate the number of non-null elements in {@link #inodes} 251 */ 252 private int numNonNull; 253 /** 254 * The path for a snapshot file/dir contains the .snapshot thus makes the 255 * length of the path components larger the number of inodes. We use 256 * the capacity to control this special case. 257 */ 258 private int capacity; 259 /** 260 * true if this path corresponds to a snapshot 261 */ 262 private boolean isSnapshot; 263 /** 264 * index of the {@link Snapshot.Root} node in the inodes array, 265 * -1 for non-snapshot paths. 266 */ 267 private int snapshotRootIndex; 268 /** 269 * For snapshot paths, it is the id of the snapshot; or 270 * {@link Snapshot#CURRENT_STATE_ID} if the snapshot does not exist. For 271 * non-snapshot paths, it is the id of the latest snapshot found in the path; 272 * or {@link Snapshot#CURRENT_STATE_ID} if no snapshot is found. 273 */ 274 private int snapshotId = Snapshot.CURRENT_STATE_ID; 275 276 private INodesInPath(byte[][] path, int number) { 277 this.path = path; 278 assert (number >= 0); 279 inodes = new INode[number]; 280 capacity = number; 281 numNonNull = 0; 282 isSnapshot = false; 283 snapshotRootIndex = -1; 284 } 285 286 /** 287 * For non-snapshot paths, return the latest snapshot id found in the path. 288 */ 289 public int getLatestSnapshotId() { 290 Preconditions.checkState(!isSnapshot); 291 return snapshotId; 292 } 293 294 /** 295 * For snapshot paths, return the id of the snapshot specified in the path. 296 * For non-snapshot paths, return {@link Snapshot#CURRENT_STATE_ID}. 297 */ 298 public int getPathSnapshotId() { 299 return isSnapshot ? snapshotId : Snapshot.CURRENT_STATE_ID; 300 } 301 302 private void setSnapshotId(int sid) { 303 snapshotId = sid; 304 } 305 306 private void updateLatestSnapshotId(int sid) { 307 if (snapshotId == Snapshot.CURRENT_STATE_ID 308 || (sid != Snapshot.CURRENT_STATE_ID && Snapshot.ID_INTEGER_COMPARATOR 309 .compare(snapshotId, sid) < 0)) { 310 snapshotId = sid; 311 } 312 } 313 314 /** 315 * @return a new array of inodes excluding the null elements introduced by 316 * snapshot path elements. E.g., after resolving path "/dir/.snapshot", 317 * {@link #inodes} is {/, dir, null}, while the returned array only contains 318 * inodes of "/" and "dir". Note the length of the returned array is always 319 * equal to {@link #capacity}. 320 */ 321 INode[] getINodes() { 322 if (capacity == inodes.length) { 323 return inodes; 324 } 325 326 INode[] newNodes = new INode[capacity]; 327 System.arraycopy(inodes, 0, newNodes, 0, capacity); 328 return newNodes; 329 } 330 331 /** 332 * @return the i-th inode if i >= 0; 333 * otherwise, i < 0, return the (length + i)-th inode. 334 */ 335 public INode getINode(int i) { 336 return inodes[i >= 0? i: inodes.length + i]; 337 } 338 339 /** @return the last inode. */ 340 public INode getLastINode() { 341 return inodes[inodes.length - 1]; 342 } 343 344 byte[] getLastLocalName() { 345 return path[path.length - 1]; 346 } 347 348 /** 349 * @return index of the {@link Snapshot.Root} node in the inodes array, 350 * -1 for non-snapshot paths. 351 */ 352 int getSnapshotRootIndex() { 353 return this.snapshotRootIndex; 354 } 355 356 /** 357 * @return isSnapshot true for a snapshot path 358 */ 359 boolean isSnapshot() { 360 return this.isSnapshot; 361 } 362 363 /** 364 * Add an INode at the end of the array 365 */ 366 private void addNode(INode node) { 367 inodes[numNonNull++] = node; 368 } 369 370 private void setINodes(INode inodes[]) { 371 this.inodes = inodes; 372 this.numNonNull = this.inodes.length; 373 } 374 375 void setINode(int i, INode inode) { 376 inodes[i >= 0? i: inodes.length + i] = inode; 377 } 378 379 void setLastINode(INode last) { 380 inodes[inodes.length - 1] = last; 381 } 382 383 /** 384 * @return The number of non-null elements 385 */ 386 int getNumNonNull() { 387 return numNonNull; 388 } 389 390 private static String toString(INode inode) { 391 return inode == null? null: inode.getLocalName(); 392 } 393 394 @Override 395 public String toString() { 396 return toString(true); 397 } 398 399 private String toString(boolean vaildateObject) { 400 if (vaildateObject) { 401 vaildate(); 402 } 403 404 final StringBuilder b = new StringBuilder(getClass().getSimpleName()) 405 .append(": path = ").append(DFSUtil.byteArray2PathString(path)) 406 .append("\n inodes = "); 407 if (inodes == null) { 408 b.append("null"); 409 } else if (inodes.length == 0) { 410 b.append("[]"); 411 } else { 412 b.append("[").append(toString(inodes[0])); 413 for(int i = 1; i < inodes.length; i++) { 414 b.append(", ").append(toString(inodes[i])); 415 } 416 b.append("], length=").append(inodes.length); 417 } 418 b.append("\n numNonNull = ").append(numNonNull) 419 .append("\n capacity = ").append(capacity) 420 .append("\n isSnapshot = ").append(isSnapshot) 421 .append("\n snapshotRootIndex = ").append(snapshotRootIndex) 422 .append("\n snapshotId = ").append(snapshotId); 423 return b.toString(); 424 } 425 426 void vaildate() { 427 // check parent up to snapshotRootIndex or numNonNull 428 final int n = snapshotRootIndex >= 0? snapshotRootIndex + 1: numNonNull; 429 int i = 0; 430 if (inodes[i] != null) { 431 for(i++; i < n && inodes[i] != null; i++) { 432 final INodeDirectory parent_i = inodes[i].getParent(); 433 final INodeDirectory parent_i_1 = inodes[i-1].getParent(); 434 if (parent_i != inodes[i-1] && 435 (parent_i_1 == null || !parent_i_1.isSnapshottable() 436 || parent_i != parent_i_1)) { 437 throw new AssertionError( 438 "inodes[" + i + "].getParent() != inodes[" + (i-1) 439 + "]\n inodes[" + i + "]=" + inodes[i].toDetailString() 440 + "\n inodes[" + (i-1) + "]=" + inodes[i-1].toDetailString() 441 + "\n this=" + toString(false)); 442 } 443 } 444 } 445 if (i != n) { 446 throw new AssertionError("i = " + i + " != " + n 447 + ", this=" + toString(false)); 448 } 449 } 450 }