in src/tools/singlejar/output_jar.cc [320:551]
bool OutputJar::AddJar(int jar_path_index) {
const std::string &input_jar_path =
options_->input_jars[jar_path_index].first;
const std::string &input_jar_aux_label =
options_->input_jars[jar_path_index].second;
InputJar input_jar;
if (!input_jar.Open(input_jar_path)) {
return false;
}
const CDH *jar_entry;
const LH *lh;
while ((jar_entry = input_jar.NextEntry(&lh))) {
const char *file_name = jar_entry->file_name();
auto file_name_length = jar_entry->file_name_length();
if (!file_name_length) {
diag_errx(
1, "%s:%d: Bad central directory record in %s at offset 0x%" PRIx64,
__FILE__, __LINE__, input_jar_path.c_str(),
input_jar.CentralDirectoryRecordOffset(jar_entry));
}
// Special files that cannot be handled by looking up known_members_ map:
// * ignore *.SF, *.RSA, *.DSA
// (TODO(asmundak): should this be done only in META-INF?
//
if (ends_with(file_name, file_name_length, ".SF") ||
ends_with(file_name, file_name_length, ".RSA") ||
ends_with(file_name, file_name_length, ".DSA")) {
continue;
}
bool include_entry = true;
if (!options_->include_prefixes.empty()) {
for (auto &prefix : options_->include_prefixes) {
if ((include_entry =
(prefix.size() <= file_name_length &&
0 == strncmp(file_name, prefix.c_str(), prefix.size())))) {
break;
}
}
}
if (!include_entry) {
continue;
}
bool is_file = (file_name[file_name_length - 1] != '/');
if (is_file &&
begins_with(file_name, file_name_length, "META-INF/services/")) {
// The contents of the META-INF/services/<SERVICE> on the output is the
// concatenation of the META-INF/services/<SERVICE> files from all inputs.
std::string service_path(file_name, file_name_length);
if (NewEntry(service_path)) {
// Create a concatenator and add it to the known_members_ map.
// The call to Merge() below will then take care of the rest.
Concatenator *service_handler = new Concatenator(service_path);
service_handlers_.emplace_back(service_handler);
known_members_.emplace(service_path, EntryInfo{service_handler});
}
} else {
ExtraHandler(input_jar_path, jar_entry, &input_jar_aux_label);
}
if (options_->check_desugar_deps &&
begins_with(file_name, file_name_length, "j$/")) {
diag_errx(1, "%s:%d: desugar_jdk_libs file %.*s unexpectedly found in %s",
__FILE__, __LINE__, file_name_length, file_name,
input_jar_path.c_str());
}
// Install a new entry unless it is already present. All the plain (non-dir)
// entries that require a combiner have been already installed, so the call
// will add either a directory entry whose handler will ignore subsequent
// duplicates, or an ordinary plain entry, for which we save the index of
// the first input jar (in order to provide diagnostics on duplicate).
auto got =
known_members_.emplace(std::string(file_name, file_name_length),
EntryInfo{is_file ? nullptr : &null_combiner_,
is_file ? jar_path_index : -1});
if (!got.second) {
auto &entry_info = got.first->second;
// Handle special entries (the ones that have a combiner).
if (entry_info.combiner_ != nullptr) {
// TODO(kmb,asmundak): Should be checking Merge() return value but fails
// for build-data.properties when merging deploy jars into deploy jars.
entry_info.combiner_->Merge(jar_entry, lh);
continue;
}
// Plain file entry. If duplicates are not allowed, bail out. Otherwise
// just ignore this entry.
if (options_->no_duplicates ||
(options_->no_duplicate_classes &&
ends_with(file_name, file_name_length, ".class"))) {
diag_errx(
1, "%s:%d: %.*s is present both in %s and %s", __FILE__, __LINE__,
file_name_length, file_name,
options_->input_jars[entry_info.input_jar_index_].first.c_str(),
input_jar_path.c_str());
} else {
duplicate_entries_++;
continue;
}
}
// Add any missing parent directory entries (first) if requested.
if (options_->add_missing_directories) {
// Ignore very last character in case this entry is a directory itself.
for (size_t pos = 0; pos < static_cast<size_t>(file_name_length - 1);
++pos) {
if (file_name[pos] == '/') {
std::string dir(file_name, 0, pos + 1);
if (NewEntry(dir)) {
WriteDirEntry(dir, nullptr, 0);
}
}
}
}
// For the file entries, decide whether output should be compressed.
if (is_file) {
bool input_compressed =
jar_entry->compression_method() != Z_NO_COMPRESSION;
bool output_compressed =
options_->force_compression ||
(options_->preserve_compression && input_compressed);
if (output_compressed && !options_->nocompress_suffixes.empty()) {
for (auto &suffix : options_->nocompress_suffixes) {
if (file_name_length >= suffix.size() &&
!strncmp(file_name + file_name_length - suffix.size(),
suffix.c_str(), suffix.size())) {
output_compressed = false;
break;
}
}
}
if (input_compressed != output_compressed) {
Concatenator combiner(jar_entry->file_name_string());
if (!combiner.Merge(jar_entry, lh)) {
diag_err(1, "%s:%d: cannot add %.*s", __FILE__, __LINE__,
jar_entry->file_name_length(), jar_entry->file_name());
}
WriteEntry(combiner.OutputEntry(output_compressed));
continue;
}
}
// Now we have to copy:
// local header
// file data
// data descriptor, if present.
off64_t copy_from = jar_entry->local_header_offset();
size_t num_bytes = lh->size();
if (jar_entry->no_size_in_local_header()) {
const DDR *ddr = reinterpret_cast<const DDR *>(
lh->data() + jar_entry->compressed_file_size());
num_bytes +=
jar_entry->compressed_file_size() +
ddr->size(
ziph::zfield_has_ext64(jar_entry->compressed_file_size32()),
ziph::zfield_has_ext64(jar_entry->uncompressed_file_size32()));
} else {
num_bytes += lh->compressed_file_size();
}
off64_t local_header_offset = Position();
// When normalize_timestamps is set, entry's timestamp is to be set to
// 01/01/2010 00:00:00 (or to 01/01/2010 00:00:02, if an entry is a .class
// file). This is somewhat expensive because we have to copy the local
// header to memory as input jar is memory mapped as read-only. Try to copy
// as little as possible.
uint16_t normalized_time = 0;
const UnixTimeExtraField *lh_field_to_remove = nullptr;
bool fix_timestamp = false;
if (options_->normalize_timestamps) {
if (ends_with(file_name, file_name_length, ".class")) {
normalized_time = 1;
}
lh_field_to_remove = lh->unix_time_extra_field();
fix_timestamp = jar_entry->last_mod_file_date() != kDefaultDate ||
jar_entry->last_mod_file_time() != normalized_time ||
lh_field_to_remove != nullptr;
}
if (fix_timestamp) {
uint8_t lh_buffer[512];
size_t lh_size = lh->size();
LH *lh_new = lh_size > sizeof(lh_buffer)
? reinterpret_cast<LH *>(malloc(lh_size))
: reinterpret_cast<LH *>(lh_buffer);
// Remove Unix timestamp field.
if (lh_field_to_remove != nullptr) {
auto from_end = ziph::byte_ptr(lh) + lh->size();
size_t removed_size = lh_field_to_remove->size();
size_t chunk1_size =
ziph::byte_ptr(lh_field_to_remove) - ziph::byte_ptr(lh);
size_t chunk2_size = lh->size() - (chunk1_size + removed_size);
memcpy(lh_new, lh, chunk1_size);
if (chunk2_size) {
memcpy(reinterpret_cast<uint8_t *>(lh_new) + chunk1_size,
from_end - chunk2_size, chunk2_size);
}
lh_new->extra_fields(lh_new->extra_fields(),
lh->extra_fields_length() - removed_size);
} else {
memcpy(lh_new, lh, lh_size);
}
lh_new->last_mod_file_date(kDefaultDate);
lh_new->last_mod_file_time(normalized_time);
// Now write these few bytes and adjust read/write positions accordingly.
if (!WriteBytes(lh_new, lh_new->size())) {
diag_err(1, "%s:%d: Cannot copy modified local header for %.*s",
__FILE__, __LINE__, file_name_length, file_name);
}
copy_from += lh_size;
num_bytes -= lh_size;
if (reinterpret_cast<uint8_t *>(lh_new) != lh_buffer) {
free(lh_new);
}
}
// Do the actual copy.
if (!WriteBytes(input_jar.mapped_start() + copy_from, num_bytes)) {
diag_err(1, "%s:%d: Cannot write %zu bytes of %.*s from %s", __FILE__,
__LINE__, num_bytes, file_name_length, file_name,
input_jar_path.c_str());
}
AppendToDirectoryBuffer(jar_entry, local_header_offset, normalized_time,
fix_timestamp);
++entries_;
}
return input_jar.Close();
}