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