/*
* 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;
import com.facebook.buck.io.FileContentsScrubber;
import com.facebook.buck.io.FileScrubber;
import com.facebook.buck.rules.BuildRule;
import com.facebook.buck.rules.RuleKeyObjectSink;
import com.facebook.buck.rules.SourcePath;
import com.facebook.buck.rules.SourcePathResolver;
import com.facebook.buck.rules.SourcePathRuleFinder;
import com.facebook.buck.rules.Tool;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.util.Arrays;
public class BsdArchiver implements Archiver {
private static final byte[] EXPECTED_GLOBAL_HEADER = "!<arch>\n".getBytes(Charsets.US_ASCII);
private static final byte[] LONG_NAME_MARKER = "#1/".getBytes(Charsets.US_ASCII);
private static final FileContentsScrubber SYMBOL_NAME_TABLE_PADDING_SCRUBBER =
file -> {
MappedByteBuffer map = file.map(FileChannel.MapMode.READ_WRITE, 0, file.size());
// Grab the global header chunk and verify it's accurate.
byte[] globalHeader = ObjectFileScrubbers.getBytes(map, EXPECTED_GLOBAL_HEADER.length);
ObjectFileScrubbers.checkArchive(
Arrays.equals(EXPECTED_GLOBAL_HEADER, globalHeader), "invalid global header");
byte[] marker = ObjectFileScrubbers.getBytes(map, 3);
if (!Arrays.equals(LONG_NAME_MARKER, marker)) {
// This file isn't actually made with BSD ar; skip scrubbing it.
return;
}
int nameLength = ObjectFileScrubbers.getDecimalStringAsInt(map, 13);
/* File modification timestamp */ ObjectFileScrubbers.getDecimalStringAsInt(map, 12);
/* Owner ID */ ObjectFileScrubbers.getDecimalStringAsInt(map, 6);
/* Group ID */ ObjectFileScrubbers.getDecimalStringAsInt(map, 6);
/* File mode */ ObjectFileScrubbers.getOctalStringAsInt(map, 8);
/* File size */ ObjectFileScrubbers.getDecimalStringAsInt(map, 10);
// Lastly, grab the file magic entry and verify it's accurate.
byte[] fileMagic = ObjectFileScrubbers.getBytes(map, 2);
ObjectFileScrubbers.checkArchive(
Arrays.equals(ObjectFileScrubbers.END_OF_FILE_HEADER_MARKER, fileMagic),
"invalid file magic");
// Skip the file name
map.position(map.position() + nameLength);
int descriptorsSize = ObjectFileScrubbers.getLittleEndianInt(map);
if (descriptorsSize > 0) {
// We need to find where the last symbol name entry is in the symbol name table, as
// we need to sanitize the padding that comes immediately after it.
// There are two types of symbol table formats, one where the descriptors are ordered by
// archive ordering and one where the descriptors are ordered alphabetically by their
// name. In the former case, we could just read the last descriptors offset into the
// symbol name table. However, this seems implementation-specific and won't work in the
// latter case (where the order of the descriptors doesn't correspond to the order of
// the symbol name table). So, just search through all the descriptors to find the last
// symbol name table offset.
int lastSymbolNameOffset = 0;
for (int i = 0; i < descriptorsSize / 8; i++) {
lastSymbolNameOffset =
Math.max(lastSymbolNameOffset, ObjectFileScrubbers.getLittleEndianInt(map));
// Skip the corresponding object offset
ObjectFileScrubbers.getLittleEndianInt(map);
}
int symbolNameTableSize = ObjectFileScrubbers.getLittleEndianInt(map);
int endOfSymbolNameTableOffset = map.position() + symbolNameTableSize;
// Skip to the last symbol name
map.position(map.position() + lastSymbolNameOffset);
// Skip to the terminating null
while (map.get() != 0x00) { // NOPMD
}
while (map.position() < endOfSymbolNameTableOffset) {
map.put((byte) 0x00);
}
} else {
int symbolNameTableSize = ObjectFileScrubbers.getLittleEndianInt(map);
ObjectFileScrubbers.checkArchive(
symbolNameTableSize == 0, "archive has no symbol descriptors but has symbol names");
}
};
private final Tool tool;
public BsdArchiver(Tool tool) {
this.tool = tool;
}
@Override
public ImmutableList<FileScrubber> getScrubbers() {
return ImmutableList.of(
ObjectFileScrubbers.createDateUidGidScrubber(ObjectFileScrubbers.PaddingStyle.RIGHT),
SYMBOL_NAME_TABLE_PADDING_SCRUBBER);
}
@Override
public boolean supportsThinArchives() {
return false;
}
@Override
public ImmutableList<String> getArchiveOptions(boolean isThinArchive) {
String options = isThinArchive ? "qcT" : "qc";
return ImmutableList.of(options);
}
@Override
public ImmutableList<String> outputArgs(String outputPath) {
return ImmutableList.of(outputPath);
}
@Override
public boolean isRanLibStepRequired() {
return true;
}
@Override
public ImmutableCollection<BuildRule> getDeps(SourcePathRuleFinder ruleFinder) {
return tool.getDeps(ruleFinder);
}
@Override
public ImmutableCollection<SourcePath> getInputs() {
return tool.getInputs();
}
@Override
public ImmutableList<String> getCommandPrefix(SourcePathResolver resolver) {
return tool.getCommandPrefix(resolver);
}
@Override
public ImmutableMap<String, String> getEnvironment(SourcePathResolver resolver) {
return tool.getEnvironment(resolver);
}
@Override
public void appendToRuleKey(RuleKeyObjectSink sink) {
sink.setReflectively("tool", tool).setReflectively("type", getClass().getSimpleName());
}
}