1 /**
2  * Licensed to the Apache Software Foundation (ASF) under one
3  * or more contributor license agreements.  See the NOTICE file
4  * distributed with this work for additional information
5  * regarding copyright ownership.  The ASF licenses this file
6  * to you under the Apache License, Version 2.0 (the
7  * "License"); you may not use this file except in compliance
8  * with the License.  You may obtain a copy of the License at
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18 
19 package org.apache.hadoop.hdfs.server.namenode;
20 
21 import com.google.common.base.Preconditions;
22 import org.apache.commons.io.Charsets;
23 import org.apache.hadoop.fs.ContentSummary;
24 import org.apache.hadoop.fs.DirectoryListingStartAfterNotFoundException;
25 import org.apache.hadoop.fs.FileEncryptionInfo;
26 import org.apache.hadoop.fs.InvalidPathException;
27 import org.apache.hadoop.fs.UnresolvedLinkException;
28 import org.apache.hadoop.fs.permission.FsAction;
29 import org.apache.hadoop.fs.permission.FsPermission;
30 import org.apache.hadoop.hdfs.DFSUtil;
31 import org.apache.hadoop.hdfs.protocol.DirectoryListing;
32 import org.apache.hadoop.hdfs.protocol.FsPermissionExtension;
33 import org.apache.hadoop.hdfs.protocol.HdfsConstants;
34 import org.apache.hadoop.hdfs.protocol.HdfsFileStatus;
35 import org.apache.hadoop.hdfs.protocol.HdfsLocatedFileStatus;
36 import org.apache.hadoop.hdfs.protocol.LocatedBlock;
37 import org.apache.hadoop.hdfs.protocol.LocatedBlocks;
38 import org.apache.hadoop.hdfs.protocol.SnapshotException;
39 import org.apache.hadoop.hdfs.server.blockmanagement.BlockStoragePolicySuite;
40 import org.apache.hadoop.hdfs.server.namenode.snapshot.DirectorySnapshottableFeature;
41 import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot;
42 import org.apache.hadoop.hdfs.util.ReadOnlyList;
43 
44 import java.io.FileNotFoundException;
45 import java.io.IOException;
46 import java.util.Arrays;
47 
48 class FSDirStatAndListingOp {
getListingInt(FSDirectory fsd, final String srcArg, byte[] startAfter, boolean needLocation)49   static DirectoryListing getListingInt(FSDirectory fsd, final String srcArg,
50       byte[] startAfter, boolean needLocation) throws IOException {
51     FSPermissionChecker pc = fsd.getPermissionChecker();
52     byte[][] pathComponents = FSDirectory
53         .getPathComponentsForReservedPath(srcArg);
54     final String startAfterString = new String(startAfter, Charsets.UTF_8);
55     final String src = fsd.resolvePath(pc, srcArg, pathComponents);
56     final INodesInPath iip = fsd.getINodesInPath(src, true);
57 
58     // Get file name when startAfter is an INodePath
59     if (FSDirectory.isReservedName(startAfterString)) {
60       byte[][] startAfterComponents = FSDirectory
61           .getPathComponentsForReservedPath(startAfterString);
62       try {
63         String tmp = FSDirectory.resolvePath(src, startAfterComponents, fsd);
64         byte[][] regularPath = INode.getPathComponents(tmp);
65         startAfter = regularPath[regularPath.length - 1];
66       } catch (IOException e) {
67         // Possibly the inode is deleted
68         throw new DirectoryListingStartAfterNotFoundException(
69             "Can't find startAfter " + startAfterString);
70       }
71     }
72 
73     boolean isSuperUser = true;
74     if (fsd.isPermissionEnabled()) {
75       if (iip.getLastINode() != null && iip.getLastINode().isDirectory()) {
76         fsd.checkPathAccess(pc, iip, FsAction.READ_EXECUTE);
77       } else {
78         fsd.checkTraverse(pc, iip);
79       }
80       isSuperUser = pc.isSuperUser();
81     }
82     return getListing(fsd, iip, src, startAfter, needLocation, isSuperUser);
83   }
84 
85   /**
86    * Get the file info for a specific file.
87    *
88    * @param srcArg The string representation of the path to the file
89    * @param resolveLink whether to throw UnresolvedLinkException
90    *        if src refers to a symlink
91    *
92    * @return object containing information regarding the file
93    *         or null if file not found
94    */
getFileInfo( FSDirectory fsd, String srcArg, boolean resolveLink)95   static HdfsFileStatus getFileInfo(
96       FSDirectory fsd, String srcArg, boolean resolveLink)
97       throws IOException {
98     String src = srcArg;
99     if (!DFSUtil.isValidName(src)) {
100       throw new InvalidPathException("Invalid file name: " + src);
101     }
102     FSPermissionChecker pc = fsd.getPermissionChecker();
103     byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src);
104     src = fsd.resolvePath(pc, src, pathComponents);
105     final INodesInPath iip = fsd.getINodesInPath(src, resolveLink);
106     boolean isSuperUser = true;
107     if (fsd.isPermissionEnabled()) {
108       fsd.checkPermission(pc, iip, false, null, null, null, null, false);
109       isSuperUser = pc.isSuperUser();
110     }
111     return getFileInfo(fsd, src, resolveLink,
112         FSDirectory.isReservedRawName(srcArg), isSuperUser);
113   }
114 
115   /**
116    * Returns true if the file is closed
117    */
isFileClosed(FSDirectory fsd, String src)118   static boolean isFileClosed(FSDirectory fsd, String src) throws IOException {
119     FSPermissionChecker pc = fsd.getPermissionChecker();
120     byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src);
121     src = fsd.resolvePath(pc, src, pathComponents);
122     final INodesInPath iip = fsd.getINodesInPath(src, true);
123     if (fsd.isPermissionEnabled()) {
124       fsd.checkTraverse(pc, iip);
125     }
126     return !INodeFile.valueOf(iip.getLastINode(), src).isUnderConstruction();
127   }
128 
getContentSummary( FSDirectory fsd, String src)129   static ContentSummary getContentSummary(
130       FSDirectory fsd, String src) throws IOException {
131     byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src);
132     FSPermissionChecker pc = fsd.getPermissionChecker();
133     src = fsd.resolvePath(pc, src, pathComponents);
134     final INodesInPath iip = fsd.getINodesInPath(src, false);
135     if (fsd.isPermissionEnabled()) {
136       fsd.checkPermission(pc, iip, false, null, null, null,
137           FsAction.READ_EXECUTE);
138     }
139     return getContentSummaryInt(fsd, iip);
140   }
141 
getStoragePolicyID(byte inodePolicy, byte parentPolicy)142   private static byte getStoragePolicyID(byte inodePolicy, byte parentPolicy) {
143     return inodePolicy != BlockStoragePolicySuite.ID_UNSPECIFIED ? inodePolicy :
144         parentPolicy;
145   }
146 
147   /**
148    * Get a partial listing of the indicated directory
149    *
150    * We will stop when any of the following conditions is met:
151    * 1) this.lsLimit files have been added
152    * 2) needLocation is true AND enough files have been added such
153    * that at least this.lsLimit block locations are in the response
154    *
155    * @param fsd FSDirectory
156    * @param iip the INodesInPath instance containing all the INodes along the
157    *            path
158    * @param src the directory name
159    * @param startAfter the name to start listing after
160    * @param needLocation if block locations are returned
161    * @return a partial listing starting after startAfter
162    */
getListing(FSDirectory fsd, INodesInPath iip, String src, byte[] startAfter, boolean needLocation, boolean isSuperUser)163   private static DirectoryListing getListing(FSDirectory fsd, INodesInPath iip,
164       String src, byte[] startAfter, boolean needLocation, boolean isSuperUser)
165       throws IOException {
166     String srcs = FSDirectory.normalizePath(src);
167     final boolean isRawPath = FSDirectory.isReservedRawName(src);
168 
169     fsd.readLock();
170     try {
171       if (srcs.endsWith(HdfsConstants.SEPARATOR_DOT_SNAPSHOT_DIR)) {
172         return getSnapshotsListing(fsd, srcs, startAfter);
173       }
174       final int snapshot = iip.getPathSnapshotId();
175       final INode targetNode = iip.getLastINode();
176       if (targetNode == null)
177         return null;
178       byte parentStoragePolicy = isSuperUser ?
179           targetNode.getStoragePolicyID() : BlockStoragePolicySuite
180           .ID_UNSPECIFIED;
181 
182       if (!targetNode.isDirectory()) {
183         return new DirectoryListing(
184             new HdfsFileStatus[]{createFileStatus(fsd, src,
185                 HdfsFileStatus.EMPTY_NAME, targetNode, needLocation,
186                 parentStoragePolicy, snapshot, isRawPath, iip)}, 0);
187       }
188 
189       final INodeDirectory dirInode = targetNode.asDirectory();
190       final ReadOnlyList<INode> contents = dirInode.getChildrenList(snapshot);
191       int startChild = INodeDirectory.nextChild(contents, startAfter);
192       int totalNumChildren = contents.size();
193       int numOfListing = Math.min(totalNumChildren - startChild,
194           fsd.getLsLimit());
195       int locationBudget = fsd.getLsLimit();
196       int listingCnt = 0;
197       HdfsFileStatus listing[] = new HdfsFileStatus[numOfListing];
198       for (int i=0; i<numOfListing && locationBudget>0; i++) {
199         INode cur = contents.get(startChild+i);
200         byte curPolicy = isSuperUser && !cur.isSymlink()?
201             cur.getLocalStoragePolicyID():
202             BlockStoragePolicySuite.ID_UNSPECIFIED;
203         listing[i] = createFileStatus(fsd, src, cur.getLocalNameBytes(), cur,
204             needLocation, getStoragePolicyID(curPolicy,
205                 parentStoragePolicy), snapshot, isRawPath, iip);
206         listingCnt++;
207         if (needLocation) {
208             // Once we  hit lsLimit locations, stop.
209             // This helps to prevent excessively large response payloads.
210             // Approximate #locations with locatedBlockCount() * repl_factor
211             LocatedBlocks blks =
212                 ((HdfsLocatedFileStatus)listing[i]).getBlockLocations();
213             locationBudget -= (blks == null) ? 0 :
214                blks.locatedBlockCount() * listing[i].getReplication();
215         }
216       }
217       // truncate return array if necessary
218       if (listingCnt < numOfListing) {
219           listing = Arrays.copyOf(listing, listingCnt);
220       }
221       return new DirectoryListing(
222           listing, totalNumChildren-startChild-listingCnt);
223     } finally {
224       fsd.readUnlock();
225     }
226   }
227 
228   /**
229    * Get a listing of all the snapshots of a snapshottable directory
230    */
getSnapshotsListing( FSDirectory fsd, String src, byte[] startAfter)231   private static DirectoryListing getSnapshotsListing(
232       FSDirectory fsd, String src, byte[] startAfter)
233       throws IOException {
234     Preconditions.checkState(fsd.hasReadLock());
235     Preconditions.checkArgument(
236         src.endsWith(HdfsConstants.SEPARATOR_DOT_SNAPSHOT_DIR),
237         "%s does not end with %s", src, HdfsConstants.SEPARATOR_DOT_SNAPSHOT_DIR);
238 
239     final String dirPath = FSDirectory.normalizePath(src.substring(0,
240         src.length() - HdfsConstants.DOT_SNAPSHOT_DIR.length()));
241 
242     final INode node = fsd.getINode(dirPath);
243     final INodeDirectory dirNode = INodeDirectory.valueOf(node, dirPath);
244     final DirectorySnapshottableFeature sf = dirNode.getDirectorySnapshottableFeature();
245     if (sf == null) {
246       throw new SnapshotException(
247           "Directory is not a snapshottable directory: " + dirPath);
248     }
249     final ReadOnlyList<Snapshot> snapshots = sf.getSnapshotList();
250     int skipSize = ReadOnlyList.Util.binarySearch(snapshots, startAfter);
251     skipSize = skipSize < 0 ? -skipSize - 1 : skipSize + 1;
252     int numOfListing = Math.min(snapshots.size() - skipSize, fsd.getLsLimit());
253     final HdfsFileStatus listing[] = new HdfsFileStatus[numOfListing];
254     for (int i = 0; i < numOfListing; i++) {
255       Snapshot.Root sRoot = snapshots.get(i + skipSize).getRoot();
256       listing[i] = createFileStatus(fsd, src, sRoot.getLocalNameBytes(), sRoot,
257           BlockStoragePolicySuite.ID_UNSPECIFIED, Snapshot.CURRENT_STATE_ID,
258           false, INodesInPath.fromINode(sRoot));
259     }
260     return new DirectoryListing(
261         listing, snapshots.size() - skipSize - numOfListing);
262   }
263 
264   /** Get the file info for a specific file.
265    * @param fsd FSDirectory
266    * @param src The string representation of the path to the file
267    * @param isRawPath true if a /.reserved/raw pathname was passed by the user
268    * @param includeStoragePolicy whether to include storage policy
269    * @return object containing information regarding the file
270    *         or null if file not found
271    */
272   static HdfsFileStatus getFileInfo(
273       FSDirectory fsd, String path, INodesInPath src, boolean isRawPath,
274       boolean includeStoragePolicy)
275       throws IOException {
276     fsd.readLock();
277     try {
278       final INode i = src.getLastINode();
279       byte policyId = includeStoragePolicy && i != null && !i.isSymlink() ?
280           i.getStoragePolicyID() : BlockStoragePolicySuite.ID_UNSPECIFIED;
281       return i == null ? null : createFileStatus(
282           fsd, path, HdfsFileStatus.EMPTY_NAME, i, policyId,
283           src.getPathSnapshotId(), isRawPath, src);
284     } finally {
285       fsd.readUnlock();
286     }
287   }
288 
289   static HdfsFileStatus getFileInfo(
290       FSDirectory fsd, String src, boolean resolveLink, boolean isRawPath,
291       boolean includeStoragePolicy)
292     throws IOException {
293     String srcs = FSDirectory.normalizePath(src);
294     if (srcs.endsWith(HdfsConstants.SEPARATOR_DOT_SNAPSHOT_DIR)) {
295       if (fsd.getINode4DotSnapshot(srcs) != null) {
296         return new HdfsFileStatus(0, true, 0, 0, 0, 0, null, null, null, null,
297             HdfsFileStatus.EMPTY_NAME, -1L, 0, null,
298             BlockStoragePolicySuite.ID_UNSPECIFIED);
299       }
300       return null;
301     }
302 
303     fsd.readLock();
304     try {
305       final INodesInPath iip = fsd.getINodesInPath(srcs, resolveLink);
306       return getFileInfo(fsd, src, iip, isRawPath, includeStoragePolicy);
307     } finally {
308       fsd.readUnlock();
309     }
310   }
311 
312   /**
313    * Currently we only support "ls /xxx/.snapshot" which will return all the
314    * snapshots of a directory. The FSCommand Ls will first call getFileInfo to
315    * make sure the file/directory exists (before the real getListing call).
316    * Since we do not have a real INode for ".snapshot", we return an empty
317    * non-null HdfsFileStatus here.
318    */
319   private static HdfsFileStatus getFileInfo4DotSnapshot(
320       FSDirectory fsd, String src)
321       throws UnresolvedLinkException {
322     if (fsd.getINode4DotSnapshot(src) != null) {
323       return new HdfsFileStatus(0, true, 0, 0, 0, 0, null, null, null, null,
324           HdfsFileStatus.EMPTY_NAME, -1L, 0, null,
325           BlockStoragePolicySuite.ID_UNSPECIFIED);
326     }
327     return null;
328   }
329 
330   /**
331    * create an hdfs file status from an inode
332    *
333    * @param fsd FSDirectory
334    * @param path the local name
335    * @param node inode
336    * @param needLocation if block locations need to be included or not
337    * @param isRawPath true if this is being called on behalf of a path in
338    *                  /.reserved/raw
339    * @return a file status
340    * @throws java.io.IOException if any error occurs
341    */
342   static HdfsFileStatus createFileStatus(
343       FSDirectory fsd, String fullPath, byte[] path, INode node,
344       boolean needLocation, byte storagePolicy, int snapshot, boolean isRawPath,
345       INodesInPath iip)
346       throws IOException {
347     if (needLocation) {
348       return createLocatedFileStatus(fsd, fullPath, path, node, storagePolicy,
349           snapshot, isRawPath, iip);
350     } else {
351       return createFileStatus(fsd, fullPath, path, node, storagePolicy, snapshot,
352           isRawPath, iip);
353     }
354   }
355 
356   /**
357    * Create FileStatus by file INode
358    */
359   static HdfsFileStatus createFileStatus(
360       FSDirectory fsd, String fullPath, byte[] path, INode node,
361       byte storagePolicy, int snapshot, boolean isRawPath,
362       INodesInPath iip) throws IOException {
363      long size = 0;     // length is zero for directories
364      short replication = 0;
365      long blocksize = 0;
366      final boolean isEncrypted;
367 
368      final FileEncryptionInfo feInfo = isRawPath ? null :
369          fsd.getFileEncryptionInfo(node, snapshot, iip);
370 
371      if (node.isFile()) {
372        final INodeFile fileNode = node.asFile();
373        size = fileNode.computeFileSize(snapshot);
374        replication = fileNode.getFileReplication(snapshot);
375        blocksize = fileNode.getPreferredBlockSize();
376        isEncrypted = (feInfo != null) ||
377            (isRawPath && fsd.isInAnEZ(INodesInPath.fromINode(node)));
378      } else {
379        isEncrypted = fsd.isInAnEZ(INodesInPath.fromINode(node));
380      }
381 
382      int childrenNum = node.isDirectory() ?
383          node.asDirectory().getChildrenNum(snapshot) : 0;
384 
385      INodeAttributes nodeAttrs =
386          fsd.getAttributes(fullPath, path, node, snapshot);
387      return new HdfsFileStatus(
388         size,
389         node.isDirectory(),
390         replication,
391         blocksize,
392         node.getModificationTime(snapshot),
393         node.getAccessTime(snapshot),
394         getPermissionForFileStatus(nodeAttrs, isEncrypted),
395         nodeAttrs.getUserName(),
396         nodeAttrs.getGroupName(),
397         node.isSymlink() ? node.asSymlink().getSymlink() : null,
398         path,
399         node.getId(),
400         childrenNum,
401         feInfo,
402         storagePolicy);
403   }
404 
405   /**
406    * Create FileStatus with location info by file INode
407    */
408   private static HdfsLocatedFileStatus createLocatedFileStatus(
409       FSDirectory fsd, String fullPath, byte[] path, INode node,
410       byte storagePolicy, int snapshot, boolean isRawPath,
411       INodesInPath iip) throws IOException {
412     assert fsd.hasReadLock();
413     long size = 0; // length is zero for directories
414     short replication = 0;
415     long blocksize = 0;
416     LocatedBlocks loc = null;
417     final boolean isEncrypted;
418     final FileEncryptionInfo feInfo = isRawPath ? null :
419         fsd.getFileEncryptionInfo(node, snapshot, iip);
420     if (node.isFile()) {
421       final INodeFile fileNode = node.asFile();
422       size = fileNode.computeFileSize(snapshot);
423       replication = fileNode.getFileReplication(snapshot);
424       blocksize = fileNode.getPreferredBlockSize();
425 
426       final boolean inSnapshot = snapshot != Snapshot.CURRENT_STATE_ID;
427       final boolean isUc = !inSnapshot && fileNode.isUnderConstruction();
428       final long fileSize = !inSnapshot && isUc ?
429           fileNode.computeFileSizeNotIncludingLastUcBlock() : size;
430 
431       loc = fsd.getFSNamesystem().getBlockManager().createLocatedBlocks(
432           fileNode.getBlocks(snapshot), fileSize, isUc, 0L, size, false,
433           inSnapshot, feInfo);
434       if (loc == null) {
435         loc = new LocatedBlocks();
436       }
437       isEncrypted = (feInfo != null) ||
438           (isRawPath && fsd.isInAnEZ(INodesInPath.fromINode(node)));
439     } else {
440       isEncrypted = fsd.isInAnEZ(INodesInPath.fromINode(node));
441     }
442     int childrenNum = node.isDirectory() ?
443         node.asDirectory().getChildrenNum(snapshot) : 0;
444 
445     INodeAttributes nodeAttrs =
446         fsd.getAttributes(fullPath, path, node, snapshot);
447     HdfsLocatedFileStatus status =
448         new HdfsLocatedFileStatus(size, node.isDirectory(), replication,
449           blocksize, node.getModificationTime(snapshot),
450           node.getAccessTime(snapshot),
451           getPermissionForFileStatus(nodeAttrs, isEncrypted),
452           nodeAttrs.getUserName(), nodeAttrs.getGroupName(),
453           node.isSymlink() ? node.asSymlink().getSymlink() : null, path,
454           node.getId(), loc, childrenNum, feInfo, storagePolicy);
455     // Set caching information for the located blocks.
456     if (loc != null) {
457       CacheManager cacheManager = fsd.getFSNamesystem().getCacheManager();
458       for (LocatedBlock lb: loc.getLocatedBlocks()) {
459         cacheManager.setCachedLocations(lb);
460       }
461     }
462     return status;
463   }
464 
465   /**
466    * Returns an inode's FsPermission for use in an outbound FileStatus.  If the
467    * inode has an ACL or is for an encrypted file/dir, then this method will
468    * return an FsPermissionExtension.
469    *
470    * @param node INode to check
471    * @param snapshot int snapshot ID
472    * @param isEncrypted boolean true if the file/dir is encrypted
473    * @return FsPermission from inode, with ACL bit on if the inode has an ACL
474    * and encrypted bit on if it represents an encrypted file/dir.
475    */
476   private static FsPermission getPermissionForFileStatus(
477       INodeAttributes node, boolean isEncrypted) {
478     FsPermission perm = node.getFsPermission();
479     boolean hasAcl = node.getAclFeature() != null;
480     if (hasAcl || isEncrypted) {
481       perm = new FsPermissionExtension(perm, hasAcl, isEncrypted);
482     }
483     return perm;
484   }
485 
486   private static ContentSummary getContentSummaryInt(FSDirectory fsd,
487       INodesInPath iip) throws IOException {
488     fsd.readLock();
489     try {
490       INode targetNode = iip.getLastINode();
491       if (targetNode == null) {
492         throw new FileNotFoundException("File does not exist: " + iip.getPath());
493       }
494       else {
495         // Make it relinquish locks everytime contentCountLimit entries are
496         // processed. 0 means disabled. I.e. blocking for the entire duration.
497         ContentSummaryComputationContext cscc =
498             new ContentSummaryComputationContext(fsd, fsd.getFSNamesystem(),
499                 fsd.getContentCountLimit(), fsd.getContentSleepMicroSec());
500         ContentSummary cs = targetNode.computeAndConvertContentSummary(cscc);
501         fsd.addYieldCount(cscc.getYieldCount());
502         return cs;
503       }
504     } finally {
505       fsd.readUnlock();
506     }
507   }
508 }
509