Files
swift-mirror/tools/swift-stdlib-tool/swift-stdlib-tool.cpp
salinas-miguel 815b784988 fix typo
2021-09-20 11:40:00 -07:00

1210 lines
42 KiB
C++

//===----------------------------------------------------------------------===//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
const char *usage =
" swift-stdlib-tool --print [options...]\n"
" Find and print the Swift libraries required by an app.\n"
"\n"
" swift-stdlib-tool --copy [options...]\n"
" Copy the Swift libraries into an app bundle, and optionally sign them.\n"
"\n"
" --verbose\n"
" Print progress.\n"
" --verbose --verbose\n"
" Print debugging details.\n"
" --help\n"
" Print usage.\n"
"\n"
" Options for lookup:\n"
" --scan-executable <path>\n"
" Scan the executable at <path> for references to Swift libraries.\n"
" This option may be set multiple times.\n"
" --scan-folder <path>\n"
" Scan any executables inside <path> for references to Swift libraries.\n"
" This option may be set multiple times.\n"
" --platform <macosx|iphoneos|iphonesimulator>\n"
" Use the Swift libraries for <platform>.\n"
" --source-libraries <path>\n"
" Search <path> for Swift libraries.\n"
" The default is /path/to/swift-stdlib-tool/../../lib/swift/<platform>/\n"
"\n"
" Options for copying and signing:\n"
" --destination <path>\n"
" Copy Swift libraries into <path>.\n"
" --unsigned-destination <path>\n"
" Copy Swift libraries into <path> without signing them.\n"
" --sign <identity>\n"
" Sign copied Swift libraries using <identity>.\n"
" --keychain <keychain>\n"
" Search <keychain> for the code signing identity.\n"
" --Xcodesign <option>\n"
" Pass <option> to the codesign tool.\n"
" --strip-bitcode\n"
" Remove embedded bitcode from libraries copied to --destination.\n"
" Libraries copied to --unsigned-destination are unmodified.\n"
"\n"
" Options for libraries copied as resources\n"
" --resource-library <library>\n"
" Copy <library> and its dependencies as resources without signing\n"
" them. These copies are in addition to any libraries copied as a result\n"
" of the --scan-executable option.\n"
" Any library in the Swift library search path can be specified for\n"
" <library>.\n"
" This option may be set multiple times.\n"
" --resource-destination <path>\n"
" The <path> to copy Swift resource libraries to.\n"
"\n"
;
#include <sys/types.h>
#include <sys/param.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <os/overflow.h>
#include <mach-o/fat.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <libkern/OSByteOrder.h>
#include <uuid/uuid.h>
#include <dispatch/dispatch.h>
#include <algorithm>
#include <vector>
#include <string>
#include <filesystem>
#include <unordered_map>
#include <unordered_set>
#include <mutex>
#pragma clang diagnostic ignored "-Wgcc-compat"
#ifndef CPU_TYPE_ARM64
# define CPU_TYPE_ARM64 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64))
#endif
static int Verbose = 0;
#ifdef __OPTIMIZE__
#define INLINE __attribute__((always_inline))
#else
#define INLINE
#endif
//
// This abstraction layer is for use with file formats that have 64-bit/32-bit and Big-Endian/Little-Endian variants
//
// For example: to make a utility that handles 32-bit little enidan files use: Pointer32<LittleEndian>
//
//
// get16() read a 16-bit number from an E endian struct
// set16() write a 16-bit number to an E endian struct
// get32() read a 32-bit number from an E endian struct
// set32() write a 32-bit number to an E endian struct
// get64() read a 64-bit number from an E endian struct
// set64() write a 64-bit number to an E endian struct
//
// getBits() read a bit field from an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
// setBits() write a bit field to an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
//
// getBitsRaw() read a bit field from a struct with native endianness
// setBitsRaw() write a bit field from a struct with native endianness
//
class BigEndian
{
public:
static uint16_t get16(const uint16_t& from) INLINE { return OSReadBigInt16(&from, 0); }
static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteBigInt16(&into, 0, value); }
static uint32_t get32(const uint32_t& from) INLINE { return OSReadBigInt32(&from, 0); }
static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteBigInt32(&into, 0, value); }
static uint64_t get64(const uint64_t& from) INLINE { return OSReadBigInt64(&from, 0); }
static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteBigInt64(&into, 0, value); }
static uint32_t getBits(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); }
static void setBits(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); }
static uint32_t getBitsRaw(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> (32-firstBit-bitCount)) & ((1<<bitCount)-1)); }
static void setBitsRaw(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = into;
const uint32_t mask = ((1<<bitCount)-1);
temp &= ~(mask << (32-firstBit-bitCount));
temp |= ((value & mask) << (32-firstBit-bitCount));
into = temp; }
enum { little_endian = 0 };
};
class LittleEndian
{
public:
static uint16_t get16(const uint16_t& from) INLINE { return OSReadLittleInt16(&from, 0); }
static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteLittleInt16(&into, 0, value); }
static uint32_t get32(const uint32_t& from) INLINE { return OSReadLittleInt32(&from, 0); }
static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteLittleInt32(&into, 0, value); }
static uint64_t get64(const uint64_t& from) INLINE { return OSReadLittleInt64(&from, 0); }
static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteLittleInt64(&into, 0, value); }
static uint32_t getBits(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); }
static void setBits(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); }
static uint32_t getBitsRaw(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> firstBit) & ((1<<bitCount)-1)); }
static void setBitsRaw(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = into;
const uint32_t mask = ((1<<bitCount)-1);
temp &= ~(mask << firstBit);
temp |= ((value & mask) << firstBit);
into = temp; }
enum { little_endian = 1 };
};
#if __BIG_ENDIAN__
typedef BigEndian CurrentEndian;
typedef LittleEndian OtherEndian;
#elif __LITTLE_ENDIAN__
typedef LittleEndian CurrentEndian;
typedef BigEndian OtherEndian;
#else
#error unknown endianness
#endif
template <typename _E>
class Pointer32
{
public:
typedef uint32_t uint_t;
typedef int32_t sint_t;
typedef _E E;
static uint64_t getP(const uint_t& from) INLINE { return _E::get32(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set32(into, value); }
};
template <typename _E>
class Pointer64
{
public:
typedef uint64_t uint_t;
typedef int64_t sint_t;
typedef _E E;
static uint64_t getP(const uint_t& from) INLINE { return _E::get64(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set64(into, value); }
};
//
// mach-o file header
//
template <typename P> struct macho_header_content {};
template <> struct macho_header_content<Pointer32<BigEndian> > { mach_header fields; };
template <> struct macho_header_content<Pointer64<BigEndian> > { mach_header_64 fields; };
template <> struct macho_header_content<Pointer32<LittleEndian> > { mach_header fields; };
template <> struct macho_header_content<Pointer64<LittleEndian> > { mach_header_64 fields; };
template <typename P>
class macho_header {
public:
uint32_t magic() const INLINE { return E::get32(header.fields.magic); }
void set_magic(uint32_t value) INLINE { E::set32(header.fields.magic, value); }
uint32_t cputype() const INLINE { return E::get32(header.fields.cputype); }
void set_cputype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cputype, value); }
uint32_t cpusubtype() const INLINE { return E::get32(header.fields.cpusubtype); }
void set_cpusubtype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cpusubtype, value); }
uint32_t filetype() const INLINE { return E::get32(header.fields.filetype); }
void set_filetype(uint32_t value) INLINE { E::set32(header.fields.filetype, value); }
uint32_t ncmds() const INLINE { return E::get32(header.fields.ncmds); }
void set_ncmds(uint32_t value) INLINE { E::set32(header.fields.ncmds, value); }
uint32_t sizeofcmds() const INLINE { return E::get32(header.fields.sizeofcmds); }
void set_sizeofcmds(uint32_t value) INLINE { E::set32(header.fields.sizeofcmds, value); }
uint32_t flags() const INLINE { return E::get32(header.fields.flags); }
void set_flags(uint32_t value) INLINE { E::set32(header.fields.flags, value); }
uint32_t reserved() const INLINE { return E::get32(header.fields.reserved); }
void set_reserved(uint32_t value) INLINE { E::set32(header.fields.reserved, value); }
typedef typename P::E E;
private:
macho_header_content<P> header;
};
//
// mach-o load command
//
template <typename P>
class macho_load_command {
public:
uint32_t cmd() const INLINE { return E::get32(command.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(command.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(command.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(command.cmdsize, value); }
typedef typename P::E E;
private:
load_command command;
};
//
// mach-o uuid load command
//
template <typename P>
class macho_uuid_command {
public:
uint32_t cmd() const INLINE { return E::get32(fields.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); }
const uint8_t* uuid() const INLINE { return fields.uuid; }
void set_uuid(uint8_t value[16]) INLINE { memcpy(&fields.uuid, value, 16); }
typedef typename P::E E;
private:
uuid_command fields;
};
//
// mach-o dylib load command
//
template <typename P>
class macho_dylib_command {
public:
uint32_t cmd() const INLINE { return E::get32(fields.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); }
uint32_t name_offset() const INLINE { return E::get32(fields.dylib.name.offset); }
void set_name_offset(uint32_t value) INLINE { E::set32(fields.dylib.name.offset, value); }
uint32_t timestamp() const INLINE { return E::get32(fields.dylib.timestamp); }
void set_timestamp(uint32_t value) INLINE { E::set32(fields.dylib.timestamp, value); }
uint32_t current_version() const INLINE { return E::get32(fields.dylib.current_version); }
void set_current_version(uint32_t value) INLINE { E::set32(fields.dylib.current_version, value); }
uint32_t compatibility_version() const INLINE { return E::get32(fields.dylib.compatibility_version); }
void set_compatibility_version(uint32_t value) INLINE { E::set32(fields.dylib.compatibility_version, value); }
const char* name() const INLINE { return (const char*)&fields + name_offset(); }
void set_name_offset() INLINE { set_name_offset(sizeof(fields)); }
typedef typename P::E E;
private:
dylib_command fields;
};
// Print everything to standard output.
// Mixing stdout and stderr looks bad when the output is reprinted by Xcode.
void printUsage()
{
fprintf(stdout, "%s", usage);
}
void fail(const char *msg, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn))
{
va_list args;
va_start(args, msg);
char *msg2;
asprintf(&msg2, "*** error: %s\n", msg);
vfprintf(stdout, msg2, args);
exit(1);
}
void fail_errno(const char *msg, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn))
{
va_list args;
va_start(args, msg);
char *msg2;
asprintf(&msg2, "*** error: %s: %s\n", msg, strerror(errno));
vfprintf(stdout, msg2, args);
exit(1);
}
void fail_usage(const char *msg, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn))
{
va_list args;
va_start(args, msg);
char *msg2;
asprintf(&msg2, "*** error: %s\n\n", msg);
vfprintf(stdout, msg2, args);
printUsage();
exit(1);
}
void log_vn(int verbosity, const char *msg, va_list args)
__attribute__((format(printf, 2, 0)))
{
if (verbosity <= Verbose) {
char *msg2;
asprintf(&msg2, "%s\n", msg);
vfprintf(stdout, msg2, args);
free(msg2);
}
}
int log_v(const char *msg, ...)
__attribute__((format(printf, 1, 2)))
{
va_list args;
va_start(args, msg);
log_vn(1, msg, args);
return -1;
}
int log_vv(const char *msg, ...)
__attribute__((format(printf, 1, 2)))
{
va_list args;
va_start(args, msg);
log_vn(2, msg, args);
return -1;
}
ssize_t pread_all(int fd, void *buf, size_t count, off_t offset)
{
size_t total = 0;
while (total < count) {
ssize_t readed = pread(fd, (void *)((char *)buf+total),
count-total, offset+total);
if (readed > 0) total += readed; // got data
else if (readed == 0) return total; // EOF: done
else if (readed == -1 && errno != EINTR) return -1;
// error but not EINTR: fail
}
return total;
}
template <typename T>
int parse_macho(int fd, uint32_t offset, uint32_t size,
void (^dylibVisitor)(std::filesystem::path const &path),
void (^uuidVisitor)(uuid_t const uuid))
{
ssize_t readed;
macho_header<T> mh;
if (size < sizeof(mh)) return log_vv("file is too small");
readed = pread_all(fd, &mh, sizeof(mh), offset);
if (readed != sizeof(mh)) return log_vv("pread failed");
uint32_t sizeofcmds = mh.sizeofcmds();
size -= sizeof(mh);
offset += sizeof(mh);
if (size < sizeofcmds) return log_vv("file is badly formed");
uint8_t *cmdp = (uint8_t *)malloc(sizeofcmds);
if (!cmdp) return log_vv("malloc(sizeofcmds) failed");
readed = pread_all(fd, cmdp, sizeofcmds, offset);
if (readed == sizeofcmds) {
uint8_t *cmds = cmdp;
for (uint32_t c = 0; c < mh.ncmds(); c++) {
macho_load_command<T> *cmd;
if (size < sizeof(*cmd)) return log_vv("file is badly formed");
cmd = (macho_load_command<T>*) cmds;
if (size < cmd->cmdsize()) return log_vv("file is badly formed");
cmds += cmd->cmdsize();
size -= cmd->cmdsize();
if (dylibVisitor &&
(cmd->cmd() == LC_LOAD_DYLIB ||
cmd->cmd() == LC_LOAD_WEAK_DYLIB ||
cmd->cmd() == LC_LAZY_LOAD_DYLIB))
{
macho_dylib_command<T>* dylib = (macho_dylib_command<T>*)cmd;
if (dylib->cmdsize() < dylib->name_offset()) continue;
char *name = (char *)dylib + dylib->name_offset();
size_t name_len =
strnlen(name, dylib->cmdsize() - dylib->name_offset());
log_vv(" loads %.*s", (int)name_len, name);
#define PREPREFIX "@rpath/"
#define PREFIX PREPREFIX "libswift"
if (0 == strncmp(name, PREFIX, strlen(PREFIX))) {
dylibVisitor(name + strlen(PREPREFIX));
}
}
else if (uuidVisitor && cmd->cmd() == LC_UUID) {
macho_uuid_command<T>* uuid_cmd = (macho_uuid_command<T>*)cmd;
if (uuid_cmd->cmdsize() < sizeof(uuid_command)) continue;
uuidVisitor(uuid_cmd->uuid());
}
}
}
free(cmdp);
return 0;
}
int parse_macho(int fd, uint32_t offset, uint32_t size,
void (^dylibVisitor)(std::filesystem::path const &path),
void (^uuidVisitor)(uuid_t const uuid))
{
uint32_t magic;
if (size < sizeof(magic)) return log_vv("file is too small");
ssize_t readed = pread_all(fd, &magic, sizeof(magic), offset);
if (readed != sizeof(magic)) return log_vv("pread failed");
switch (magic) {
case MH_MAGIC_64:
return parse_macho<Pointer64<CurrentEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
case MH_MAGIC:
return parse_macho<Pointer32<CurrentEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
case MH_CIGAM_64:
return parse_macho<Pointer64<OtherEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
case MH_CIGAM:
return parse_macho<Pointer32<OtherEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
default:
return log_vv("file is not mach-o");
}
}
int parse_fat(int fd, off_t fsize, char *buffer, size_t size,
void (^dylibVisitor)(std::filesystem::path const &path),
void (^uuidVisitor)(uuid_t const uuid))
{
uint32_t magic;
if (size < sizeof(magic)) {
return log_vv("file is too small");
}
magic = *(uint32_t *)buffer;
if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
struct fat_header *fh;
uint32_t fat_magic, fat_nfat_arch;
struct fat_arch *archs;
uint32_t i;
if (size < sizeof(struct fat_header)) {
return log_vv("file is too small");
}
fh = (struct fat_header *)buffer;
fat_magic = OSSwapBigToHostInt32(fh->magic);
fat_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
size_t fat_arch_size;
// fat_nfat_arch * sizeof(struct fat_arch) + sizeof(struct fat_header)
if (os_mul_and_add_overflow(fat_nfat_arch, sizeof(struct fat_arch),
sizeof(struct fat_header), &fat_arch_size))
{
return log_vv("too many fat archs\n");
}
if (size < fat_arch_size) {
return log_vv("file is too small");
}
archs = (struct fat_arch *)(buffer + sizeof(struct fat_header));
/* Special case hidden CPU_TYPE_ARM64 */
size_t fat_arch_plus_one_size;
if (os_add_overflow(fat_arch_size, sizeof(struct fat_arch),
&fat_arch_plus_one_size))
{
return log_vv("too many fat archs\n");
}
if (size >= fat_arch_plus_one_size) {
if (fat_nfat_arch > 0
&& OSSwapBigToHostInt32(archs[fat_nfat_arch].cputype) == CPU_TYPE_ARM64) {
fat_nfat_arch++;
}
}
/* End special case hidden CPU_TYPE_ARM64 */
for (i=0; i < fat_nfat_arch; i++) {
int ret;
uint32_t arch_cputype, arch_cpusubtype, arch_offset, arch_size, arch_align;
arch_cputype = OSSwapBigToHostInt32(archs[i].cputype);
arch_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype);
arch_offset = OSSwapBigToHostInt32(archs[i].offset);
arch_size = OSSwapBigToHostInt32(archs[i].size);
arch_align = OSSwapBigToHostInt32(archs[i].align);
/* Check that slice data is after all fat headers and archs */
if (arch_offset < fat_arch_size) {
return log_vv("file is badly formed");
}
/* Check that the slice ends before the file does */
if (arch_offset > fsize) {
return log_vv("file is badly formed");
}
if (arch_size > fsize) {
return log_vv("file is badly formed");
}
if (arch_offset > (fsize - arch_size)) {
return log_vv("file is badly formed");
}
ret = parse_macho(fd, arch_offset, arch_size,
dylibVisitor, uuidVisitor);
if (ret != 0) {
return ret;
}
}
return 0;
} else {
/* Not a fat file */
return parse_macho(fd, 0, fsize, dylibVisitor, uuidVisitor);
}
}
void process(std::filesystem::path const& path, void(^dylibVisitor)(std::filesystem::path const&),
void (^uuidVisitor)(uuid_t const))
{
log_vv("Scanning %s...", path.c_str());
int fd = open(path.c_str(), O_RDONLY);
if (fd < 0) log_vv("%s: open failed: %s",
path.c_str(), strerror(errno));
struct stat st;
if (fstat(fd, &st) < 0) {
log_vv("%s: stat failed: %s",
path.c_str(), strerror(errno));
} else {
const int len = 4096;
char buf[len];
ssize_t readed = pread_all(fd, buf, len, 0);
if (readed != len) {
log_vv("%s: pread failed: %s",
path.c_str(), strerror(errno));
} else {
parse_fat(fd, st.st_size, buf, len, dylibVisitor, uuidVisitor);
}
}
close(fd);
}
bool operator <= (const struct timespec &lhs, const struct timespec &rhs)
{
if (lhs.tv_sec == rhs.tv_sec) return lhs.tv_nsec <= rhs.tv_nsec;
return lhs.tv_sec <= rhs.tv_sec;
}
// This executable's own path.
std::filesystem::path self_executable = []() -> std::filesystem::path {
char path[MAXPATHLEN] = {0};
uint32_t len = sizeof(path);
_NSGetExecutablePath(path, &len);
return std::filesystem::path(path);
}();
// This executable's own xctoolchain path.
std::filesystem::path self_toolchain = []() -> std::filesystem::path {
auto result = self_executable;
// Remove the executable name.
result = result.parent_path();
// Remove trailing /usr/bin, if any
if (result.filename() == std::filesystem::path("bin")) {
result = result.parent_path();
}
if (result.filename() == std::filesystem::path("usr")) {
result = result.parent_path();
}
return result;
}();
std::vector<uint8_t> readToEOF(int fd) {
std::vector<uint8_t> retData;
#define BUFFER_SIZE 1024
uint8_t readBuffer[BUFFER_SIZE];
ssize_t readSize = 0;
while ((readSize = read(fd, readBuffer, BUFFER_SIZE)) > 0) {
retData.reserve(retData.size() + readSize);
std::copy(readBuffer, readBuffer + readSize,
std::back_inserter(retData));
}
return retData;
}
// Runs a tool with `xcrun`.
// Returns NSTask.terminationStatus.
// Prints the tool's command line if we are verbose.
// Prints the tool's stdout and stderr if terminationStatus is non-zero
// or if we are very verbose.
typedef void (^XcrunToolBlock)(std::vector<uint8_t> stdOutData, std::vector<uint8_t> stdErrorData, int err);
int xcrunToolCommand(std::vector<std::string> commandAndArguments, XcrunToolBlock block = 0)
{
const char *launchPath = "/usr/bin/xcrun";
// Tell xcrun to search our toolchain first.
std::vector<const char *> arguments;
arguments.push_back(launchPath);
arguments.push_back("--toolchain");
arguments.push_back(self_toolchain.c_str());
// Tell xcrun to print its command if we are very verbose.
if (Verbose > 1) {
arguments.push_back("--log");
}
for (auto const &string : commandAndArguments) {
arguments.push_back(string.c_str());
}
arguments.push_back(NULL);
int outPipe[2];
int errPipe[2];
pipe(outPipe);
pipe(errPipe);
log_v(" %s", launchPath);
int childPid = fork();
if (childPid == 0) {
dup2(outPipe[1], STDOUT_FILENO);
dup2(errPipe[1], STDERR_FILENO);
execv(launchPath, (char *const *)arguments.data());
}
// Read stdout and stderr in parallel, then wait for the task
// to exit. Anything else risks deadlock if the task fills
// one of the output buffers.
int errPipeReadFd = errPipe[1];
__block std::vector<uint8_t> stdErrData;
dispatch_semaphore_t gotStdErr = dispatch_semaphore_create(0);
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
stdErrData = readToEOF(errPipeReadFd);
dispatch_semaphore_signal(gotStdErr);
});
auto const stdOutData = readToEOF(outPipe[0]);
dispatch_semaphore_wait(gotStdErr, DISPATCH_TIME_FOREVER);
dispatch_release(gotStdErr);
int status = 0;
waitpid(childPid, &status, 0);
// Task is finished and we have its stdout and stderr output.
// Print its stdout and stderr if it failed or we are verbose.
// (Print nothing by default because codesign is noisy.)
if (status || Verbose > 1) {
fwrite(stdErrData.data(), stdErrData.size(), 1, stdout);
fwrite(stdOutData.data(), stdOutData.size(), 1, stdout);
}
if (block) { block(stdOutData, stdErrData, status); }
return status;
}
void copyAndStripBitcode(std::filesystem::path src, std::filesystem::path dst)
{
// -r removes bitcode
std::vector<std::string> commandAndArgs = {"bitcode_strip", src, "-r", "-o", dst};
int err = xcrunToolCommand(commandAndArgs);
// Fail if bitcode_strip failed.
if (err) {
fail("Couldn't copy and strip bitcode %s to %s: bitcode_strip failed "
"with exit code %d", src.c_str(), dst.c_str(), err);
}
}
void
copyFile(std::filesystem::path src, std::filesystem::path dst, bool stripBitcode)
{
if (stripBitcode) {
copyAndStripBitcode(src, dst);
} else {
if (rename(src.c_str(), dst.c_str()) != 0) {
fail("Couldn't copy %s to %s: %s", src.c_str(), dst.c_str(), strerror(errno));
}
}
}
std::string uuidString(uuid_t const uuid) {
char buffer[37];
uuid_unparse(uuid, buffer);
return buffer;
}
void copyLibraries(std::filesystem::path src_dir, std::filesystem::path dst_dir,
std::unordered_map<std::string,
std::unordered_set<std::string>> const &libs,
bool stripBitcode)
{
mkpath_np(dst_dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
for (auto const &[lib, srcUUIDs]: libs) {
std::filesystem::path src = src_dir/lib;
std::filesystem::path dst = dst_dir/lib;
// Compare UUIDs of src and dst and don't copy if they're the same.
// Do not use mod times for this task: the dst copy gets code-signed
// and bitcode-stripped so it can look newer than it really is.
__block std::unordered_set<std::string> dstUUIDs;
process(dst, NULL, ^(uuid_t const uuid) {
dstUUIDs.insert(uuidString(uuid));
});
std::string srcUUIDsString;
srcUUIDsString.reserve(37 * srcUUIDs.size());
for (auto const &uuidString : srcUUIDs) {
srcUUIDsString.append(uuidString + std::string(" "));
}
std::string dstUUIDsString;
dstUUIDsString.reserve(37 * dstUUIDs.size());
for (auto const &uuidString : dstUUIDs) {
dstUUIDsString.append(uuidString + " ");
}
log_vv("Source UUIDs %s: %s", src.c_str(), srcUUIDsString.c_str());
log_vv("Destination UUIDs %s: %s", dst.c_str(), dstUUIDsString.c_str());
if (srcUUIDs == dstUUIDs) {
log_v("%s is up to date at %s",
lib.c_str(), dst.c_str());
continue;
}
// Perform the copy.
log_v("Copying %s from %s to %s",
lib.c_str(),
src_dir.c_str(),
dst_dir.c_str());
unlink(dst.c_str());
copyFile(std::filesystem::canonical(src), dst, stripBitcode);
}
}
std::vector<uint8_t> query_code_signature(std::string file) {
__block std::vector<uint8_t> d;
std::vector<std::string> command = {"codesign", "-r-", "--display", file};
log_v("Probing signature of %s", file.c_str());
xcrunToolCommand(command,
^(std::vector<uint8_t> stdOutData, std::vector<uint8_t> stdErrData, int err) {
if (!err) { d = stdOutData; }
});
return d;
}
int main(int argc, const char *argv[])
{
// Executables to scan for Swift references.
// --scan-executable
std::vector<std::filesystem::path> executables;
// Directories to scan for more executables.
// --scan-folder
std::vector<std::string> embedDirs;
// Platform name.
// --platform
// or the last path component of --source-libraries
std::string platform;
// Copy source.
// --source-libraries
// or /path/to/swift-stdlib-tool/../../lib/swift/<--platform>
std::filesystem::path src_dir;
// Copy destinations, signed and unsigned.
// --destination and --unsigned-destination
std::filesystem::path dst_dir;
std::filesystem::path unsigned_dst_dir;
// Resource copy destination.
// --resource-destination
std::string resource_dst_dir;
// Resource libraries.
// --resource-library
std::vector<std::string> resourceLibraries;
// Code signing options.
std::string ident;
std::string keychain;
std::vector<std::string> otherCodesignFlags;
// Read arguments
bool print = false;
bool copy = false;
bool stripBitcode = false;
for (int i = 1; i < argc; i++) {
if (0 == strcmp(argv[i], "--print")) print = true;
if (0 == strcmp(argv[i], "--copy")) copy = true;
if (0 == strcmp(argv[i], "--verbose")) Verbose++;
if (0 == strcmp(argv[i], "--help")) {
printUsage();
exit(0);
}
if (0 == strcmp(argv[i], "--scan-executable")) {
executables.emplace_back(argv[++i]);
}
if (0 == strcmp(argv[i], "--scan-folder")) {
embedDirs.emplace_back(argv[++i]);
}
if (0 == strcmp(argv[i], "--source-libraries")) {
src_dir = std::string(argv[++i]);
}
if (0 == strcmp(argv[i], "--platform")) {
platform = std::string(argv[++i]);
}
if (0 == strcmp(argv[i], "--destination")) {
dst_dir = std::filesystem::path(argv[++i]);
}
if (0 == strcmp(argv[i], "--unsigned-destination")) {
unsigned_dst_dir = std::filesystem::path(argv[++i]);
}
if (0 == strcmp(argv[i], "--sign")) {
ident = std::string(argv[++i]);
}
if (0 == strcmp(argv[i], "--keychain")) {
keychain = std::string(argv[++i]);
}
if (0 == strcmp(argv[i], "--Xcodesign")) {
otherCodesignFlags.push_back(std::string(argv[++i]));
}
if (0 == strcmp(argv[i], "--strip-bitcode")) {
stripBitcode = true;
}
if (0 == strcmp(argv[i], "--resource-destination")) {
resource_dst_dir = std::string(argv[++i]);
}
if (0 == strcmp(argv[i], "--resource-library")) {
resourceLibraries.push_back(std::string(argv[++i]));
}
}
// Fix up src_dir and platform values.
if (src_dir.empty() && platform.empty()) {
// Neither src_dir nor platform is set. Die.
fail_usage("At least one of --source-libraries and --platform "
"must be set.");
} else if (src_dir.empty()) {
// platform is set but src_dir is not.
// Use platform to set src_dir relative to us.
src_dir = self_executable.parent_path().parent_path()/
"lib"/"swift-5.0"/platform;
} else if (platform.empty()) {
// src_dir is set but platform is not.
// Pick platform from src_dir's name.
platform = src_dir.filename();
}
// Add the platform to unsigned_dst_dir if it is not already present.
if (!unsigned_dst_dir.empty()) {
auto const unsigned_platform = unsigned_dst_dir.filename();
if (platform != unsigned_platform) {
unsigned_dst_dir = unsigned_dst_dir/platform;
}
}
// If the user specifies --strip-bitcode but not --sign, this
// will cause the dylibs to get copied, stripped, but not resigned.
// This will cause apps to fail to launch because the code signature
// is invalid. In this case, ignore --strip-bitcode.
if (stripBitcode && ident.empty()) {
stripBitcode = false;
}
// Collect executables from the --scan-folder locations.
for (auto const &embedDir : embedDirs) {
for (auto const &entry : std::filesystem::recursive_directory_iterator(embedDir)) {
if (entry.exists() && !entry.is_directory() &&
0 == access(entry.path().c_str(), X_OK))
{
executables.push_back(entry.path());
} else {
log_vv("%s is not an executable file", entry.path().c_str());
}
}
}
// Collect Swift library names from the input files.
// If the library does not exist in src_dir then assume the user wrote
// their own library named libswift* and is handling it elsewhere.
__block std::unordered_map<std::string,
std::unordered_set<std::string>> swiftLibs;
for (auto const &path : executables) {
process(path,
^(std::filesystem::path const &linkedLib) {
auto const linkedSrc = src_dir/linkedLib;
if (std::filesystem::exists(linkedSrc)) {
swiftLibs[linkedLib] = std::unordered_set<std::string>();
}
},
NULL);
}
// Collect more Swift library names from the Swift libraries themselves.
// Also collect the Swift libraries' UUIDs.
__block std::vector<std::string> worklist;
worklist.reserve(swiftLibs.size());
for (auto const &[lib, _] : swiftLibs) {
worklist.push_back(lib);
}
while (worklist.size()) {
auto const &lib = worklist.back();
worklist.pop_back();
auto const path = src_dir/lib;
process(path,
^(std::filesystem::path const &linkedLib) {
auto const linkedSrc = src_dir/linkedLib;
if (swiftLibs.count(linkedLib) == 0 &&
std::filesystem::exists(linkedSrc))
{
swiftLibs[linkedLib] = std::unordered_set<std::string>();
worklist.push_back(linkedLib);
}
},
^(uuid_t const uuid) {
swiftLibs[lib].insert(uuidString(uuid));
});
}
// Collect all the Swift libraries that the user requested
// with --resource-library.
__block std::unordered_map<std::string,
std::unordered_set<std::string>> swiftLibsForResources;
for (auto const &lib : resourceLibraries) {
auto const libSrc = src_dir/lib;
if (std::filesystem::exists(libSrc)) {
swiftLibsForResources[lib] = std::unordered_set<std::string>();
}
}
// Collect dependencies of --resource-library libs.
worklist.clear();
for (auto const &[lib, _] : swiftLibsForResources) {
worklist.push_back(lib);
}
while (worklist.size()) {
auto const &lib = worklist.back();
worklist.pop_back();
auto const path = src_dir/lib;
process(path,
^(std::filesystem::path const &linkedLib) {
auto const linkedSrc = src_dir/linkedLib;
if (swiftLibsForResources.count(linkedLib) == 0 &&
std::filesystem::exists(linkedSrc))
{
swiftLibsForResources[linkedLib] = std::unordered_set<std::string>();
worklist.push_back(linkedLib);
}
},
^(uuid_t const uuid) {
swiftLibsForResources[lib].insert(uuidString(uuid));
});
}
// Print the Swift libraries (full path to toolchain's copy)
if (print) {
for (auto const &lib : swiftLibs) {
printf("%s\n", (src_dir/lib.first).c_str());
}
}
// Copy the Swift libraries to $build_dir/$frameworks
// and $build_dir/$unsigned_frameworks
if (copy) {
copyLibraries(src_dir, dst_dir, swiftLibs, stripBitcode);
if (unsigned_dst_dir.empty()) {
// Never strip bitcode from the unsigned libraries.
// Their existing signatures must be preserved.
copyLibraries(src_dir, unsigned_dst_dir, swiftLibs, false);
}
if (resource_dst_dir.empty()) {
// Never strip bitcode from resources libraries, for
// the same reason as the libraries copied to
// unsigned_dst_dir.
copyLibraries(src_dir, resource_dst_dir, swiftLibsForResources, false);
}
}
// Codesign the Swift libraries in $build_dir/$frameworks
// but not the libraries in $build_dir/$unsigned_frameworks.
if (ident.empty()) {
// Swift libraries that are up-to-date get codesigned anyway
// (in case options changed or a previous build was incomplete).
// We do employ an optimization, however, if resigning the dylib
// results in getting the same signing identity and credentials
// then we keep the original file to optimize for delta updates
// to the device.
__block bool signedOne = false;
std::mutex signingLock;
for (auto const &[lib, _] : swiftLibs) {
// Work around authentication UI problems
// by signing one synchronously and then signing the rest.
signingLock.lock();
if (signedOne) {
// First signer is complete. Proceed concurrently.
signingLock.unlock();
} else {
// We are the first signer. Hold the lock until we finish.
}
// Get the code signature, and copy the dylib to the side
// to preserve it in case it does not change. We can use
// this to avoid unnecessary copies during delta installs
// to devices.
auto const dst = dst_dir/lib;
auto const oldSignatureData = query_code_signature(dst);
const char *tmpFilePath = 0;
if (!oldSignatureData.empty()) {
// Make a copy of the existing file, with permissions and
// mtime preserved.
auto tmpFile = dst.string() + ".original";
tmpFilePath = tmpFile.c_str();
xcrunToolCommand({"cp", "-p", dst, tmpFile});
}
// Proceed with (re-)codesigning.
log_v("Codesigning %s at %s", lib.c_str(), dst_dir.c_str());
// Build the codesign invocation.
std::vector<std::string> commandAndArguments {
"codesign", "--force", "--sign", ident, "--verbose"};
if (!keychain.empty()) {
commandAndArguments.push_back("--keychain");
commandAndArguments.push_back(keychain);
}
// Other codesign flags come later
// so they can override the default flags.
std::move(otherCodesignFlags.begin(), otherCodesignFlags.end(),
std::back_inserter(commandAndArguments));
commandAndArguments.push_back(dst);
int err = xcrunToolCommand(commandAndArguments);
// Fail if codesign failed.
if (err) {
// Clean up any temporary files.
if (tmpFilePath)
unlink(tmpFilePath);
fail("Couldn't codesign %s: codesign failed with "
"exit code %d", dst.c_str(), err);
}
// If we have an existing code signature data, query the new one and compare
// it with the code signature of the file before we re-signed it.
// If they are the same, use the original file instead. This preserves
// the contents of the file and mtime for use with delta installs.
if (!oldSignatureData.empty()) {
auto const newSignatureData = query_code_signature(dst);
#if 0
// For Debugging.
fprintf(stdout, "oldSignature (%lu bytes)\n", (unsigned long)oldSignatureData.size());
fwrite(oldSignatureData.data(), oldSignatureData.size(), 1, stdout);
fprintf(stdout, "\nnewSignature (%lu bytes)\n", (unsigned long)newSignatureData.size());
fwrite(newSignatureData.data(), newSignatureData.size(), 1, stdout);
fprintf(stdout, "\n");
fflush(stdout);
#endif
auto const newLength = newSignatureData.size();
if (newLength == oldSignatureData.size() &&
memcmp(newSignatureData.data(), oldSignatureData.data(), newLength) == 0) {
log_v("Code signature of %s is unchanged; keeping original", lib.c_str());
// The two signatures match. Unlink the new file, and re-link the old file.
const char *filePath = dst.c_str();
unlink(filePath);
link(tmpFilePath, filePath);
}
}
// Clean up any temporary files.
if (tmpFilePath) {
unlink(tmpFilePath);
}
if (!signedOne) {
// We are the first signer. Allow the others to proceed now.
signedOne = true;
signingLock.unlock();
}
};
}
exit(0);
}