com.facebook.buck.cxx.platform.ObjectFileScrubbers.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.cxx.platform.ObjectFileScrubbers.java

Source

/*
 * Copyright 2015-present Facebook, Inc.
 *
 * Licensed 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 com.facebook.buck.cxx.platform;

import com.facebook.buck.io.FileContentsScrubber;
import com.google.common.base.Charsets;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.common.primitives.Shorts;
import java.io.IOException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.nio.ReadOnlyBufferException;
import java.nio.channels.FileChannel;
import java.util.Arrays;

public class ObjectFileScrubbers {

    private static final int GLOBAL_HEADER_SIZE = 8;
    private static final ImmutableSet<String> SPECIAL_ENTRIES = ImmutableSet.of("/", "//");
    public static final byte[] GLOBAL_HEADER = "!<arch>\n".getBytes(Charsets.US_ASCII);
    public static final byte[] GLOBAL_THIN_HEADER = "!<thin>\n".getBytes(Charsets.US_ASCII);
    public static final byte[] END_OF_FILE_HEADER_MARKER = { 0x60, 0x0A };

    public enum PaddingStyle {
        LEFT, RIGHT,
    };

    private ObjectFileScrubbers() {
    }

    private static boolean checkHeader(byte[] header) throws FileContentsScrubber.ScrubException {
        checkArchive(Arrays.equals(GLOBAL_HEADER, header) || Arrays.equals(GLOBAL_THIN_HEADER, header),
                "invalid global header");
        return Arrays.equals(GLOBAL_THIN_HEADER, header);
    }

    public static FileContentsScrubber createDateUidGidScrubber(final PaddingStyle paddingStyle) {
        return new FileContentsScrubber() {

            /**
             * Efficiently modifies the archive backed by the given buffer to remove any non-deterministic
             * meta-data such as timestamps, UIDs, and GIDs.
             */
            @SuppressWarnings("PMD.AvoidUsingOctalValues")
            @Override
            public void scrubFile(FileChannel file) throws IOException, ScrubException {
                try {
                    ByteBuffer header = ByteBuffer.allocate(GLOBAL_HEADER_SIZE);
                    file.read(header);
                    // Grab the global header chunk and verify it's accurate.
                    header.position(0);
                    byte[] globalHeader = getBytes(header, GLOBAL_HEADER_SIZE);
                    boolean thin = checkHeader(globalHeader);

                    // Iterate over all the file meta-data entries, injecting zero's for timestamp,
                    // UID, and GID.
                    final int entrySize = 16 /* fileName */
                            + 12 /* file modification time */
                            + 6 /* owner ID */
                            + 6 /* group ID */
                            + 8 /* file mode */
                            + 10 /* file size */
                            + 2 /* file magic */;

                    long start = GLOBAL_HEADER_SIZE;
                    ByteBuffer buffer = ByteBuffer.allocate(entrySize);
                    while (start < file.size()) {
                        checkArchive(file.size() - start >= entrySize, "Invalid entry metadata format");

                        buffer.clear();
                        file.position(start);
                        int read = file.read(buffer);
                        checkArchive(read == entrySize, "Not all bytes have been read");

                        buffer.position(0); // position points just past the last byte read, so need to reset
                        String fileName = new String(getBytes(buffer, 16), Charsets.US_ASCII).trim();

                        // Inject 0's for the non-deterministic meta-data entries.
                        /* File modification timestamp */ putIntAsDecimalString(buffer, 12,
                                ObjectFileCommonModificationDate.COMMON_MODIFICATION_TIME_STAMP, paddingStyle);
                        /* Owner ID */ putIntAsDecimalString(buffer, 6, 0, paddingStyle);
                        /* Group ID */ putIntAsDecimalString(buffer, 6, 0, paddingStyle);

                        /* File mode */ putIntAsOctalString(buffer, 8, 0100644, paddingStyle);
                        long fileSize = getDecimalStringAsLong(buffer, 10);

                        // Lastly, grab the file magic entry and verify it's accurate.
                        byte[] fileMagic = getBytes(buffer, 2);
                        checkArchive(Arrays.equals(END_OF_FILE_HEADER_MARKER, fileMagic), "invalid file magic");

                        // write the changes
                        buffer.position(0); // position points just past the last byte accessed, need to reset
                        file.position(start);
                        int written = file.write(buffer);
                        checkArchive(written == entrySize, "Not all bytes have been written");

                        // Skip the file data.
                        start += entrySize;
                        if (!thin || SPECIAL_ENTRIES.contains(fileName)) {
                            start += fileSize + fileSize % 2;
                        }
                    }

                    // Convert any low-level exceptions to `ArchiveExceptions`s.
                } catch (BufferUnderflowException | ReadOnlyBufferException e) {
                    throw new ScrubException(e.getMessage());
                }
            }
        };
    }

    public static byte[] getBytes(ByteBuffer buffer, int len) {
        byte[] bytes = new byte[len];
        buffer.get(bytes);
        return bytes;
    }

    public static int getOctalStringAsInt(ByteBuffer buffer, int len) {
        byte[] bytes = getBytes(buffer, len);
        String str = new String(bytes, Charsets.US_ASCII);
        return Integer.parseInt(str.trim(), 8);
    }

    public static int getDecimalStringAsInt(ByteBuffer buffer, int len) {
        byte[] bytes = getBytes(buffer, len);
        String str = new String(bytes, Charsets.US_ASCII);
        return Integer.parseInt(str.trim());
    }

    public static long getDecimalStringAsLong(ByteBuffer buffer, int len) {
        byte[] bytes = getBytes(buffer, len);
        String str = new String(bytes, Charsets.US_ASCII).trim();
        return str.isEmpty() ? 0 : Long.parseLong(str.trim());
    }

    public static long getLittleEndianLong(ByteBuffer buffer) {
        byte b1 = buffer.get();
        byte b2 = buffer.get();
        byte b3 = buffer.get();
        byte b4 = buffer.get();
        byte b5 = buffer.get();
        byte b6 = buffer.get();
        byte b7 = buffer.get();
        byte b8 = buffer.get();
        return Longs.fromBytes(b8, b7, b6, b5, b4, b3, b2, b1);
    }

    public static int getLittleEndianInt(ByteBuffer buffer) {
        byte b1 = buffer.get();
        byte b2 = buffer.get();
        byte b3 = buffer.get();
        byte b4 = buffer.get();
        return Ints.fromBytes(b4, b3, b2, b1);
    }

    public static short getLittleEndianShort(ByteBuffer buffer) {
        byte b1 = buffer.get();
        byte b2 = buffer.get();
        return Shorts.fromBytes(b2, b1);
    }

    public static String getAsciiString(ByteBuffer buffer) {
        int position = buffer.position();
        int length = 0;
        do {
            length++;
        } while (buffer.get() != 0x00);
        byte[] bytes = new byte[length - 1];
        buffer.position(position);
        buffer.get(bytes, 0, length - 1);
        return new String(bytes, Charsets.US_ASCII);
    }

    private static void putSpaceLeftPaddedString(ByteBuffer buffer, int len, String value) {
        Preconditions.checkState(value.length() <= len);
        value = Strings.padStart(value, len, ' ');
        buffer.put(value.getBytes(Charsets.US_ASCII));
    }

    private static void putSpaceRightPaddedString(ByteBuffer buffer, int len, String value) {
        Preconditions.checkState(value.length() <= len);
        value = Strings.padEnd(value, len, ' ');
        buffer.put(value.getBytes(Charsets.US_ASCII));
    }

    public static void putBytes(ByteBuffer buffer, byte[] bytes) {
        buffer.put(bytes);
    }

    public static void putIntAsOctalString(ByteBuffer buffer, int len, int value, PaddingStyle paddingStyle) {
        if (paddingStyle == PaddingStyle.LEFT) {
            putSpaceLeftPaddedString(buffer, len, String.format("0%o", value));
        } else {
            putSpaceRightPaddedString(buffer, len, String.format("0%o", value));
        }
    }

    public static void putIntAsDecimalString(ByteBuffer buffer, int len, int value, PaddingStyle paddingStyle) {
        if (paddingStyle == PaddingStyle.LEFT) {
            putSpaceLeftPaddedString(buffer, len, String.format("%d", value));
        } else {
            putSpaceRightPaddedString(buffer, len, String.format("%d", value));
        }
    }

    public static void putLittleEndianLong(ByteBuffer buffer, long value) {
        byte[] bytes = Longs.toByteArray(value);
        byte[] flipped = { bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0] };
        buffer.put(flipped);
    }

    public static void putLittleEndianInt(ByteBuffer buffer, int value) {
        byte[] bytes = Ints.toByteArray(value);
        byte[] flipped = { bytes[3], bytes[2], bytes[1], bytes[0] };
        buffer.put(flipped);
    }

    public static void putAsciiString(ByteBuffer buffer, String string) {
        byte[] bytes = string.getBytes(Charsets.US_ASCII);
        buffer.put(bytes);
        buffer.put((byte) 0x00);
    }

    public static void checkArchive(boolean expression, String msg) throws FileContentsScrubber.ScrubException {
        if (!expression) {
            throw new FileContentsScrubber.ScrubException(msg);
        }
    }
}