#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <limits.h>
#include <fcntl.h>
#include <ctype.h>
#include <sys/stat.h>
#include <libgen.h>
#include <time.h>
#include <Block.h>
#include "Snapshot.h"
#include "Options.h"
#include "compile_stubs.h"
static const char *frameworksString = "frameworks"; static const char *dylibsString = "dylibs"; static const char *archiveFilesString = "archive_files"; static const char *origCommandLineString = "orig_command_line"; static const char *linkCommandString = "link_command"; static const char *dataFilesString = "data_files"; static const char *objectsString = "objects"; static const char *frameworkStubsString = "framework_stubs"; static const char *dylibStubsString = "dylib_stubs"; static const char *assertFileString = "assert_info"; static const char *compileFileString = "compile_stubs";
Snapshot *Snapshot::globalSnapshot = NULL;
Snapshot::Snapshot() : fRecordArgs(false), fRecordObjects(false), fRecordDylibSymbols(false), fRecordArchiveFiles(false), fRecordUmbrellaFiles(false), fRecordDataFiles(false), fFrameworkArgAdded(false), fSnapshotLocation(NULL), fSnapshotName(NULL), fRootDir(NULL), fFilelistFile(-1), fCopiedArchives(NULL)
{
if (globalSnapshot != NULL)
throw "only one snapshot supported";
globalSnapshot = this;
}
Snapshot::~Snapshot()
{
}
void Snapshot::setSnapshotPath(const char *path)
{
if (fRootDir == NULL) {
fSnapshotLocation = strdup(path);
}
}
void Snapshot::setSnapshotMode(SnapshotMode mode)
{
if (fRootDir == NULL) {
fRecordArgs = false;
fRecordObjects = false;
fRecordDylibSymbols = false;
fRecordArchiveFiles = false;
fRecordUmbrellaFiles = false;
fRecordDataFiles = false;
switch (mode) {
case SNAPSHOT_DISABLED:
break;
case SNAPSHOT_DEBUG:
fRecordArgs = fRecordObjects = fRecordDylibSymbols = fRecordArchiveFiles = fRecordUmbrellaFiles = fRecordDataFiles = true;
break;
default:
break;
}
}
}
void Snapshot::setSnapshotName(const char *path)
{
if (fRootDir == NULL) {
const char *base = basename((char *)path);
time_t now = time(NULL);
struct tm t;
localtime_r(&now, &t);
char buf[PATH_MAX];
snprintf(buf, sizeof(buf)-1, "%s-%4.4d-%2.2d-%2.2d-%2.2d%2.2d%2.2d.ld-snapshot", base, t.tm_year+1900, t.tm_mon, t.tm_mday, t.tm_hour, t.tm_min, t.tm_sec);
fSnapshotName = strdup(buf);
}
}
void Snapshot::buildPath(char *buf, const char *subdir, const char *file)
{
if (fRootDir == NULL)
throw "snapshot not created";
strcpy(buf, fRootDir);
strcat(buf, "/");
if (subdir) {
strcat(buf, subdir);
mkdir(buf, S_IRUSR|S_IWUSR|S_IXUSR);
strcat(buf, "/");
}
if (file != NULL)
strcat(buf, basename((char *)file));
}
void Snapshot::buildUniquePath(char *buf, const char *subdir, const char *file)
{
buildPath(buf, subdir, file);
struct stat st;
if (stat(buf, &st)==0) {
int counter=1;
char *number = strrchr(buf, 0);
number[0]='-';
number++;
do {
sprintf(number, "%d", counter++);
} while (stat(buf, &st) == 0);
}
}
void Snapshot::copyFileToSnapshot(const char *sourcePath, const char *subdir, char *path)
{
const int copyBufSize=(1<<14); static void *copyBuf = NULL;
if (copyBuf == NULL)
copyBuf = malloc(copyBufSize);
char *file=basename((char *)sourcePath);
char buf[PATH_MAX];
if (path == NULL) path = buf;
buildUniquePath(path, subdir, file);
int out_fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
int in_fd = open(sourcePath, O_RDONLY);
int len;
if (out_fd != -1 && in_fd != -1) {
do {
len = read(in_fd, copyBuf, copyBufSize);
if (len > 0) write(out_fd, copyBuf, len);
} while (len == copyBufSize);
}
close(in_fd);
close(out_fd);
}
void Snapshot::createSnapshot()
{
if (fRootDir == NULL) {
if (fSnapshotLocation == NULL)
fSnapshotLocation = "/tmp";
if (fSnapshotName == NULL) {
setSnapshotName("ld_snapshot");
}
char buf[PATH_MAX];
fRootDir = (char *)fSnapshotLocation;
buildUniquePath(buf, NULL, fSnapshotName);
fRootDir = strdup(buf);
if (mkdir(fRootDir, S_IRUSR|S_IWUSR|S_IXUSR)!=0) {
warning("unable to create link snapshot directory: %s", fRootDir);
fRootDir = NULL;
setSnapshotMode(SNAPSHOT_DISABLED); }
buildPath(buf, NULL, compileFileString);
int compileScript = open(buf, O_WRONLY|O_CREAT|O_TRUNC, S_IXUSR|S_IRUSR|S_IWUSR);
write(compileScript, compile_stubs, strlen(compile_stubs));
close(compileScript);
SnapshotLog::iterator it;
for (it = fLog.begin(); it != fLog.end(); it++) {
void (^logItem)(void) = *it;
logItem();
Block_release(logItem);
}
fLog.erase(fLog.begin(), fLog.end());
if (fRecordArgs) {
writeCommandLine(fRawArgs, origCommandLineString, true);
writeCommandLine(fArgs);
}
#if STORE_PID_IN_SNAPSHOT
char path[PATH_MAX];
buildUniquePath(path, NULL, pidString);
int pidfile = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
char pid_buf[32];
sprintf(pid_buf, "%lu\n", (long unsigned)getpid());
write(pidfile, pid_buf, strlen(pid_buf));
write(pidfile, "\n", 1);
close(pidfile);
#endif
}
}
void Snapshot::writeCommandLine(StringVector &args, const char *filename, bool includeCWD)
{
if (!isLazy() && fRecordArgs) {
if (filename == NULL)
filename = linkCommandString;
char path[PATH_MAX];
buildPath(path, NULL, filename);
int argsFile = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IXUSR|S_IRUSR|S_IWUSR);
FILE *argsStream = fdopen(argsFile, "w");
if (includeCWD)
fprintf(argsStream, "cd %s\n", getcwd(path, sizeof(path)));
StringVector::iterator it;
for (it = args.begin(); it != args.end(); it++) {
const char *arg = *it;
bool needQuotes = false;
for (const char *c = arg; *c != 0 && !needQuotes; c++) {
if (isspace(*c))
needQuotes = true;
}
if (it != args.begin()) fprintf(argsStream, " ");
if (needQuotes) fprintf(argsStream, "\"");
fprintf(argsStream, "%s", arg);
if (needQuotes) fprintf(argsStream, "\"");
}
fprintf(argsStream, "\n");
fclose(argsStream);
}
}
void Snapshot::recordRawArgs(int argc, const char *argv[])
{
for (int i=0; i<argc; i++) {
fRawArgs.push_back(argv[i]);
}
fArgs.insert(fArgs.begin(), argv[0]);
fArgs.insert(fArgs.begin()+1, "-Z"); }
void Snapshot::addSnapshotLinkArg(int argIndex, int argCount, int fileArg)
{
if (fRootDir == NULL) {
fLog.push_back(Block_copy(^{ this->addSnapshotLinkArg(argIndex, argCount, fileArg); }));
} else {
char buf[PATH_MAX];
const char *subdir = dataFilesString;
for (int i=0, arg=argIndex; i<argCount && argIndex+1<(int)fRawArgs.size(); i++, arg++) {
if (i != fileArg) {
fArgs.push_back(fRawArgs[arg]);
} else {
if (fRecordDataFiles) {
copyFileToSnapshot(fRawArgs[arg], subdir, buf);
fArgs.push_back(strdup(snapshotRelativePath(buf)));
} else {
fArgs.push_back(strdup(fRawArgs[arg]));
}
}
}
}
}
void Snapshot::recordArch(const char *arch)
{
if (fRawArgs.size() == 0)
throw "raw args not set";
bool archInArgs = false;
StringVector::iterator it;
for (it = fRawArgs.begin(); it != fRawArgs.end() && !archInArgs; it++) {
const char *arg = *it;
if (strcmp(arg, "-arch") == 0)
archInArgs = true;
}
if (!archInArgs) {
if (fRootDir == NULL) {
fLog.push_back(Block_copy(^{ this->recordArch(arch); }));
} else {
char path_buf[PATH_MAX];
buildUniquePath(path_buf, NULL, "arch");
int fd=open(path_buf, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
write(fd, arch, strlen(arch));
close(fd);
}
}
}
void Snapshot::recordObjectFile(const char *path)
{
if (fRootDir == NULL) {
fLog.push_back(Block_copy(^{ this->recordObjectFile(path); }));
} else {
if (fRecordObjects) {
char path_buf[PATH_MAX];
copyFileToSnapshot(path, objectsString, path_buf);
if (fFilelistFile == -1) {
char filelist_path[PATH_MAX];
buildUniquePath(filelist_path, objectsString, "filelist");
fFilelistFile = open(filelist_path, O_WRONLY|O_CREAT|O_TRUNC, S_IRUSR|S_IWUSR);
fArgs.push_back("-filelist");
fArgs.push_back(strdup(snapshotRelativePath(filelist_path)));
writeCommandLine(fArgs);
}
const char *relative_path = snapshotRelativePath(path_buf);
write(fFilelistFile, relative_path, strlen(relative_path));
write(fFilelistFile, "\n", 1);
}
}
}
void Snapshot::addFrameworkArg(const char *framework)
{
bool found=false;
for (unsigned i=0; i<fArgs.size()-1; i++) {
if (strcmp(fArgs[i], "-framework") == 0 && strcmp(fArgs[i+1], framework) == 0)
found = true;
}
if (!found) {
if (!fFrameworkArgAdded) {
fFrameworkArgAdded = true;
fArgs.push_back("-Fframeworks");
}
fArgs.push_back("-framework");
fArgs.push_back(strdup(framework));
writeCommandLine(fArgs);
}
}
void Snapshot::addDylibArg(const char *dylib)
{
bool found=false;
for (unsigned i=0; i<fArgs.size()-1; i++) {
if (strcmp(fArgs[i], dylib) == 0)
found = true;
}
if (!found) {
char buf[ARG_MAX];
sprintf(buf, "%s/%s", dylibsString, dylib);
fArgs.push_back(strdup(buf));
writeCommandLine(fArgs);
}
}
void Snapshot::recordDylibSymbol(ld::dylib::File* dylibFile, const char *name)
{
if (fRootDir == NULL) {
fLog.push_back(Block_copy(^{ this->recordDylibSymbol(dylibFile, name); }));
} else {
if (fRecordDylibSymbols) {
DylibMap::iterator it;
const char *dylibPath = dylibFile->path();
it = fDylibSymbols.find(dylibPath);
bool isFramework = (strstr(dylibPath, "framework") != NULL);
int dylibFd;
if (it == fDylibSymbols.end()) {
char path_buf[PATH_MAX];
buildUniquePath(path_buf, isFramework ? frameworkStubsString : dylibStubsString, dylibPath);
dylibFd = open(path_buf, O_WRONLY|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
fDylibSymbols.insert(std::pair<const char *, int>(dylibPath, dylibFd));
char *base_name = strdup(basename(path_buf));
if (isFramework) {
addFrameworkArg(base_name);
} else {
addDylibArg(base_name);
}
writeCommandLine(fArgs);
} else {
dylibFd = it->second;
}
bool isIdentifier = (name[0] == '_');
for (const char *c = name; *c != 0 && isIdentifier; c++)
if (!isalnum(*c) && *c!='_')
isIdentifier = false;
const char *prefix = "void ";
const char *weakAttr = "__attribute__ ((weak)) ";
const char *suffix = "(void){}\n";
if (isIdentifier) {
write(dylibFd, prefix, strlen(prefix));
if (dylibFile->hasWeakExternals() && dylibFile->hasWeakDefinition(name))
write(dylibFd, weakAttr, strlen(weakAttr));
if (*name == '_') name++;
write(dylibFd, name, strlen(name));
write(dylibFd, suffix, strlen(suffix));
} else {
static int symbolCounter = 0;
char buf[64+strlen(name)];
sprintf(buf, "void s_%5.5d(void) __asm(\"%s\");\nvoid s_%5.5d(){}\n", symbolCounter, name, symbolCounter);
write(dylibFd, buf, strlen(buf));
symbolCounter++;
}
}
}
}
void Snapshot::recordArchive(const char *archiveFile)
{
if (fRootDir == NULL) {
const char *copy = strdup(archiveFile);
fLog.push_back(Block_copy(^{ this->recordArchive(archiveFile); ::free((void *)copy); }));
} else {
if (fRecordArchiveFiles) {
if (fCopiedArchives == NULL) {
fCopiedArchives = new StringVector;
}
StringVector::iterator it;
bool found = false;
for (it = fCopiedArchives->begin(); it != fCopiedArchives->end() && !found; it++) {
if (strcmp(archiveFile, *it) == 0)
found = true;
}
if (!found) {
char path[PATH_MAX];
fCopiedArchives->push_back(archiveFile);
copyFileToSnapshot(archiveFile, archiveFilesString, path);
fArgs.push_back(strdup(snapshotRelativePath(path)));
writeCommandLine(fArgs);
}
}
}
}
void Snapshot::recordSubUmbrella(const char *frameworkPath)
{
if (fRootDir == NULL) {
const char *copy = strdup(frameworkPath);
fLog.push_back(Block_copy(^{ this->recordSubUmbrella(copy); ::free((void *)copy); }));
} else {
if (fRecordUmbrellaFiles) {
const char *framework = basename((char *)frameworkPath);
char buf[PATH_MAX], wrapper[PATH_MAX];
strcpy(wrapper, frameworksString);
buildPath(buf, wrapper, NULL); strcat(wrapper, "/");
strcat(wrapper, framework);
strcat(wrapper, ".framework");
copyFileToSnapshot(frameworkPath, wrapper);
addFrameworkArg(framework);
}
}
}
void Snapshot::recordSubLibrary(const char *dylibPath)
{
if (fRootDir == NULL) {
const char *copy = strdup(dylibPath);
fLog.push_back(Block_copy(^{ this->recordSubLibrary(copy); ::free((void *)copy); }));
} else {
if (fRecordUmbrellaFiles) {
copyFileToSnapshot(dylibPath, dylibsString);
addDylibArg(basename((char *)dylibPath));
}
}
}
void Snapshot::recordAssertionMessage(const char *fmt, ...)
{
char *msg;
va_list args;
va_start(args, fmt);
vasprintf(&msg, fmt, args);
va_end(args);
if (msg != NULL) {
if (fRootDir == NULL) {
fLog.push_back(Block_copy(^{ this->recordAssertionMessage("%s", msg); free(msg); }));
} else {
char path[PATH_MAX];
buildPath(path, NULL, assertFileString);
int log = open(path, O_WRONLY|O_APPEND|O_CREAT, S_IRUSR|S_IWUSR);
write(log, msg, strlen(msg));
close(log);
free(msg);
}
}
}