| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "target_archive/target_archive.h" |
| |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| // |
| // |
| // |
| |
| // clang-format off |
| #define TARGET_ARCHIVE_BUFLEN 8192 |
| #define TARGET_ARCHIVE_BINARY_ALIGN 8 |
| // clang-format on |
| |
| // |
| // Target Archive |
| // |
| |
| static void |
| target_archive_usage(char const * argv[]) |
| { |
| fprintf( |
| stderr, |
| "Usage: %s <target archive name> <rebased target archive filename prefix> [ <binary file> ]+\n" |
| "\n" |
| "Concatenates one or more binaries prefixed by a table containing \n" |
| "the number of binaries and the offset and size of each binary.\n" |
| "\n" |
| " - Offsets are relative to the end of the entries[] table.\n" |
| " - Offsets and sizes are in bytes.\n" |
| " - Offsets and sizes are 64-bit.\n" |
| " - Binaries and their offsets are 8-byte aligned.\n" |
| "\n" |
| "Three files are output:\n" |
| " - <output_name>.ar : Target archive\n" |
| " - <output_name>.S\n : Linkable source file for including target archive\n" |
| " - <output_name>.h\n : Include file containing linkable symbol name of target archive\n", |
| argv[0]); |
| } |
| |
| // |
| // Copy files_in[] to file_out. |
| // |
| // NOTE: Use of `sendfile()` probably isn't worth loss of portability. |
| // |
| |
| static void |
| target_archive_copy(FILE * file_out, |
| FILE ** files_in, |
| struct target_archive_entry * entries, |
| uint32_t count, |
| int * const exit_code) |
| { |
| if (count == 0) |
| { |
| return; |
| } |
| |
| char * buf = malloc(TARGET_ARCHIVE_BUFLEN); |
| |
| if (buf == NULL) |
| { |
| exit(EXIT_FAILURE); |
| } |
| |
| while (true) |
| { |
| // copy until error |
| while (true) |
| { |
| size_t const bytes = fread(buf, 1, TARGET_ARCHIVE_BUFLEN, *files_in); |
| |
| if (fwrite(buf, 1, bytes, file_out) != bytes) |
| { |
| *exit_code = EXIT_FAILURE; |
| } |
| |
| entries->size += bytes; |
| |
| if (bytes < TARGET_ARCHIVE_BUFLEN) |
| { |
| if (ferror(*files_in)) |
| { |
| *exit_code = EXIT_FAILURE; |
| } |
| |
| break; |
| } |
| } |
| |
| // pad with zeroes to next 8-byte offset |
| uint32_t const rem = (entries->size & (TARGET_ARCHIVE_BINARY_ALIGN - 1)); |
| bool const is_unaligned = (rem != 0); |
| uint32_t const skip = is_unaligned ? TARGET_ARCHIVE_BINARY_ALIGN - rem : 0; |
| |
| if (is_unaligned) |
| { |
| // store up to 7 zero bytes |
| uint8_t const zeroes[TARGET_ARCHIVE_BINARY_ALIGN - 1] = { 0 }; |
| |
| if (fwrite(zeroes, 1, skip, file_out) != skip) |
| { |
| *exit_code = EXIT_FAILURE; |
| } |
| } |
| |
| if (--count == 0) |
| { |
| break; |
| } |
| |
| uint64_t const offset_ru = entries->offset + entries->size + skip; |
| |
| ++entries; |
| ++files_in; |
| |
| entries->offset = offset_ru; |
| } |
| |
| free(buf); |
| } |
| |
| // |
| // |
| // |
| |
| static bool |
| target_archive_verify(FILE ** files, char const * argv[], uint32_t count) |
| { |
| bool is_verified = true; |
| |
| for (uint32_t ii = 0; ii < count; ii++) |
| { |
| if (files[ii] == NULL) |
| { |
| is_verified = false; |
| |
| fprintf(stderr, "File not found: %s\n", argv[ii]); |
| } |
| } |
| |
| return is_verified; |
| } |
| |
| // |
| // |
| // |
| |
| static void |
| target_archive_str_replace(char file_name[], size_t file_name_len, char from, char to) |
| { |
| for (size_t ii = 0; ii < file_name_len; ii++) |
| { |
| if (file_name[ii] == from) |
| { |
| file_name[ii] = to; |
| } |
| } |
| } |
| |
| // |
| // |
| // |
| |
| static void |
| target_archive_write_dot_h(char const * generator, char const * name, char const * rebased_name) |
| { |
| // |
| // What is the name of the include file? |
| // |
| size_t const name_len = strlen(rebased_name); |
| size_t const file_name_len = name_len + strlen(".h") + 1; |
| |
| char file_name[file_name_len]; |
| |
| strcpy(file_name, rebased_name); |
| strcpy(file_name + name_len, ".h"); |
| |
| FILE * file = fopen(file_name, "w"); |
| |
| if (file == NULL) |
| { |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // Symbol name can be long but that's OK |
| // |
| target_archive_str_replace(file_name, name_len, '/', '_'); |
| file_name[name_len] = '\0'; |
| |
| fprintf(file, |
| "// Copyright 2021 The Fuchsia Authors.All rights reserved.\n" |
| "// Use of this source code is governed by a BSD - style license that can be\n" |
| "// found in the LICENSE file.\n" |
| "//\n" |
| "// WARNING: File generated by %s\n" |
| "\n", |
| generator); |
| |
| fprintf(file, |
| "#pragma once\n" |
| "\n" |
| "#include \"target_archive/target_archive.h\"\n" |
| "\n" |
| "#ifdef __cplusplus\n" |
| "extern \"C\" {\n" |
| "#endif\n" |
| "\n" |
| "extern struct target_archive_header const %s[];\n" |
| "\n" |
| "#ifdef __cplusplus\n" |
| "}\n" |
| "#endif\n", |
| name); |
| |
| fclose(file); |
| } |
| |
| // |
| // |
| // |
| |
| static void |
| target_archive_write_dot_S(char const * generator, char const * name, char const * rebased_name) |
| { |
| // |
| // What is the name of the include file? |
| // |
| size_t const name_len = strlen(rebased_name); |
| size_t const file_name_len = name_len + strlen(".S") + 1; |
| |
| char file_name[file_name_len]; |
| |
| strcpy(file_name, rebased_name); |
| strcpy(file_name + name_len, ".S"); |
| |
| FILE * file = fopen(file_name, "w"); |
| |
| if (file == NULL) |
| { |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // Symbol name can be long but that's OK |
| // |
| fprintf(file, |
| "# Copyright 2021 The Fuchsia Authors.All rights reserved.\n" |
| "# Use of this source code is governed by a BSD - style license that can be\n" |
| "# found in the LICENSE file.\n" |
| "#\n" |
| "# WARNING: File generated by %s\n" |
| "\n", |
| generator); |
| |
| fprintf(file, |
| ".section .rodata\n" |
| "\n" |
| ".global %s\n" |
| ".type %s, @object\n" |
| ".align 8\n" |
| "\n" |
| "%s: .incbin \"%s.ar\"\n" |
| "\n", |
| name, |
| name, |
| name, |
| rebased_name); |
| |
| fclose(file); |
| } |
| |
| // |
| // |
| // |
| |
| static void |
| target_archive_dump_linkables(char const * argv[]) |
| { |
| target_archive_write_dot_h(argv[0], argv[1], argv[2]); |
| target_archive_write_dot_S(argv[0], argv[1], argv[2]); |
| } |
| |
| // |
| // |
| // |
| |
| int |
| main(int argc, char const * argv[]) |
| { |
| if (argc < 3) |
| { |
| target_archive_usage(argv); |
| |
| exit(EXIT_FAILURE); |
| } |
| |
| // |
| // Dump linkable files first |
| // |
| target_archive_dump_linkables(argv); |
| |
| // |
| // How many files to concatenate? |
| // |
| uint32_t const file_count = argc - 2; |
| uint32_t const read_count = argc - 3; |
| |
| // |
| // Create header |
| // |
| size_t const entries_size = sizeof(struct target_archive_entry) * read_count; |
| size_t const header_size = sizeof(struct target_archive_header) + entries_size; |
| |
| struct target_archive_header * const header = calloc(1, header_size); |
| |
| if (header == NULL) |
| { |
| exit(EXIT_FAILURE); |
| } |
| |
| struct target_archive_entry * entries = header->entries; |
| |
| header->magic = TARGET_ARCHIVE_MAGIC; |
| header->count = read_count; |
| |
| // |
| // What is the file name of the target archive? |
| // |
| size_t const name_len = strlen(argv[2]); |
| size_t const file_name_len = name_len + strlen(".ar") + 1; |
| |
| char file_name[file_name_len]; |
| |
| strcpy(file_name, argv[2]); |
| strcpy(file_name + name_len, ".ar"); |
| |
| // |
| // Blindly open all files |
| // |
| FILE ** files = calloc(file_count, sizeof(*files)); |
| |
| if (files == NULL) |
| { |
| exit(EXIT_FAILURE); |
| } |
| |
| files[0] = fopen(file_name, "wb"); |
| |
| for (uint32_t ii = 1; ii < file_count; ii++) |
| { |
| files[ii] = fopen(argv[2 + ii], "rb"); |
| } |
| |
| // |
| // Verify all files were opened |
| // |
| int exit_code = EXIT_FAILURE; |
| |
| if (target_archive_verify(files, argv + 2, file_count)) |
| { |
| exit_code = EXIT_SUCCESS; |
| |
| // |
| // Copy all files |
| // |
| if (fseek(files[0], (long)header_size, SEEK_SET) != 0) |
| { |
| exit_code = EXIT_FAILURE; |
| } |
| |
| target_archive_copy(files[0], files + 1, entries, read_count, &exit_code); |
| |
| // |
| // Write header |
| // |
| if (fseek(files[0], 0, SEEK_SET) != 0) |
| { |
| exit_code = EXIT_FAILURE; |
| } |
| |
| if (fwrite(header, 1, header_size, files[0]) != header_size) |
| { |
| exit_code = EXIT_FAILURE; |
| } |
| } |
| |
| // |
| // Cleanup |
| // |
| for (uint32_t ii = 0; ii < file_count; ii++) |
| { |
| if (files[ii] != NULL) |
| { |
| fclose(files[ii]); |
| } |
| } |
| |
| free(files); |
| free(header); |
| |
| exit(exit_code); |
| } |
| |
| // |
| // |
| // |