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    }