Files
swift-mirror/tools/swift-stdlib-tool/swift-stdlib-tool.cpp
Rose 8d06ca7f4e Prefer std::copy_n over std::copy where appropriate.
std::copy_n saves us from having to do the addition manually.
2023-03-16 16:25:51 -04:00

1364 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 <copyfile.h>
#include <dirent.h>
#include <dispatch/dispatch.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <libkern/OSByteOrder.h>
#include <mach-o/dyld.h>
#include <mach-o/fat.h>
#include <mach-o/loader.h>
#include <os/overflow.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
#include <uuid/uuid.h>
#include <algorithm>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#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)(const std::string &path),
void (^uuidVisitor)(const uuid_t 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)(const std::string &path),
void (^uuidVisitor)(const uuid_t 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)(const std::string &path),
void (^uuidVisitor)(const uuid_t 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_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_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_offset, arch_size;
arch_offset = OSSwapBigToHostInt32(archs[i].offset);
arch_size = OSSwapBigToHostInt32(archs[i].size);
/* 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(const std::string &path, void (^dylibVisitor)(const std::string &),
void (^uuidVisitor)(const uuid_t)) {
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;
}
std::string parentPath(std::string path) {
const char *pathCstr = path.c_str();
char parent[MAXPATHLEN];
return dirname_r(pathCstr, parent) ? parent : pathCstr;
}
std::string filename(std::string path) {
const char *pathCstr = path.c_str();
char filename[MAXPATHLEN];
return basename_r(pathCstr, filename) ? filename : pathCstr;
}
bool directory_exists(const std::string &path) {
struct stat st;
return stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode);
}
// This executable's own path.
std::string self_executable = []() -> std::string {
char path[MAXPATHLEN] = {0};
uint32_t len = sizeof(path);
_NSGetExecutablePath(path, &len);
return std::string(path);
}();
// This executable's own xctoolchain path.
std::string self_toolchain = []() -> std::string {
auto result = self_executable;
// Remove the executable name.
result = parentPath(result);
// Remove trailing /usr/bin, if any
if (filename(result) == "bin") {
result = parentPath(result);
}
if (filename(result) == "usr") {
result = parentPath(result);
}
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_n(readBuffer, readSize, std::back_inserter(retData));
}
return retData;
}
// Runs a tool with `xcrun`.
// Returns the tool's termination status.
// 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 (const auto &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);
close(outPipe[0]);
close(errPipe[0]);
execv(launchPath, const_cast<char **>(arguments.data()));
}
close(outPipe[1]);
close(errPipe[1]);
// 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);
});
const auto stdOutData = readToEOF(outPipe[0]);
close(outPipe[0]);
dispatch_semaphore_wait(gotStdErr, DISPATCH_TIME_FOREVER);
dispatch_release(gotStdErr);
close(errPipe[0]);
int status = 0;
waitpid(childPid, &status, 0);
status = WIFSIGNALED(status) ? WTERMSIG(status)
: (WIFEXITED(status) ? WEXITSTATUS(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::string src, std::string 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::string src, std::string dst, bool stripBitcode) {
if (stripBitcode) {
copyAndStripBitcode(src, dst);
} else {
if (copyfile(src.c_str(), dst.c_str(), NULL, COPYFILE_ALL) != 0) {
fail("Couldn't copy %s to %s: %s", src.c_str(), dst.c_str(),
strerror(errno));
}
}
}
std::string uuidString(const uuid_t uuid) {
char buffer[37];
uuid_unparse(uuid, buffer);
return buffer;
}
void copyLibraries(
std::string dst_dir,
const std::unordered_map<std::string, std::unordered_set<std::string>>
&libs,
bool stripBitcode) {
mkpath_np(dst_dir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
for (const auto &pair : libs) {
const auto &src = pair.first;
const auto &srcUUIDs = pair.second;
std::string dst = dst_dir + "/" + filename(src);
// 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, ^(const uuid_t uuid) {
dstUUIDs.insert(uuidString(uuid));
});
std::string srcUUIDsString;
srcUUIDsString.reserve(37 * srcUUIDs.size());
for (const auto &uuidString : srcUUIDs) {
srcUUIDsString.append(uuidString + std::string(" "));
}
std::string dstUUIDsString;
dstUUIDsString.reserve(37 * dstUUIDs.size());
for (const auto &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", src.c_str(), dst.c_str());
continue;
}
// Perform the copy.
log_v("Copying %s to %s", src.c_str(), dst_dir.c_str());
unlink(dst.c_str());
copyFile(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;
}
template <typename F>
void listDirectoryContents(std::string directory, F &&func) {
DIR *dir = opendir(directory.c_str());
if (dir == NULL) {
return;
}
struct dirent *entry;
while ((entry = readdir(dir))) {
func(directory + "/" + entry->d_name);
}
closedir(dir);
}
template <typename F>
void recursivelyListFiles(std::string directory, F &&func) {
DIR *dir = opendir(directory.c_str());
if (dir == NULL) {
return;
}
std::vector<std::string> subpaths;
struct dirent *entry;
while ((entry = readdir(dir)) != NULL) {
std::string path = directory + "/" + entry->d_name;
if (entry->d_type == DT_REG) {
func(path);
} else if (entry->d_type == DT_DIR) {
// check if . or ..
if (strncmp(entry->d_name, "..", entry->d_namlen) == 0) {
continue;
}
subpaths.push_back(path);
}
}
closedir(dir);
for (const auto &path : subpaths) {
recursivelyListFiles(path, func);
}
}
int main(int argc, const char *argv[]) {
// Executables to scan for Swift references.
// --scan-executable
std::vector<std::string> 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::vector<std::string> src_dirs;
// Copy destinations, signed and unsigned.
// --destination and --unsigned-destination
std::string dst_dir;
std::string 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;
} else if (0 == strcmp(argv[i], "--copy")) {
copy = true;
} else if (0 == strcmp(argv[i], "--verbose")) {
Verbose++;
} else if (0 == strcmp(argv[i], "--help")) {
printUsage();
exit(0);
} else if (0 == strcmp(argv[i], "--scan-executable")) {
executables.emplace_back(argv[++i]);
} else if (0 == strcmp(argv[i], "--scan-folder")) {
embedDirs.emplace_back(argv[++i]);
} else if (0 == strcmp(argv[i], "--source-libraries")) {
src_dirs.emplace_back(argv[++i]);
} else if (0 == strcmp(argv[i], "--platform")) {
platform = std::string(argv[++i]);
} else if (0 == strcmp(argv[i], "--destination")) {
dst_dir = std::string(argv[++i]);
} else if (0 == strcmp(argv[i], "--unsigned-destination")) {
unsigned_dst_dir = std::string(argv[++i]);
} else if (0 == strcmp(argv[i], "--sign")) {
ident = std::string(argv[++i]);
} else if (0 == strcmp(argv[i], "--keychain")) {
keychain = std::string(argv[++i]);
} else if (0 == strcmp(argv[i], "--Xcodesign")) {
otherCodesignFlags.push_back(std::string(argv[++i]));
} else if (0 == strcmp(argv[i], "--strip-bitcode")) {
stripBitcode = true;
} else if (0 == strcmp(argv[i], "--resource-destination")) {
resource_dst_dir = std::string(argv[++i]);
} else if (0 == strcmp(argv[i], "--resource-library")) {
resourceLibraries.push_back(std::string(argv[++i]));
} else {
fail("Unknown argument: %s", argv[i]);
}
}
// Fix up src_dirs and platform values.
if (src_dirs.empty() && platform.empty()) {
// Neither src_dirs nor platform is set. Die.
fail_usage("At least one of --source-libraries and --platform "
"must be set.");
} else if (src_dirs.empty()) {
// platform is set but src_dirs is not.
// Use platform to set src_dirs relative to us.
std::string root_path =
parentPath(parentPath(self_executable)) + "/" + "lib";
listDirectoryContents(root_path, [&](std::string entry) {
if (filename(entry).compare(0, strlen("swift-"), "swift-") == 0) {
src_dirs.push_back(entry + "/" + platform);
}
});
if (src_dirs.empty()) {
fail("Couldn't discover Swift library directories in: %s",
root_path.c_str());
}
} else if (platform.empty()) {
// src_dirs is set but platform is not.
// Pick platform from any src_dirs's name.
platform = filename(src_dirs.front());
}
for (const auto &src_dir : src_dirs) {
if (!directory_exists(src_dir))
fail("Source directory does not exist: %s", src_dir.c_str());
}
// Add the platform to unsigned_dst_dir if it is not already present.
if (!unsigned_dst_dir.empty()) {
const auto unsigned_platform = unsigned_dst_dir;
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 (const auto &embedDir : embedDirs) {
recursivelyListFiles(embedDir, [&](std::string entry) {
if (0 == access(entry.c_str(), X_OK)) {
executables.push_back(entry);
} else {
log_vv("%s is not an executable file", entry.c_str());
}
});
}
// Collect Swift library names from the input files.
// If the library does not exist in src_dirs 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 (const auto &path : executables) {
process(
path,
^(const std::string &linkedLib) {
for (const auto &src_dir : src_dirs) {
const auto linkedSrc = src_dir + "/" + linkedLib;
if (access(linkedSrc.c_str(), F_OK) == 0) {
swiftLibs[linkedSrc] = std::unordered_set<std::string>();
break;
}
}
},
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 (const auto &pair : swiftLibs) {
worklist.push_back(pair.first);
}
while (worklist.size()) {
const auto path = worklist.back();
worklist.pop_back();
process(
path,
^(const std::string &linkedLib) {
for (const auto &src_dir : src_dirs) {
const auto linkedSrc = src_dir + "/" + linkedLib;
if (swiftLibs.count(linkedSrc) == 0 &&
access(linkedSrc.c_str(), F_OK) == 0) {
swiftLibs[linkedSrc] = std::unordered_set<std::string>();
worklist.push_back(linkedSrc);
break;
}
}
},
^(const uuid_t uuid) {
swiftLibs[path].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 (const auto &lib : resourceLibraries) {
for (const auto &src_dir : src_dirs) {
const auto libSrc = src_dir + "/" + lib;
if (access(libSrc.c_str(), F_OK) == 0) {
swiftLibsForResources[libSrc] = std::unordered_set<std::string>();
}
}
}
// Collect dependencies of --resource-library libs.
worklist.clear();
for (const auto &pair : swiftLibsForResources) {
worklist.push_back(pair.first);
}
while (worklist.size()) {
const auto path = worklist.back();
worklist.pop_back();
process(
path,
^(const std::string &linkedLib) {
for (const auto &src_dir : src_dirs) {
const auto linkedSrc = src_dir + "/" + linkedLib;
if (swiftLibsForResources.count(linkedSrc) == 0 &&
access(linkedSrc.c_str(), F_OK) == 0) {
swiftLibsForResources[linkedSrc] =
std::unordered_set<std::string>();
worklist.push_back(linkedSrc);
}
}
},
^(const uuid_t uuid) {
swiftLibsForResources[path].insert(uuidString(uuid));
});
}
// Print the Swift libraries (full path to toolchain's copy)
if (print) {
for (const auto &lib : swiftLibs) {
printf("%s\n", lib.first.c_str());
}
}
// Copy the Swift libraries to $build_dir/$frameworks
// and $build_dir/$unsigned_frameworks
if (copy) {
copyLibraries(dst_dir, swiftLibs, stripBitcode);
if (!unsigned_dst_dir.empty()) {
// Never strip bitcode from the unsigned libraries.
// Their existing signatures must be preserved.
copyLibraries(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(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 (const auto &pair : swiftLibs) {
const auto &lib = pair.first;
// 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.
const auto dst = dst_dir + "/" + filename(lib);
const auto 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 + ".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::copy(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()) {
const auto 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
const auto 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);
}