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    }