mirror of
https://github.com/skeeto/w64devkit.git
synced 2025-11-04 13:11:39 +03:00
w64devkit.c: Improve legibility and maintenance
I've learned a lot since I wrote this two and half years ago. This was one of my earliest serious attempts at building a CRT-free program, and I had not yet learned the techniques to do so effectively. I lacked the experience for dealing with certain common problems, and in some areas approached it with the wrong mindset. For example, much of the original program concerns tracking many little string lengths, which is finicky, hard to follow, and difficult to modify. Better to operate in terms of appending to buffers. I also relied far too much on stack allocation, which has multiple problems. First is friction with the toolchain. By default on Windows, GCC inserts stack probes for frames larger than 4kiB in order to safely commit more stack. Otherwise the program might skip the guard page. To avoid this, I turned off probes and increased the committed stack size, which requires multiple compiler/linker arguments. I'd like to avoid that. The second is that stack allocations have fixed, static sizes if you're not using VLAs, which you're not if you're sane. So I need to figure out and use the worst cast maximum sizes for everything. The stack has a small, limited size, so I can't overestimate by too much either. The third is that the stack remains committed until exit. There's no giving it back before waiting on the long-lived shell process. It's hardly anything, but I'd at least like the option. All these problems are solved with region-based allocation. I get all the nice stack semantics and lifetimes without any of the downsides. I only really got the hang of arenas in the past couple years, which is why I didn't use them in the original version. Now the entire program probably uses under 1kiB of its stack. I've also honed my style, in this case most notably in type names and Win32 declarations. Along with no const, I find it much more readable than the official names and style. As a bonus there's a nice, short listing of all the required external functionality. This could be plucked out as a .def file and used to build an import library, which would eliminate the final dependency on Mingw-w64. Another major benefit is faster build times. The new w64devkit.c cuts the build time by more than 99%, merely by not including windows.h. I've also learned other subtleties these past couple years: 1. GCC assumes the stack is 16-byte aligned, even on x86, but does not guarantee 4-byte alignment, and such functions are usually called by code not generated by GCC (e.g. called by Windows). This isn't only a problem for CRT-free programs. It comes up most often with CreateThread, and many GCC-compiled programs are subtly broken because of this. MSVC and Clang do not have this issue. 2. GCC does not reliably generate correct code without -fno-builtin when CRT-free, particularly for standard functions. Despite -fno-builtin, it may generate calls to these functions, so sometimes you must provide them. Clang also generates such calls despite being asked not to do so, but it does not require -fno-builtin for correctness. You cannot even ask MSVC not to call standard functions, but like Clang, it's careful not to generate the wrong code for your own implementations. So always use -fno-builtin with GCC when not linking a standard library. 3. While returning from the process entrypoint is technically valid and generally works (in console applications), calling ExitProcess is more robust and reliable. On Windows it's not uncommon for other processes to meddle, including spawning uncooperative threads. These are bugs in the other program, but they go unnoticed since virtually nothing actually exits by returning from the entrypoint. It pays to play along. 4. In documentation, -nostartfiles is better than -nostdlib. It's simpler and more fool-proof. When something's not working quite right, it tends to quietly fill in the gaps. I still use -nostdlib in scripts because it's stricter, and I can fix the problems that might arise.
This commit is contained in:
@@ -472,12 +472,9 @@ RUN rm -rf $PREFIX/share/man/ $PREFIX/share/info/ $PREFIX/share/gcc-* \
|
|||||||
COPY README.md Dockerfile src/w64devkit.ini $PREFIX/
|
COPY README.md Dockerfile src/w64devkit.ini $PREFIX/
|
||||||
RUN printf "id ICON \"$PREFIX/src/w64devkit.ico\"" >w64devkit.rc \
|
RUN printf "id ICON \"$PREFIX/src/w64devkit.ico\"" >w64devkit.rc \
|
||||||
&& $ARCH-windres -o w64devkit.o w64devkit.rc \
|
&& $ARCH-windres -o w64devkit.o w64devkit.rc \
|
||||||
&& $ARCH-gcc -DVERSION=$VERSION \
|
&& $ARCH-gcc -DVERSION=$VERSION -nostdlib -fno-asynchronous-unwind-tables \
|
||||||
-mno-stack-arg-probe -Xlinker --stack=0x10000,0x10000 \
|
-fno-builtin -Wl,--gc-sections -s -o $PREFIX/w64devkit.exe \
|
||||||
-Os -fno-asynchronous-unwind-tables \
|
$PREFIX/src/w64devkit.c w64devkit.o -lkernel32 -luser32 \
|
||||||
-Wl,--gc-sections -s -nostdlib \
|
|
||||||
-o $PREFIX/w64devkit.exe $PREFIX/src/w64devkit.c w64devkit.o \
|
|
||||||
-lkernel32 \
|
|
||||||
&& $ARCH-gcc \
|
&& $ARCH-gcc \
|
||||||
-Os -fno-asynchronous-unwind-tables \
|
-Os -fno-asynchronous-unwind-tables \
|
||||||
-Wl,--gc-sections -s -nostdlib \
|
-Wl,--gc-sections -s -nostdlib \
|
||||||
|
|||||||
646
src/w64devkit.c
646
src/w64devkit.c
@@ -1,168 +1,552 @@
|
|||||||
/* Tiny, standalone launcher for w64devkit
|
// Tiny, standalone launcher for w64devkit
|
||||||
* This avoids running a misbehaving monitor cmd.exe in the background.
|
// * Sets $W64DEVKIT to the release version (-DVERSION)
|
||||||
*
|
// * Sets $W64DEVKIT_HOME to the install location
|
||||||
* $ gcc -DVERSION="$VERSION" \
|
// * Maybe sets $HOME according to w64devkit.ini
|
||||||
* -mno-stack-arg-probe -Xlinker --stack=0x10000,0x10000 \
|
// * Starts a login shell with "sh -l"
|
||||||
* -Os -fno-asynchronous-unwind-tables -Wl,--gc-sections \
|
//
|
||||||
* -s -nostdlib -o w64devkit.exe w64devkit.c -lkernel32
|
// $ gcc -DVERSION="$VERSION" -nostartfiles -fno-builtin
|
||||||
*
|
// -o w64devkit.exe w64devkit.c
|
||||||
* This is free and unencumbered software released into the public domain.
|
//
|
||||||
*/
|
// This is free and unencumbered software released into the public domain.
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
#define COUNTOF(a) (sizeof(a) / sizeof(0[a]))
|
#define sizeof(a) (size)(sizeof(a))
|
||||||
#define LSTR(s) XSTR(s)
|
#define alignof(a) (size)(_Alignof(a))
|
||||||
#define XSTR(s) L ## # s
|
#define countof(a) (sizeof(a) / sizeof(*(a)))
|
||||||
#define MAX_VAR 32767
|
#define lengthof(s) (countof(s) - 1)
|
||||||
|
|
||||||
enum err {ERR_PATH, ERR_EXEC};
|
#define new(...) newx(__VA_ARGS__, new4, new3, new2)(__VA_ARGS__)
|
||||||
static const char err_path[] = "w64devkit: failed to set $PATH\n";
|
#define newx(a,b,c,d,e,...) e
|
||||||
static const char err_exec[] = "w64devkit: failed to start shell\n";
|
#define new2(a, t) (t *)alloc(a, sizeof(t), alignof(t), 1, 0)
|
||||||
|
#define new3(a, t, n) (t *)alloc(a, sizeof(t), alignof(t), n, 0)
|
||||||
|
#define new4(a, t, n, f) (t *)alloc(a, sizeof(t), alignof(t), n, f)
|
||||||
|
|
||||||
static int
|
typedef unsigned char byte;
|
||||||
fatal(enum err e)
|
typedef __UINT8_TYPE__ u8;
|
||||||
|
typedef unsigned short u16;
|
||||||
|
typedef signed int i32;
|
||||||
|
typedef signed int b32;
|
||||||
|
typedef unsigned int u32;
|
||||||
|
typedef __UINTPTR_TYPE__ uptr;
|
||||||
|
typedef __PTRDIFF_TYPE__ size;
|
||||||
|
typedef __SIZE_TYPE__ usize;
|
||||||
|
typedef unsigned short char16_t; // for GDB
|
||||||
|
typedef char16_t c16;
|
||||||
|
|
||||||
|
typedef struct {} *handle;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u32 cb;
|
||||||
|
uptr a, b, c;
|
||||||
|
i32 d, e, f, g, h, i, j, k;
|
||||||
|
u16 l, m;
|
||||||
|
uptr n, o, p, q;
|
||||||
|
} si;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
handle process;
|
||||||
|
handle thread;
|
||||||
|
u32 pid;
|
||||||
|
u32 tid;
|
||||||
|
} pi;
|
||||||
|
|
||||||
|
#define MAX_PATH 260
|
||||||
|
#define MAX_ENVVAR 32767
|
||||||
|
#define MAX_CMDLINE 32767
|
||||||
|
#define MAX_INI (1<<18)
|
||||||
|
#define CP_UTF8 65001
|
||||||
|
#define PAGE_READWRITE 0x04
|
||||||
|
#define MEM_COMMIT 0x1000
|
||||||
|
#define MEM_RESERVE 0x2000
|
||||||
|
#define MEM_RELEASE 0x8000
|
||||||
|
#define GENERIC_READ 0x80000000
|
||||||
|
#define OPEN_EXISTING 3
|
||||||
|
#define FILE_SHARE_ALL 7
|
||||||
|
|
||||||
|
#define W32 __attribute((dllimport,stdcall))
|
||||||
|
W32 b32 CloseHandle(handle);
|
||||||
|
W32 handle CreateFileW(c16 *, u32, u32, void *, u32, u32, handle);
|
||||||
|
W32 b32 CreateProcessW(c16*,c16*,void*,void*,i32,u32,c16*,c16*,si*,pi*);
|
||||||
|
W32 void ExitProcess(u32) __attribute((noreturn));
|
||||||
|
W32 u32 ExpandEnvironmentStringsW(c16 *, c16 *, u32);
|
||||||
|
W32 u32 GetEnvironmentVariableW(c16 *, c16 *, u32);
|
||||||
|
W32 i32 GetExitCodeProcess(handle, u32 *);
|
||||||
|
W32 u32 GetFullPathNameW(c16 *, u32, c16 *, c16 *);
|
||||||
|
W32 u32 GetModuleFileNameW(handle, c16 *, u32);
|
||||||
|
W32 i32 MessageBoxW(handle, c16 *, c16 *, u32);
|
||||||
|
W32 i32 MultiByteToWideChar(u32, u32, u8 *, i32, c16 *, i32);
|
||||||
|
W32 b32 ReadFile(handle, u8 *, u32, u32 *, void *);
|
||||||
|
W32 b32 SetConsoleTitleW(c16 *);
|
||||||
|
W32 b32 SetCurrentDirectoryW(c16 *);
|
||||||
|
W32 b32 SetEnvironmentVariableW(c16 *, c16 *);
|
||||||
|
W32 byte *VirtualAlloc(byte *, usize, u32, u32);
|
||||||
|
W32 b32 VirtualFree(byte *, usize, u32);
|
||||||
|
W32 u32 WaitForSingleObject(handle, u32);
|
||||||
|
|
||||||
|
#define S(s) (s8){(u8 *)s, lengthof(s)}
|
||||||
|
typedef struct {
|
||||||
|
u8 *s;
|
||||||
|
size len;
|
||||||
|
} s8;
|
||||||
|
|
||||||
|
#define U(s) (s16){s, lengthof(s)}
|
||||||
|
typedef struct {
|
||||||
|
c16 *s;
|
||||||
|
size len;
|
||||||
|
} s16;
|
||||||
|
|
||||||
|
static s8 s8span(u8 *beg, u8 *end)
|
||||||
{
|
{
|
||||||
size_t len = 0;
|
s8 s = {};
|
||||||
const char *msg = 0;
|
s.s = beg;
|
||||||
switch (e) {
|
s.len = end - beg;
|
||||||
case ERR_PATH: msg = err_path; len = sizeof(err_path) - 1; break;
|
return s;
|
||||||
case ERR_EXEC: msg = err_exec; len = sizeof(err_exec) - 1; break;
|
}
|
||||||
|
|
||||||
|
static b32 s8equals(s8 a, s8 b)
|
||||||
|
{
|
||||||
|
if (a.len != b.len) {
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
HANDLE out = GetStdHandle(STD_ERROR_HANDLE);
|
for (size i = 0; i < a.len; i++) {
|
||||||
DWORD dummy;
|
if (a.s[i] != b.s[i]) {
|
||||||
WriteFile(out, msg, len, &dummy, 0);
|
return 0;
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
lmemmove(WCHAR *dst, const WCHAR *src, size_t len)
|
|
||||||
{
|
|
||||||
for (size_t i = 0; i < len; i++) dst[i] = src[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
static WCHAR *
|
|
||||||
findfile(WCHAR *s)
|
|
||||||
{
|
|
||||||
for (WCHAR *r = s; ; s++) {
|
|
||||||
switch (*s) {
|
|
||||||
case 0: return r;
|
|
||||||
case '/':
|
|
||||||
case '\\': r = s + 1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Read and process "w64devkit.home" from "w64devkit.ini". Environment
|
static void fatal(c16 *msg)
|
||||||
* variables are expanded, and if relative, the result is converted into
|
|
||||||
* an absolute path. The destination length must be MAX_PATH. If this
|
|
||||||
* fails for any reason, the string will be zero length.
|
|
||||||
*
|
|
||||||
* Before calling, the current working directory must be changed to the
|
|
||||||
* location of w64devkit.exe.
|
|
||||||
*/
|
|
||||||
static void
|
|
||||||
homeconfig(WCHAR *path)
|
|
||||||
{
|
{
|
||||||
char home[MAX_PATH*3]; /* UTF-8 MAX_PATH */
|
MessageBoxW(0, msg, u"w64devkit launcher", 0x10);
|
||||||
WCHAR whome[MAX_PATH*2]; /* extra room for variables */
|
ExitProcess(2);
|
||||||
WCHAR expanded[MAX_PATH];
|
|
||||||
|
|
||||||
/* If anything fails, leave empty. */
|
|
||||||
path[0] = 0;
|
|
||||||
|
|
||||||
/* Windows thinks this is a narrow, "ANSI"-encoded file, but it's
|
|
||||||
* really UTF-8. It's decoded to UTF-16 after reading. This means
|
|
||||||
* the INI path must be a narrow string, hence the requirement of
|
|
||||||
* changing the current directory before this call, in case the
|
|
||||||
* INI's absolute path contains non-ASCII characters.
|
|
||||||
*
|
|
||||||
* NOTE(Chris): This Win32 function works fine for now, but in the
|
|
||||||
* future I may replace it with a simple, embedded INI parser. I
|
|
||||||
* already have one written and available in my scratch repository.
|
|
||||||
*/
|
|
||||||
GetPrivateProfileStringA(
|
|
||||||
"w64devkit",
|
|
||||||
"home",
|
|
||||||
0,
|
|
||||||
home,
|
|
||||||
sizeof(home),
|
|
||||||
"./w64devkit.ini"
|
|
||||||
);
|
|
||||||
if (!MultiByteToWideChar(CP_UTF8, 0, home, -1, whome, MAX_PATH*2)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Process INI string into a final HOME path */
|
|
||||||
if (ExpandEnvironmentStringsW(whome, expanded, MAX_PATH) > MAX_PATH) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
GetFullPathNameW(expanded, MAX_PATH, path, 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static DWORD w64devkit(void)
|
typedef struct {
|
||||||
|
byte *mem;
|
||||||
|
size cap;
|
||||||
|
size off;
|
||||||
|
} arena;
|
||||||
|
|
||||||
|
static arena *newarena(size cap)
|
||||||
{
|
{
|
||||||
WCHAR path[MAX_PATH + MAX_VAR];
|
arena *a = 0;
|
||||||
|
byte *mem = VirtualAlloc(0, cap, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
|
||||||
|
if (mem) {
|
||||||
|
a = (arena *)mem;
|
||||||
|
a->mem = mem;
|
||||||
|
a->cap = cap;
|
||||||
|
a->off = sizeof(arena);
|
||||||
|
}
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
/* Construct a path to bin/ directory */
|
static void freearena(arena *a)
|
||||||
static const WCHAR bin[] = L"bin;";
|
{
|
||||||
GetModuleFileNameW(0, path, MAX_PATH);
|
VirtualFree(a->mem, 0, MEM_RELEASE);
|
||||||
WCHAR *tail = findfile(path);
|
}
|
||||||
{
|
|
||||||
/* Set W64DEVKIT_HOME to this module's directory */
|
|
||||||
int save = tail[-1];
|
|
||||||
tail[-1] = 0;
|
|
||||||
SetEnvironmentVariableW(L"W64DEVKIT_HOME", path); // ignore errors
|
|
||||||
|
|
||||||
/* Maybe set HOME from w64devkit.ini */
|
#define NOZERO (1<<0)
|
||||||
if (SetCurrentDirectoryW(path)) {
|
#define SOFTFAIL (1<<1)
|
||||||
WCHAR home[MAX_PATH];
|
__attribute((malloc))
|
||||||
homeconfig(home);
|
__attribute((alloc_align(3)))
|
||||||
if (home[0]) {
|
__attribute((alloc_size(2, 4)))
|
||||||
SetEnvironmentVariableW(L"HOME", home); // ignore errors
|
static byte *alloc(arena *a, size objsize, size align, size count, i32 flags)
|
||||||
|
{
|
||||||
|
size avail = a->cap - a->off;
|
||||||
|
size pad = -a->off & (align - 1);
|
||||||
|
if (count > (avail - pad)/objsize) {
|
||||||
|
if (flags & SOFTFAIL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
fatal(u"Out of memory");
|
||||||
|
}
|
||||||
|
size total = count*objsize;
|
||||||
|
byte *p = a->mem + a->off + pad;
|
||||||
|
if (!(flags & NOZERO)) {
|
||||||
|
for (size i = 0; i < total; i++) {
|
||||||
|
p[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
a->off += pad + total;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
static arena splitarena(arena *a, i32 div)
|
||||||
|
{
|
||||||
|
size avail = a->cap - a->off;
|
||||||
|
size cap = avail / div;
|
||||||
|
arena sub = {};
|
||||||
|
sub.mem = alloc(a, 1, 32, cap, NOZERO);
|
||||||
|
sub.cap = cap;
|
||||||
|
return sub;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
INI_eof,
|
||||||
|
INI_section,
|
||||||
|
INI_key,
|
||||||
|
INI_value
|
||||||
|
} initype;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
s8 name;
|
||||||
|
initype type;
|
||||||
|
} initoken;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
u8 *beg;
|
||||||
|
u8 *end;
|
||||||
|
b32 invalue;
|
||||||
|
} iniparser;
|
||||||
|
|
||||||
|
static b32 inidone(iniparser *p)
|
||||||
|
{
|
||||||
|
return p->beg == p->end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 ininext(iniparser *p)
|
||||||
|
{
|
||||||
|
return *p->beg++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 iniblank(u8 c)
|
||||||
|
{
|
||||||
|
return c==' ' || c=='\t' || c=='\r';
|
||||||
|
}
|
||||||
|
|
||||||
|
static void iniskip(iniparser *p)
|
||||||
|
{
|
||||||
|
for (; !inidone(p) && iniblank(*p->beg); p->beg++) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 *initok(iniparser *p, u8 term)
|
||||||
|
{
|
||||||
|
u8 *end = p->beg;
|
||||||
|
while (!inidone(p)) {
|
||||||
|
u8 c = ininext(p);
|
||||||
|
if (c == term) {
|
||||||
|
return end;
|
||||||
|
} else if (c == '\n') {
|
||||||
|
break;
|
||||||
|
} else if (!iniblank(c)) {
|
||||||
|
end = p->beg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return term=='\n' ? end : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static b32 iniquoted(s8 s)
|
||||||
|
{
|
||||||
|
return s.len>1 && s.s[0]=='"' && s.s[s.len-1]=='"';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses like GetPrivateProfileString except sections cannot contain
|
||||||
|
// newlines. Invalid input lines are ignored. Comment lines begin with
|
||||||
|
// ';' following any whitespace. No trailing comments. Trims leading and
|
||||||
|
// trailing whitespace from sections, keys, and values. To preserve
|
||||||
|
// whitespace, values may be double-quoted. No quote escapes. Content on
|
||||||
|
// a line following a closing section ']' is ignored. Token encoding
|
||||||
|
// matches input encoding. An INI_value always follows an INI_key key.
|
||||||
|
static initoken iniparse(iniparser *p)
|
||||||
|
{
|
||||||
|
initoken r = {};
|
||||||
|
|
||||||
|
if (p->invalue) {
|
||||||
|
p->invalue = 0;
|
||||||
|
iniskip(p);
|
||||||
|
u8 *beg = p->beg;
|
||||||
|
u8 *end = initok(p, '\n');
|
||||||
|
r.name = s8span(beg, end);
|
||||||
|
r.type = INI_value;
|
||||||
|
if (iniquoted(r.name)) {
|
||||||
|
r.name.s++;
|
||||||
|
r.name.len -= 2;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
iniskip(p);
|
||||||
|
if (inidone(p)) {
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 *end;
|
||||||
|
u8 *beg = p->beg;
|
||||||
|
switch (ininext(p)) {
|
||||||
|
case ';':
|
||||||
|
while (!inidone(p) && ininext(p)!='\n') {}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
iniskip(p);
|
||||||
|
beg = p->beg;
|
||||||
|
end = initok(p, ']');
|
||||||
|
if (end) {
|
||||||
|
// skip over anything else on the line
|
||||||
|
while (!inidone(p) && ininext(p)!='\n') {}
|
||||||
|
r.name = s8span(beg, end);
|
||||||
|
r.type = INI_section;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case '\n':
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
end = initok(p, '=');
|
||||||
|
if (end) {
|
||||||
|
p->invalue = 1;
|
||||||
|
r.name = s8span(beg, end);
|
||||||
|
r.type = INI_key;
|
||||||
|
return r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tail[-1] = save;
|
|
||||||
}
|
}
|
||||||
lmemmove(tail, bin, COUNTOF(bin));
|
}
|
||||||
size_t binlen = tail - path + COUNTOF(bin) - 1;
|
|
||||||
|
|
||||||
/* Preprend bin/ path to $PATH */
|
typedef enum {
|
||||||
GetEnvironmentVariableW(L"PATH", path+binlen, MAX_VAR);
|
sym_null = 0,
|
||||||
if (!SetEnvironmentVariableW(L"PATH", path)) {
|
sym_w64devkit,
|
||||||
return fatal(ERR_PATH);
|
sym_home,
|
||||||
|
} symbol;
|
||||||
|
|
||||||
|
static symbol intern(s8 s)
|
||||||
|
{
|
||||||
|
static struct {
|
||||||
|
s8 name;
|
||||||
|
symbol symbol;
|
||||||
|
} symbols[] = {
|
||||||
|
{S("w64devkit"), sym_w64devkit},
|
||||||
|
{S("home"), sym_home},
|
||||||
|
};
|
||||||
|
for (size i = 0; i < countof(symbols); i++) {
|
||||||
|
if (s8equals(symbols[i].name, s)) {
|
||||||
|
return symbols[i].symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sym_null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u8 *makecstr(arena *a, s8 s)
|
||||||
|
{
|
||||||
|
u8 *r = new(a, u8, s.len+1);
|
||||||
|
for (size i = 0; i < s.len; i++) {
|
||||||
|
r[i] = s.s[i];
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read and process "w64devkit.home" from "w64devkit.ini". Environment
|
||||||
|
// variables are expanded, and if relative, the result is converted into
|
||||||
|
// an absolute path. Returns null on error.
|
||||||
|
//
|
||||||
|
// Before calling, the current working directory must be changed to the
|
||||||
|
// location of w64devkit.exe.
|
||||||
|
static c16 *homeconfig(arena *perm, arena scratch)
|
||||||
|
{
|
||||||
|
handle h = CreateFileW(
|
||||||
|
u"w64devkit.ini",
|
||||||
|
GENERIC_READ,
|
||||||
|
FILE_SHARE_ALL,
|
||||||
|
0,
|
||||||
|
OPEN_EXISTING,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
if (h == (handle)-1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
iniparser *p = new(&scratch, iniparser);
|
||||||
|
p->beg = new(&scratch, u8, MAX_INI, NOZERO);
|
||||||
|
u32 inilen;
|
||||||
|
b32 r = ReadFile(h, p->beg, MAX_INI, &inilen, 0);
|
||||||
|
CloseHandle(h);
|
||||||
|
if (!r || inilen == MAX_INI) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
p->end = p->beg + inilen;
|
||||||
|
|
||||||
|
u8 *home = 0;
|
||||||
|
u32 len = 0;
|
||||||
|
for (symbol section = 0, key = 0;;) {
|
||||||
|
initoken t = iniparse(p);
|
||||||
|
switch (t.type) {
|
||||||
|
case INI_eof:
|
||||||
|
break;
|
||||||
|
case INI_section:
|
||||||
|
section = intern(t.name);
|
||||||
|
continue;
|
||||||
|
case INI_key:
|
||||||
|
key = intern(t.name);
|
||||||
|
continue;
|
||||||
|
case INI_value:
|
||||||
|
if (!home && section==sym_w64devkit && key==sym_home) {
|
||||||
|
home = makecstr(&scratch, t.name);
|
||||||
|
len = (u32)(t.name.len + 1); // include terminator
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
c16 *whome = new(&scratch, c16, len);
|
||||||
|
if (!MultiByteToWideChar(CP_UTF8, 0, home, len, whome, len)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process INI string into a final HOME path. Allocate a bit more
|
||||||
|
// than MAX_PATH, because GetFullPathNameW could technically reduce
|
||||||
|
// it to within MAX_PATH if there are lots of relative components.
|
||||||
|
u32 cap = MAX_PATH*4;
|
||||||
|
c16 *expanded = new(&scratch, c16, cap);
|
||||||
|
len = ExpandEnvironmentStringsW(whome, expanded, cap);
|
||||||
|
if (!len || len>cap) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The final result must fit within MAX_PATH in order to be useful.
|
||||||
|
c16 *path = new(perm, c16, MAX_PATH);
|
||||||
|
len = GetFullPathNameW(expanded, MAX_PATH, path, 0);
|
||||||
|
if (!len || len>=MAX_PATH) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
c16 *buf;
|
||||||
|
size cap;
|
||||||
|
size len;
|
||||||
|
b32 err;
|
||||||
|
} buf16;
|
||||||
|
|
||||||
|
static buf16 newbuf16(arena *a, size cap)
|
||||||
|
{
|
||||||
|
buf16 buf = {};
|
||||||
|
buf.buf = new(a, c16, cap, NOZERO);
|
||||||
|
buf.cap = cap;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buf16cat(buf16 *buf, s16 s)
|
||||||
|
{
|
||||||
|
size avail = buf->cap - buf->len;
|
||||||
|
size count = s.len<avail ? s.len : avail;
|
||||||
|
c16 *dst = buf->buf + buf->len;
|
||||||
|
for (size i = 0; i < count; i++) {
|
||||||
|
dst[i] = s.s[i];
|
||||||
|
}
|
||||||
|
buf->len += count;
|
||||||
|
buf->err |= count < s.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buf16c16(buf16 *buf, c16 c)
|
||||||
|
{
|
||||||
|
s16 s = {&c, 1};
|
||||||
|
buf16cat(buf, s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buf16moduledir(buf16 *buf, arena scratch)
|
||||||
|
{
|
||||||
|
c16 *path = new(&scratch, c16, MAX_PATH);
|
||||||
|
size len = GetModuleFileNameW(0, path, MAX_PATH);
|
||||||
|
for (; len; len--) {
|
||||||
|
switch (path[len-1]) {
|
||||||
|
case '/':
|
||||||
|
case '\\': buf16cat(buf, (s16){path, len-1});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void buf16getenv(buf16 *buf, c16 *key, arena scratch)
|
||||||
|
{
|
||||||
|
s16 var = {};
|
||||||
|
var.s = new(&scratch, c16, MAX_ENVVAR, NOZERO);
|
||||||
|
u32 len = GetEnvironmentVariableW(key, var.s, MAX_ENVVAR);
|
||||||
|
var.len = len>=MAX_ENVVAR ? 0 : len;
|
||||||
|
buf16cat(buf, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void toslashes(c16 *path)
|
||||||
|
{
|
||||||
|
for (size i = 0; i < path[i]; i++) {
|
||||||
|
path[i] = path[i]=='\\' ? '/' : path[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 w64devkit(void)
|
||||||
|
{
|
||||||
|
arena *perm = newarena(1<<22);
|
||||||
|
if (!perm) {
|
||||||
|
fatal(u"Out of memory on startup");
|
||||||
|
}
|
||||||
|
arena scratch = splitarena(perm, 2);
|
||||||
|
|
||||||
|
// First load the module directory into the fresh buffer, and use it
|
||||||
|
// for a few different operations.
|
||||||
|
buf16 path = newbuf16(perm, MAX_ENVVAR);
|
||||||
|
buf16moduledir(&path, scratch);
|
||||||
|
buf16 moduledir = path; // to truncate back to the module dir
|
||||||
|
|
||||||
|
buf16c16(&path, 0); // null terminator
|
||||||
|
SetEnvironmentVariableW(u"W64DEVKIT_HOME", path.buf); // ignore errors
|
||||||
|
|
||||||
|
// Maybe set HOME from w64devkit.ini
|
||||||
|
if (SetCurrentDirectoryW(path.buf)) {
|
||||||
|
c16 *home = homeconfig(perm, scratch);
|
||||||
|
if (home) {
|
||||||
|
toslashes(home);
|
||||||
|
SetEnvironmentVariableW(u"HOME", home); // ignore errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Continue building PATH
|
||||||
|
path = moduledir;
|
||||||
|
buf16cat(&path, U(u"\\bin;"));
|
||||||
|
buf16getenv(&path, u"PATH", scratch);
|
||||||
|
buf16c16(&path, 0); // null terminator
|
||||||
|
if (path.err || !SetEnvironmentVariableW(u"PATH", path.buf)) {
|
||||||
|
fatal(u"Failed to configure $PATH");
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef VERSION
|
#ifdef VERSION
|
||||||
SetEnvironmentVariableW(L"W64DEVKIT", LSTR(VERSION)); // ignore errors
|
#define LSTR(s) XSTR(s)
|
||||||
|
#define XSTR(s) u ## # s
|
||||||
|
SetEnvironmentVariableW(u"W64DEVKIT", LSTR(VERSION)); // ignore errors
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Set the console title as late as possible, but not after starting
|
// Set the console title as late as possible, but not after starting
|
||||||
// the shell because .profile might change it.
|
// the shell because .profile might change it.
|
||||||
SetConsoleTitleA("w64devkit");
|
SetConsoleTitleW(u"w64devkit"); // ignore errors
|
||||||
|
|
||||||
/* Start a BusyBox login shell */
|
path = moduledir;
|
||||||
STARTUPINFOW si;
|
buf16cat(&path, U(u"\\bin\\busybox.exe"));
|
||||||
GetStartupInfoW(&si);
|
buf16c16(&path, 0); // null terminator
|
||||||
PROCESS_INFORMATION pi;
|
|
||||||
static const WCHAR busybox[] = L"bin\\busybox.exe";
|
// Start a BusyBox login shell
|
||||||
lmemmove(tail, busybox, COUNTOF(busybox));
|
si si = {};
|
||||||
if (!CreateProcessW(path, L"sh -l", 0, 0, TRUE, 0, 0, 0, &si, &pi)) {
|
si.cb = sizeof(si);
|
||||||
return fatal(ERR_EXEC);
|
pi pi;
|
||||||
|
c16 cmdline[] = u"sh -l"; // NOTE: must be mutable!
|
||||||
|
if (!CreateProcessW(path.buf, cmdline, 0, 0, 1, 0, 0, 0, &si, &pi)) {
|
||||||
|
fatal(u"Failed to launch a login shell");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Wait for shell to exit */
|
// Wait for shell to exit
|
||||||
DWORD ret;
|
freearena(perm);
|
||||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
u32 ret;
|
||||||
GetExitCodeProcess(pi.hProcess, &ret);
|
WaitForSingleObject(pi.process, -1);
|
||||||
|
GetExitCodeProcess(pi.process, &ret);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if __i386
|
|
||||||
__attribute((force_align_arg_pointer))
|
__attribute((force_align_arg_pointer))
|
||||||
#endif
|
|
||||||
void mainCRTStartup(void)
|
void mainCRTStartup(void)
|
||||||
{
|
{
|
||||||
DWORD r = w64devkit();
|
u32 r = w64devkit();
|
||||||
ExitProcess(r);
|
ExitProcess(r);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,4 @@
|
|||||||
; paths, so be mindful when using non-ASCII paths for this value.
|
; paths, so be mindful when using non-ASCII paths for this value.
|
||||||
;
|
;
|
||||||
;home = ..\home
|
;home = ..\home
|
||||||
;home = %HOMEDRIVE%\%HOMEPATH%
|
;home = %HOMEDRIVE%%HOMEPATH%
|
||||||
|
|||||||
Reference in New Issue
Block a user