158 lines
4.8 KiB
C
158 lines
4.8 KiB
C
#ifdef __unix__
|
|
#define _GNU_SOURCE
|
|
#endif
|
|
|
|
#include "io.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#ifdef __unix__
|
|
// This program can be trivially converted to work with only the C standard library
|
|
// at the cost of not being able to link the output file atomically.
|
|
#include <fcntl.h>
|
|
#include <libgen.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
static const char* outfile_name;
|
|
FILE* infile;
|
|
FILE* outfile;
|
|
|
|
#ifdef __unix__
|
|
void open_files(const char* infile_name, const char* outfile_name_) {
|
|
outfile_name = outfile_name_;
|
|
// To avoid creating a corrupt or incomplete output file,
|
|
// we operate on a temporary file and atomically link it only once compilation has succeeded.
|
|
unlink(outfile_name);
|
|
|
|
int infile_fd = open(infile_name, O_RDONLY);
|
|
if (infile_fd == -1) {
|
|
fprintf(stderr, "failed to open source file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
off_t infile_len = lseek(infile_fd, 0, SEEK_END);
|
|
if (infile_len == (off_t) -1) {
|
|
fprintf(stderr, "failed to get length of source file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
// There'll probably never be a source file large enough for this to make a difference,
|
|
// and I *certainly* haven't profiled, but... I've always wanted to use these syscalls. :)
|
|
posix_fadvise(infile_fd, 0, infile_len, POSIX_FADV_SEQUENTIAL);
|
|
posix_fadvise(infile_fd, 0, infile_len, POSIX_FADV_NOREUSE);
|
|
infile = fdopen(infile_fd, "rb");
|
|
if (infile_fd == -1) {
|
|
fprintf(stderr, "failed to open source file fd as file handle: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
int outfile_fd = open(dirname((char*) outfile_name), O_WRONLY | O_TMPFILE, S_IRWXU | S_IRWXG | S_IRWXO);
|
|
if (outfile_fd == -1) {
|
|
fprintf(stderr, "failed to create temporary output file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
outfile = fdopen(outfile_fd, "wb");
|
|
if (outfile == NULL) {
|
|
fprintf(stderr, "failed to open output file fd as file handle: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void close_files(void) {
|
|
if (fflush(outfile) != 0) {
|
|
fprintf(stderr, "failed to flush output file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
char outfile_tempname[20];
|
|
snprintf(outfile_tempname, 20, "/proc/self/fd/%d", fileno(outfile));
|
|
if (linkat(AT_FDCWD, outfile_tempname, AT_FDCWD, outfile_name, AT_SYMLINK_FOLLOW) == -1) {
|
|
fprintf(stderr, "failed to link output file into file system: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
fclose(outfile);
|
|
fclose(infile);
|
|
}
|
|
#else
|
|
void open_files(const char* infile_name, const char* outfile_name) {
|
|
infile = fopen(infile_name, "rb");
|
|
if (infile == NULL) {
|
|
fprintf(stderr, "failed to open source file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
// There is no way for us to mark the file as executable.
|
|
// Then again, if it's not Unix, that probably doesn't matter.
|
|
outfile = fopen(outfile_name, "wb");
|
|
if (outfile == NULL) {
|
|
fprintf(stderr, "failed to open output file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void close_files(void) {
|
|
if (fclose(outfile) != 0) {
|
|
fprintf(stderr, "failed to close output file: %s\n", strerror(errno));
|
|
// NOTE: ideally we'd do this on any dirty exit
|
|
// TODO: use portable tempfiles and then just copy the entire file at the end?
|
|
if (remove(outfile_name) != 0) {
|
|
fprintf(stderr, "failed to remove output file, if it exists, it is corrupt: %s\n", strerror(errno));
|
|
}
|
|
exit(1);
|
|
}
|
|
fclose(infile);
|
|
}
|
|
#endif
|
|
|
|
void emit(const void* restrict ptr, size_t count) {
|
|
fwrite(ptr, 1, count, outfile);
|
|
if (ferror(outfile)) {
|
|
fprintf(stderr, "failed to write to output file\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void emit_u8(uint8_t x) {
|
|
emit(&x, sizeof(uint8_t));
|
|
}
|
|
|
|
void emit_u32(uint32_t x) {
|
|
emit(&x, sizeof(uint32_t));
|
|
}
|
|
|
|
void emit_u64(uint64_t x) {
|
|
emit(&x, sizeof(uint64_t));
|
|
}
|
|
|
|
void patch(size_t off, const void* ptr, size_t count) {
|
|
fpos_t save;
|
|
if (fgetpos(outfile, &save) != 0) {
|
|
fprintf(stderr, "failed to save file position before patch: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (fseek(outfile, (long) off, SEEK_SET) != 0) {
|
|
fprintf(stderr, "failed to set file position for patch: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
fwrite(ptr, 1, count, outfile);
|
|
if (ferror(outfile) != 0) {
|
|
fprintf(stderr, "failed to patch output file: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (fsetpos(outfile, &save) != 0) {
|
|
fprintf(stderr, "failed to restore file position after patch: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void patch_u32(size_t off, uint32_t x) {
|
|
patch(off, &x, sizeof(uint32_t));
|
|
}
|
|
|
|
void patch_i32(size_t off, int32_t x) {
|
|
patch_u32(off, (uint32_t) x);
|
|
}
|