2025-03-14 16:15:51 +08:00

1159 lines
30 KiB
C

// Copyright 2022, Kay Hayen, mailto:kay.hayen@gmail.com
//
// Part of "Nuitka", an optimizing Python compiler that is compatible and
// integrates with CPython, but also works on its own.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
/* The main program for onefile bootstrap.
*
* It needs to unpack the attached files and and then loads and executes
* the compiled program as a separate process.
*
* spell-checker: ignore _wrename,SHFILEOPSTRUCTW,FOF_NOCONFIRMATION,FOF_NOERRORUI
* spell-checker: ignore HRESULT,HINSTANCE,lpUnkcaller,MAKELANGID,SUBLANG
*
*/
#define _CRT_SECURE_NO_WARNINGS
#if !defined(_WIN32)
#define _POSIX_C_SOURCE 200809L
#endif
#ifdef __NUITKA_NO_ASSERT__
#undef NDEBUG
#define NDEBUG
#endif
#if defined(_WIN32)
// Note: Keep this separate line, must be included before other Windows headers.
#include <windows.h>
#endif
#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <wchar.h>
/* Type bool */
#ifndef __cplusplus
#include "stdbool.h"
#endif
#if defined(_WIN32)
#include <imagehlp.h>
#else
#include <dirent.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#endif
#ifndef __IDE_ONLY__
// Generated during build with optional defines.
#include "onefile_definitions.h"
#else
#define _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_CACHING
#define _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
#define _NUITKA_ONEFILE_TEMP_BOOL 0
#define _NUITKA_ONEFILE_CHILD_GRACE_TIME_INT 5000
#define _NUITKA_ONEFILE_TEMP_SPEC "%TEMP%/onefile_%PID%_%TIME%"
#define _NUITKA_EXPERIMENTAL_DEBUG_AUTO_UPDATE
#define _NUITKA_AUTO_UPDATE_BOOL 1
#define _NUITKA_AUTO_UPDATE_URL_SPEC "https://..."
#if __APPLE__
#define _NUITKA_PAYLOAD_FROM_MACOS_SECTION
#endif
#endif
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
// Header of zstd goes first
#define ZSTDERRORLIB_VISIBILITY
#define ZSTDLIB_VISIBILITY
#include "zstd.h"
// Should be in our inline copy, we include all C files into this one.
#include "common/error_private.c"
#include "common/fse_decompress.c"
#include "common/xxhash.c"
#include "common/zstd_common.c"
// Need to make sure this is last in common as it depends on the others.
#include "common/entropy_common.c"
// Decompression stuff.
#include "decompress/huf_decompress.c"
#include "decompress/zstd_ddict.c"
#include "decompress/zstd_decompress.c"
#include "decompress/zstd_decompress_block.c"
#endif
// Some handy macro definitions, e.g. unlikely.
#include "nuitka/hedley.h"
#define likely(x) HEDLEY_LIKELY(x)
#define unlikely(x) HEDLEY_UNLIKELY(x)
#include "HelpersChecksumTools.c"
#include "HelpersFilesystemPaths.c"
#include "HelpersSafeStrings.c"
// For tracing outputs if enabled at compile time.
#include "nuitka/tracing.h"
#ifdef _WIN32
typedef DWORD error_code_t;
static inline error_code_t getCurrentErrorCode(void) { return GetLastError(); }
#else
typedef int error_code_t;
static inline error_code_t getCurrentErrorCode(void) { return errno; }
#endif
static void printError(char const *message, error_code_t error_code) {
#if defined(_WIN32)
LPCTSTR err_buffer;
FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&err_buffer, 0, NULL);
printf("%s ([Error %d] %s)\n", message, error_code, err_buffer);
#else
printf("%s: %s\n", message, strerror(error_code));
perror(message);
#endif
}
static void fatalError(char const *message) {
puts(message);
exit(2);
}
static void fatalIOError(char const *message, error_code_t error_code) {
printError(message, error_code);
exit(2);
}
// Failure to expand the template for where to extract to.
static void fatalErrorTempFiles(void) { fatalError("Error, couldn't runtime expand target path."); }
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
static void fatalErrorAttachedData(void) { fatalError("Error, couldn't decode attached data."); }
#endif
static void fatalErrorFindAttachedData(char const *erroring_function, error_code_t error_code) {
char buffer[1024] = "Error, couldn't find attached data:";
appendStringSafe(buffer, erroring_function, sizeof(buffer));
fatalIOError(buffer, error_code);
}
static void fatalErrorHeaderAttachedData(void) { fatalError("Error, could find attached data header."); }
// Left over data in attached payload should not happen.
static void fatalErrorReadAttachedData(void) { fatalError("Error, couldn't read attached data."); }
// Out of memory error.
static void fatalErrorMemory(void) { fatalError("Error, couldn't allocate memory."); }
// Could not launch child process.
static void fatalErrorChild(char const *message, error_code_t error_code) { fatalIOError(message, error_code); }
#if defined(_WIN32)
static void appendWCharSafeW(wchar_t *target, wchar_t c, size_t buffer_size) {
while (*target != 0) {
target++;
buffer_size -= 1;
}
if (buffer_size < 1) {
abort();
}
*target++ = c;
*target = 0;
}
#endif
static void fatalErrorTempFileCreate(filename_char_t const *filename) {
fprintf(stderr, "Error, failed to open '" FILENAME_FORMAT_STR "' for writing.\n", filename);
exit(2);
}
static void fatalErrorSpec(filename_char_t const *spec) {
fprintf(stderr, "Error, couldn't runtime expand spec '" FILENAME_FORMAT_STR "'.\n", spec);
abort();
}
static FILE_HANDLE createFileForWritingChecked(filename_char_t const *filename) {
FILE_HANDLE result = createFileForWriting(filename);
if (result == FILE_HANDLE_NULL) {
fatalErrorTempFileCreate(filename);
}
return result;
}
static int getMyPid(void) {
#if defined(_WIN32)
return GetCurrentProcessId();
#else
return getpid();
#endif
}
static void setEnvironVar(char const *var_name, char const *value) {
#if defined(_WIN32)
SetEnvironmentVariable("NUITKA_ONEFILE_PARENT", value);
#else
setenv(var_name, value, 1);
#endif
}
static unsigned char const *payload_data = NULL;
static unsigned char const *payload_current = NULL;
static size_t stream_end_pos;
#ifdef _NUITKA_PAYLOAD_FROM_MACOS_SECTION
#include <mach-o/getsect.h>
#include <mach-o/ldsyms.h>
static unsigned char *findMacOSBinarySection(void) {
const struct mach_header *header = &_mh_execute_header;
unsigned long section_size;
unsigned char *result = getsectiondata(header, "payload", "payload", &section_size);
stream_end_pos = (size_t)section_size;
return result;
}
static void initPayloadData(void) {
payload_data = findMacOSBinarySection();
payload_current = payload_data;
}
static void closePayloadData(void) {}
#elif defined(_WIN32)
static void initPayloadData(void) {
payload_data =
(const unsigned char *)LockResource(LoadResource(NULL, FindResource(NULL, MAKEINTRESOURCE(27), RT_RCDATA)));
payload_current = payload_data;
}
// Note: it appears unlocking the resource is not actually foreseen.
static void closePayloadData(void) {}
#else
static struct MapFileToMemoryInfo exe_file_mapped;
static void initPayloadData(void) {
exe_file_mapped = mapFileToMemory(getBinaryPath());
if (exe_file_mapped.error) {
fatalErrorFindAttachedData(exe_file_mapped.erroring_function, exe_file_mapped.error_code);
}
payload_data = exe_file_mapped.data;
payload_current = payload_data;
}
static void closePayloadData(void) { unmapFileFromMemory(&exe_file_mapped); }
#endif
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
static ZSTD_DCtx *dest_ctx = NULL;
static ZSTD_inBuffer input = {NULL, 0, 0};
static ZSTD_outBuffer output = {NULL, 0, 0};
static void initZSTD(void) {
input.src = NULL;
size_t const output_buffer_size = ZSTD_DStreamOutSize();
output.dst = malloc(output_buffer_size);
if (output.dst == NULL) {
fatalErrorMemory();
}
dest_ctx = ZSTD_createDCtx();
if (dest_ctx == NULL) {
fatalErrorMemory();
}
}
static void releaseZSTD(void) {
ZSTD_freeDCtx(dest_ctx);
free(output.dst);
}
#endif
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
static size_t getPosition(void) { return payload_current - payload_data; }
#endif
static void readChunk(void *buffer, size_t size) {
// printf("Reading %d\n", size);
memcpy(buffer, payload_current, size);
payload_current += size;
}
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
static void const *readChunkPointer(size_t size) {
// printf("Reading %d\n", size);
void const *result = payload_current;
payload_current += size;
return result;
}
#endif
static void readPayloadChunk(void *buffer, size_t size) {
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
// bool no_payload = false;
bool end_of_buffer = false;
// Loop until finished with asked chunk.
while (size > 0) {
size_t available = output.size - output.pos;
// printf("already available %d asking for %d\n", available, size);
// Consider available data.
if (available != 0) {
size_t use = available;
if (size < use) {
use = size;
}
memcpy(buffer, ((char *)output.dst) + output.pos, use);
buffer = (void *)(((char *)buffer) + use);
size -= use;
output.pos += use;
// Loop end check may exist when "size" is "use".
continue;
}
// Nothing available, make sure to make it available from existing input.
if (input.pos < input.size || end_of_buffer) {
output.pos = 0;
output.size = ZSTD_DStreamOutSize();
size_t const ret = ZSTD_decompressStream(dest_ctx, &output, &input);
// printf("return output %d %d\n", output.pos, output.size);
end_of_buffer = (output.pos == output.size);
if (ZSTD_isError(ret)) {
fatalErrorAttachedData();
}
output.size = output.pos;
output.pos = 0;
// printf("made output %d %d\n", output.pos, output.size);
// Above code gets a turn.
continue;
}
if (input.size != input.pos) {
fatalErrorAttachedData();
}
// No input available, make it available from stream respecting end.
size_t to_read = ZSTD_DStreamInSize();
size_t payload_available = stream_end_pos - getPosition();
static size_t payload_so_far = 0;
if (payload_available == 0) {
continue;
}
if (to_read > payload_available) {
to_read = payload_available;
}
input.src = readChunkPointer(to_read);
input.pos = 0;
input.size = to_read;
payload_so_far += to_read;
}
#else
readChunk(buffer, size);
#endif
}
#if _NUITKA_ONEFILE_TEMP_BOOL == 0
static uint32_t readPayloadChecksumValue(void) {
unsigned int result;
readPayloadChunk(&result, sizeof(unsigned int));
return (uint32_t)result;
}
#endif
#if !defined(_WIN32) && !defined(__MSYS__)
static unsigned char readPayloadFileFlagsValue(void) {
unsigned char result;
readPayloadChunk(&result, 1);
return result;
}
#endif
static unsigned long long readPayloadSizeValue(void) {
unsigned long long result;
readPayloadChunk(&result, sizeof(unsigned long long));
return result;
}
static filename_char_t readPayloadFilenameCharacter(void) {
filename_char_t result;
readPayloadChunk(&result, sizeof(filename_char_t));
return result;
}
static filename_char_t *readPayloadFilename(void) {
static filename_char_t buffer[1024];
filename_char_t *w = buffer;
for (;;) {
*w = readPayloadFilenameCharacter();
if (*w == 0) {
break;
}
w += 1;
}
return buffer;
}
// Zero means, not yet created, created unsuccessfully, terminated already.
#if defined(_WIN32)
HANDLE handle_process = 0;
#else
pid_t handle_process = 0;
#endif
static filename_char_t payload_path[4096] = {0};
#if _NUITKA_ONEFILE_TEMP_BOOL == 1
static bool payload_created = false;
#endif
#define MAX_CREATED_DIRS 1024
static filename_char_t *created_dir_paths[MAX_CREATED_DIRS];
int created_dir_count = 0;
static bool createDirectory(filename_char_t const *path) {
bool bool_res;
#if defined(_WIN32)
if (created_dir_count == 0) {
filename_char_t home_path[4096];
wchar_t *pattern = L"%HOME%";
bool_res = expandTemplatePathFilename(home_path, pattern, sizeof(payload_path) / sizeof(filename_char_t));
if (unlikely(bool_res == false)) {
fatalErrorSpec(pattern);
}
created_dir_paths[created_dir_count] = wcsdup(home_path);
created_dir_count += 1;
}
#endif
for (int i = 0; i < created_dir_count; i++) {
if (strcmpFilename(path, created_dir_paths[i]) == 0) {
return true;
}
}
#if defined(_WIN32)
// On Windows, lets ignore drive letters.
if (strlenFilename(path) == 2 && path[1] == L':') {
return true;
}
#endif
#if defined(_WIN32)
bool_res = CreateDirectoryW(path, NULL);
if (bool_res == false && GetLastError() == 183) {
bool_res = true;
}
#else
if (access(path, F_OK) != -1) {
bool_res = true;
} else {
bool_res = mkdir(path, 0700) == 0;
}
#endif
if (bool_res != false && created_dir_count < MAX_CREATED_DIRS) {
created_dir_paths[created_dir_count] = strdupFilename(path);
created_dir_count += 1;
}
return bool_res;
}
static bool createContainingDirectory(filename_char_t const *path) {
filename_char_t dir_path[4096] = {0};
dir_path[0] = 0;
appendStringSafeFilename(dir_path, path, sizeof(dir_path) / sizeof(filename_char_t));
filename_char_t *w = dir_path + strlenFilename(dir_path);
while (w > dir_path) {
if (*w == FILENAME_SEP_CHAR) {
*w = 0;
bool res = createDirectory(dir_path);
if (res != false) {
return true;
}
createContainingDirectory(dir_path);
return createDirectory(dir_path);
}
w--;
}
return true;
}
#if defined(_WIN32)
static bool isDirectory(wchar_t const *path) {
DWORD dwAttrib = GetFileAttributesW(path);
return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0);
}
static void _removeDirectory(wchar_t const *path) {
SHFILEOPSTRUCTW file_op_struct = {
NULL, FO_DELETE, payload_path, L"", FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT, false, 0, L""};
SHFileOperationW(&file_op_struct);
}
static void removeDirectory(wchar_t const *path) {
_removeDirectory(path);
for (int i = 0; i < 20; i++) {
if (!isDirectory(path)) {
break;
}
// Delay 0.1s before trying again.
Sleep(100);
_removeDirectory(path);
}
}
#else
static int removeDirectory(char const *path) {
DIR *d = opendir(path);
size_t path_len = strlen(path);
int r = -1;
if (d != NULL) {
struct dirent *p;
r = 0;
while (!r && (p = readdir(d))) {
int r2 = -1;
size_t len;
// Ignore special names
if (!strcmp(p->d_name, ".") || !strcmp(p->d_name, "..")) {
continue;
}
len = path_len + strlen(p->d_name) + 2;
char *buf = (char *)malloc(len);
if (buf == NULL) {
fatalErrorMemory();
}
struct stat statbuf;
snprintf(buf, len, "%s/%s", path, p->d_name);
if (!stat(buf, &statbuf)) {
if (S_ISDIR(statbuf.st_mode))
r2 = removeDirectory(buf);
else
r2 = unlink(buf);
}
free(buf);
r = r2;
}
closedir(d);
}
if (!r) {
rmdir(path);
}
return r;
}
#endif
#if !defined(_WIN32)
static int waitpid_retried(pid_t pid, int *status, bool async) {
int res;
for (;;) {
if (status != NULL) {
*status = 0;
}
res = waitpid(pid, status, async ? WNOHANG : 0);
if ((res == -1) && (errno == EINTR)) {
continue;
}
break;
}
return res;
}
static int waitpid_timeout(pid_t pid) {
// Check if already exited.
if (waitpid(pid, NULL, WNOHANG) == -1) {
return 0;
}
// Checking 5 times per second should be good enough.
long ns = 200000000L; // 0.2s
// Seconds, nanoseconds from our milliseconds value.
struct timespec timeout = {
_NUITKA_ONEFILE_CHILD_GRACE_TIME_INT / 1000,
(_NUITKA_ONEFILE_CHILD_GRACE_TIME_INT % 1000) * 1000,
};
struct timespec delay = {0, ns};
struct timespec elapsed = {0, 0};
struct timespec rem;
do {
// Only want to care about SIGCHLD here.
int res = waitpid_retried(pid, NULL, true);
if (unlikely(res < 0)) {
perror("waitpid");
return -1;
}
if (res != 0) {
break;
}
nanosleep(&delay, &rem);
elapsed.tv_sec += (elapsed.tv_nsec + ns) / 1000000000L;
elapsed.tv_nsec = (elapsed.tv_nsec + ns) % 1000000000L;
} while (elapsed.tv_sec < timeout.tv_sec ||
(elapsed.tv_sec == timeout.tv_sec && elapsed.tv_nsec < timeout.tv_nsec));
return 0;
}
#endif
static void cleanupChildProcess(bool send_sigint) {
// Cause KeyboardInterrupt in the child process.
if (handle_process != 0) {
if (send_sigint) {
#if defined(_NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING)
puts("Sending CTRL-C to child\n");
#endif
#if defined(_WIN32)
BOOL res = GenerateConsoleCtrlEvent(CTRL_C_EVENT, GetProcessId(handle_process));
if (res == false) {
printError("Failed to send CTRL-C to child process.", GetLastError());
// No error exit is done, we still want to cleanup when it does exit
}
#else
kill(handle_process, SIGINT);
#endif
}
// TODO: We ought to only need to wait if there is a need to cleanup
// files when we are on Windows, on Linux maybe exec can be used so
// this process to exist anymore if there is nothing to do.
#if _NUITKA_ONEFILE_TEMP_BOOL == 1 || 1
NUITKA_PRINT_TRACE("Waiting for child to exit.\n");
#if defined(_WIN32)
if (WaitForSingleObject(handle_process, _NUITKA_ONEFILE_CHILD_GRACE_TIME_INT) != 0) {
TerminateProcess(handle_process, 0);
}
CloseHandle(handle_process);
#else
waitpid_timeout(handle_process);
kill(handle_process, SIGKILL);
#endif
NUITKA_PRINT_TRACE("Child is exited.\n");
#endif
}
#if _NUITKA_ONEFILE_TEMP_BOOL == 1
if (payload_created) {
#if _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
wprintf(L"Removing payload path '%lS'\n", payload_path);
#endif
removeDirectory(payload_path);
}
#endif
}
#if defined(_WIN32)
BOOL WINAPI ourConsoleCtrlHandler(DWORD fdwCtrlType) {
switch (fdwCtrlType) {
// Handle the CTRL-C signal.
case CTRL_C_EVENT:
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
puts("Ctrl-C event");
#endif
cleanupChildProcess(false);
return FALSE;
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_CLOSE_EVENT:
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
puts("Ctrl-Close event");
#endif
cleanupChildProcess(false);
return FALSE;
// Pass other signals to the next handler.
case CTRL_BREAK_EVENT:
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
puts("Ctrl-Break event");
#endif
cleanupChildProcess(false);
return FALSE;
case CTRL_LOGOFF_EVENT:
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
puts("Ctrl-Logoff event");
#endif
cleanupChildProcess(false);
return FALSE;
case CTRL_SHUTDOWN_EVENT:
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING
puts("Ctrl-Shutdown event");
#endif
cleanupChildProcess(false);
return FALSE;
default:
return FALSE;
}
}
#else
void ourConsoleCtrlHandler(int sig) { cleanupChildProcess(false); }
#endif
#if _NUITKA_ONEFILE_SPLASH_SCREEN
#include "OnefileSplashScreen.cpp"
#endif
#if _NUITKA_AUTO_UPDATE_BOOL && !defined(__IDE_ONLY__)
#include "nuitka_onefile_auto_updater.h"
#endif
#if _NUITKA_AUTO_UPDATE_BOOL
extern bool exe_file_updatable;
#endif
#ifdef _NUITKA_WINMAIN_ENTRY_POINT
int __stdcall wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, wchar_t *lpCmdLine, int nCmdShow) {
int argc = __argc;
wchar_t **argv = __wargv;
#else
#if defined(_WIN32)
int wmain(int argc, wchar_t **argv) {
#else
int main(int argc, char **argv) {
#endif
#endif
NUITKA_PRINT_TIMING("ONEFILE: Entered main().");
filename_char_t const *pattern = FILENAME_EMPTY_STR _NUITKA_ONEFILE_TEMP_SPEC;
bool bool_res = expandTemplatePathFilename(payload_path, pattern, sizeof(payload_path) / sizeof(filename_char_t));
if (unlikely(bool_res == false)) {
fatalErrorSpec(pattern);
}
#if defined(_NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_HANDLING)
wprintf(L"payload path: '%lS'\n", payload_path);
#endif
#if defined(_WIN32)
bool_res = SetConsoleCtrlHandler(ourConsoleCtrlHandler, true);
if (bool_res == false) {
fatalError("Error, failed to register signal handler.");
}
#else
signal(SIGINT, ourConsoleCtrlHandler);
#endif
#ifdef _NUITKA_AUTO_UPDATE_BOOL
checkAutoUpdates();
#endif
NUITKA_PRINT_TIMING("ONEFILE: Unpacking payload.");
initPayloadData();
#if !defined(_NUITKA_PAYLOAD_FROM_MACOS_SECTION) && !defined(_WIN32)
const off_t size_end_offset = exe_file_mapped.file_size;
NUITKA_PRINT_TIMING("ONEFILE: Determining payload start position.");
unsigned long long payload_size;
memcpy(&payload_size, payload_data + size_end_offset - sizeof(payload_size), sizeof(payload_size));
unsigned long long start_pos = size_end_offset - sizeof(payload_size) - payload_size;
payload_current += start_pos;
payload_data += start_pos;
stream_end_pos = size_end_offset - sizeof(payload_size) - start_pos;
#endif
NUITKA_PRINT_TIMING("ONEFILE: Checking header for compression.");
char header[3];
readChunk(&header, sizeof(header));
if (header[0] != 'K' || header[1] != 'A') {
fatalErrorHeaderAttachedData();
}
// The 'X' stands for no compression, 'Y' is compressed, handle that.
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
if (header[2] != 'Y') {
fatalErrorHeaderAttachedData();
}
initZSTD();
#else
if (header[2] != 'X') {
fatalErrorHeaderAttachedData();
}
#endif
static filename_char_t first_filename[1024] = {0};
#if _NUITKA_ONEFILE_SPLASH_SCREEN
NUITKA_PRINT_TIMING("ONEFILE: Init splash screen.");
initSplashScreen();
#endif
NUITKA_PRINT_TIMING("ONEFILE: Entering decompression.");
#if _NUITKA_ONEFILE_TEMP_BOOL == 1
payload_created = true;
#endif
for (;;) {
filename_char_t *filename = readPayloadFilename();
// printf("Filename: " FILENAME_FORMAT_STR "\n", filename);
// Detect EOF from empty filename.
if (filename[0] == 0) {
break;
}
static filename_char_t target_path[4096] = {0};
target_path[0] = 0;
appendStringSafeFilename(target_path, payload_path, sizeof(target_path) / sizeof(filename_char_t));
appendCharSafeFilename(target_path, FILENAME_SEP_CHAR, sizeof(target_path) / sizeof(filename_char_t));
appendStringSafeFilename(target_path, filename, sizeof(target_path) / sizeof(filename_char_t));
if (first_filename[0] == 0) {
appendStringSafeFilename(first_filename, target_path, sizeof(target_path) / sizeof(filename_char_t));
}
// _putws(target_path);
unsigned long long file_size = readPayloadSizeValue();
#if !defined(_WIN32) && !defined(__MSYS__)
unsigned char file_flags = readPayloadFileFlagsValue();
#endif
bool needs_write = true;
#if _NUITKA_ONEFILE_TEMP_BOOL == 0
uint32_t contained_file_checksum = readPayloadChecksumValue();
uint32_t existing_file_checksum = getFileCRC32(target_path);
if (contained_file_checksum == existing_file_checksum) {
needs_write = false;
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_CACHING
printf(stderr, "CACHE HIT for '" FILENAME_FORMAT_STR "'.", target_path);
#endif
} else {
#ifdef _NUITKA_EXPERIMENTAL_DEBUG_ONEFILE_CACHING
printf(stderr, "CACHE HIT for '" FILENAME_FORMAT_STR "'.", target_path);
#endif
}
#endif
FILE_HANDLE target_file = FILE_HANDLE_NULL;
if (needs_write) {
createContainingDirectory(target_path);
target_file = createFileForWritingChecked(target_path);
}
while (file_size > 0) {
static char chunk[32768];
long chunk_size;
// Doing min manually, as otherwise the compiler is confused from types.
if (file_size <= sizeof(chunk)) {
chunk_size = (long)file_size;
} else {
chunk_size = sizeof(chunk);
}
// TODO: Does zstd support skipping data as well, such that we
// do not have to fully decode.
readPayloadChunk(chunk, chunk_size);
if (target_file != FILE_HANDLE_NULL) {
if (writeFileChunk(target_file, chunk, chunk_size) == false) {
fatalErrorTempFiles();
}
}
file_size -= chunk_size;
}
if (file_size != 0) {
fatalErrorReadAttachedData();
}
#if !defined(_WIN32) && !defined(__MSYS__)
if ((file_flags & 1) && (target_file != FILE_HANDLE_NULL)) {
int fd = fileno(target_file);
struct stat stat_buffer;
int res = fstat(fd, &stat_buffer);
if (res == -1) {
printError("fstat", errno);
}
// User shall be able to execute if at least.
stat_buffer.st_mode |= S_IXUSR;
// Follow read flags for group, others according to umask.
if ((stat_buffer.st_mode & S_IRGRP) != 0) {
stat_buffer.st_mode |= S_IXOTH;
}
if ((stat_buffer.st_mode & S_IRGRP) != 0) {
stat_buffer.st_mode |= S_IXOTH;
}
res = fchmod(fd, stat_buffer.st_mode);
if (res == -1) {
printError("fchmod", errno);
}
}
#endif
if (target_file != FILE_HANDLE_NULL) {
if (closeFile(target_file) == false) {
fatalErrorTempFiles();
}
}
}
NUITKA_PRINT_TIMING("ONEFILE: Finishing decompression, cleanup payload.");
closePayloadData();
#ifdef _NUITKA_AUTO_UPDATE_BOOL
exe_file_updatable = true;
#endif
#if _NUITKA_ONEFILE_COMPRESSION_BOOL == 1
releaseZSTD();
#endif
// Pass our pid by value to the child. If we exit for some reason, re-parenting
// might change it by the time the child looks at its parent.
{
char buffer[128];
snprintf(buffer, sizeof(buffer), "%d", getMyPid());
setEnvironVar("NUITKA_ONEFILE_PARENT", buffer);
}
NUITKA_PRINT_TIMING("ONEFILE: Preparing forking of slave process.");
#if defined(_WIN32)
STARTUPINFOW si;
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
PROCESS_INFORMATION pi;
bool_res = CreateProcessW(first_filename, // application name
GetCommandLineW(), // command line
NULL, // process attributes
NULL, // thread attributes
FALSE, // inherit handles
NORMAL_PRIORITY_CLASS, // creation flags
NULL, NULL, &si, &pi);
NUITKA_PRINT_TIMING("ONEFILE: Started slave process.");
if (bool_res == false) {
fatalErrorChild("Error, couldn't launch child.", GetLastError());
}
CloseHandle(pi.hThread);
handle_process = pi.hProcess;
DWORD exit_code = 0;
#if _NUITKA_ONEFILE_SPLASH_SCREEN
DWORD wait_time = 50;
#else
DWORD wait_time = INFINITE;
#endif
// Loop with splash screen, otherwise this will be only once.
while (handle_process != 0) {
WaitForSingleObject(handle_process, wait_time);
if (!GetExitCodeProcess(handle_process, &exit_code)) {
exit_code = 1;
}
#if _NUITKA_ONEFILE_SPLASH_SCREEN
if (exit_code == STILL_ACTIVE) {
bool done = checkSplashScreen();
// Stop checking splash screen, can increase timeout.
if (done) {
wait_time = INFINITE;
}
continue;
}
#endif
CloseHandle(handle_process);
handle_process = 0;
}
cleanupChildProcess(false);
#else
pid_t pid = fork();
int exit_code;
if (pid < 0) {
int error_code = errno;
cleanupChildProcess(false);
fatalErrorChild("Error, couldn't launch child (fork).", error_code);
} else if (pid == 0) {
// Child process
execv(first_filename, argv);
fatalErrorChild("Error, couldn't launch child (exec).", errno);
} else {
// Onefile bootstrap process
handle_process = pid;
int status;
int res = waitpid_retried(handle_process, &status, false);
if (res == -1 && errno != ECHILD) {
exit_code = 2;
} else {
exit_code = WEXITSTATUS(status);
}
cleanupChildProcess(false);
}
#endif
NUITKA_PRINT_TIMING("ONEFILE: Exiting.");
return exit_code;
}