Skip to content

Instantly share code, notes, and snippets.

@juntalis
Created November 20, 2012 05:05
Show Gist options
  • Select an option

  • Save juntalis/4116150 to your computer and use it in GitHub Desktop.

Select an option

Save juntalis/4116150 to your computer and use it in GitHub Desktop.
// Needed for registry stuff
#pragma comment(lib, "advapi32.lib")
// For message boxes
#pragma comment(lib, "user32.lib")
// Use wchar_t
#define _UNICODE 1
#define UNICODE _UNICODE
// Speed up build process with minimal headers.
#define WIN32_LEAN_AND_MEAN
#define VC_EXTRALEAN
#include <windows.h>
#include <strsafe.h>
#include <stdlib.h>
#include <stdio.h>
#include <process.h>
#include <io.h>
#ifndef MAX_PATH
# define MAX_PATH _MAX_PATH
#endif
#ifndef bool
# define bool int
# define true 1
# define false 0
#endif
#ifndef handle
# define handle HANDLE
#endif
#ifndef inline
# define inline __forceinline
#endif
#ifndef null
# define null NULL
#endif
#ifndef dword
# define dword DWORD
#endif
#ifndef DEBUG
# ifdef _DEBUG
# define DEBUG _DEBUG
# endif
#endif
#ifndef MAX_ENV
# define MAX_ENV _MAX_ENV
#endif
#ifndef MAX_EXE_NAME
// I guess if the virtual env was rooted at the drive, you could manage a filename
// this long. (Filepaths can be longer on NTFS, but I'm not about to start using
// a size of 32,000 for statically declared file path buffers, and having to a
// dynamically allocate every buffer intended for use as a path is a pain in the
// ass.
# define MAX_EXE_NAME MAX_PATH - 11
#endif
#define EMPTY L""
// The two macros below were grabbed off the MSDN
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
#define __WFUNCTION__ WIDEN(__FUNCTION__)
static bool verbose_flag = false;
/** Fatal error handlers. */
#define fatal_call() fatal(0L, null)
#define fatal_api_call(f, ...) fatal(0L, f, __VA_ARGS__)
static void fatal(dword dw, wchar_t* message, ...)
{
void *lpDisplayBuf, *lpMsgBuf;
// If Message is null, use the name of our function.
if(message == null) {
message = __WFUNCTION__;
}
if(dw == 0) {
// If no return code was specified, we assume that the message
// contains a function name that failed. In that case, we retrieve
// the system error message for the last-error code
dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |FORMAT_MESSAGE_IGNORE_INSERTS,
null,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(wchar_t*) &lpMsgBuf,
0,
null
);
// Allocate our buffer for the error message.
lpDisplayBuf = (void*)LocalAlloc(
LMEM_ZEROINIT,
(lstrlenW((const wchar_t*)lpMsgBuf) + lstrlenW((const wchar_t*)message) + 47) * sizeof(wchar_t)
);
StringCchPrintfW(
(wchar_t*)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(wchar_t),
L"FATAL: %s failed with error %d: %s",
message,
dw,
lpMsgBuf
);
} else {
// Otherwise, we assume that the error message is a format string.
va_list args = null;
// Allocate buffer for our resulting format string.
lpMsgBuf = (void*)LocalAlloc(
LMEM_ZEROINIT,
(lstrlenW((const wchar_t*)message) + 8) * sizeof(wchar_t)
);
StringCchPrintfW(
(wchar_t*)lpMsgBuf,
LocalSize(lpMsgBuf) / sizeof(wchar_t),
L"FATAL: %s",
message
);
// Might as well use the maximum allowed buffer, since there's no way I know of the
// get the size of the resulting buff.
lpDisplayBuf = (void*)LocalAlloc(LMEM_ZEROINIT, STRSAFE_MAX_CCH * sizeof(wchar_t));
va_start(args, lpMsgBuf);
StringCbVPrintfW(
(wchar_t*)lpDisplayBuf,
LocalSize(lpDisplayBuf) / sizeof(wchar_t),
lpMsgBuf,
args
);
va_end(args);
}
MessageBoxW(GetConsoleWindow(), (const wchar_t*)lpDisplayBuf, L"Fatal Error", MB_OK);
LocalFree(lpMsgBuf);
LocalFree(lpDisplayBuf);
ExitProcess(dw);
}
/**
* Inline helper for allocating a wstring buffer, or throwing
* a fatal error if unable to. Will also zero out the memory
* once it allocates it.
*/
static inline wchar_t* walloc(size_t count)
{
wchar_t* result = null;
size_t szresult = (count + 1) * sizeof(wchar_t);
if(!(result = (wchar_t*)malloc(szresult))) {
fatal_call();
}
memset((void*)result, 0, szresult);
return result;
}
/**
* Inline helper for safely freeing allocated data.
* Along with wafree, these functions were grabbed
* from the cygspawn project:
* https://github.com/mturk/cygspawn
*/
static inline void xfree(void *m)
{
if (m != null) free(m);
}
/** Helper for freeing a wchar string array. */
static void wafree(wchar_t **array)
{
wchar_t **ptr = array;
if (array == null) return;
while (*ptr != null) xfree((void*)*(ptr++));
xfree(array);
}
/** Simple inline helper to check if a path exists. */
static inline bool exists(wchar_t* path)
{
return _waccess((const wchar_t*)path, 00) != -1;
}
/**
* Simple inline helper to check if a given path exists, and
* represent a folder.
*/
static inline bool is_folder(wchar_t* path)
{
return GetFileAttributesW((const wchar_t*)path) & FILE_ATTRIBUTE_DIRECTORY;
}
/**
* Simple inline helper to check if a given path exists, and
* represent a file.
*/
static inline bool is_file(wchar_t* path)
{
return exists(path) && !is_folder(path);
}
/* Platform-specific Cygwin stuff */
#define CYGWIN_SUBKEY L"rootdir"
#ifdef _M_X64
# pragma message("WARNING: Compiling the 64-bit version of this app removes the ability to use Cygwin to convert paths. It is therefore suggested that you only compile use the x86 tools.")
# define CYWGIN_REGKEY L"SOFTWARE\\Wow6432Node\\Cygwin\\setup"
#else
# define CYGWIN_REGKEY L"SOFTWARE\\Cygwin\\setup"
# define CYGWIN_DLL L"cygwin1.dll"
#ifndef NOCONVERTENV
/** Conversion tab entry */
typedef struct { const wchar_t *name; dword flags; } env_entry;
/**
* Some flag to help interpret the environment
* variable tab.
*/
#define NONE 0x000L
#define SPATH 0x001L // Single path
#define LPATH 0x002L // Pathlist
#define VROOT 0x004L // Set to the root folder of our virtual env.
#define UNSET 0x008L // Unset if set
/**
* All environment variables that should be converted to
* their cygwin-alternatives. (if present)
*
* Note: To be honest, aside from maybe HOME (and even that
* only needs a change if you reference it in your script),
* I didn't find that any of these needed converting in Python
* 2.7. Still, since I don't know all the internal workings of
* Python or Cygwin, and since I don't know how older versions
* of Python would handle it, I'm going to leave this in for
* the time being.
*/
static const env_entry vars_tab[] = {
{ L"PYTHONSTARTUP", SPATH },
{ L"PYTHONPATH", LPATH },
{ L"PYTHONHOME", UNSET },
{ L"VIRTUAL_ENV", (SPATH | VROOT) },
{ null, 0 }
};
#endif
typedef HMODULE* PHMODULE; // Thought this was already declared.
/** Our base cygwin function type. */
typedef void(*cygwin_func)();
/** Function tab entry */
typedef struct { const char *name; cygwin_func proc; } cygwin_func_entry;
/** Typedefs taken from Python for defining ssize_t on MSVC */
typedef _W64 int ssize_t;
/** Possible 'what' values in calls to cygwin_conv_path/cygwin_create_path. */
enum {
CCP_POSIX_TO_WIN_A = 0, /* from is char*, to is char* */
CCP_POSIX_TO_WIN_W, /* from is char*, to is wchar_t* */
CCP_WIN_A_TO_POSIX, /* from is char*, to is char* */
CCP_WIN_W_TO_POSIX, /* from is wchar_t*, to is char* */
/* Or these values to the above as needed. */
CCP_ABSOLUTE = 0, /* Request absolute path (default). */
CCP_RELATIVE = 0x100 /* Request to keep path relative. */
};
/** Used to describe the enum values above. */
typedef unsigned int cygwin_conv_path_t;
/** Finally, our function tab. */
static cygwin_func_entry cygwin_funcs[] = {
/**
* If size is 0, cygwin_conv_path returns the required buffer size in bytes.
* Otherwise, it returns 0 on success, or -1 on error and errno is set to
* one of the below values:
*
* EINVAL what has an invalid value.
* EFAULT from or to point into nirvana.
* ENAMETOOLONG the resulting path is longer than 32K, or, in case
* of what == CCP_POSIX_TO_WIN_A, longer than MAX_PATH.
* ENOSPC size is less than required for the conversion.
*/
{ "cygwin_conv_path", null },
# define cygwin_conv_path ((ssize_t(*)(cygwin_conv_path_t, const void*, void*, size_t))(cygwin_funcs[0].proc))
{ "cygwin_conv_path_list", null },
# define cygwin_conv_path_list ((ssize_t(*)(cygwin_conv_path_t, const void*, void*, size_t))(cygwin_funcs[1].proc))
/**
* Allocate a buffer for the conversion result using malloc(3), and return
* a pointer to it. Returns null if something goes wrong with errno set
* to one of the above values, or to ENOMEM if malloc fails.
*/
{ "cygwin_create_path", null },
# define cygwin_create_path ((void*(*)(cygwin_conv_path_t, const void*))(cygwin_funcs[2].proc))
/**
* For the purposes of freeing the buffer allocated in the previous function,
* we're also going to grab cygwin's implementation of free, just to be safe.
*/
{ "free", null },
# define cygwin_free ((void(*)(void*))(cygwin_funcs[3].proc))
/**
* Left null to allow us to iterate and setup the last 4 functions at once.
*/
{ null, null }
};
/** Load our cygwin DLL or return false on failure. */
static bool load_cygwin_library(PHMODULE phCygwin)
{
if(!(*phCygwin = GetModuleHandleW(CYGWIN_DLL)))
if(!(*phCygwin = LoadLibraryW(CYGWIN_DLL)))
return false;
return true;
}
/** Initialize the cygwin environment or false on failure. */
static bool init_cygwin_library(HMODULE hCygwin)
{
cygwin_func cygwin_dll_init = null;
if((cygwin_dll_init = (cygwin_func)GetProcAddress(hCygwin,"cygwin_dll_init")) == null) {
FreeLibrary(hCygwin);
return false;
}
cygwin_dll_init();
return true;
}
/**
* Load our cygwin DLL, initialize the cygwin environment, and
* populate our function tab.
*/
static bool setup_cygwin(PHMODULE phCygwin)
{
int i = -1;
// Load the cygwin dll into our application.
if(!load_cygwin_library(phCygwin)) return false;
// Init the cygwin environment. (Required)
if(!init_cygwin_library(*phCygwin)) {
FreeLibrary(*phCygwin);
return false;
}
// Populate the remaining function tab.
while(cygwin_funcs[++i].name != null) {
if(!(cygwin_funcs[i].proc = (cygwin_func)GetProcAddress(*phCygwin, cygwin_funcs[i].name))) {
FreeLibrary(*phCygwin);
return false;
}
}
return true;
}
/** Does any necessary path format conversion and quoting for paths/path lists needing them. */
static wchar_t* fix_path_type(PHMODULE phCygwin, wchar_t* arg, bool islist)
{
const char* cygpath = null;
wchar_t* result = null;
size_t szcount = 0, szresult = 0;
ssize_t(*conversion_func)(cygwin_conv_path_t, const void*, void*, size_t);
// We'll use cygwin to allocate the buffer for the original value.
if(!(cygpath = (const char*)cygwin_create_path(CCP_WIN_W_TO_POSIX, (const void*)arg))) {
FreeLibrary(*phCygwin);
*phCygwin = null;
return null;
}
// Figure out which function to use.
if(islist) {
conversion_func = cygwin_conv_path_list;
} else {
conversion_func = cygwin_conv_path;
}
// Since cygwin allocated our buffer, we can assume it's big enough to hold the output.
// For now, though, we'll just use MAX_ENV+1
if(conversion_func(CCP_WIN_W_TO_POSIX, (const void*)arg, (void*)cygpath, (MAX_ENV+1) * sizeof(char)) == -1) {
cygwin_free((void*)cygpath);
FreeLibrary(*phCygwin);
*phCygwin = null;
return null;
}
// Finally, allocate our result buffer.
szcount = lstrlenA(cygpath) + 1;
szresult = szcount * sizeof(wchar_t);
if(!szresult || !(result = (wchar_t*)malloc(szresult))) {
cygwin_free((void*)cygpath);
FreeLibrary(*phCygwin);
*phCygwin = null;
return null;
}
// Zero out the allocated data.
memset((void*)result, 0, szresult);
// And convert the char string to our resulting wchar string.
mbstowcs(result, cygpath, szcount);
// At this point, we can free up the buffer cygwin created.
cygwin_free((void*)cygpath);
if(verbose_flag) {
wprintf(L"Converted:\n %s\nto\n %s\n\n", arg, result);
}
return result;
}
#define fix_path(x, y) fix_path_type(x, y, false)
#define fix_path_list(x, y) fix_path_type(x, y, true)
static wchar_t* virtRootWin = NULL;
#ifndef NOCONVERTENV
/* Handles changes made to our environment variables */
static void fix_env(PHMODULE phCygwin)
{
int i = -1;
wchar_t* virtRoot;
if(verbose_flag) wprintf(L"Pre-converting virtual environment root, in case var found to be missing from environment.\n\n");
virtRoot = fix_path(phCygwin, virtRootWin);
while(vars_tab[++i].name && *phCygwin) {
wchar_t current[MAX_ENV+1] = EMPTY, *converted;
// Get our environment variable.
if(GetEnvironmentVariableW(vars_tab[i].name, current, MAX_ENV+1)) {
if(verbose_flag) wprintf(L"Detected environment variable, %s.\n", vars_tab[i].name);
// If it exists, check against the following flags..
if(vars_tab[i].flags & UNSET) {
if(verbose_flag) wprintf(L"Unsetting..\n");
converted = null;
} else if(vars_tab[i].flags & SPATH) {
if(verbose_flag) wprintf(L"Attempting to convert path..\n");
converted = fix_path(phCygwin, current);
} else if(vars_tab[i].flags & LPATH) {
if(verbose_flag) wprintf(L"Attempting to convert path list..\n");
converted = fix_path_list(phCygwin, current);
}
// Set it to our new value.
if(!SetEnvironmentVariableW(vars_tab[i].name, converted)) {
fatal_api_call(L"SetEnvironmentVariableW");
}
xfree(converted);
} else {
// Otherwise..
if(vars_tab[i].flags & VROOT) {
if(verbose_flag) {
wprintf(L"Detected lack of environment variable, %s\n", vars_tab[i].name);
wprintf(L"Setting to converted virtual environment path:\n %s\n\n", virtRoot);
}
if(!SetEnvironmentVariableW(vars_tab[i].name, virtRoot)) {
fatal_api_call(L"SetEnvironmentVariableW");
}
}
}
}
xfree(virtRoot);
}
#endif
#endif
/** Allocates, and adds quotes to an arg. */
static wchar_t* quote_arg(wchar_t* arg)
{
wchar_t* result = null;
size_t szcount = 0;
// Check for empty args. If found, don't both adding quotes.
szcount = lstrlenW(arg);
if(szcount == 0) {
return walloc(0);
}
szcount += 2;
result = walloc(szcount);
szcount = (szcount + 1) * sizeof(wchar_t);
// Now set result to the value of arg, with surrounding quotes.
if(FAILED(StringCchPrintfW(result, szcount, L"\"%s\"", arg))) {
fatal_call();
}
return result;
}
/**
* Iterates through our args, quoting all of them and in the cases of paths,
* converting them to a cygwin-compatible format.
*/
static wchar_t** fix_argv(int argc, wchar_t** argv)
{
HMODULE hCygwin;
int i;
bool useCygwin;
wchar_t** result = null;
size_t szresult;
szresult = (argc + 1) * sizeof(wchar_t*);
#ifdef _M_X64
// Not going to get hit by x64 anyways.
# define fix_path_arg(x,y) quote_arg(y)
useCygwin = false;
#else
useCygwin = setup_cygwin(&hCygwin);
#endif
// First allocate our array of arguments.
if(!(result = (wchar_t**)malloc(szresult))) {
fatal(ERROR_NOT_ENOUGH_MEMORY, L"Could not allocate our args buffer.");
}
// Zero out the allocated data.
memset((void*)result, 0, szresult);
result[0] = _wcsdup(argv[0]);
if(result[0] == null) {
fatal_api_call(L"wcsdup");
}
// Now, begin the fixes.
for(i = 1; i < argc; i++) {
wchar_t* check = NULL;
if(useCygwin && *(argv[i]) && is_file(argv[i])) {
if(verbose_flag) wprintf(L"Detected path argument, %s. Attempting to convert..\n", argv[i]);
if(check = fix_path(&hCygwin, argv[i])) {
argv[i] = check;
} else {
useCygwin = false;
}
}
result[i] = quote_arg(argv[i]);
if(useCygwin && check) {
xfree(check);
}
}
if(useCygwin) {
#ifndef NOCONVERTENV
fix_env(&hCygwin);
#endif
FreeLibrary(hCygwin);
}
return result;
}
static int exec_cmd(wchar_t* cmd, int argc, wchar_t** argv)
{
int r;
if(argc > 1) {
// Fuck, now we actually have to load cygwin to convert the paths from Windows -> Cygwin format.
// Though, if we're on a x64 platform, we can't do that, so we'll just quote our args and hope
// for the best.
wchar_t** args = fix_argv(argc, argv);
r = (int)_wspawnvp(_P_WAIT, cmd, (const wchar_t* const*)args);
wafree(args);
} else {
r = (int)_wspawnlp(_P_WAIT, cmd, cmd, null);
}
return r;
}
/** Inline helper to get the parent folder and length of a the parent quickly */
static inline bool get_dirname(wchar_t* sExecutable, size_t lpszExecutable, wchar_t* sParentOut, size_t* lpszParentOut)
{
if(!lstrcpynW(sParentOut, sExecutable, (int)lpszExecutable)) return false;
*lpszParentOut = lpszExecutable;
while(sParentOut[--*lpszParentOut] != L'\\' && *lpszParentOut) sParentOut[*lpszParentOut] = L'\0';
// Remove the trailing new line.
if(*lpszParentOut) sParentOut[*lpszParentOut] = L'\0';
return (*lpszParentOut > 0) && is_folder(sParentOut);
}
/**
* Determine the root folder of our cygwin installation by
* reading the value of the registry key found at:
* HKEY_LOCAL_MACHINE\SOFTWARE\Cygwin\setup
* or if compiled as a 64-bit program,
* HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Cygwin\setup
*/
static void get_cygwin_root(wchar_t* sBuffer, dword szBuffer)
{
HKEY hkeyCygSetup;
dword dwValType;
long lRet;
if(RegOpenKeyExW(HKEY_LOCAL_MACHINE, CYGWIN_REGKEY, 0L, KEY_READ , &hkeyCygSetup) != ERROR_SUCCESS) {
fatal_api_call(L"RegOpenKeyExW");
}
lRet = RegQueryValueExW(hkeyCygSetup, CYGWIN_SUBKEY, null, &dwValType, (LPBYTE)sBuffer, &szBuffer);
if(lRet != ERROR_SUCCESS) {
if(lRet == ERROR_MORE_DATA) {
fatal(ERROR_MORE_DATA, L"Our buffer was not large enough to contain the value found at the registry key: HKEY_LOCAL_MACHINE\\" CYGWIN_SUBKEY);
} else {
fatal_api_call(L"RegQueryValueExW");
}
}
if(dwValType != REG_SZ && dwValType != REG_EXPAND_SZ) {
fatal(2, L"Found a non-string value at the following registry key: HKEY_LOCAL_MACHINE\\" CYGWIN_SUBKEY);
}
if(RegCloseKey(hkeyCygSetup) != ERROR_SUCCESS) {
fatal_api_call(L"RegCloseKey");
}
if(!is_folder(sBuffer)) {
fatal(ERROR_PATH_NOT_FOUND, L"Did not find an existing folder at %s", sBuffer);
}
}
int wmain(int argc, wchar_t* argv[])
{
wchar_t sParentDir[MAX_PATH+1] = EMPTY,
sExecutable[MAX_PATH+1] = EMPTY,
sTarget[MAX_PATH+1] = EMPTY,
// MAX_ENV + 1 for trailing 0
sPATH[MAX_ENV+1] = EMPTY,
sCygRoot[MAX_PATH+1] = EMPTY,
sSystemDir[MAX_PATH+1] = EMPTY,
sWinDir[MAX_PATH+1] = EMPTY,
*sExecutableName;
int i = 0;
size_t szPath = 0, szParent = 0, szName = 0;
/* First, get the path to our real executable */
// Get our module filepath.
if(!(szPath = GetModuleFileNameW(null, sExecutable, MAX_PATH))) {
fatal_api_call(L"GetModuleFileNameW");
}
// Get our parent folder
if(!get_dirname(sExecutable, szPath, sParentDir, &szParent)) {
fatal(ERROR_PATH_NOT_FOUND, L"Could not get the parent folder of our executable.");
}
// Get a string containing just the last part of the module path (ex: \python.exe)
sExecutableName = ((wchar_t*)&(sExecutable[szParent]));
// We still need to get the bin folder path, so we'll continue working.
if(!get_dirname(sParentDir, szParent, sParentDir, &szParent)) {
fatal(ERROR_PATH_NOT_FOUND, L"Could not get the root folder of our virtual environment.");
}
// Possibly needed later.
virtRootWin = sParentDir;
// Finally, append bin\exename to the root virtualenv folder.
if(FAILED(StringCchPrintfW(sTarget, MAX_PATH+1, L"%s\\bin%s", sParentDir, sExecutableName))) {
fatal_api_call(L"StringCchPrintfW (Building path to real executable)");
}
// Make sure that our real executable exists.
if(FAILED(sTarget)) {
fatal(ERROR_FILE_NOT_FOUND, L"Did not find an existing file at %s", sTarget);
}
/* Now that we have the real path, set up our environment variables. */
// First, find our cygwin root folder
get_cygwin_root(sCygRoot, MAX_PATH + 1);
// And then our system directory.
if(!(szPath = (size_t)GetSystemDirectoryW(sSystemDir, MAX_PATH))) {
fatal_api_call(L"GetSystemDirectoryW");
}
// Get our Windows folder (parent folder of our system folder)
if(!get_dirname(sSystemDir, szPath, sWinDir, &szParent)) {
fatal(1, L"Could not get our Windows directory.");
}
// Build our PATH variable.
if(FAILED(StringCchPrintfW(
sPATH,
MAX_ENV+1,
// PATH=DirOfExe;Cygwin\bin;Cygwin\usr\bin;Cygwin\usr\local\bin;WindowsDir;SystemDir
L"%s\\bin;%s\\bin;%s\\usr\\bin;%s\\usr\\local\\bin;%s;%s",
sParentDir,
sCygRoot, sCygRoot, sCygRoot,
sWinDir,
sSystemDir
))) {
fatal_api_call(L"StringCchPrintfW (Building PATH variable)");
}
// Handle the verbose flag by adding in our own debug output.
while(++i < argc && argv[i] && argv[i][0] == L'-') {
if(argv[i][1] == 'v') {
wprintf(L"Detected verbose flag. Enabling debug output.\n\n");
verbose_flag = true;
break;
}
}
if(verbose_flag) {
wprintf(L"Forwarding to:\n %s\n\n", sTarget);
wprintf(L"Setting PATH env var to:\n %s\n\n", sPATH);
}
if(!SetEnvironmentVariableW(L"PATH", sPATH)) {
fatal_api_call(L"SetEnvironmentVariableW");
}
argv[0] = sTarget;
return exec_cmd(sTarget, argc, argv);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment