Skip to content

Instantly share code, notes, and snippets.

@ookiineko
Last active May 9, 2024 02:12
Show Gist options
  • Save ookiineko/863304fb9e943824969085ab6397c377 to your computer and use it in GitHub Desktop.
Save ookiineko/863304fb9e943824969085ab6397c377 to your computer and use it in GitHub Desktop.
Invoke Cygwin programs for msys2-cygwin
/* SPDX-License-Identifier: MIT */
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define AUTOCLO __attribute__((cleanup(__drop_fd)))
#define AUTOFREE __attribute__((cleanup(__drop_mem)))
#define err(_fmt, ...) fprintf (stderr, "%s: " _fmt "\n", progname, ##__VA_ARGS__)
#define die(_fmt, ...) \
{ \
err(_fmt, ##__VA_ARGS__); \
exit (1); \
}
static const char *progname;
static void
__drop_fd (int *fd)
{
if (*fd < 0)
return;
close (*fd);
}
static void
__drop_mem (void *ptr)
{
#define _ptr (*(void **)ptr)
if (!_ptr)
return;
free (_ptr);
}
static inline const char *
_map2tgt (unsigned short nt_mach)
{
switch (nt_mach)
{
case IMAGE_FILE_MACHINE_AMD64:
return "x86_64-pc-cygwin";
default:
return NULL;
}
}
__attribute__((noreturn)) static inline void
_cygexec (const char *prog,
const char *tgt,
const char *const *argv)
{
AUTOFREE char *pfx = NULL;
AUTOFREE char *old_path = NULL;
AUTOFREE char *buff = NULL;
ssize_t len;
int ex;
#define _agetenv(name) strdup (getenv (name))
// e.g. /mingw64
pfx = _agetenv("MINGW_PREFIX");
if (!pfx)
die("Not running in an MSYS2 MINGW terminal");
old_path = _agetenv("PATH");
if (!old_path)
die("PATH is not set");
// make target DLLs available in PATH
#define _format_new_path(_buff, _len) \
snprintf (_buff, _len, "PATH=%s/%s/bin;%s", pfx, tgt, old_path)
len = _format_new_path(NULL, 0);
if (len < 0)
print_err:
die("%s", strerror (errno));
len += 1; // terminate byte
buff = malloc (len);
if (!buff)
goto print_err;
if (_format_new_path(buff, len) < 0)
goto print_err;
if (putenv (buff))
goto print_err;
// avoid freeing its now a part of environ
buff = NULL;
ex = _spawnv (P_WAIT, prog, argv);
if (ex < 0)
goto print_err;
exit (ex);
}
int
main (int argc, char **argv)
{
AUTOCLO int fd = -1;
union
{
IMAGE_DOS_HEADER dos;
IMAGE_NT_HEADERS nt;
} hdr;
union
{
ssize_t cnt;
off_t off;
} err;
const char *tgt;
progname = argv[0];
if (argc < 2)
{
fprintf (stderr, "Usage: %s PROGRAM [ARGUMENTS...]\t"
"Run the specified program\n", progname);
return 1;
}
// check dos header
#define _prog argv[1]
fd = open (_prog, O_RDONLY | O_BINARY);
if (fd < 0)
{
print_err:
err("%s: %s", _prog, strerror (errno));
return 1;
}
err.cnt = read (fd, &hdr.dos, sizeof(hdr.dos));
if (err.cnt < 0)
goto print_err;
else if (err.cnt != sizeof(hdr.dos))
{
too_small:
err("%s: File too small to be an valid executable", _prog);
return 1;
}
if (hdr.dos.e_magic != IMAGE_DOS_SIGNATURE)
{
bad_prog:
err("%s: Is not a Cygwin program", _prog);
return 1;
}
// check nt header
#define _nt_hdr_off (hdr.dos.e_lfanew)
err.off = lseek (fd, _nt_hdr_off, SEEK_SET);
if (err.off < 0)
goto print_err;
else if (err.off != _nt_hdr_off)
goto too_small;
err.cnt = read (fd, &hdr.nt, sizeof(hdr.nt));
if (err.cnt < 0)
goto print_err;
else if (err.cnt != sizeof(hdr.nt))
goto too_small;
if (hdr.nt.Signature != IMAGE_NT_SIGNATURE)
goto bad_prog;
tgt = _map2tgt (hdr.nt.FileHeader.Machine);
if (!tgt)
{
err("%s: Unknown architecture", _prog);
return 1;
}
_cygexec (_prog, tgt, (const char *const *)&argv[1]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment