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 }