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.INodeDirectorySnapshottable; 030 import org.apache.hadoop.hdfs.server.namenode.snapshot.INodeDirectoryWithSnapshot; 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 instanceof INodeDirectoryWithSnapshot) { 136 //if the path is a non-snapshot path, update the latest snapshot. 137 if (!existing.isSnapshot()) { 138 existing.updateLatestSnapshot( 139 ((INodeDirectoryWithSnapshot)dir).getLastSnapshot()); 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 Snapshot latest = existing.getLatestSnapshot(); 155 if (latest == null || // no snapshot in dst tree of rename 156 dstSnapshotId >= latest.getId()) { // the above scenario 157 Snapshot lastSnapshot = null; 158 if (curNode.isDirectory() 159 && curNode.asDirectory() instanceof INodeDirectoryWithSnapshot) { 160 lastSnapshot = ((INodeDirectoryWithSnapshot) curNode 161 .asDirectory()).getLastSnapshot(); 162 } 163 existing.setSnapshot(lastSnapshot); 164 } 165 } 166 } 167 if (curNode.isSymlink() && (!lastComp || (lastComp && resolveLink))) { 168 final String path = constructPath(components, 0, components.length); 169 final String preceding = constructPath(components, 0, count); 170 final String remainder = 171 constructPath(components, count + 1, components.length); 172 final String link = DFSUtil.bytes2String(components[count]); 173 final String target = curNode.asSymlink().getSymlinkString(); 174 if (LOG.isDebugEnabled()) { 175 LOG.debug("UnresolvedPathException " + 176 " path: " + path + " preceding: " + preceding + 177 " count: " + count + " link: " + link + " target: " + target + 178 " remainder: " + remainder); 179 } 180 throw new UnresolvedPathException(path, preceding, remainder, target); 181 } 182 if (lastComp || !isDir) { 183 break; 184 } 185 final byte[] childName = components[count + 1]; 186 187 // check if the next byte[] in components is for ".snapshot" 188 if (isDotSnapshotDir(childName) 189 && isDir && dir instanceof INodeDirectorySnapshottable) { 190 // skip the ".snapshot" in components 191 count++; 192 index++; 193 existing.isSnapshot = true; 194 if (index >= 0) { // decrease the capacity by 1 to account for .snapshot 195 existing.capacity--; 196 } 197 // check if ".snapshot" is the last element of components 198 if (count == components.length - 1) { 199 break; 200 } 201 // Resolve snapshot root 202 final Snapshot s = ((INodeDirectorySnapshottable)dir).getSnapshot( 203 components[count + 1]); 204 if (s == null) { 205 //snapshot not found 206 curNode = null; 207 } else { 208 curNode = s.getRoot(); 209 existing.setSnapshot(s); 210 } 211 if (index >= -1) { 212 existing.snapshotRootIndex = existing.numNonNull; 213 } 214 } else { 215 // normal case, and also for resolving file/dir under snapshot root 216 curNode = dir.getChild(childName, existing.getPathSnapshot()); 217 } 218 count++; 219 index++; 220 } 221 return existing; 222 } 223 224 private final byte[][] path; 225 /** 226 * Array with the specified number of INodes resolved for a given path. 227 */ 228 private INode[] inodes; 229 /** 230 * Indicate the number of non-null elements in {@link #inodes} 231 */ 232 private int numNonNull; 233 /** 234 * The path for a snapshot file/dir contains the .snapshot thus makes the 235 * length of the path components larger the number of inodes. We use 236 * the capacity to control this special case. 237 */ 238 private int capacity; 239 /** 240 * true if this path corresponds to a snapshot 241 */ 242 private boolean isSnapshot; 243 /** 244 * Index of {@link INodeDirectoryWithSnapshot} for snapshot path, else -1 245 */ 246 private int snapshotRootIndex; 247 /** 248 * For snapshot paths, it is the reference to the snapshot; or null if the 249 * snapshot does not exist. For non-snapshot paths, it is the reference to 250 * the latest snapshot found in the path; or null if no snapshot is found. 251 */ 252 private Snapshot snapshot = null; 253 254 private INodesInPath(byte[][] path, int number) { 255 this.path = path; 256 assert (number >= 0); 257 inodes = new INode[number]; 258 capacity = number; 259 numNonNull = 0; 260 isSnapshot = false; 261 snapshotRootIndex = -1; 262 } 263 264 /** 265 * For non-snapshot paths, return the latest snapshot found in the path. 266 * For snapshot paths, return null. 267 */ 268 public Snapshot getLatestSnapshot() { 269 return isSnapshot? null: snapshot; 270 } 271 272 /** 273 * For snapshot paths, return the snapshot specified in the path. 274 * For non-snapshot paths, return null. 275 */ 276 public Snapshot getPathSnapshot() { 277 return isSnapshot? snapshot: null; 278 } 279 280 private void setSnapshot(Snapshot s) { 281 snapshot = s; 282 } 283 284 private void updateLatestSnapshot(Snapshot s) { 285 if (snapshot == null 286 || (s != null && Snapshot.ID_COMPARATOR.compare(snapshot, s) < 0)) { 287 snapshot = s; 288 } 289 } 290 291 /** 292 * @return the whole inodes array including the null elements. 293 */ 294 INode[] getINodes() { 295 if (capacity < inodes.length) { 296 INode[] newNodes = new INode[capacity]; 297 System.arraycopy(inodes, 0, newNodes, 0, capacity); 298 inodes = newNodes; 299 } 300 return inodes; 301 } 302 303 /** 304 * @return the i-th inode if i >= 0; 305 * otherwise, i < 0, return the (length + i)-th inode. 306 */ 307 public INode getINode(int i) { 308 return inodes[i >= 0? i: inodes.length + i]; 309 } 310 311 /** @return the last inode. */ 312 public INode getLastINode() { 313 return inodes[inodes.length - 1]; 314 } 315 316 byte[] getLastLocalName() { 317 return path[path.length - 1]; 318 } 319 320 /** 321 * @return index of the {@link INodeDirectoryWithSnapshot} in 322 * {@link #inodes} for snapshot path, else -1. 323 */ 324 int getSnapshotRootIndex() { 325 return this.snapshotRootIndex; 326 } 327 328 /** 329 * @return isSnapshot true for a snapshot path 330 */ 331 boolean isSnapshot() { 332 return this.isSnapshot; 333 } 334 335 /** 336 * Add an INode at the end of the array 337 */ 338 private void addNode(INode node) { 339 inodes[numNonNull++] = node; 340 } 341 342 void setINode(int i, INode inode) { 343 inodes[i >= 0? i: inodes.length + i] = inode; 344 } 345 346 void setLastINode(INode last) { 347 inodes[inodes.length - 1] = last; 348 } 349 350 /** 351 * @return The number of non-null elements 352 */ 353 int getNumNonNull() { 354 return numNonNull; 355 } 356 357 private static String toString(INode inode) { 358 return inode == null? null: inode.getLocalName(); 359 } 360 361 @Override 362 public String toString() { 363 return toString(true); 364 } 365 366 private String toString(boolean vaildateObject) { 367 if (vaildateObject) { 368 vaildate(); 369 } 370 371 final StringBuilder b = new StringBuilder(getClass().getSimpleName()) 372 .append(": path = ").append(DFSUtil.byteArray2PathString(path)) 373 .append("\n inodes = "); 374 if (inodes == null) { 375 b.append("null"); 376 } else if (inodes.length == 0) { 377 b.append("[]"); 378 } else { 379 b.append("[").append(toString(inodes[0])); 380 for(int i = 1; i < inodes.length; i++) { 381 b.append(", ").append(toString(inodes[i])); 382 } 383 b.append("], length=").append(inodes.length); 384 } 385 b.append("\n numNonNull = ").append(numNonNull) 386 .append("\n capacity = ").append(capacity) 387 .append("\n isSnapshot = ").append(isSnapshot) 388 .append("\n snapshotRootIndex = ").append(snapshotRootIndex) 389 .append("\n snapshot = ").append(snapshot); 390 return b.toString(); 391 } 392 393 void vaildate() { 394 // check parent up to snapshotRootIndex or numNonNull 395 final int n = snapshotRootIndex >= 0? snapshotRootIndex + 1: numNonNull; 396 int i = 0; 397 if (inodes[i] != null) { 398 for(i++; i < n && inodes[i] != null; i++) { 399 final INodeDirectory parent_i = inodes[i].getParent(); 400 final INodeDirectory parent_i_1 = inodes[i-1].getParent(); 401 if (parent_i != inodes[i-1] && 402 (parent_i_1 == null || !parent_i_1.isSnapshottable() 403 || parent_i != parent_i_1)) { 404 throw new AssertionError( 405 "inodes[" + i + "].getParent() != inodes[" + (i-1) 406 + "]\n inodes[" + i + "]=" + inodes[i].toDetailString() 407 + "\n inodes[" + (i-1) + "]=" + inodes[i-1].toDetailString() 408 + "\n this=" + toString(false)); 409 } 410 } 411 } 412 if (i != n) { 413 throw new AssertionError("i = " + i + " != " + n 414 + ", this=" + toString(false)); 415 } 416 } 417 }