MetaFooter.java

// 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.doris.persist.meta;

import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.List;

/**
 * Footer Format:
 * |- Footer -----------------------------|
 * | - Checksum (8 bytes)                 |
 * | |- object index --------------|      |
 * | | - index a                   |      |
 * | | - index b                   |      |
 * | | ...                         |      |
 * | |-----------------------------|      |
 * | - other value(undecided)             |
 * |--------------------------------------|
 * - Footer Length (8 bytes)
 * - Magic String (4 bytes)
 */

public class MetaFooter {
    private static final Logger LOG = LogManager.getLogger(MetaFooter.class);

    public static final long FOOTER_LENGTH_SIZE = 8L;
    private static final long CHECKSUM_LENGTH_SIZE = 8L;

    // checksum
    public long checksum;
    // length of footer
    public long length;
    // meta indices
    public List<MetaIndex> metaIndices;

    public static MetaFooter read(File imageFile) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(imageFile, "r")) {
            long fileLength = raf.length();
            long footerLengthIndex = fileLength - FOOTER_LENGTH_SIZE - MetaMagicNumber.MAGIC_STR.length();
            raf.seek(footerLengthIndex);
            long footerLength = raf.readLong();
            MetaMagicNumber magicNumber = MetaMagicNumber.read(raf);
            if (!Arrays.equals(MetaMagicNumber.MAGIC, magicNumber.getBytes())) {
                LOG.warn("Image file {} format mismatch. Expected magic number is {}, actual is {}",
                        imageFile.getPath(), Arrays.toString(MetaMagicNumber.MAGIC),
                        Arrays.toString(magicNumber.getBytes()));
                // this will compatible with old image
                long footerIndex = fileLength - CHECKSUM_LENGTH_SIZE;
                raf.seek(footerIndex);
                long checksum = raf.readLong();
                return new MetaFooter(Lists.newArrayList(), checksum, CHECKSUM_LENGTH_SIZE);
            }
            long footerIndex = footerLengthIndex - footerLength;
            raf.seek(footerIndex);
            long checksum = raf.readLong();
            int indexNum = raf.readInt();
            List<MetaIndex> metaIndices = Lists.newArrayList();
            for (int i = 0; i < indexNum; i++) {
                MetaIndex index = MetaIndex.read(raf);
                metaIndices.add(index);
            }
            LOG.info("Image footer length: {}, indices: {}", footerLength, metaIndices.toArray());
            return new MetaFooter(metaIndices, checksum, footerLength);
        }
    }

    public static void write(File imageFile, List<MetaIndex> metaIndices, long checksum) throws IOException {
        try (RandomAccessFile raf = new RandomAccessFile(imageFile, "rw")) {
            long startIndex = raf.length();
            raf.seek(startIndex);
            raf.writeLong(checksum);
            raf.writeInt(metaIndices.size());
            for (MetaIndex metaIndex : metaIndices) {
                MetaIndex.write(raf, metaIndex);
            }
            long endIndex = raf.length();
            raf.writeLong(endIndex - startIndex);
            MetaMagicNumber.write(raf);
            raf.getChannel().force(true);
        }
    }

    public MetaFooter(List<MetaIndex> metaIndices, long checksum, long length) {
        this.checksum = checksum;
        this.metaIndices = metaIndices;
        this.length = length;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("checksum: ").append(checksum);
        sb.append("\nlength: ").append(length);
        sb.append("\nindices:");
        for (MetaIndex metaIndex : metaIndices) {
            sb.append("\n\t").append(metaIndex.toString());
        }
        return sb.toString();
    }
}