/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hdfs.tools.offlineImageViewer;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import com.google.common.collect.ImmutableList;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.XAttr;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.fs.permission.PermissionStatus;
import org.apache.hadoop.hdfs.XAttrHelper;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf;
import org.apache.hadoop.hdfs.server.namenode.FSImageUtil;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto;
import org.apache.hadoop.hdfs.server.namenode.INodeId;
import org.apache.hadoop.hdfs.web.JsonUtil;
import org.apache.hadoop.hdfs.web.resources.XAttrEncodingParam;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.LimitInputStream;
import org.codehaus.jackson.map.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* FSImageLoader loads fsimage and provide methods to return JSON formatted
* file status of the namespace of the fsimage.
*/
class FSImageLoader {
public static final Log LOG = LogFactory.getLog(FSImageHandler.class);
private final String[] stringTable;
// byte representation of inodes, sorted by id
private final byte[][] inodes;
private final Map<Long, long[]> dirmap;
private static final Comparator<byte[]> INODE_BYTES_COMPARATOR = new
Comparator<byte[]>() {
@Override
public int compare(byte[] o1, byte[] o2) {
try {
final FsImageProto.INodeSection.INode l = FsImageProto.INodeSection
.INode.parseFrom(o1);
final FsImageProto.INodeSection.INode r = FsImageProto.INodeSection
.INode.parseFrom(o2);
if (l.getId() < r.getId()) {
return -1;
} else if (l.getId() > r.getId()) {
return 1;
} else {
return 0;
}
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
}
};
private FSImageLoader(String[] stringTable, byte[][] inodes,
Map<Long, long[]> dirmap) {
this.stringTable = stringTable;
this.inodes = inodes;
this.dirmap = dirmap;
}
/**
* Load fsimage into the memory.
* @param inputFile the filepath of the fsimage to load.
* @return FSImageLoader
* @throws IOException if failed to load fsimage.
*/
static FSImageLoader load(String inputFile) throws IOException {
Configuration conf = new Configuration();
RandomAccessFile file = new RandomAccessFile(inputFile, "r");
if (!FSImageUtil.checkFileFormat(file)) {
throw new IOException("Unrecognized FSImage");
}
FsImageProto.FileSummary summary = FSImageUtil.loadSummary(file);
try (FileInputStream fin = new FileInputStream(file.getFD())) {
// Map to record INodeReference to the referred id
ImmutableList<Long> refIdList = null;
String[] stringTable = null;
byte[][] inodes = null;
Map<Long, long[]> dirmap = null;
ArrayList<FsImageProto.FileSummary.Section> sections =
Lists.newArrayList(summary.getSectionsList());
Collections.sort(sections,
new Comparator<FsImageProto.FileSummary.Section>() {
@Override
public int compare(FsImageProto.FileSummary.Section s1,
FsImageProto.FileSummary.Section s2) {
FSImageFormatProtobuf.SectionName n1 =
FSImageFormatProtobuf.SectionName.fromString(s1.getName());
FSImageFormatProtobuf.SectionName n2 =
FSImageFormatProtobuf.SectionName.fromString(s2.getName());
if (n1 == null) {
return n2 == null ? 0 : -1;
} else if (n2 == null) {
return -1;
} else {
return n1.ordinal() - n2.ordinal();
}
}
});
for (FsImageProto.FileSummary.Section s : sections) {
fin.getChannel().position(s.getOffset());
InputStream is = FSImageUtil.wrapInputStreamForCompression(conf,
summary.getCodec(), new BufferedInputStream(new LimitInputStream(
fin, s.getLength())));
if (LOG.isDebugEnabled()) {
LOG.debug("Loading section " + s.getName() + " length: " + s.getLength
());
}
switch (FSImageFormatProtobuf.SectionName.fromString(s.getName())) {
case STRING_TABLE:
stringTable = loadStringTable(is);
break;
case INODE:
inodes = loadINodeSection(is);
break;
case INODE_REFERENCE:
refIdList = loadINodeReferenceSection(is);
break;
case INODE_DIR:
dirmap = loadINodeDirectorySection(is, refIdList);
break;
default:
break;
}
}
return new FSImageLoader(stringTable, inodes, dirmap);
}
}
private static Map<Long, long[]> loadINodeDirectorySection
(InputStream in, List<Long> refIdList)
throws IOException {
LOG.info("Loading inode directory section");
Map<Long, long[]> dirs = Maps.newHashMap();
long counter = 0;
while (true) {
FsImageProto.INodeDirectorySection.DirEntry e =
FsImageProto.INodeDirectorySection.DirEntry.parseDelimitedFrom(in);
// note that in is a LimitedInputStream
if (e == null) {
break;
}
++counter;
long[] l = new long[e.getChildrenCount() + e.getRefChildrenCount()];
for (int i = 0; i < e.getChildrenCount(); ++i) {
l[i] = e.getChildren(i);
}
for (int i = e.getChildrenCount(); i < l.length; i++) {
int refId = e.getRefChildren(i - e.getChildrenCount());
l[i] = refIdList.get(refId);
}
dirs.put(e.getParent(), l);
}
LOG.info("Loaded " + counter + " directories");
return dirs;
}
private static ImmutableList<Long> loadINodeReferenceSection(InputStream in)
throws IOException {
LOG.info("Loading inode references");
ImmutableList.Builder<Long> builder = ImmutableList.builder();
long counter = 0;
while (true) {
FsImageProto.INodeReferenceSection.INodeReference e =
FsImageProto.INodeReferenceSection.INodeReference
.parseDelimitedFrom(in);
if (e == null) {
break;
}
++counter;
builder.add(e.getReferredId());
}
LOG.info("Loaded " + counter + " inode references");
return builder.build();
}
private static byte[][] loadINodeSection(InputStream in)
throws IOException {
FsImageProto.INodeSection s = FsImageProto.INodeSection
.parseDelimitedFrom(in);
LOG.info("Loading " + s.getNumInodes() + " inodes.");
final byte[][] inodes = new byte[(int) s.getNumInodes()][];
for (int i = 0; i < s.getNumInodes(); ++i) {
int size = CodedInputStream.readRawVarint32(in.read(), in);
byte[] bytes = new byte[size];
IOUtils.readFully(in, bytes, 0, size);
inodes[i] = bytes;
}
LOG.debug("Sorting inodes");
Arrays.sort(inodes, INODE_BYTES_COMPARATOR);
LOG.debug("Finished sorting inodes");
return inodes;
}
static String[] loadStringTable(InputStream in) throws
IOException {
FsImageProto.StringTableSection s = FsImageProto.StringTableSection
.parseDelimitedFrom(in);
LOG.info("Loading " + s.getNumEntry() + " strings");
String[] stringTable = new String[s.getNumEntry() + 1];
for (int i = 0; i < s.getNumEntry(); ++i) {
FsImageProto.StringTableSection.Entry e = FsImageProto
.StringTableSection.Entry.parseDelimitedFrom(in);
stringTable[e.getId()] = e.getStr();
}
return stringTable;
}
/**
* Return the JSON formatted FileStatus of the specified file.
* @param path a path specifies a file
* @return JSON formatted FileStatus
* @throws IOException if failed to serialize fileStatus to JSON.
*/
String getFileStatus(String path) throws IOException {
ObjectMapper mapper = new ObjectMapper();
FsImageProto.INodeSection.INode inode = fromINodeId(lookup(path));
return "{\"FileStatus\":\n"
+ mapper.writeValueAsString(getFileStatus(inode, false)) + "\n}\n";
}
/**
* Return the JSON formatted list of the files in the specified directory.
* @param path a path specifies a directory to list
* @return JSON formatted file list in the directory
* @throws IOException if failed to serialize fileStatus to JSON.
*/
String listStatus(String path) throws IOException {
StringBuilder sb = new StringBuilder();
ObjectMapper mapper = new ObjectMapper();
List<Map<String, Object>> fileStatusList = getFileStatusList(path);
sb.append("{\"FileStatuses\":{\"FileStatus\":[\n");
int i = 0;
for (Map<String, Object> fileStatusMap : fileStatusList) {
if (i++ != 0) {
sb.append(',');
}
sb.append(mapper.writeValueAsString(fileStatusMap));
}
sb.append("\n]}}\n");
return sb.toString();
}
private List<Map<String, Object>> getFileStatusList(String path)
throws IOException {
List<Map<String, Object>> list = new ArrayList<Map<String, Object>>();
long id = lookup(path);
FsImageProto.INodeSection.INode inode = fromINodeId(id);
if (inode.getType() == FsImageProto.INodeSection.INode.Type.DIRECTORY) {
if (!dirmap.containsKey(id)) {
// if the directory is empty, return empty list
return list;
}
long[] children = dirmap.get(id);
for (long cid : children) {
list.add(getFileStatus(fromINodeId(cid), true));
}
} else {
list.add(getFileStatus(inode, false));
}
return list;
}
/**
* Return the JSON formatted XAttrNames of the specified file.
*
* @param path
* a path specifies a file
* @return JSON formatted XAttrNames
* @throws IOException
* if failed to serialize fileStatus to JSON.
*/
String listXAttrs(String path) throws IOException {
return JsonUtil.toJsonString(getXAttrList(path));
}
/**
* Return the JSON formatted XAttrs of the specified file.
*
* @param path
* a path specifies a file
* @return JSON formatted XAttrs
* @throws IOException
* if failed to serialize fileStatus to JSON.
*/
String getXAttrs(String path, List<String> names, String encoder)
throws IOException {
List<XAttr> xAttrs = getXAttrList(path);
List<XAttr> filtered;
if (names == null || names.size() == 0) {
filtered = xAttrs;
} else {
filtered = Lists.newArrayListWithCapacity(names.size());
for (String name : names) {
XAttr search = XAttrHelper.buildXAttr(name);
boolean found = false;
for (XAttr aXAttr : xAttrs) {
if (aXAttr.getNameSpace() == search.getNameSpace()
&& aXAttr.getName().equals(search.getName())) {
filtered.add(aXAttr);
found = true;
break;
}
}
if (!found) {
throw new IOException(
"At least one of the attributes provided was not found.");
}
}
}
return JsonUtil.toJsonString(filtered,
new XAttrEncodingParam(encoder).getEncoding());
}
private List<XAttr> getXAttrList(String path) throws IOException {
long id = lookup(path);
FsImageProto.INodeSection.INode inode = fromINodeId(id);
switch (inode.getType()) {
case FILE:
return FSImageFormatPBINode.Loader.loadXAttrs(
inode.getFile().getXAttrs(), stringTable);
case DIRECTORY:
return FSImageFormatPBINode.Loader.loadXAttrs(inode.getDirectory()
.getXAttrs(), stringTable);
default:
return null;
}
}
/**
* Return the JSON formatted ACL status of the specified file.
* @param path a path specifies a file
* @return JSON formatted AclStatus
* @throws IOException if failed to serialize fileStatus to JSON.
*/
String getAclStatus(String path) throws IOException {
PermissionStatus p = getPermissionStatus(path);
List<AclEntry> aclEntryList = getAclEntryList(path);
FsPermission permission = p.getPermission();
AclStatus.Builder builder = new AclStatus.Builder();
builder.owner(p.getUserName()).group(p.getGroupName())
.addEntries(aclEntryList).setPermission(permission)
.stickyBit(permission.getStickyBit());
AclStatus aclStatus = builder.build();
return JsonUtil.toJsonString(aclStatus);
}
private List<AclEntry> getAclEntryList(String path) throws IOException {
long id = lookup(path);
FsImageProto.INodeSection.INode inode = fromINodeId(id);
switch (inode.getType()) {
case FILE: {
FsImageProto.INodeSection.INodeFile f = inode.getFile();
return FSImageFormatPBINode.Loader.loadAclEntries(
f.getAcl(), stringTable);
}
case DIRECTORY: {
FsImageProto.INodeSection.INodeDirectory d = inode.getDirectory();
return FSImageFormatPBINode.Loader.loadAclEntries(
d.getAcl(), stringTable);
}
default: {
return new ArrayList<AclEntry>();
}
}
}
private PermissionStatus getPermissionStatus(String path) throws IOException {
long id = lookup(path);
FsImageProto.INodeSection.INode inode = fromINodeId(id);
switch (inode.getType()) {
case FILE: {
FsImageProto.INodeSection.INodeFile f = inode.getFile();
return FSImageFormatPBINode.Loader.loadPermission(
f.getPermission(), stringTable);
}
case DIRECTORY: {
FsImageProto.INodeSection.INodeDirectory d = inode.getDirectory();
return FSImageFormatPBINode.Loader.loadPermission(
d.getPermission(), stringTable);
}
case SYMLINK: {
FsImageProto.INodeSection.INodeSymlink s = inode.getSymlink();
return FSImageFormatPBINode.Loader.loadPermission(
s.getPermission(), stringTable);
}
default: {
return null;
}
}
}
/**
* Return the INodeId of the specified path.
*/
private long lookup(String path) throws IOException {
Preconditions.checkArgument(path.startsWith("/"));
long id = INodeId.ROOT_INODE_ID;
for (int offset = 0, next; offset < path.length(); offset = next) {
next = path.indexOf('/', offset + 1);
if (next == -1) {
next = path.length();
}
if (offset + 1 > next) {
break;
}
final String component = path.substring(offset + 1, next);
if (component.isEmpty()) {
continue;
}
final long[] children = dirmap.get(id);
if (children == null) {
throw new FileNotFoundException(path);
}
boolean found = false;
for (long cid : children) {
FsImageProto.INodeSection.INode child = fromINodeId(cid);
if (component.equals(child.getName().toStringUtf8())) {
found = true;
id = child.getId();
break;
}
}
if (!found) {
throw new FileNotFoundException(path);
}
}
return id;
}
private Map<String, Object> getFileStatus
(FsImageProto.INodeSection.INode inode, boolean printSuffix){
Map<String, Object> map = Maps.newHashMap();
switch (inode.getType()) {
case FILE: {
FsImageProto.INodeSection.INodeFile f = inode.getFile();
PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(
f.getPermission(), stringTable);
map.put("accessTime", f.getAccessTime());
map.put("blockSize", f.getPreferredBlockSize());
map.put("group", p.getGroupName());
map.put("length", getFileSize(f));
map.put("modificationTime", f.getModificationTime());
map.put("owner", p.getUserName());
map.put("pathSuffix",
printSuffix ? inode.getName().toStringUtf8() : "");
map.put("permission", toString(p.getPermission()));
map.put("replication", f.getReplication());
map.put("type", inode.getType());
map.put("fileId", inode.getId());
map.put("childrenNum", 0);
return map;
}
case DIRECTORY: {
FsImageProto.INodeSection.INodeDirectory d = inode.getDirectory();
PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(
d.getPermission(), stringTable);
map.put("accessTime", 0);
map.put("blockSize", 0);
map.put("group", p.getGroupName());
map.put("length", 0);
map.put("modificationTime", d.getModificationTime());
map.put("owner", p.getUserName());
map.put("pathSuffix",
printSuffix ? inode.getName().toStringUtf8() : "");
map.put("permission", toString(p.getPermission()));
map.put("replication", 0);
map.put("type", inode.getType());
map.put("fileId", inode.getId());
map.put("childrenNum", dirmap.containsKey(inode.getId()) ?
dirmap.get(inode.getId()).length : 0);
return map;
}
case SYMLINK: {
FsImageProto.INodeSection.INodeSymlink d = inode.getSymlink();
PermissionStatus p = FSImageFormatPBINode.Loader.loadPermission(
d.getPermission(), stringTable);
map.put("accessTime", d.getAccessTime());
map.put("blockSize", 0);
map.put("group", p.getGroupName());
map.put("length", 0);
map.put("modificationTime", d.getModificationTime());
map.put("owner", p.getUserName());
map.put("pathSuffix",
printSuffix ? inode.getName().toStringUtf8() : "");
map.put("permission", toString(p.getPermission()));
map.put("replication", 0);
map.put("type", inode.getType());
map.put("symlink", d.getTarget().toStringUtf8());
map.put("fileId", inode.getId());
map.put("childrenNum", 0);
return map;
}
default:
return null;
}
}
static long getFileSize(FsImageProto.INodeSection.INodeFile f) {
long size = 0;
for (HdfsProtos.BlockProto p : f.getBlocksList()) {
size += p.getNumBytes();
}
return size;
}
private String toString(FsPermission permission) {
return String.format("%o", permission.toShort());
}
private FsImageProto.INodeSection.INode fromINodeId(final long id)
throws IOException {
int l = 0, r = inodes.length;
while (l < r) {
int mid = l + (r - l) / 2;
FsImageProto.INodeSection.INode n = FsImageProto.INodeSection.INode
.parseFrom(inodes[mid]);
long nid = n.getId();
if (id > nid) {
l = mid + 1;
} else if (id < nid) {
r = mid;
} else {
return n;
}
}
return null;
}
}