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    }