org.apache.hadoop.hdfs.tools.offlineImageViewer.PBImageXmlWriter.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hdfs.tools.offlineImageViewer.PBImageXmlWriter.java

Source

/**
 * 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.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.TimeZone;

import com.google.protobuf.ByteString;
import org.apache.commons.codec.binary.Hex;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.PermissionStatus;
import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoExpirationProto;
import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CacheDirectiveInfoProto;
import org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos.CachePoolInfoProto;
import org.apache.hadoop.hdfs.protocol.proto.HdfsProtos.BlockProto;
import org.apache.hadoop.hdfs.protocol.proto.XAttrProtos;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode;
import org.apache.hadoop.hdfs.server.namenode.FSImageFormatProtobuf.SectionName;
import org.apache.hadoop.hdfs.server.namenode.FSImageUtil;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.CacheManagerSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FileSummary;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.FilesUnderConstructionSection.FileUnderConstructionEntry;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeDirectorySection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.AclFeatureProto;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeDirectory;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeSection.INodeSymlink;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.INodeReferenceSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.NameSystemSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SecretManagerSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotDiffSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.SnapshotSection;
import org.apache.hadoop.hdfs.server.namenode.FsImageProto.StringTableSection;
import org.apache.hadoop.hdfs.util.XMLUtils;
import org.apache.hadoop.util.LimitInputStream;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.apache.hadoop.util.VersionInfo;

import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_MASK;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_EXT_MASK;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAMESPACE_EXT_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAME_OFFSET;
import static org.apache.hadoop.hdfs.server.namenode.FSImageFormatPBINode.XATTR_NAME_MASK;

/**
 * PBImageXmlWriter walks over an fsimage structure and writes out
 * an equivalent XML document that contains the fsimage's components.
 */
@InterfaceAudience.Private
public final class PBImageXmlWriter {
    public static final String NAME_SECTION_NAME = "NameSection";
    public static final String INODE_SECTION_NAME = "INodeSection";
    public static final String SECRET_MANAGER_SECTION_NAME = "SecretManagerSection";
    public static final String CACHE_MANAGER_SECTION_NAME = "CacheManagerSection";
    public static final String SNAPSHOT_DIFF_SECTION_NAME = "SnapshotDiffSection";
    public static final String INODE_REFERENCE_SECTION_NAME = "INodeReferenceSection";
    public static final String INODE_DIRECTORY_SECTION_NAME = "INodeDirectorySection";
    public static final String FILE_UNDER_CONSTRUCTION_SECTION_NAME = "FileUnderConstructionSection";
    public static final String SNAPSHOT_SECTION_NAME = "SnapshotSection";

    public static final String SECTION_ID = "id";
    public static final String SECTION_REPLICATION = "replication";
    public static final String SECTION_PATH = "path";
    public static final String SECTION_NAME = "name";

    public static final String NAME_SECTION_NAMESPACE_ID = "namespaceId";
    public static final String NAME_SECTION_GENSTAMPV1 = "genstampV1";
    public static final String NAME_SECTION_GENSTAMPV2 = "genstampV2";
    public static final String NAME_SECTION_GENSTAMPV1_LIMIT = "genstampV1Limit";
    public static final String NAME_SECTION_LAST_ALLOCATED_BLOCK_ID = "lastAllocatedBlockId";
    public static final String NAME_SECTION_TXID = "txid";
    public static final String NAME_SECTION_ROLLING_UPGRADE_START_TIME = "rollingUpgradeStartTime";
    public static final String NAME_SECTION_LAST_ALLOCATED_STRIPED_BLOCK_ID = "lastAllocatedStripedBlockId";

    public static final String INODE_SECTION_LAST_INODE_ID = "lastInodeId";
    public static final String INODE_SECTION_NUM_INODES = "numInodes";
    public static final String INODE_SECTION_TYPE = "type";
    public static final String INODE_SECTION_MTIME = "mtime";
    public static final String INODE_SECTION_ATIME = "atime";
    public static final String INODE_SECTION_PREFERRED_BLOCK_SIZE = "preferredBlockSize";
    public static final String INODE_SECTION_PERMISSION = "permission";
    public static final String INODE_SECTION_BLOCKS = "blocks";
    public static final String INODE_SECTION_BLOCK = "block";
    public static final String INODE_SECTION_GEMSTAMP = "genstamp";
    public static final String INODE_SECTION_NUM_BYTES = "numBytes";
    public static final String INODE_SECTION_FILE_UNDER_CONSTRUCTION = "file-under-construction";
    public static final String INODE_SECTION_CLIENT_NAME = "clientName";
    public static final String INODE_SECTION_CLIENT_MACHINE = "clientMachine";
    public static final String INODE_SECTION_ACL = "acl";
    public static final String INODE_SECTION_ACLS = "acls";
    public static final String INODE_SECTION_XATTR = "xattr";
    public static final String INODE_SECTION_XATTRS = "xattrs";
    public static final String INODE_SECTION_STORAGE_POLICY_ID = "storagePolicyId";
    public static final String INODE_SECTION_IS_STRIPED = "isStriped";
    public static final String INODE_SECTION_NS_QUOTA = "nsquota";
    public static final String INODE_SECTION_DS_QUOTA = "dsquota";
    public static final String INODE_SECTION_TYPE_QUOTA = "typeQuota";
    public static final String INODE_SECTION_QUOTA = "quota";
    public static final String INODE_SECTION_TARGET = "target";
    public static final String INODE_SECTION_NS = "ns";
    public static final String INODE_SECTION_VAL = "val";
    public static final String INODE_SECTION_VAL_HEX = "valHex";
    public static final String INODE_SECTION_INODE = "inode";

    public static final String SECRET_MANAGER_SECTION_CURRENT_ID = "currentId";
    public static final String SECRET_MANAGER_SECTION_TOKEN_SEQUENCE_NUMBER = "tokenSequenceNumber";
    public static final String SECRET_MANAGER_SECTION_NUM_DELEGATION_KEYS = "numDelegationKeys";
    public static final String SECRET_MANAGER_SECTION_NUM_TOKENS = "numTokens";
    public static final String SECRET_MANAGER_SECTION_EXPIRY = "expiry";
    public static final String SECRET_MANAGER_SECTION_KEY = "key";
    public static final String SECRET_MANAGER_SECTION_DELEGATION_KEY = "delegationKey";
    public static final String SECRET_MANAGER_SECTION_VERSION = "version";
    public static final String SECRET_MANAGER_SECTION_OWNER = "owner";
    public static final String SECRET_MANAGER_SECTION_RENEWER = "renewer";
    public static final String SECRET_MANAGER_SECTION_REAL_USER = "realUser";
    public static final String SECRET_MANAGER_SECTION_ISSUE_DATE = "issueDate";
    public static final String SECRET_MANAGER_SECTION_MAX_DATE = "maxDate";
    public static final String SECRET_MANAGER_SECTION_SEQUENCE_NUMBER = "sequenceNumber";
    public static final String SECRET_MANAGER_SECTION_MASTER_KEY_ID = "masterKeyId";
    public static final String SECRET_MANAGER_SECTION_EXPIRY_DATE = "expiryDate";
    public static final String SECRET_MANAGER_SECTION_TOKEN = "token";

    public static final String CACHE_MANAGER_SECTION_NEXT_DIRECTIVE_ID = "nextDirectiveId";
    public static final String CACHE_MANAGER_SECTION_NUM_POOLS = "numPools";
    public static final String CACHE_MANAGER_SECTION_NUM_DIRECTIVES = "numDirectives";
    public static final String CACHE_MANAGER_SECTION_POOL_NAME = "poolName";
    public static final String CACHE_MANAGER_SECTION_OWNER_NAME = "ownerName";
    public static final String CACHE_MANAGER_SECTION_GROUP_NAME = "groupName";
    public static final String CACHE_MANAGER_SECTION_MODE = "mode";
    public static final String CACHE_MANAGER_SECTION_LIMIT = "limit";
    public static final String CACHE_MANAGER_SECTION_MAX_RELATIVE_EXPIRY = "maxRelativeExpiry";
    public static final String CACHE_MANAGER_SECTION_POOL = "pool";
    public static final String CACHE_MANAGER_SECTION_EXPIRATION = "expiration";
    public static final String CACHE_MANAGER_SECTION_MILLIS = "millis";
    public static final String CACHE_MANAGER_SECTION_RELATIVE = "relative";
    public static final String CACHE_MANAGER_SECTION_DIRECTIVE = "directive";

    public static final String SNAPSHOT_DIFF_SECTION_INODE_ID = "inodeId";
    public static final String SNAPSHOT_DIFF_SECTION_COUNT = "count";
    public static final String SNAPSHOT_DIFF_SECTION_SNAPSHOT_ID = "snapshotId";
    public static final String SNAPSHOT_DIFF_SECTION_CHILDREN_SIZE = "childrenSize";
    public static final String SNAPSHOT_DIFF_SECTION_IS_SNAPSHOT_ROOT = "isSnapshotRoot";
    public static final String SNAPSHOT_DIFF_SECTION_CREATED_LIST_SIZE = "createdListSize";
    public static final String SNAPSHOT_DIFF_SECTION_DELETED_INODE = "deletedInode";
    public static final String SNAPSHOT_DIFF_SECTION_DELETED_INODE_REF = "deletedInoderef";
    public static final String SNAPSHOT_DIFF_SECTION_CREATED = "created";
    public static final String SNAPSHOT_DIFF_SECTION_SIZE = "size";
    public static final String SNAPSHOT_DIFF_SECTION_FILE_DIFF_ENTRY = "fileDiffEntry";
    public static final String SNAPSHOT_DIFF_SECTION_DIR_DIFF_ENTRY = "dirDiffEntry";
    public static final String SNAPSHOT_DIFF_SECTION_FILE_DIFF = "fileDiff";
    public static final String SNAPSHOT_DIFF_SECTION_DIR_DIFF = "dirDiff";

    public static final String INODE_REFERENCE_SECTION_REFERRED_ID = "referredId";
    public static final String INODE_REFERENCE_SECTION_DST_SNAPSHOT_ID = "dstSnapshotId";
    public static final String INODE_REFERENCE_SECTION_LAST_SNAPSHOT_ID = "lastSnapshotId";
    public static final String INODE_REFERENCE_SECTION_REF = "ref";

    public static final String INODE_DIRECTORY_SECTION_PARENT = "parent";
    public static final String INODE_DIRECTORY_SECTION_CHILD = "child";
    public static final String INODE_DIRECTORY_SECTION_REF_CHILD = "refChild";
    public static final String INODE_DIRECTORY_SECTION_DIRECTORY = "directory";

    public static final String SNAPSHOT_SECTION_SNAPSHOT_COUNTER = "snapshotCounter";
    public static final String SNAPSHOT_SECTION_NUM_SNAPSHOTS = "numSnapshots";
    public static final String SNAPSHOT_SECTION_SNAPSHOT_TABLE_DIR = "snapshottableDir";
    public static final String SNAPSHOT_SECTION_DIR = "dir";
    public static final String SNAPSHOT_SECTION_ROOT = "root";
    public static final String SNAPSHOT_SECTION_SNAPSHOT = "snapshot";

    private final Configuration conf;
    private final PrintStream out;
    private final SimpleDateFormat isoDateFormat;
    private String[] stringTable;

    public static SimpleDateFormat createSimpleDateFormat() {
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS");
        format.setTimeZone(TimeZone.getTimeZone("UTC"));
        return format;
    }

    public PBImageXmlWriter(Configuration conf, PrintStream out) {
        this.conf = conf;
        this.out = out;
        this.isoDateFormat = createSimpleDateFormat();
    }

    public void visit(RandomAccessFile file) throws IOException {
        if (!FSImageUtil.checkFileFormat(file)) {
            throw new IOException("Unrecognized FSImage");
        }

        FileSummary summary = FSImageUtil.loadSummary(file);
        try (FileInputStream fin = new FileInputStream(file.getFD())) {
            out.print("<?xml version=\"1.0\"?>\n<fsimage>");

            out.print("<version>");
            o("layoutVersion", summary.getLayoutVersion());
            o("onDiskVersion", summary.getOndiskVersion());
            // Output the version of OIV (which is not necessarily the version of
            // the fsimage file).  This could be helpful in the case where a bug
            // in OIV leads to information loss in the XML-- we can quickly tell
            // if a specific fsimage XML file is affected by this bug.
            o("oivRevision", VersionInfo.getRevision());
            out.print("</version>\n");

            ArrayList<FileSummary.Section> sections = Lists.newArrayList(summary.getSectionsList());
            Collections.sort(sections, new Comparator<FileSummary.Section>() {
                @Override
                public int compare(FileSummary.Section s1, FileSummary.Section s2) {
                    SectionName n1 = SectionName.fromString(s1.getName());
                    SectionName n2 = 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 (FileSummary.Section s : sections) {
                fin.getChannel().position(s.getOffset());
                InputStream is = FSImageUtil.wrapInputStreamForCompression(conf, summary.getCodec(),
                        new BufferedInputStream(new LimitInputStream(fin, s.getLength())));

                switch (SectionName.fromString(s.getName())) {
                case NS_INFO:
                    dumpNameSection(is);
                    break;
                case STRING_TABLE:
                    loadStringTable(is);
                    break;
                case INODE:
                    dumpINodeSection(is);
                    break;
                case INODE_REFERENCE:
                    dumpINodeReferenceSection(is);
                    break;
                case INODE_DIR:
                    dumpINodeDirectorySection(is);
                    break;
                case FILES_UNDERCONSTRUCTION:
                    dumpFileUnderConstructionSection(is);
                    break;
                case SNAPSHOT:
                    dumpSnapshotSection(is);
                    break;
                case SNAPSHOT_DIFF:
                    dumpSnapshotDiffSection(is);
                    break;
                case SECRET_MANAGER:
                    dumpSecretManagerSection(is);
                    break;
                case CACHE_MANAGER:
                    dumpCacheManagerSection(is);
                    break;
                default:
                    break;
                }
            }
            out.print("</fsimage>\n");
        }
    }

    private void dumpCacheManagerSection(InputStream is) throws IOException {
        out.print("<" + CACHE_MANAGER_SECTION_NAME + ">");
        CacheManagerSection s = CacheManagerSection.parseDelimitedFrom(is);
        o(CACHE_MANAGER_SECTION_NEXT_DIRECTIVE_ID, s.getNextDirectiveId());
        o(CACHE_MANAGER_SECTION_NUM_DIRECTIVES, s.getNumDirectives());
        o(CACHE_MANAGER_SECTION_NUM_POOLS, s.getNumPools());
        for (int i = 0; i < s.getNumPools(); ++i) {
            CachePoolInfoProto p = CachePoolInfoProto.parseDelimitedFrom(is);
            out.print("<" + CACHE_MANAGER_SECTION_POOL + ">");
            o(CACHE_MANAGER_SECTION_POOL_NAME, p.getPoolName())
                    .o(CACHE_MANAGER_SECTION_OWNER_NAME, p.getOwnerName())
                    .o(CACHE_MANAGER_SECTION_GROUP_NAME, p.getGroupName())
                    .o(CACHE_MANAGER_SECTION_MODE, p.getMode()).o(CACHE_MANAGER_SECTION_LIMIT, p.getLimit())
                    .o(CACHE_MANAGER_SECTION_MAX_RELATIVE_EXPIRY, p.getMaxRelativeExpiry());
            out.print("</" + CACHE_MANAGER_SECTION_POOL + ">\n");
        }
        for (int i = 0; i < s.getNumDirectives(); ++i) {
            CacheDirectiveInfoProto p = CacheDirectiveInfoProto.parseDelimitedFrom(is);
            out.print("<" + CACHE_MANAGER_SECTION_DIRECTIVE + ">");
            o(SECTION_ID, p.getId()).o(SECTION_PATH, p.getPath()).o(SECTION_REPLICATION, p.getReplication())
                    .o(CACHE_MANAGER_SECTION_POOL, p.getPool());
            out.print("<" + CACHE_MANAGER_SECTION_EXPIRATION + ">");
            CacheDirectiveInfoExpirationProto e = p.getExpiration();
            o(CACHE_MANAGER_SECTION_MILLIS, e.getMillis()).o(CACHE_MANAGER_SECTION_RELATIVE, e.getIsRelative());
            out.print("</" + CACHE_MANAGER_SECTION_EXPIRATION + ">\n");
            out.print("</" + CACHE_MANAGER_SECTION_DIRECTIVE + ">\n");
        }
        out.print("</" + CACHE_MANAGER_SECTION_NAME + ">\n");

    }

    private void dumpFileUnderConstructionSection(InputStream in) throws IOException {
        out.print("<" + FILE_UNDER_CONSTRUCTION_SECTION_NAME + ">");
        while (true) {
            FileUnderConstructionEntry e = FileUnderConstructionEntry.parseDelimitedFrom(in);
            if (e == null) {
                break;
            }
            out.print("<" + INODE_SECTION_INODE + ">");
            o(SECTION_ID, e.getInodeId()).o(SECTION_PATH, e.getFullPath());
            out.print("</" + INODE_SECTION_INODE + ">\n");
        }
        out.print("</" + FILE_UNDER_CONSTRUCTION_SECTION_NAME + ">\n");
    }

    private void dumpXattrs(INodeSection.XAttrFeatureProto xattrs) {
        out.print("<" + INODE_SECTION_XATTRS + ">");
        for (INodeSection.XAttrCompactProto xattr : xattrs.getXAttrsList()) {
            out.print("<" + INODE_SECTION_XATTR + ">");
            int encodedName = xattr.getName();
            int ns = (XATTR_NAMESPACE_MASK & (encodedName >> XATTR_NAMESPACE_OFFSET))
                    | ((XATTR_NAMESPACE_EXT_MASK & (encodedName >> XATTR_NAMESPACE_EXT_OFFSET)) << 2);
            o(INODE_SECTION_NS, XAttrProtos.XAttrProto.XAttrNamespaceProto.valueOf(ns).toString());
            o(SECTION_NAME, stringTable[XATTR_NAME_MASK & (encodedName >> XATTR_NAME_OFFSET)]);
            ByteString val = xattr.getValue();
            if (val.isValidUtf8()) {
                o(INODE_SECTION_VAL, val.toStringUtf8());
            } else {
                o(INODE_SECTION_VAL_HEX, Hex.encodeHexString(val.toByteArray()));
            }
            out.print("</" + INODE_SECTION_XATTR + ">");
        }
        out.print("</" + INODE_SECTION_XATTRS + ">");
    }

    private void dumpINodeDirectory(INodeDirectory d) {
        o(INODE_SECTION_MTIME, d.getModificationTime()).o(INODE_SECTION_PERMISSION,
                dumpPermission(d.getPermission()));
        if (d.hasXAttrs()) {
            dumpXattrs(d.getXAttrs());
        }
        dumpAcls(d.getAcl());
        if (d.hasDsQuota() && d.hasNsQuota()) {
            o(INODE_SECTION_NS_QUOTA, d.getNsQuota()).o(INODE_SECTION_DS_QUOTA, d.getDsQuota());
        }
        INodeSection.QuotaByStorageTypeFeatureProto typeQuotas = d.getTypeQuotas();
        if (typeQuotas != null) {
            for (INodeSection.QuotaByStorageTypeEntryProto entry : typeQuotas.getQuotasList()) {
                out.print("<" + INODE_SECTION_TYPE_QUOTA + ">");
                o(INODE_SECTION_TYPE, entry.getStorageType().toString());
                o(INODE_SECTION_QUOTA, entry.getQuota());
                out.print("</" + INODE_SECTION_TYPE_QUOTA + ">");
            }
        }
    }

    private void dumpINodeDirectorySection(InputStream in) throws IOException {
        out.print("<" + INODE_DIRECTORY_SECTION_NAME + ">");
        while (true) {
            INodeDirectorySection.DirEntry e = INodeDirectorySection.DirEntry.parseDelimitedFrom(in);
            // note that in is a LimitedInputStream
            if (e == null) {
                break;
            }
            out.print("<" + INODE_DIRECTORY_SECTION_DIRECTORY + ">");
            o(INODE_DIRECTORY_SECTION_PARENT, e.getParent());
            for (long id : e.getChildrenList()) {
                o(INODE_DIRECTORY_SECTION_CHILD, id);
            }
            for (int refId : e.getRefChildrenList()) {
                o(INODE_DIRECTORY_SECTION_REF_CHILD, refId);
            }
            out.print("</" + INODE_DIRECTORY_SECTION_DIRECTORY + ">\n");
        }
        out.print("</" + INODE_DIRECTORY_SECTION_NAME + ">\n");
    }

    private void dumpINodeReferenceSection(InputStream in) throws IOException {
        out.print("<" + INODE_REFERENCE_SECTION_NAME + ">");
        while (true) {
            INodeReferenceSection.INodeReference e = INodeReferenceSection.INodeReference.parseDelimitedFrom(in);
            if (e == null) {
                break;
            }
            dumpINodeReference(e);
        }
        out.print("</" + INODE_REFERENCE_SECTION_NAME + ">");
    }

    private void dumpINodeReference(INodeReferenceSection.INodeReference r) {
        out.print("<" + INODE_REFERENCE_SECTION_REF + ">");
        o(INODE_REFERENCE_SECTION_REFERRED_ID, r.getReferredId()).o(SECTION_NAME, r.getName().toStringUtf8())
                .o(INODE_REFERENCE_SECTION_DST_SNAPSHOT_ID, r.getDstSnapshotId())
                .o(INODE_REFERENCE_SECTION_LAST_SNAPSHOT_ID, r.getLastSnapshotId());
        out.print("</" + INODE_REFERENCE_SECTION_REF + ">\n");
    }

    private void dumpINodeFile(INodeSection.INodeFile f) {
        o(SECTION_REPLICATION, f.getReplication()).o(INODE_SECTION_MTIME, f.getModificationTime())
                .o(INODE_SECTION_ATIME, f.getAccessTime())
                .o(INODE_SECTION_PREFERRED_BLOCK_SIZE, f.getPreferredBlockSize())
                .o(INODE_SECTION_PERMISSION, dumpPermission(f.getPermission()));
        if (f.hasXAttrs()) {
            dumpXattrs(f.getXAttrs());
        }
        dumpAcls(f.getAcl());
        if (f.getBlocksCount() > 0) {
            out.print("<" + INODE_SECTION_BLOCKS + ">");
            for (BlockProto b : f.getBlocksList()) {
                out.print("<" + INODE_SECTION_BLOCK + ">");
                o(SECTION_ID, b.getBlockId()).o(INODE_SECTION_GEMSTAMP, b.getGenStamp()).o(INODE_SECTION_NUM_BYTES,
                        b.getNumBytes());
                out.print("</" + INODE_SECTION_BLOCK + ">\n");
            }
            out.print("</" + INODE_SECTION_BLOCKS + ">\n");
        }
        if (f.hasStoragePolicyID()) {
            o(INODE_SECTION_STORAGE_POLICY_ID, f.getStoragePolicyID());
        }
        if (f.getIsStriped()) {
            out.print("<" + INODE_SECTION_IS_STRIPED + "/>");
        }

        if (f.hasFileUC()) {
            INodeSection.FileUnderConstructionFeature u = f.getFileUC();
            out.print("<" + INODE_SECTION_FILE_UNDER_CONSTRUCTION + ">");
            o(INODE_SECTION_CLIENT_NAME, u.getClientName()).o(INODE_SECTION_CLIENT_MACHINE, u.getClientMachine());
            out.print("</" + INODE_SECTION_FILE_UNDER_CONSTRUCTION + ">\n");
        }
    }

    private void dumpAcls(AclFeatureProto aclFeatureProto) {
        ImmutableList<AclEntry> aclEntryList = FSImageFormatPBINode.Loader.loadAclEntries(aclFeatureProto,
                stringTable);
        if (aclEntryList.size() > 0) {
            out.print("<" + INODE_SECTION_ACLS + ">");
            for (AclEntry aclEntry : aclEntryList) {
                o(INODE_SECTION_ACL, aclEntry.toString());
            }
            out.print("</" + INODE_SECTION_ACLS + ">");
        }
    }

    private void dumpINodeSection(InputStream in) throws IOException {
        INodeSection s = INodeSection.parseDelimitedFrom(in);
        out.print("<" + INODE_SECTION_NAME + ">");
        o(INODE_SECTION_LAST_INODE_ID, s.getLastInodeId());
        o(INODE_SECTION_NUM_INODES, s.getNumInodes());
        for (int i = 0; i < s.getNumInodes(); ++i) {
            INodeSection.INode p = INodeSection.INode.parseDelimitedFrom(in);
            out.print("<" + INODE_SECTION_INODE + ">");
            dumpINodeFields(p);
            out.print("</" + INODE_SECTION_INODE + ">\n");
        }
        out.print("</" + INODE_SECTION_NAME + ">\n");
    }

    private void dumpINodeFields(INodeSection.INode p) {
        o(SECTION_ID, p.getId()).o(INODE_SECTION_TYPE, p.getType()).o(SECTION_NAME, p.getName().toStringUtf8());
        if (p.hasFile()) {
            dumpINodeFile(p.getFile());
        } else if (p.hasDirectory()) {
            dumpINodeDirectory(p.getDirectory());
        } else if (p.hasSymlink()) {
            dumpINodeSymlink(p.getSymlink());
        }
    }

    private void dumpINodeSymlink(INodeSymlink s) {
        o(INODE_SECTION_PERMISSION, dumpPermission(s.getPermission()))
                .o(INODE_SECTION_TARGET, s.getTarget().toStringUtf8())
                .o(INODE_SECTION_MTIME, s.getModificationTime()).o(INODE_SECTION_ATIME, s.getAccessTime());
    }

    private void dumpNameSection(InputStream in) throws IOException {
        NameSystemSection s = NameSystemSection.parseDelimitedFrom(in);
        out.print("<" + NAME_SECTION_NAME + ">");
        o(NAME_SECTION_NAMESPACE_ID, s.getNamespaceId());
        o(NAME_SECTION_GENSTAMPV1, s.getGenstampV1()).o(NAME_SECTION_GENSTAMPV2, s.getGenstampV2())
                .o(NAME_SECTION_GENSTAMPV1_LIMIT, s.getGenstampV1Limit())
                .o(NAME_SECTION_LAST_ALLOCATED_BLOCK_ID, s.getLastAllocatedBlockId())
                .o(NAME_SECTION_TXID, s.getTransactionId());
        out.print("</" + NAME_SECTION_NAME + ">\n");
    }

    private String dumpPermission(long permission) {
        PermissionStatus permStatus = FSImageFormatPBINode.Loader.loadPermission(permission, stringTable);
        return String.format("%s:%s:%04o", permStatus.getUserName(), permStatus.getGroupName(),
                permStatus.getPermission().toExtendedShort());
    }

    private void dumpSecretManagerSection(InputStream is) throws IOException {
        out.print("<" + SECRET_MANAGER_SECTION_NAME + ">");
        SecretManagerSection s = SecretManagerSection.parseDelimitedFrom(is);
        int expectedNumDelegationKeys = s.getNumKeys();
        int expectedNumTokens = s.getNumTokens();
        o(SECRET_MANAGER_SECTION_CURRENT_ID, s.getCurrentId())
                .o(SECRET_MANAGER_SECTION_TOKEN_SEQUENCE_NUMBER, s.getTokenSequenceNumber())
                .o(SECRET_MANAGER_SECTION_NUM_DELEGATION_KEYS, expectedNumDelegationKeys)
                .o(SECRET_MANAGER_SECTION_NUM_TOKENS, expectedNumTokens);
        for (int i = 0; i < expectedNumDelegationKeys; i++) {
            SecretManagerSection.DelegationKey dkey = SecretManagerSection.DelegationKey.parseDelimitedFrom(is);
            out.print("<" + SECRET_MANAGER_SECTION_DELEGATION_KEY + ">");
            o(SECTION_ID, dkey.getId());
            o(SECRET_MANAGER_SECTION_KEY, Hex.encodeHexString(dkey.getKey().toByteArray()));
            if (dkey.hasExpiryDate()) {
                dumpDate(SECRET_MANAGER_SECTION_EXPIRY, dkey.getExpiryDate());
            }
            out.print("</" + SECRET_MANAGER_SECTION_DELEGATION_KEY + ">");
        }
        for (int i = 0; i < expectedNumTokens; i++) {
            SecretManagerSection.PersistToken token = SecretManagerSection.PersistToken.parseDelimitedFrom(is);
            out.print("<" + SECRET_MANAGER_SECTION_TOKEN + ">");
            if (token.hasVersion()) {
                o(SECRET_MANAGER_SECTION_VERSION, token.getVersion());
            }
            if (token.hasOwner()) {
                o(SECRET_MANAGER_SECTION_OWNER, token.getOwner());
            }
            if (token.hasRenewer()) {
                o(SECRET_MANAGER_SECTION_RENEWER, token.getRenewer());
            }
            if (token.hasRealUser()) {
                o(SECRET_MANAGER_SECTION_REAL_USER, token.getRealUser());
            }
            if (token.hasIssueDate()) {
                dumpDate(SECRET_MANAGER_SECTION_ISSUE_DATE, token.getIssueDate());
            }
            if (token.hasMaxDate()) {
                dumpDate(SECRET_MANAGER_SECTION_MAX_DATE, token.getMaxDate());
            }
            if (token.hasSequenceNumber()) {
                o(SECRET_MANAGER_SECTION_SEQUENCE_NUMBER, token.getSequenceNumber());
            }
            if (token.hasMasterKeyId()) {
                o(SECRET_MANAGER_SECTION_MASTER_KEY_ID, token.getMasterKeyId());
            }
            if (token.hasExpiryDate()) {
                dumpDate(SECRET_MANAGER_SECTION_EXPIRY_DATE, token.getExpiryDate());
            }
            out.print("</" + SECRET_MANAGER_SECTION_TOKEN + ">");
        }
        out.print("</" + SECRET_MANAGER_SECTION_NAME + ">");
    }

    private void dumpDate(String tag, long date) {
        out.print("<" + tag + ">" + isoDateFormat.format(new Date(date)) + "</" + tag + ">");
    }

    private void dumpSnapshotDiffSection(InputStream in) throws IOException {
        out.print("<" + SNAPSHOT_DIFF_SECTION_NAME + ">");
        while (true) {
            SnapshotDiffSection.DiffEntry e = SnapshotDiffSection.DiffEntry.parseDelimitedFrom(in);
            if (e == null) {
                break;
            }
            switch (e.getType()) {
            case FILEDIFF:
                out.print("<" + SNAPSHOT_DIFF_SECTION_FILE_DIFF_ENTRY + ">");
                break;
            case DIRECTORYDIFF:
                out.print("<" + SNAPSHOT_DIFF_SECTION_DIR_DIFF_ENTRY + ">");
                break;
            default:
                throw new IOException("unknown DiffEntry type " + e.getType());
            }
            o(SNAPSHOT_DIFF_SECTION_INODE_ID, e.getInodeId());
            o(SNAPSHOT_DIFF_SECTION_COUNT, e.getNumOfDiff());
            switch (e.getType()) {
            case FILEDIFF: {
                for (int i = 0; i < e.getNumOfDiff(); ++i) {
                    out.print("<" + SNAPSHOT_DIFF_SECTION_FILE_DIFF + ">");
                    SnapshotDiffSection.FileDiff f = SnapshotDiffSection.FileDiff.parseDelimitedFrom(in);
                    o(SNAPSHOT_DIFF_SECTION_SNAPSHOT_ID, f.getSnapshotId())
                            .o(SNAPSHOT_DIFF_SECTION_SIZE, f.getFileSize())
                            .o(SECTION_NAME, f.getName().toStringUtf8());
                    out.print("</" + SNAPSHOT_DIFF_SECTION_FILE_DIFF + ">\n");
                }
            }
                break;
            case DIRECTORYDIFF: {
                for (int i = 0; i < e.getNumOfDiff(); ++i) {
                    out.print("<" + SNAPSHOT_DIFF_SECTION_DIR_DIFF + ">");
                    SnapshotDiffSection.DirectoryDiff d = SnapshotDiffSection.DirectoryDiff.parseDelimitedFrom(in);
                    o(SNAPSHOT_DIFF_SECTION_SNAPSHOT_ID, d.getSnapshotId())
                            .o(SNAPSHOT_DIFF_SECTION_CHILDREN_SIZE, d.getChildrenSize())
                            .o(SNAPSHOT_DIFF_SECTION_IS_SNAPSHOT_ROOT, d.getIsSnapshotRoot())
                            .o(SECTION_NAME, d.getName().toStringUtf8())
                            .o(SNAPSHOT_DIFF_SECTION_CREATED_LIST_SIZE, d.getCreatedListSize());
                    for (long did : d.getDeletedINodeList()) {
                        o(SNAPSHOT_DIFF_SECTION_DELETED_INODE, did);
                    }
                    for (int dRefid : d.getDeletedINodeRefList()) {
                        o(SNAPSHOT_DIFF_SECTION_DELETED_INODE_REF, dRefid);
                    }
                    for (int j = 0; j < d.getCreatedListSize(); ++j) {
                        SnapshotDiffSection.CreatedListEntry ce = SnapshotDiffSection.CreatedListEntry
                                .parseDelimitedFrom(in);
                        out.print("<" + SNAPSHOT_DIFF_SECTION_CREATED + ">");
                        o(SECTION_NAME, ce.getName().toStringUtf8());
                        out.print("</" + SNAPSHOT_DIFF_SECTION_CREATED + ">\n");
                    }
                    out.print("</" + SNAPSHOT_DIFF_SECTION_DIR_DIFF + ">\n");
                }
                break;
            }
            default:
                break;
            }
            switch (e.getType()) {
            case FILEDIFF:
                out.print("</" + SNAPSHOT_DIFF_SECTION_FILE_DIFF_ENTRY + ">");
                break;
            case DIRECTORYDIFF:
                out.print("</" + SNAPSHOT_DIFF_SECTION_DIR_DIFF_ENTRY + ">");
                break;
            default:
                throw new IOException("unknown DiffEntry type " + e.getType());
            }
        }
        out.print("</" + SNAPSHOT_DIFF_SECTION_NAME + ">\n");
    }

    private void dumpSnapshotSection(InputStream in) throws IOException {
        out.print("<" + SNAPSHOT_SECTION_NAME + ">");
        SnapshotSection s = SnapshotSection.parseDelimitedFrom(in);
        o(SNAPSHOT_SECTION_SNAPSHOT_COUNTER, s.getSnapshotCounter());
        o(SNAPSHOT_SECTION_NUM_SNAPSHOTS, s.getNumSnapshots());
        if (s.getSnapshottableDirCount() > 0) {
            out.print("<" + SNAPSHOT_SECTION_SNAPSHOT_TABLE_DIR + ">");
            for (long id : s.getSnapshottableDirList()) {
                o(SNAPSHOT_SECTION_DIR, id);
            }
            out.print("</" + SNAPSHOT_SECTION_SNAPSHOT_TABLE_DIR + ">\n");
        }
        for (int i = 0; i < s.getNumSnapshots(); ++i) {
            SnapshotSection.Snapshot pbs = SnapshotSection.Snapshot.parseDelimitedFrom(in);
            out.print("<" + SNAPSHOT_SECTION_SNAPSHOT + ">");
            o(SECTION_ID, pbs.getSnapshotId());
            out.print("<" + SNAPSHOT_SECTION_ROOT + ">");
            dumpINodeFields(pbs.getRoot());
            out.print("</" + SNAPSHOT_SECTION_ROOT + ">");
            out.print("</" + SNAPSHOT_SECTION_SNAPSHOT + ">");
        }
        out.print("</" + SNAPSHOT_SECTION_NAME + ">\n");
    }

    private void loadStringTable(InputStream in) throws IOException {
        StringTableSection s = StringTableSection.parseDelimitedFrom(in);
        stringTable = new String[s.getNumEntry() + 1];
        for (int i = 0; i < s.getNumEntry(); ++i) {
            StringTableSection.Entry e = StringTableSection.Entry.parseDelimitedFrom(in);
            stringTable[e.getId()] = e.getStr();
        }
    }

    private PBImageXmlWriter o(final String e, final Object v) {
        if (v instanceof Boolean) {
            // For booleans, the presence of the element indicates true, and its
            // absence indicates false.
            if ((Boolean) v != false) {
                out.print("<" + e + "/>");
            }
            return this;
        }
        out.print("<" + e + ">" + XMLUtils.mangleXmlString(v.toString(), true) + "</" + e + ">");
        return this;
    }
}