com.facebook.buck.macho.CompDirReplacer.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.macho.CompDirReplacer.java

Source

/*
 * Copyright 2016-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.macho;

import com.facebook.buck.charset.NulTerminatedCharsetDecoder;
import com.facebook.buck.log.Logger;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

public class CompDirReplacer {

    private static final Logger LOG = Logger.get(CompDirReplacer.class);

    private final ByteBuffer buffer;
    private final NulTerminatedCharsetDecoder nulTerminatedCharsetDecoder;

    public static void replaceCompDirInFile(Path path, String oldCompDir, String newCompDir,
            NulTerminatedCharsetDecoder decoder) throws IOException {
        try (FileChannel file = FileChannel.open(path, StandardOpenOption.READ, StandardOpenOption.WRITE)) {
            ByteBuffer byteBuffer = file.map(FileChannel.MapMode.READ_WRITE, 0, file.size());
            CompDirReplacer compDirReplacer = new CompDirReplacer(byteBuffer, decoder);
            compDirReplacer.replaceCompDir(oldCompDir, newCompDir);
        }
    }

    public CompDirReplacer(ByteBuffer byteBuffer, NulTerminatedCharsetDecoder nulTerminatedCharsetDecoder) {
        this.buffer = byteBuffer;
        this.nulTerminatedCharsetDecoder = nulTerminatedCharsetDecoder;
    }

    private void processThinBinary(final MachoMagicInfo magicInfo, final String oldCompDir,
            final String updatedCompDir) {
        buffer.position(0);
        ImmutableList<SegmentCommand> segmentCommands = LoadCommandUtils.findLoadCommandsWithClass(buffer,
                nulTerminatedCharsetDecoder, SegmentCommand.class);
        Preconditions.checkArgument(segmentCommands.size() == 1, "Found %d SegmentCommands, expected 1",
                segmentCommands.size());

        processSectionsInSegmentCommand(segmentCommands.get(0), magicInfo, oldCompDir, updatedCompDir);
    }

    private void processSectionsInSegmentCommand(SegmentCommand segmentCommand, MachoMagicInfo magicInfo,
            final String oldCompDir, final String updatedCompDir) {
        try {
            SegmentCommandUtils.enumerateSectionsInSegmentLoadCommand(buffer, magicInfo, segmentCommand,
                    nulTerminatedCharsetDecoder,
                    input -> updateCompDirInSection(input, oldCompDir, updatedCompDir));
        } catch (IOException e) {
            LOG.error(e, "Unable to process __DWARF.__debug_str section");
        }
    }

    private Boolean updateCompDirInSection(Section section, String oldCompDir, String updatedCompDir) {
        if (section.getSegname().equals(CommandSegmentSectionNames.SEGMENT_NAME_DWARF)
                && section.getSectname().equals(CommandSegmentSectionNames.SECTION_NAME_DEBUG_STR)) {
            findAndUpdateCompDirInDebugSection(section, oldCompDir, updatedCompDir);
            return false;
        }
        return true;
    }

    private void findAndUpdateCompDirInDebugSection(Section section, String oldCompDir, String updatedCompDir) {
        final long maximumValidOffset = section.getOffset().longValue() + section.getSize().longValue();
        int offset = section.getOffset().intValue();
        while (offset < maximumValidOffset) {
            buffer.position(offset);
            String string;
            try {
                string = nulTerminatedCharsetDecoder.decodeString(buffer);
            } catch (CharacterCodingException e) {
                LOG.error(e, "Unable to read read string from debug string table, offset %d", offset);
                break;
            }
            if (string.equals(oldCompDir)) {
                LOG.verbose("Found comp dir at %d, overwriting it with %s", offset, updatedCompDir);
                buffer.position(offset);
                buffer.put(updatedCompDir.getBytes(StandardCharsets.UTF_8));
                buffer.put((byte) 0x00);
                break;
            }
            offset += SymTabCommandUtils.sizeOfStringTableEntryWithContents(string);
        }
    }

    public void replaceCompDir(String oldCompDir, String updatedCompDir) throws IOException {
        Preconditions.checkArgument(oldCompDir.length() >= updatedCompDir.length(),
                "Updated compdir length must be less or equal to old compdir length as replace is "
                        + "performed in place");
        Preconditions.checkArgument(!oldCompDir.equals(updatedCompDir),
                "Updated compdir must be different from old compdir");

        MachoMagicInfo magicInfo = MachoMagicInfoUtils.getMachMagicInfo(buffer);
        if (!magicInfo.isValidMachMagic()) {
            throw new IOException("Cannot locate magic for Mach O binary.");
        }
        if (magicInfo.isFatBinaryHeaderMagic()) {
            throw new IOException("Fat binaries are not supported at this level.");
        }
        buffer.order(magicInfo.isSwapped() ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
        processThinBinary(magicInfo, oldCompDir, updatedCompDir);
    }
}