|
@@ -0,0 +1,821 @@
|
|
|
|
+/**
|
|
|
|
+ * 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.DataInputStream;
|
|
|
|
+import java.io.IOException;
|
|
|
|
+import java.text.DateFormat;
|
|
|
|
+import java.text.SimpleDateFormat;
|
|
|
|
+import java.util.Date;
|
|
|
|
+import java.util.HashMap;
|
|
|
|
+import java.util.Map;
|
|
|
|
+
|
|
|
|
+import org.apache.hadoop.conf.Configuration;
|
|
|
|
+import org.apache.hadoop.fs.permission.FsPermission;
|
|
|
|
+import org.apache.hadoop.hdfs.protocol.DatanodeInfo.AdminStates;
|
|
|
|
+import org.apache.hadoop.hdfs.protocol.LayoutFlags;
|
|
|
|
+import org.apache.hadoop.hdfs.protocol.LayoutVersion.Feature;
|
|
|
|
+import org.apache.hadoop.hdfs.security.token.delegation.DelegationTokenIdentifier;
|
|
|
|
+import org.apache.hadoop.hdfs.server.namenode.FSImageSerialization;
|
|
|
|
+import org.apache.hadoop.hdfs.server.namenode.INodeId;
|
|
|
|
+import org.apache.hadoop.hdfs.server.namenode.NameNodeLayoutVersion;
|
|
|
|
+import org.apache.hadoop.hdfs.tools.offlineImageViewer.ImageVisitor.ImageElement;
|
|
|
|
+import org.apache.hadoop.io.Text;
|
|
|
|
+import org.apache.hadoop.io.WritableUtils;
|
|
|
|
+import org.apache.hadoop.io.compress.CompressionCodec;
|
|
|
|
+import org.apache.hadoop.io.compress.CompressionCodecFactory;
|
|
|
|
+import org.apache.hadoop.security.token.delegation.DelegationKey;
|
|
|
|
+
|
|
|
|
+/**
|
|
|
|
+ * ImageLoaderCurrent processes Hadoop FSImage files and walks over
|
|
|
|
+ * them using a provided ImageVisitor, calling the visitor at each element
|
|
|
|
+ * enumerated below.
|
|
|
|
+ *
|
|
|
|
+ * The only difference between v18 and v19 was the utilization of the
|
|
|
|
+ * stickybit. Therefore, the same viewer can reader either format.
|
|
|
|
+ *
|
|
|
|
+ * Versions -19 fsimage layout (with changes from -16 up):
|
|
|
|
+ * Image version (int)
|
|
|
|
+ * Namepsace ID (int)
|
|
|
|
+ * NumFiles (long)
|
|
|
|
+ * Generation stamp (long)
|
|
|
|
+ * INodes (count = NumFiles)
|
|
|
|
+ * INode
|
|
|
|
+ * Path (String)
|
|
|
|
+ * Replication (short)
|
|
|
|
+ * Modification Time (long as date)
|
|
|
|
+ * Access Time (long) // added in -16
|
|
|
|
+ * Block size (long)
|
|
|
|
+ * Num blocks (int)
|
|
|
|
+ * Blocks (count = Num blocks)
|
|
|
|
+ * Block
|
|
|
|
+ * Block ID (long)
|
|
|
|
+ * Num bytes (long)
|
|
|
|
+ * Generation stamp (long)
|
|
|
|
+ * Namespace Quota (long)
|
|
|
|
+ * Diskspace Quota (long) // added in -18
|
|
|
|
+ * Permissions
|
|
|
|
+ * Username (String)
|
|
|
|
+ * Groupname (String)
|
|
|
|
+ * OctalPerms (short -> String) // Modified in -19
|
|
|
|
+ * Symlink (String) // added in -23
|
|
|
|
+ * NumINodesUnderConstruction (int)
|
|
|
|
+ * INodesUnderConstruction (count = NumINodesUnderConstruction)
|
|
|
|
+ * INodeUnderConstruction
|
|
|
|
+ * Path (bytes as string)
|
|
|
|
+ * Replication (short)
|
|
|
|
+ * Modification time (long as date)
|
|
|
|
+ * Preferred block size (long)
|
|
|
|
+ * Num blocks (int)
|
|
|
|
+ * Blocks
|
|
|
|
+ * Block
|
|
|
|
+ * Block ID (long)
|
|
|
|
+ * Num bytes (long)
|
|
|
|
+ * Generation stamp (long)
|
|
|
|
+ * Permissions
|
|
|
|
+ * Username (String)
|
|
|
|
+ * Groupname (String)
|
|
|
|
+ * OctalPerms (short -> String)
|
|
|
|
+ * Client Name (String)
|
|
|
|
+ * Client Machine (String)
|
|
|
|
+ * NumLocations (int)
|
|
|
|
+ * DatanodeDescriptors (count = numLocations) // not loaded into memory
|
|
|
|
+ * short // but still in file
|
|
|
|
+ * long
|
|
|
|
+ * string
|
|
|
|
+ * long
|
|
|
|
+ * int
|
|
|
|
+ * string
|
|
|
|
+ * string
|
|
|
|
+ * enum
|
|
|
|
+ * CurrentDelegationKeyId (int)
|
|
|
|
+ * NumDelegationKeys (int)
|
|
|
|
+ * DelegationKeys (count = NumDelegationKeys)
|
|
|
|
+ * DelegationKeyLength (vint)
|
|
|
|
+ * DelegationKey (bytes)
|
|
|
|
+ * DelegationTokenSequenceNumber (int)
|
|
|
|
+ * NumDelegationTokens (int)
|
|
|
|
+ * DelegationTokens (count = NumDelegationTokens)
|
|
|
|
+ * DelegationTokenIdentifier
|
|
|
|
+ * owner (String)
|
|
|
|
+ * renewer (String)
|
|
|
|
+ * realUser (String)
|
|
|
|
+ * issueDate (vlong)
|
|
|
|
+ * maxDate (vlong)
|
|
|
|
+ * sequenceNumber (vint)
|
|
|
|
+ * masterKeyId (vint)
|
|
|
|
+ * expiryTime (long)
|
|
|
|
+ *
|
|
|
|
+ */
|
|
|
|
+class ImageLoaderCurrent implements ImageLoader {
|
|
|
|
+ protected final DateFormat dateFormat =
|
|
|
|
+ new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
|
|
|
+ private static int[] versions = { -16, -17, -18, -19, -20, -21, -22, -23,
|
|
|
|
+ -24, -25, -26, -27, -28, -30, -31, -32, -33, -34, -35, -36, -37, -38, -39,
|
|
|
|
+ -40, -41, -42, -43, -44, -45, -46, -47, -48, -49, -50, -51 };
|
|
|
|
+ private int imageVersion = 0;
|
|
|
|
+
|
|
|
|
+ private final Map<Long, Boolean> subtreeMap = new HashMap<Long, Boolean>();
|
|
|
|
+ private final Map<Long, String> dirNodeMap = new HashMap<Long, String>();
|
|
|
|
+
|
|
|
|
+ /* (non-Javadoc)
|
|
|
|
+ * @see ImageLoader#canProcessVersion(int)
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public boolean canLoadVersion(int version) {
|
|
|
|
+ for(int v : versions)
|
|
|
|
+ if(v == version) return true;
|
|
|
|
+
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /* (non-Javadoc)
|
|
|
|
+ * @see ImageLoader#processImage(java.io.DataInputStream, ImageVisitor, boolean)
|
|
|
|
+ */
|
|
|
|
+ @Override
|
|
|
|
+ public void loadImage(DataInputStream in, ImageVisitor v,
|
|
|
|
+ boolean skipBlocks) throws IOException {
|
|
|
|
+ boolean done = false;
|
|
|
|
+ try {
|
|
|
|
+ v.start();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.FS_IMAGE);
|
|
|
|
+
|
|
|
|
+ imageVersion = in.readInt();
|
|
|
|
+ if( !canLoadVersion(imageVersion))
|
|
|
|
+ throw new IOException("Cannot process fslayout version " + imageVersion);
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.ADD_LAYOUT_FLAGS, imageVersion)) {
|
|
|
|
+ LayoutFlags.read(in);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ v.visit(ImageElement.IMAGE_VERSION, imageVersion);
|
|
|
|
+ v.visit(ImageElement.NAMESPACE_ID, in.readInt());
|
|
|
|
+
|
|
|
|
+ long numInodes = in.readLong();
|
|
|
|
+
|
|
|
|
+ v.visit(ImageElement.GENERATION_STAMP, in.readLong());
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.SEQUENTIAL_BLOCK_ID, imageVersion)) {
|
|
|
|
+ v.visit(ImageElement.GENERATION_STAMP_V2, in.readLong());
|
|
|
|
+ v.visit(ImageElement.GENERATION_STAMP_V1_LIMIT, in.readLong());
|
|
|
|
+ v.visit(ImageElement.LAST_ALLOCATED_BLOCK_ID, in.readLong());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.STORED_TXIDS, imageVersion)) {
|
|
|
|
+ v.visit(ImageElement.TRANSACTION_ID, in.readLong());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.ADD_INODE_ID, imageVersion)) {
|
|
|
|
+ v.visit(ImageElement.LAST_INODE_ID, in.readLong());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ boolean supportSnapshot = NameNodeLayoutVersion.supports(Feature.SNAPSHOT,
|
|
|
|
+ imageVersion);
|
|
|
|
+ if (supportSnapshot) {
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_COUNTER, in.readInt());
|
|
|
|
+ int numSnapshots = in.readInt();
|
|
|
|
+ v.visit(ImageElement.NUM_SNAPSHOTS_TOTAL, numSnapshots);
|
|
|
|
+ for (int i = 0; i < numSnapshots; i++) {
|
|
|
|
+ processSnapshot(in, v);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.FSIMAGE_COMPRESSION, imageVersion)) {
|
|
|
|
+ boolean isCompressed = in.readBoolean();
|
|
|
|
+ v.visit(ImageElement.IS_COMPRESSED, String.valueOf(isCompressed));
|
|
|
|
+ if (isCompressed) {
|
|
|
|
+ String codecClassName = Text.readString(in);
|
|
|
|
+ v.visit(ImageElement.COMPRESS_CODEC, codecClassName);
|
|
|
|
+ CompressionCodecFactory codecFac = new CompressionCodecFactory(
|
|
|
|
+ new Configuration());
|
|
|
|
+ CompressionCodec codec = codecFac.getCodecByClassName(codecClassName);
|
|
|
|
+ if (codec == null) {
|
|
|
|
+ throw new IOException("Image compression codec not supported: "
|
|
|
|
+ + codecClassName);
|
|
|
|
+ }
|
|
|
|
+ in = new DataInputStream(codec.createInputStream(in));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ processINodes(in, v, numInodes, skipBlocks, supportSnapshot);
|
|
|
|
+ subtreeMap.clear();
|
|
|
|
+ dirNodeMap.clear();
|
|
|
|
+
|
|
|
|
+ processINodesUC(in, v, skipBlocks);
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.DELEGATION_TOKEN, imageVersion)) {
|
|
|
|
+ processDelegationTokens(in, v);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.CACHING, imageVersion)) {
|
|
|
|
+ processCacheManagerState(in, v);
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement(); // FSImage
|
|
|
|
+ done = true;
|
|
|
|
+ } finally {
|
|
|
|
+ if (done) {
|
|
|
|
+ v.finish();
|
|
|
|
+ } else {
|
|
|
|
+ v.finishAbnormally();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process CacheManager state from the fsimage.
|
|
|
|
+ */
|
|
|
|
+ private void processCacheManagerState(DataInputStream in, ImageVisitor v)
|
|
|
|
+ throws IOException {
|
|
|
|
+ v.visit(ImageElement.CACHE_NEXT_ENTRY_ID, in.readLong());
|
|
|
|
+ final int numPools = in.readInt();
|
|
|
|
+ for (int i=0; i<numPools; i++) {
|
|
|
|
+ v.visit(ImageElement.CACHE_POOL_NAME, Text.readString(in));
|
|
|
|
+ processCachePoolPermission(in, v);
|
|
|
|
+ v.visit(ImageElement.CACHE_POOL_WEIGHT, in.readInt());
|
|
|
|
+ }
|
|
|
|
+ final int numEntries = in.readInt();
|
|
|
|
+ for (int i=0; i<numEntries; i++) {
|
|
|
|
+ v.visit(ImageElement.CACHE_ENTRY_PATH, Text.readString(in));
|
|
|
|
+ v.visit(ImageElement.CACHE_ENTRY_REPLICATION, in.readShort());
|
|
|
|
+ v.visit(ImageElement.CACHE_ENTRY_POOL_NAME, Text.readString(in));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ /**
|
|
|
|
+ * Process the Delegation Token related section in fsimage.
|
|
|
|
+ *
|
|
|
|
+ * @param in DataInputStream to process
|
|
|
|
+ * @param v Visitor to walk over records
|
|
|
|
+ */
|
|
|
|
+ private void processDelegationTokens(DataInputStream in, ImageVisitor v)
|
|
|
|
+ throws IOException {
|
|
|
|
+ v.visit(ImageElement.CURRENT_DELEGATION_KEY_ID, in.readInt());
|
|
|
|
+ int numDKeys = in.readInt();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.DELEGATION_KEYS,
|
|
|
|
+ ImageElement.NUM_DELEGATION_KEYS, numDKeys);
|
|
|
|
+ for(int i =0; i < numDKeys; i++) {
|
|
|
|
+ DelegationKey key = new DelegationKey();
|
|
|
|
+ key.readFields(in);
|
|
|
|
+ v.visit(ImageElement.DELEGATION_KEY, key.toString());
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_SEQUENCE_NUMBER, in.readInt());
|
|
|
|
+ int numDTokens = in.readInt();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.DELEGATION_TOKENS,
|
|
|
|
+ ImageElement.NUM_DELEGATION_TOKENS, numDTokens);
|
|
|
|
+ for(int i=0; i<numDTokens; i++){
|
|
|
|
+ DelegationTokenIdentifier id = new DelegationTokenIdentifier();
|
|
|
|
+ id.readFields(in);
|
|
|
|
+ long expiryTime = in.readLong();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.DELEGATION_TOKEN_IDENTIFIER);
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_KIND,
|
|
|
|
+ id.getKind().toString());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_SEQNO,
|
|
|
|
+ id.getSequenceNumber());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_OWNER,
|
|
|
|
+ id.getOwner().toString());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_RENEWER,
|
|
|
|
+ id.getRenewer().toString());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_REALUSER,
|
|
|
|
+ id.getRealUser().toString());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_ISSUE_DATE,
|
|
|
|
+ id.getIssueDate());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_MAX_DATE,
|
|
|
|
+ id.getMaxDate());
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_EXPIRY_TIME,
|
|
|
|
+ expiryTime);
|
|
|
|
+ v.visit(ImageElement.DELEGATION_TOKEN_IDENTIFIER_MASTER_KEY_ID,
|
|
|
|
+ id.getMasterKeyId());
|
|
|
|
+ v.leaveEnclosingElement(); // DELEGATION_TOKEN_IDENTIFIER
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement(); // DELEGATION_TOKENS
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process the INodes under construction section of the fsimage.
|
|
|
|
+ *
|
|
|
|
+ * @param in DataInputStream to process
|
|
|
|
+ * @param v Visitor to walk over inodes
|
|
|
|
+ * @param skipBlocks Walk over each block?
|
|
|
|
+ */
|
|
|
|
+ private void processINodesUC(DataInputStream in, ImageVisitor v,
|
|
|
|
+ boolean skipBlocks) throws IOException {
|
|
|
|
+ int numINUC = in.readInt();
|
|
|
|
+
|
|
|
|
+ v.visitEnclosingElement(ImageElement.INODES_UNDER_CONSTRUCTION,
|
|
|
|
+ ImageElement.NUM_INODES_UNDER_CONSTRUCTION, numINUC);
|
|
|
|
+
|
|
|
|
+ for(int i = 0; i < numINUC; i++) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.INODE_UNDER_CONSTRUCTION);
|
|
|
|
+ byte [] name = FSImageSerialization.readBytes(in);
|
|
|
|
+ String n = new String(name, "UTF8");
|
|
|
|
+ v.visit(ImageElement.INODE_PATH, n);
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.ADD_INODE_ID, imageVersion)) {
|
|
|
|
+ long inodeId = in.readLong();
|
|
|
|
+ v.visit(ImageElement.INODE_ID, inodeId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ v.visit(ImageElement.REPLICATION, in.readShort());
|
|
|
|
+ v.visit(ImageElement.MODIFICATION_TIME, formatDate(in.readLong()));
|
|
|
|
+
|
|
|
|
+ v.visit(ImageElement.PREFERRED_BLOCK_SIZE, in.readLong());
|
|
|
|
+ int numBlocks = in.readInt();
|
|
|
|
+ processBlocks(in, v, numBlocks, skipBlocks);
|
|
|
|
+
|
|
|
|
+ processPermission(in, v);
|
|
|
|
+ v.visit(ImageElement.CLIENT_NAME, FSImageSerialization.readString(in));
|
|
|
|
+ v.visit(ImageElement.CLIENT_MACHINE, FSImageSerialization.readString(in));
|
|
|
|
+
|
|
|
|
+ // Skip over the datanode descriptors, which are still stored in the
|
|
|
|
+ // file but are not used by the datanode or loaded into memory
|
|
|
|
+ int numLocs = in.readInt();
|
|
|
|
+ for(int j = 0; j < numLocs; j++) {
|
|
|
|
+ in.readShort();
|
|
|
|
+ in.readLong();
|
|
|
|
+ in.readLong();
|
|
|
|
+ in.readLong();
|
|
|
|
+ in.readInt();
|
|
|
|
+ FSImageSerialization.readString(in);
|
|
|
|
+ FSImageSerialization.readString(in);
|
|
|
|
+ WritableUtils.readEnum(in, AdminStates.class);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ v.leaveEnclosingElement(); // INodeUnderConstruction
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ v.leaveEnclosingElement(); // INodesUnderConstruction
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process the blocks section of the fsimage.
|
|
|
|
+ *
|
|
|
|
+ * @param in Datastream to process
|
|
|
|
+ * @param v Visitor to walk over inodes
|
|
|
|
+ * @param skipBlocks Walk over each block?
|
|
|
|
+ */
|
|
|
|
+ private void processBlocks(DataInputStream in, ImageVisitor v,
|
|
|
|
+ int numBlocks, boolean skipBlocks) throws IOException {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.BLOCKS,
|
|
|
|
+ ImageElement.NUM_BLOCKS, numBlocks);
|
|
|
|
+
|
|
|
|
+ // directory or symlink or reference node, no blocks to process
|
|
|
|
+ if(numBlocks < 0) {
|
|
|
|
+ v.leaveEnclosingElement(); // Blocks
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if(skipBlocks) {
|
|
|
|
+ int bytesToSkip = ((Long.SIZE * 3 /* fields */) / 8 /*bits*/) * numBlocks;
|
|
|
|
+ if(in.skipBytes(bytesToSkip) != bytesToSkip)
|
|
|
|
+ throw new IOException("Error skipping over blocks");
|
|
|
|
+
|
|
|
|
+ } else {
|
|
|
|
+ for(int j = 0; j < numBlocks; j++) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.BLOCK);
|
|
|
|
+ v.visit(ImageElement.BLOCK_ID, in.readLong());
|
|
|
|
+ v.visit(ImageElement.NUM_BYTES, in.readLong());
|
|
|
|
+ v.visit(ImageElement.GENERATION_STAMP, in.readLong());
|
|
|
|
+ v.leaveEnclosingElement(); // Block
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement(); // Blocks
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Extract the INode permissions stored in the fsimage file.
|
|
|
|
+ *
|
|
|
|
+ * @param in Datastream to process
|
|
|
|
+ * @param v Visitor to walk over inodes
|
|
|
|
+ */
|
|
|
|
+ private void processPermission(DataInputStream in, ImageVisitor v)
|
|
|
|
+ throws IOException {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.PERMISSIONS);
|
|
|
|
+ v.visit(ImageElement.USER_NAME, Text.readString(in));
|
|
|
|
+ v.visit(ImageElement.GROUP_NAME, Text.readString(in));
|
|
|
|
+ FsPermission fsp = new FsPermission(in.readShort());
|
|
|
|
+ v.visit(ImageElement.PERMISSION_STRING, fsp.toString());
|
|
|
|
+ v.leaveEnclosingElement(); // Permissions
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Extract CachePool permissions stored in the fsimage file.
|
|
|
|
+ *
|
|
|
|
+ * @param in Datastream to process
|
|
|
|
+ * @param v Visitor to walk over inodes
|
|
|
|
+ */
|
|
|
|
+ private void processCachePoolPermission(DataInputStream in, ImageVisitor v)
|
|
|
|
+ throws IOException {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.PERMISSIONS);
|
|
|
|
+ v.visit(ImageElement.CACHE_POOL_OWNER_NAME, Text.readString(in));
|
|
|
|
+ v.visit(ImageElement.CACHE_POOL_GROUP_NAME, Text.readString(in));
|
|
|
|
+ FsPermission fsp = new FsPermission(in.readShort());
|
|
|
|
+ v.visit(ImageElement.CACHE_POOL_PERMISSION_STRING, fsp.toString());
|
|
|
|
+ v.leaveEnclosingElement(); // Permissions
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process the INode records stored in the fsimage.
|
|
|
|
+ *
|
|
|
|
+ * @param in Datastream to process
|
|
|
|
+ * @param v Visitor to walk over INodes
|
|
|
|
+ * @param numInodes Number of INodes stored in file
|
|
|
|
+ * @param skipBlocks Process all the blocks within the INode?
|
|
|
|
+ * @param supportSnapshot Whether or not the imageVersion supports snapshot
|
|
|
|
+ * @throws VisitException
|
|
|
|
+ * @throws IOException
|
|
|
|
+ */
|
|
|
|
+ private void processINodes(DataInputStream in, ImageVisitor v,
|
|
|
|
+ long numInodes, boolean skipBlocks, boolean supportSnapshot)
|
|
|
|
+ throws IOException {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.INODES,
|
|
|
|
+ ImageElement.NUM_INODES, numInodes);
|
|
|
|
+
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.FSIMAGE_NAME_OPTIMIZATION, imageVersion)) {
|
|
|
|
+ if (!supportSnapshot) {
|
|
|
|
+ processLocalNameINodes(in, v, numInodes, skipBlocks);
|
|
|
|
+ } else {
|
|
|
|
+ processLocalNameINodesWithSnapshot(in, v, skipBlocks);
|
|
|
|
+ }
|
|
|
|
+ } else { // full path name
|
|
|
|
+ processFullNameINodes(in, v, numInodes, skipBlocks);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ v.leaveEnclosingElement(); // INodes
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process image with full path name
|
|
|
|
+ *
|
|
|
|
+ * @param in image stream
|
|
|
|
+ * @param v visitor
|
|
|
|
+ * @param numInodes number of indoes to read
|
|
|
|
+ * @param skipBlocks skip blocks or not
|
|
|
|
+ * @throws IOException if there is any error occurs
|
|
|
|
+ */
|
|
|
|
+ private void processLocalNameINodes(DataInputStream in, ImageVisitor v,
|
|
|
|
+ long numInodes, boolean skipBlocks) throws IOException {
|
|
|
|
+ // process root
|
|
|
|
+ processINode(in, v, skipBlocks, "", false);
|
|
|
|
+ numInodes--;
|
|
|
|
+ while (numInodes > 0) {
|
|
|
|
+ numInodes -= processDirectory(in, v, skipBlocks);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private int processDirectory(DataInputStream in, ImageVisitor v,
|
|
|
|
+ boolean skipBlocks) throws IOException {
|
|
|
|
+ String parentName = FSImageSerialization.readString(in);
|
|
|
|
+ return processChildren(in, v, skipBlocks, parentName);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process image with local path name and snapshot support
|
|
|
|
+ *
|
|
|
|
+ * @param in image stream
|
|
|
|
+ * @param v visitor
|
|
|
|
+ * @param skipBlocks skip blocks or not
|
|
|
|
+ */
|
|
|
|
+ private void processLocalNameINodesWithSnapshot(DataInputStream in,
|
|
|
|
+ ImageVisitor v, boolean skipBlocks) throws IOException {
|
|
|
|
+ // process root
|
|
|
|
+ processINode(in, v, skipBlocks, "", false);
|
|
|
|
+ processDirectoryWithSnapshot(in, v, skipBlocks);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process directories when snapshot is supported.
|
|
|
|
+ */
|
|
|
|
+ private void processDirectoryWithSnapshot(DataInputStream in, ImageVisitor v,
|
|
|
|
+ boolean skipBlocks) throws IOException {
|
|
|
|
+ // 1. load dir node id
|
|
|
|
+ long inodeId = in.readLong();
|
|
|
|
+
|
|
|
|
+ String dirName = dirNodeMap.remove(inodeId);
|
|
|
|
+ Boolean visitedRef = subtreeMap.get(inodeId);
|
|
|
|
+ if (visitedRef != null) {
|
|
|
|
+ if (visitedRef.booleanValue()) { // the subtree has been visited
|
|
|
|
+ return;
|
|
|
|
+ } else { // first time to visit
|
|
|
|
+ subtreeMap.put(inodeId, true);
|
|
|
|
+ }
|
|
|
|
+ } // else the dir is not linked by a RefNode, thus cannot be revisited
|
|
|
|
+
|
|
|
|
+ // 2. load possible snapshots
|
|
|
|
+ processSnapshots(in, v, dirName);
|
|
|
|
+ // 3. load children nodes
|
|
|
|
+ processChildren(in, v, skipBlocks, dirName);
|
|
|
|
+ // 4. load possible directory diff list
|
|
|
|
+ processDirectoryDiffList(in, v, dirName);
|
|
|
|
+ // recursively process sub-directories
|
|
|
|
+ final int numSubTree = in.readInt();
|
|
|
|
+ for (int i = 0; i < numSubTree; i++) {
|
|
|
|
+ processDirectoryWithSnapshot(in, v, skipBlocks);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process snapshots of a snapshottable directory
|
|
|
|
+ */
|
|
|
|
+ private void processSnapshots(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String rootName) throws IOException {
|
|
|
|
+ final int numSnapshots = in.readInt();
|
|
|
|
+ if (numSnapshots >= 0) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOTS,
|
|
|
|
+ ImageElement.NUM_SNAPSHOTS, numSnapshots);
|
|
|
|
+ for (int i = 0; i < numSnapshots; i++) {
|
|
|
|
+ // process snapshot
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT);
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_ID, in.readInt());
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_QUOTA, in.readInt());
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processSnapshot(DataInputStream in, ImageVisitor v)
|
|
|
|
+ throws IOException {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT);
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_ID, in.readInt());
|
|
|
|
+ // process root of snapshot
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_ROOT);
|
|
|
|
+ processINode(in, v, true, "", false);
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processDirectoryDiffList(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String currentINodeName) throws IOException {
|
|
|
|
+ final int numDirDiff = in.readInt();
|
|
|
|
+ if (numDirDiff >= 0) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_DIR_DIFFS,
|
|
|
|
+ ImageElement.NUM_SNAPSHOT_DIR_DIFF, numDirDiff);
|
|
|
|
+ for (int i = 0; i < numDirDiff; i++) {
|
|
|
|
+ // process directory diffs in reverse chronological oder
|
|
|
|
+ processDirectoryDiff(in, v, currentINodeName);
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processDirectoryDiff(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String currentINodeName) throws IOException {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_DIR_DIFF);
|
|
|
|
+ int snapshotId = in.readInt();
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_DIFF_SNAPSHOTID, snapshotId);
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_DIR_DIFF_CHILDREN_SIZE, in.readInt());
|
|
|
|
+
|
|
|
|
+ // process snapshotINode
|
|
|
|
+ boolean useRoot = in.readBoolean();
|
|
|
|
+ if (!useRoot) {
|
|
|
|
+ if (in.readBoolean()) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_INODE_DIRECTORY_ATTRIBUTES);
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.OPTIMIZE_SNAPSHOT_INODES, imageVersion)) {
|
|
|
|
+ processINodeDirectoryAttributes(in, v, currentINodeName);
|
|
|
|
+ } else {
|
|
|
|
+ processINode(in, v, true, currentINodeName, true);
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // process createdList
|
|
|
|
+ int createdSize = in.readInt();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_DIR_DIFF_CREATEDLIST,
|
|
|
|
+ ImageElement.SNAPSHOT_DIR_DIFF_CREATEDLIST_SIZE, createdSize);
|
|
|
|
+ for (int i = 0; i < createdSize; i++) {
|
|
|
|
+ String createdNode = FSImageSerialization.readString(in);
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_DIR_DIFF_CREATED_INODE, createdNode);
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+
|
|
|
|
+ // process deletedList
|
|
|
|
+ int deletedSize = in.readInt();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_DIR_DIFF_DELETEDLIST,
|
|
|
|
+ ImageElement.SNAPSHOT_DIR_DIFF_DELETEDLIST_SIZE, deletedSize);
|
|
|
|
+ for (int i = 0; i < deletedSize; i++) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_DIR_DIFF_DELETED_INODE);
|
|
|
|
+ processINode(in, v, false, currentINodeName, true);
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processINodeDirectoryAttributes(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String parentName) throws IOException {
|
|
|
|
+ final String pathName = readINodePath(in, parentName);
|
|
|
|
+ v.visit(ImageElement.INODE_PATH, pathName);
|
|
|
|
+ processPermission(in, v);
|
|
|
|
+ v.visit(ImageElement.MODIFICATION_TIME, formatDate(in.readLong()));
|
|
|
|
+
|
|
|
|
+ v.visit(ImageElement.NS_QUOTA, in.readLong());
|
|
|
|
+ v.visit(ImageElement.DS_QUOTA, in.readLong());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** Process children under a directory */
|
|
|
|
+ private int processChildren(DataInputStream in, ImageVisitor v,
|
|
|
|
+ boolean skipBlocks, String parentName) throws IOException {
|
|
|
|
+ int numChildren = in.readInt();
|
|
|
|
+ for (int i = 0; i < numChildren; i++) {
|
|
|
|
+ processINode(in, v, skipBlocks, parentName, false);
|
|
|
|
+ }
|
|
|
|
+ return numChildren;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process image with full path name
|
|
|
|
+ *
|
|
|
|
+ * @param in image stream
|
|
|
|
+ * @param v visitor
|
|
|
|
+ * @param numInodes number of indoes to read
|
|
|
|
+ * @param skipBlocks skip blocks or not
|
|
|
|
+ * @throws IOException if there is any error occurs
|
|
|
|
+ */
|
|
|
|
+ private void processFullNameINodes(DataInputStream in, ImageVisitor v,
|
|
|
|
+ long numInodes, boolean skipBlocks) throws IOException {
|
|
|
|
+ for(long i = 0; i < numInodes; i++) {
|
|
|
|
+ processINode(in, v, skipBlocks, null, false);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private String readINodePath(DataInputStream in, String parentName)
|
|
|
|
+ throws IOException {
|
|
|
|
+ String pathName = FSImageSerialization.readString(in);
|
|
|
|
+ if (parentName != null) { // local name
|
|
|
|
+ pathName = "/" + pathName;
|
|
|
|
+ if (!"/".equals(parentName)) { // children of non-root directory
|
|
|
|
+ pathName = parentName + pathName;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return pathName;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Process an INode
|
|
|
|
+ *
|
|
|
|
+ * @param in image stream
|
|
|
|
+ * @param v visitor
|
|
|
|
+ * @param skipBlocks skip blocks or not
|
|
|
|
+ * @param parentName the name of its parent node
|
|
|
|
+ * @param isSnapshotCopy whether or not the inode is a snapshot copy
|
|
|
|
+ * @throws IOException
|
|
|
|
+ */
|
|
|
|
+ private void processINode(DataInputStream in, ImageVisitor v,
|
|
|
|
+ boolean skipBlocks, String parentName, boolean isSnapshotCopy)
|
|
|
|
+ throws IOException {
|
|
|
|
+ boolean supportSnapshot =
|
|
|
|
+ NameNodeLayoutVersion.supports(Feature.SNAPSHOT, imageVersion);
|
|
|
|
+ boolean supportInodeId =
|
|
|
|
+ NameNodeLayoutVersion.supports(Feature.ADD_INODE_ID, imageVersion);
|
|
|
|
+
|
|
|
|
+ v.visitEnclosingElement(ImageElement.INODE);
|
|
|
|
+ final String pathName = readINodePath(in, parentName);
|
|
|
|
+ v.visit(ImageElement.INODE_PATH, pathName);
|
|
|
|
+
|
|
|
|
+ long inodeId = INodeId.GRANDFATHER_INODE_ID;
|
|
|
|
+ if (supportInodeId) {
|
|
|
|
+ inodeId = in.readLong();
|
|
|
|
+ v.visit(ImageElement.INODE_ID, inodeId);
|
|
|
|
+ }
|
|
|
|
+ v.visit(ImageElement.REPLICATION, in.readShort());
|
|
|
|
+ v.visit(ImageElement.MODIFICATION_TIME, formatDate(in.readLong()));
|
|
|
|
+ if(NameNodeLayoutVersion.supports(Feature.FILE_ACCESS_TIME, imageVersion))
|
|
|
|
+ v.visit(ImageElement.ACCESS_TIME, formatDate(in.readLong()));
|
|
|
|
+ v.visit(ImageElement.BLOCK_SIZE, in.readLong());
|
|
|
|
+ int numBlocks = in.readInt();
|
|
|
|
+
|
|
|
|
+ processBlocks(in, v, numBlocks, skipBlocks);
|
|
|
|
+
|
|
|
|
+ if (numBlocks >= 0) { // File
|
|
|
|
+ if (supportSnapshot) {
|
|
|
|
+ // make sure subtreeMap only contains entry for directory
|
|
|
|
+ subtreeMap.remove(inodeId);
|
|
|
|
+ // process file diffs
|
|
|
|
+ processFileDiffList(in, v, parentName);
|
|
|
|
+ if (isSnapshotCopy) {
|
|
|
|
+ boolean underConstruction = in.readBoolean();
|
|
|
|
+ if (underConstruction) {
|
|
|
|
+ v.visit(ImageElement.CLIENT_NAME,
|
|
|
|
+ FSImageSerialization.readString(in));
|
|
|
|
+ v.visit(ImageElement.CLIENT_MACHINE,
|
|
|
|
+ FSImageSerialization.readString(in));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ processPermission(in, v);
|
|
|
|
+ } else if (numBlocks == -1) { // Directory
|
|
|
|
+ if (supportSnapshot && supportInodeId) {
|
|
|
|
+ dirNodeMap.put(inodeId, pathName);
|
|
|
|
+ }
|
|
|
|
+ v.visit(ImageElement.NS_QUOTA, numBlocks == -1 ? in.readLong() : -1);
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.DISKSPACE_QUOTA, imageVersion))
|
|
|
|
+ v.visit(ImageElement.DS_QUOTA, numBlocks == -1 ? in.readLong() : -1);
|
|
|
|
+ if (supportSnapshot) {
|
|
|
|
+ boolean snapshottable = in.readBoolean();
|
|
|
|
+ if (!snapshottable) {
|
|
|
|
+ boolean withSnapshot = in.readBoolean();
|
|
|
|
+ v.visit(ImageElement.IS_WITHSNAPSHOT_DIR, Boolean.toString(withSnapshot));
|
|
|
|
+ } else {
|
|
|
|
+ v.visit(ImageElement.IS_SNAPSHOTTABLE_DIR, Boolean.toString(snapshottable));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ processPermission(in, v);
|
|
|
|
+ } else if (numBlocks == -2) {
|
|
|
|
+ v.visit(ImageElement.SYMLINK, Text.readString(in));
|
|
|
|
+ processPermission(in, v);
|
|
|
|
+ } else if (numBlocks == -3) { // reference node
|
|
|
|
+ final boolean isWithName = in.readBoolean();
|
|
|
|
+ int snapshotId = in.readInt();
|
|
|
|
+ if (isWithName) {
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_LAST_SNAPSHOT_ID, snapshotId);
|
|
|
|
+ } else {
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_DST_SNAPSHOT_ID, snapshotId);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ final boolean firstReferred = in.readBoolean();
|
|
|
|
+ if (firstReferred) {
|
|
|
|
+ // if a subtree is linked by multiple "parents", the corresponding dir
|
|
|
|
+ // must be referred by a reference node. we put the reference node into
|
|
|
|
+ // the subtreeMap here and let its value be false. when we later visit
|
|
|
|
+ // the subtree for the first time, we change the value to true.
|
|
|
|
+ subtreeMap.put(inodeId, false);
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_REF_INODE);
|
|
|
|
+ processINode(in, v, skipBlocks, parentName, isSnapshotCopy);
|
|
|
|
+ v.leaveEnclosingElement(); // referred inode
|
|
|
|
+ } else {
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_REF_INODE_ID, in.readLong());
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ v.leaveEnclosingElement(); // INode
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processINodeFileAttributes(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String parentName) throws IOException {
|
|
|
|
+ final String pathName = readINodePath(in, parentName);
|
|
|
|
+ v.visit(ImageElement.INODE_PATH, pathName);
|
|
|
|
+ processPermission(in, v);
|
|
|
|
+ v.visit(ImageElement.MODIFICATION_TIME, formatDate(in.readLong()));
|
|
|
|
+ if(NameNodeLayoutVersion.supports(Feature.FILE_ACCESS_TIME, imageVersion)) {
|
|
|
|
+ v.visit(ImageElement.ACCESS_TIME, formatDate(in.readLong()));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ v.visit(ImageElement.REPLICATION, in.readShort());
|
|
|
|
+ v.visit(ImageElement.BLOCK_SIZE, in.readLong());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processFileDiffList(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String currentINodeName) throws IOException {
|
|
|
|
+ final int size = in.readInt();
|
|
|
|
+ if (size >= 0) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_FILE_DIFFS,
|
|
|
|
+ ImageElement.NUM_SNAPSHOT_FILE_DIFF, size);
|
|
|
|
+ for (int i = 0; i < size; i++) {
|
|
|
|
+ processFileDiff(in, v, currentINodeName);
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private void processFileDiff(DataInputStream in, ImageVisitor v,
|
|
|
|
+ String currentINodeName) throws IOException {
|
|
|
|
+ int snapshotId = in.readInt();
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_FILE_DIFF,
|
|
|
|
+ ImageElement.SNAPSHOT_DIFF_SNAPSHOTID, snapshotId);
|
|
|
|
+ v.visit(ImageElement.SNAPSHOT_FILE_SIZE, in.readLong());
|
|
|
|
+ if (in.readBoolean()) {
|
|
|
|
+ v.visitEnclosingElement(ImageElement.SNAPSHOT_INODE_FILE_ATTRIBUTES);
|
|
|
|
+ if (NameNodeLayoutVersion.supports(Feature.OPTIMIZE_SNAPSHOT_INODES, imageVersion)) {
|
|
|
|
+ processINodeFileAttributes(in, v, currentINodeName);
|
|
|
|
+ } else {
|
|
|
|
+ processINode(in, v, true, currentINodeName, true);
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+ v.leaveEnclosingElement();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * Helper method to format dates during processing.
|
|
|
|
+ * @param date Date as read from image file
|
|
|
|
+ * @return String version of date format
|
|
|
|
+ */
|
|
|
|
+ private String formatDate(long date) {
|
|
|
|
+ return dateFormat.format(new Date(date));
|
|
|
|
+ }
|
|
|
|
+}
|