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 }