867 lines
25 KiB
C
867 lines
25 KiB
C
/**
|
|
* 2ine; an OS/2 emulator for Linux.
|
|
*
|
|
* Please see the file LICENSE.txt in the source's root directory.
|
|
*
|
|
* This file written by Ryan C. Gordon.
|
|
*/
|
|
|
|
#define _DARWIN_C_SOURCE 1
|
|
#define _POSIX_C_SOURCE 199309
|
|
#define _DEFAULT_SOURCE
|
|
#define _GNU_SOURCE
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <pthread.h>
|
|
#include <sys/time.h>
|
|
#include <sys/resource.h>
|
|
#include <assert.h>
|
|
|
|
#include "lib2ine.h"
|
|
#include "SDL.h"
|
|
|
|
extern char **environ;
|
|
|
|
LxLoaderState GLoaderState;
|
|
|
|
static pthread_key_t tlskey;
|
|
|
|
static void *readfile(const char *fname, size_t *_len)
|
|
{
|
|
const size_t chunklen = 128;
|
|
const int fd = open(fname, O_RDONLY);
|
|
if (fd == -1) {
|
|
fprintf(stderr, "Can't open '%s': %s\n", fname, strerror(errno));
|
|
return NULL;
|
|
}
|
|
|
|
char *buf = NULL;
|
|
size_t bw = 0;
|
|
|
|
while (1) {
|
|
buf = (char *) realloc(buf, bw + chunklen);
|
|
if (!buf) {
|
|
fprintf(stderr, "Out of memory\n");
|
|
free(buf);
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
const ssize_t br = read(fd, buf + bw, chunklen);
|
|
if (br < 0) {
|
|
fprintf(stderr, "Read fail on '%s': %s\n", fname, strerror(errno));
|
|
free(buf);
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
|
|
bw += br;
|
|
if (br < chunklen) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
close(fd);
|
|
*_len = bw;
|
|
return buf;
|
|
}
|
|
|
|
static void initPib(void)
|
|
{
|
|
LxPIB *pib = &GLoaderState.pib;
|
|
memset(pib, '\0', sizeof (*pib));
|
|
|
|
size_t cmdlinebuflen = 0;
|
|
char *cmdlinebuf = readfile("/proc/self/cmdline", &cmdlinebuflen);
|
|
if (!cmdlinebuf) {
|
|
abort();
|
|
}
|
|
|
|
int argc = 0;
|
|
for (size_t i = 0; i < cmdlinebuflen; i++) {
|
|
if (cmdlinebuf[i] == '\0') {
|
|
argc++;
|
|
}
|
|
}
|
|
|
|
char **argv = (char **) calloc(argc + 1, sizeof (char *));
|
|
if (!argv) {
|
|
fprintf(stderr, "Out of memory\n");
|
|
abort();
|
|
}
|
|
|
|
argv[0] = cmdlinebuf;
|
|
argc = 0;
|
|
for (size_t i = 0; i < cmdlinebuflen; i++) {
|
|
if (cmdlinebuf[i] == '\0') {
|
|
argc++;
|
|
argv[argc] = &cmdlinebuf[i + 1];
|
|
}
|
|
}
|
|
argv[argc] = NULL;
|
|
|
|
|
|
// !!! FIXME: HUGE HACK
|
|
int is_lx_loader = 0;
|
|
{
|
|
const char *ptr = strrchr(argv[0], '/');
|
|
if (ptr) { ptr++; } else { ptr = argv[0]; }
|
|
is_lx_loader = (strcmp(ptr, "lx_loader") == 0);
|
|
}
|
|
|
|
if (is_lx_loader) {
|
|
argv++;
|
|
argc--;
|
|
}
|
|
|
|
|
|
// !!! FIXME: this is incomplete.
|
|
|
|
// Eventually, the environment table looks like this (double-null to terminate list): var1=a\0var2=b\0var3=c\0\0
|
|
// The command line looks like this: \0argv0\0argv1 argv2 argvN\0\0
|
|
// Between env and cmd is the exe name: argv0\0
|
|
|
|
size_t len = 1;
|
|
for (int i = 0; i < argc; i++) {
|
|
int needs_escape = 0;
|
|
int num_backslashes = 0;
|
|
for (const char *arg = argv[i]; *arg; arg++) {
|
|
const char ch = *arg;
|
|
if ((ch == ' ') || (ch == '\t'))
|
|
needs_escape = 1;
|
|
else if ((ch == '\\') || (ch == '\"'))
|
|
num_backslashes++;
|
|
len++;
|
|
} // for
|
|
|
|
if (needs_escape) {
|
|
len += 2 + num_backslashes;
|
|
} // if
|
|
|
|
len++; // terminator
|
|
} // for
|
|
|
|
len += strlen(argv[0]) + 1; // for the exe name.
|
|
|
|
char **envp = environ;
|
|
|
|
char path[255];
|
|
snprintf(path, 254, "PATH=%s", getenv("OS2PATH"));
|
|
for (int i = 0; envp[i]; i++) {
|
|
const char *str = envp[i];
|
|
if (strncmp(str, "PATH=", 5) == 0) {
|
|
if (!GLoaderState.subprocess)
|
|
str = path;
|
|
} else if (strncmp(str, "IS_2INE=", 8) == 0) {
|
|
continue;
|
|
} // if
|
|
len += strlen(str) + 1;
|
|
} // for
|
|
|
|
len += 4; // null terminators.
|
|
|
|
// align this to 64k, because it has to be at the start of a segment
|
|
// for 16-bit code.
|
|
// cmd.exe uses realloc on the enviroment seg so make it 64k
|
|
// (!!! FIXME: this works even though it doesn't use allocSegment because
|
|
// we currently just allocate the full 64k and never actually realloc
|
|
// later. allocSegment isn't up and running at this point. But we might
|
|
// want lx_loader to copy this to an alloc'd segment later so we
|
|
// definitely have an tileable address below 512mb.)
|
|
char *env = NULL;
|
|
if (posix_memalign((void **) &env, 0x10000, 0x10000) != 0) {
|
|
fprintf(stderr, "Out of memory\n");
|
|
abort();
|
|
} // if
|
|
|
|
const char *libpath = NULL;
|
|
char *ptr = env;
|
|
for (int i = 0; envp[i]; i++) {
|
|
const char *str = envp[i];
|
|
if (strncmp(str, "PATH=", 5) == 0) {
|
|
if (!GLoaderState.subprocess)
|
|
str = path;
|
|
} else if (strncmp(str, "LIBPATH=", 8) == 0) {
|
|
libpath = str + 8;
|
|
} else if (strncmp(str, "IS_2INE=", 8) == 0) {
|
|
continue;
|
|
} // if
|
|
|
|
strcpy(ptr, str);
|
|
ptr += strlen(str) + 1;
|
|
}
|
|
*(ptr++) = '\0';
|
|
|
|
// we keep a copy of LIBPATH for undocumented API
|
|
// DosQueryHandleInfo(..., QHINF_LIBPATH, ...). I guess in case the app
|
|
// has changed the environment var after start? Java uses this.
|
|
if (libpath == NULL)
|
|
libpath = "";
|
|
|
|
GLoaderState.libpath = strdup(libpath);
|
|
GLoaderState.libpathlen = strlen(libpath) + 1;
|
|
|
|
// put the exe name between the environment and the command line.
|
|
strcpy(ptr, argv[0]);
|
|
ptr += strlen(argv[0]) + 1;
|
|
|
|
char *cmd = ptr;
|
|
strcpy(ptr, argv[0]);
|
|
ptr += strlen(argv[0]);
|
|
*(ptr++) = '\0';
|
|
for (int i = 1; i < argc; i++) {
|
|
int needs_escape = 0;
|
|
for (const char *arg = argv[i]; *arg; arg++) {
|
|
const char ch = *arg;
|
|
if ((ch == ' ') || (ch == '\t'))
|
|
needs_escape = 1;
|
|
} // for
|
|
|
|
if (needs_escape) {
|
|
*(ptr++) = '"';
|
|
for (const char *arg = argv[i]; *arg; arg++) {
|
|
const char ch = *arg;
|
|
if ((ch == '\\') || (ch == '\n'))
|
|
*(ptr++) = '\\';
|
|
*(ptr++) = ch;
|
|
} // for
|
|
*(ptr++) = '"';
|
|
} else {
|
|
for (const char *arg = argv[i]; *arg; arg++)
|
|
*(ptr++) = *arg;
|
|
} // if
|
|
|
|
if (i < (argc-1))
|
|
*(ptr++) = ' ';
|
|
} // for
|
|
|
|
*(ptr++) = '\0';
|
|
|
|
pib->pib_ulpid = (uint32) getpid();
|
|
pib->pib_ulppid = (uint32) getppid();
|
|
pib->pib_pchcmd = cmd;
|
|
pib->pib_pchenv = env;
|
|
//pib->pib_hmte is filled in later during loadModule()
|
|
// !!! FIXME: uint32 pib_flstatus;
|
|
// !!! FIXME: uint32 pib_ultype;
|
|
|
|
if (is_lx_loader) {
|
|
argv--;
|
|
}
|
|
|
|
free(cmdlinebuf);
|
|
free(argv);
|
|
} // initPib
|
|
|
|
static void dosExit_lib2ine(uint32 action, uint32 result) {}
|
|
|
|
static void initOs2Tib_lib2ine(uint8 *tibspace, void *_topOfStack, const size_t stacklen, const uint32 tid)
|
|
{
|
|
uint8 *topOfStack = (uint8 *) _topOfStack;
|
|
|
|
LxTIB *tib = (LxTIB *) tibspace;
|
|
LxTIB2 *tib2 = (LxTIB2 *) (tib + 1);
|
|
|
|
FIXME("This is probably 50% wrong");
|
|
tib->tib_pexchain = NULL;
|
|
tib->tib_pstack = topOfStack - stacklen;
|
|
tib->tib_pstacklimit = (void *) topOfStack;
|
|
tib->tib_ptib2 = tib2;
|
|
tib->tib_version = 20; // !!! FIXME
|
|
tib->tib_ordinal = 79; // !!! FIXME
|
|
|
|
tib2->tib2_ultid = tid;
|
|
tib2->tib2_ulpri = 512;
|
|
tib2->tib2_version = 20;
|
|
tib2->tib2_usMCCount = 0;
|
|
tib2->tib2_fMCForceFlag = 0;
|
|
}
|
|
|
|
static uint16 setOs2Tib_lib2ine(uint8 *tibspace)
|
|
{
|
|
pthread_setspecific(tlskey, (LxTIB *) tibspace);
|
|
return 1;
|
|
}
|
|
|
|
static LxTIB *getOs2Tib_lib2ine(void)
|
|
{
|
|
return (LxTIB *) pthread_getspecific(tlskey);
|
|
}
|
|
|
|
static void deinitOs2Tib_lib2ine(const uint16 selector)
|
|
{
|
|
pthread_setspecific(tlskey, NULL);
|
|
}
|
|
|
|
static int findSelector_lib2ine(const uint32 addr, uint16 *outselector, uint16 *outoffset, int iscode)
|
|
{
|
|
return 0; // always fail. This is for 16-bit support.
|
|
}
|
|
|
|
static void freeSelector_lib2ine(const uint16 selector)
|
|
{
|
|
// no-op. This is for 16-bit support.
|
|
}
|
|
|
|
static void *convert1616to32_lib2ine(const uint32 addr1616)
|
|
{
|
|
return NULL; // always fail. This is for 16-bit support.
|
|
}
|
|
|
|
static uint32 convert32to1616_lib2ine(void *addr32)
|
|
{
|
|
return 0; // always fail. This is for 16-bit support.
|
|
}
|
|
|
|
static LxModule *loadModule_lib2ine(const char *modname)
|
|
{
|
|
return NULL; // lx_loader will do OS/2 modules, we don't.
|
|
// !!! FIXME: maybe this should dlopen() native modules instead?
|
|
}
|
|
|
|
static char *makeUnixPath_lib2ine(const char *os2path, uint32 *err)
|
|
{
|
|
// this doesn't convert any OS/2 paths. For native apps, use native paths.
|
|
char *retval = strdup(os2path);
|
|
if (!retval) {
|
|
*err = 8; //ERROR_NOT_ENOUGH_MEMORY;
|
|
}
|
|
return retval;
|
|
}
|
|
|
|
static void *allocSegment_lib2ine(uint16 *selector, const int iscode)
|
|
{
|
|
if (selector) *selector = 0xFFFF;
|
|
return NULL;
|
|
}
|
|
|
|
static void freeSegment_lib2ine(const uint16 selector) {}
|
|
|
|
static void __attribute__((noreturn)) terminate_lib2ine(const uint32 exitcode)
|
|
{
|
|
exit((int) exitcode); // let static constructors and atexit() run.
|
|
}
|
|
|
|
|
|
static void cfgWarn(const char *fname, const int lineno, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
fprintf(stderr, "2ine cfg warning: [%s:%d] ", fname, lineno);
|
|
va_start(ap, fmt);
|
|
vfprintf(stderr, fmt, ap);
|
|
va_end(ap);
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
static int cfgIsSpace(const int ch)
|
|
{
|
|
switch (ch) { case ' ': case '\t': case '\r': case '\n': return 1; }
|
|
return 0;
|
|
}
|
|
|
|
static int cfgIsSymChar(const int ch)
|
|
{
|
|
return ((ch == '_') || ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')));
|
|
}
|
|
|
|
static void cfgProcessBoolString(const char *fname, const int lineno, int *output, const char *val)
|
|
{
|
|
static const char *y[] = { "1", "yes", "y", "true", "t", "on" };
|
|
static const char *n[] = { "0", "no", "n", "false", "f", "off" };
|
|
int i;
|
|
for (i = 0; i < (sizeof (y) / sizeof (y[0])); i++) {
|
|
if (strcasecmp(val, y[i]) == 0) { *output = 1; return; }
|
|
if (strcasecmp(val, n[i]) == 0) { *output = 0; return; }
|
|
}
|
|
cfgWarn(fname, lineno, "\"%s\" is not valid for this setting. Try 1 or 0.", val);
|
|
}
|
|
|
|
static void cfgProcessFloatString(const char *fname, const int lineno, float *output, const char *val)
|
|
{
|
|
errno = 0;
|
|
const float converted = strtof(val, NULL);
|
|
if (!errno) {
|
|
*output = converted;
|
|
} else {
|
|
cfgWarn(fname, lineno, "\"%s\" is not valid for this setting. Try a number.", val);
|
|
}
|
|
}
|
|
|
|
static void cfgProcessMountPoint(const char *fname, const int lineno, const char *var, const char *val)
|
|
{
|
|
char letter = var[0];
|
|
if ((letter >= 'a') && (letter <= 'z')) {
|
|
letter -= 'a' - 'A';
|
|
}
|
|
|
|
if ((letter < 'A') || (letter > 'Z') || (var[1] != '\0')) {
|
|
cfgWarn(fname, lineno, "Invalid disk \"%s\" (must be between 'A' and 'Z')", var);
|
|
return;
|
|
}
|
|
|
|
const int idx = (int) (letter - 'A');
|
|
free(GLoaderState.disks[idx]); // in case we're overriding.
|
|
GLoaderState.disks[idx] = NULL;
|
|
|
|
if (*val != '\0') { // val=="" means don't mount
|
|
const size_t vallen = strlen(val);
|
|
struct stat statbuf;
|
|
if (stat(val, &statbuf) == -1) {
|
|
cfgWarn(fname, lineno, "Path \"%s\" for drive %c:\\ isn't accessible: %s", val, letter, strerror(errno));
|
|
} else if (!S_ISDIR(statbuf.st_mode)) {
|
|
cfgWarn(fname, lineno, "Path \"%s\" for drive %c:\\ isn't a directory", val, letter);
|
|
} else if ((GLoaderState.disks[idx] = (char *) malloc(vallen + 2)) == NULL) {
|
|
cfgWarn(fname, lineno, "Out of memory!");
|
|
} else {
|
|
char *ptr = GLoaderState.disks[idx];
|
|
strcpy(ptr, val);
|
|
if (ptr[vallen - 1] != '/') {
|
|
ptr[vallen] = '/';
|
|
ptr[vallen+1] = '\0';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void cfgProcessSystem(const char *fname, const int lineno, const char *var, const char *val)
|
|
{
|
|
if (strcmp(var, "trace_native") == 0) {
|
|
cfgProcessBoolString(fname, lineno, &GLoaderState.trace_native, val);
|
|
} else if (strcmp(var, "trace_events") == 0) {
|
|
cfgProcessBoolString(fname, lineno, &GLoaderState.trace_events, val);
|
|
} else if (strcmp(var, "beep_volume") == 0) {
|
|
cfgProcessFloatString(fname, lineno, &GLoaderState.beep_volume, val);
|
|
} else {
|
|
cfgWarn(fname, lineno, "Unknown variable system.%s", var);
|
|
}
|
|
}
|
|
|
|
static void cfgProcessLine(const char *fname, const int lineno, const char *category, const char *var, const char *val)
|
|
{
|
|
//printf("CFG: process line cat=%s var=%s, val=%s\n", category, var, val);
|
|
if ((*category == '\0') || (*var == '\0')) {
|
|
cfgWarn(fname, lineno, "Invalid configuration line");
|
|
} else if (strcmp(category, "mountpoint") == 0) {
|
|
cfgProcessMountPoint(fname, lineno, var, val);
|
|
// eventually will map COM1, LPT1, NUL, etc.
|
|
//} else if (strcmp(category, "device") == 0) {
|
|
// cfgProcessDevice(fname, lineno, var, val);
|
|
} else if (strcmp(category, "system") == 0) {
|
|
cfgProcessSystem(fname, lineno, var, val);
|
|
} else {
|
|
cfgWarn(fname, lineno, "Unknown category \"%s\"", category);
|
|
}
|
|
}
|
|
|
|
static void cfgLoad(const char *fname)
|
|
{
|
|
FILE *io = fopen(fname, "r");
|
|
char *buffer = NULL;
|
|
size_t buflen = 0;
|
|
unsigned int lineno = 0;
|
|
ssize_t len;
|
|
|
|
if (io == NULL) {
|
|
return;
|
|
}
|
|
|
|
while ((len = getline(&buffer, &buflen, io)) >= 0) {
|
|
lineno++;
|
|
|
|
// strip whitespace and endlines off end of string.
|
|
while ((len > 0) && (cfgIsSpace(buffer[len-1]))) {
|
|
buffer[--len] = '\0';
|
|
}
|
|
|
|
//printf("CFG: fname=%s line=%d text=%s\n", fname, lineno, buffer);
|
|
|
|
#define SKIPSPACE() while (cfgIsSpace(*ptr)) { ptr++; }
|
|
|
|
char *ptr = buffer;
|
|
SKIPSPACE();
|
|
if ((*ptr == '\0') || (*ptr == ';') || (*ptr == '#') || ((ptr[0] == '/') && (ptr[1] == '/'))) {
|
|
//printf("CFG: comment or blank line\n");
|
|
continue; // it's a comment or blank line, skip it.
|
|
}
|
|
const char *category = ptr;
|
|
while (cfgIsSymChar(*ptr)) { ptr++; }
|
|
if (*ptr != '.') {
|
|
cfgWarn(fname, lineno, "Invalid configuration line");
|
|
continue;
|
|
}
|
|
*(ptr++) = '\0';
|
|
const char *var = ptr;
|
|
while (cfgIsSymChar(*ptr)) { ptr++; }
|
|
|
|
if (*ptr == '=') {
|
|
*(ptr++) = '\0';
|
|
} else if (!cfgIsSpace(*ptr)) {
|
|
cfgWarn(fname, lineno, "Invalid configuration line");
|
|
continue;
|
|
} else {
|
|
*(ptr++) = '\0';
|
|
SKIPSPACE();
|
|
if (*ptr != '=') {
|
|
cfgWarn(fname, lineno, "Invalid configuration line");
|
|
continue;
|
|
}
|
|
ptr++;
|
|
}
|
|
SKIPSPACE();
|
|
const char *val = ptr;
|
|
#undef SKIPSPACE
|
|
|
|
cfgProcessLine(fname, lineno, category, var, val);
|
|
}
|
|
|
|
free(buffer);
|
|
fclose(io);
|
|
}
|
|
|
|
static void cfgLoadFiles(void)
|
|
{
|
|
cfgLoad("/etc/2ine.cfg");
|
|
|
|
// optionally override or add config from user's config dir.
|
|
char *fname = NULL;
|
|
const char *env = getenv("XDG_CONFIG_HOME");
|
|
if (env) {
|
|
const size_t buflen = strlen(env) + 32;
|
|
fname = (char *) malloc(buflen);
|
|
if (fname) {
|
|
snprintf(fname, buflen, "%s/2ine/2ine.cfg", env);
|
|
}
|
|
} else {
|
|
env = getenv("HOME");
|
|
if (env) {
|
|
const size_t buflen = strlen(env) + 32;
|
|
fname = (char *) malloc(buflen);
|
|
if (fname) {
|
|
snprintf(fname, buflen, "%s/.config/2ine/2ine.cfg", env);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fname) {
|
|
cfgLoad(fname);
|
|
free(fname);
|
|
}
|
|
}
|
|
|
|
static void prepOs2Drives(void)
|
|
{
|
|
const int drive_c = 'C' - 'A';
|
|
const int total = (sizeof (GLoaderState.disks) / sizeof (GLoaderState.disks[0]));
|
|
uint32 diskmap = 0;
|
|
int lowestmounted = 0;
|
|
int lowestcwd = 0;
|
|
int i;
|
|
|
|
FIXME("need to pass current dirs and disks to OS/2 subprocesses");
|
|
|
|
char *cwd = getcwd(NULL, 0);
|
|
char *rp = cwd ? realpath(cwd, NULL) : NULL; // in case there's a symlink in there.
|
|
if ((cwd == NULL) || (rp == NULL)) {
|
|
fprintf(stderr, "Can't get cwd (%s), defaulting to \"/\".\n", strerror(errno));
|
|
rp = strdup("/");
|
|
chdir(rp);
|
|
}
|
|
|
|
free(cwd);
|
|
cwd = rp;
|
|
|
|
for (i = 0; i < total; i++) {
|
|
if (GLoaderState.disks[i] == NULL) {
|
|
continue;
|
|
}
|
|
|
|
rp = realpath(GLoaderState.disks[i], NULL);
|
|
if (rp == NULL) {
|
|
fprintf(stderr, "Can't realpath \"%s\" (%s), not mounting %c:\\\n", GLoaderState.disks[i], strerror(errno), i + 'A');
|
|
free(GLoaderState.disks[i]);
|
|
GLoaderState.disks[i] = NULL;
|
|
continue;
|
|
}
|
|
|
|
if (lowestmounted < 3) {
|
|
lowestmounted = i + 1;
|
|
}
|
|
|
|
diskmap |= (1 << i);
|
|
|
|
const size_t rplen = strlen(rp);
|
|
assert(rplen > 0);
|
|
if ((strncmp(rp, cwd, rplen) == 0) && ((rp[rplen-1] == '/') || (cwd[rplen] == '/') || (cwd[rplen] == '\0'))) {
|
|
// our cwd is under this mount point!
|
|
const char *d = cwd + rplen;
|
|
GLoaderState.current_dir[i] = strdup((*d == '/') ? d + 1 : d);
|
|
char *ptr = GLoaderState.current_dir[i];
|
|
while (*ptr) { // make current dir an OS/2-style relative path.
|
|
if (*ptr == '/') {
|
|
*ptr = '\\';
|
|
}
|
|
ptr++;
|
|
}
|
|
|
|
if (lowestcwd < 3) {
|
|
lowestcwd = i + 1;
|
|
}
|
|
} else {
|
|
// current directory isn't under this mount point, just make it the root.
|
|
GLoaderState.current_dir[i] = strdup("");
|
|
}
|
|
free(rp);
|
|
}
|
|
|
|
if (!lowestmounted) {
|
|
// if no disks are mapped at all, just map the Unix root filesystem to C:
|
|
GLoaderState.disks[drive_c] = strdup("/");
|
|
char *ptr = strdup(cwd + 1); // skip the initial '/'
|
|
GLoaderState.current_dir[drive_c] = ptr;
|
|
while (*ptr) { // make current dir an OS/2-style relative path.
|
|
if (*ptr == '/') {
|
|
*ptr = '\\';
|
|
}
|
|
ptr++;
|
|
}
|
|
GLoaderState.current_disk = drive_c + 1;
|
|
} else if (!lowestcwd) {
|
|
// your actual cwd isn't under any mountpoint, so pick the first mounted drive we
|
|
// saw, starting with C:\, unless we only have A:\ or B:\, then use one of those.
|
|
GLoaderState.current_disk = lowestmounted;
|
|
} else {
|
|
GLoaderState.current_disk = lowestcwd;
|
|
}
|
|
|
|
free(cwd);
|
|
|
|
GLoaderState.diskmap = diskmap;
|
|
|
|
#if 0
|
|
fprintf(stderr, "OS/2 disks:\n");
|
|
for (i = 0; i < total; i++) {
|
|
if (GLoaderState.disks[i] == NULL) continue;
|
|
fprintf(stderr, " %c: -> mountpoint='%s' cwd='%s' current=%d\n", 'A' + i, GLoaderState.disks[i], GLoaderState.current_dir[i], GLoaderState.current_disk == (i+1));
|
|
}
|
|
fprintf(stderr, "\n");
|
|
#endif
|
|
}
|
|
|
|
typedef struct LxAudioGeneratorInfo
|
|
{
|
|
LxAudioGeneratorFn fn;
|
|
void *data;
|
|
struct LxAudioGeneratorInfo *next;
|
|
} LxAudioGeneratorInfo;
|
|
|
|
static LxAudioGeneratorInfo *audio_generators = NULL;
|
|
static SDL_AudioDeviceID sdl_audio_device = 0;
|
|
static SDL_AudioSpec sdl_audio_spec;
|
|
static Uint32 no_audio_generators_timeout = 0;
|
|
|
|
static int SDLCALL closeAudioDeviceThread(void *arg)
|
|
{
|
|
const SDL_AudioDeviceID dev = (SDL_AudioDeviceID) (size_t) arg;
|
|
//printf("2ine: Closing idle audio device %u.\n", (uint) dev);
|
|
SDL_CloseAudioDevice(dev);
|
|
return 0;
|
|
}
|
|
|
|
static void closeAudioDeviceFromAnotherThread(void)
|
|
{
|
|
FIXME("Make this atomic");
|
|
const SDL_AudioDeviceID dev = sdl_audio_device;
|
|
if (!sdl_audio_device) {
|
|
return;
|
|
}
|
|
sdl_audio_device = 0;
|
|
|
|
SDL_Thread *thread = SDL_CreateThread(closeAudioDeviceThread, "2ine_close_audio_device", (void *) (size_t) dev);
|
|
if (thread) {
|
|
SDL_DetachThread(thread);
|
|
} else {
|
|
sdl_audio_device = dev; // oh well.
|
|
}
|
|
}
|
|
|
|
|
|
static void SDLCALL audioCallback(void *userdata, Uint8 *stream, int len)
|
|
{
|
|
memset(stream, '\0', len);
|
|
LxAudioGeneratorInfo *generator = audio_generators;
|
|
if (!generator) {
|
|
if (!no_audio_generators_timeout) {
|
|
no_audio_generators_timeout = SDL_GetTicks() + 5000;
|
|
} else if (SDL_TICKS_PASSED(SDL_GetTicks(), no_audio_generators_timeout)) {
|
|
closeAudioDeviceFromAnotherThread();
|
|
}
|
|
return;
|
|
}
|
|
|
|
no_audio_generators_timeout = 0;
|
|
|
|
const int samples = len / sizeof (float);
|
|
LxAudioGeneratorInfo *prev = NULL;
|
|
while (generator) {
|
|
LxAudioGeneratorInfo *next = generator->next;
|
|
if (generator->fn(generator->data, (float *) stream, samples, sdl_audio_spec.freq)) {
|
|
prev = generator;
|
|
} else {
|
|
// remove this generator, it's done.
|
|
if (prev) {
|
|
prev->next = next;
|
|
} else {
|
|
assert(generator == audio_generators);
|
|
audio_generators = next;
|
|
}
|
|
free(generator);
|
|
}
|
|
generator = next;
|
|
}
|
|
}
|
|
|
|
static int registerAudioGenerator_lib2ine(LxAudioGeneratorFn fn, void *data, const int singleton)
|
|
{
|
|
if (!sdl_audio_device) {
|
|
SDL_Init(SDL_INIT_AUDIO);
|
|
SDL_AudioSpec spec;
|
|
SDL_zero(spec);
|
|
spec.freq = 48000;
|
|
spec.format = AUDIO_F32SYS;
|
|
spec.channels = 1;
|
|
spec.samples = 1024;
|
|
spec.callback = audioCallback;
|
|
sdl_audio_device = SDL_OpenAudioDevice(NULL, 0, &spec, &sdl_audio_spec, SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
|
|
if (!sdl_audio_device) {
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
return 0;
|
|
}
|
|
no_audio_generators_timeout = 0;
|
|
SDL_PauseAudioDevice(sdl_audio_device, 0);
|
|
}
|
|
|
|
LxAudioGeneratorInfo *info = (LxAudioGeneratorInfo *) malloc(sizeof (LxAudioGeneratorInfo *));
|
|
if (!info) {
|
|
return 0;
|
|
}
|
|
|
|
info->fn = fn;
|
|
info->data = data;
|
|
|
|
SDL_LockAudioDevice(sdl_audio_device);
|
|
|
|
if (singleton) {
|
|
for (LxAudioGeneratorInfo *i = audio_generators; i != NULL; i = i->next) {
|
|
if (i->fn == fn) {
|
|
SDL_UnlockAudioDevice(sdl_audio_device);
|
|
free(info);
|
|
return 2; // already added.
|
|
}
|
|
}
|
|
}
|
|
|
|
no_audio_generators_timeout = 0;
|
|
info->next = audio_generators;
|
|
audio_generators = info;
|
|
SDL_UnlockAudioDevice(sdl_audio_device);
|
|
return 1;
|
|
} // registerAudioGenerator_lib2ine
|
|
|
|
|
|
static void lib2ine_shutdown(void)
|
|
{
|
|
if (sdl_audio_device) {
|
|
// let the audio callback run about two more times, to let any last queued things fully render.
|
|
SDL_Delay(((sdl_audio_spec.samples * 1000) / sdl_audio_spec.freq) * 2);
|
|
SDL_CloseAudioDevice(sdl_audio_device);
|
|
SDL_QuitSubSystem(SDL_INIT_AUDIO);
|
|
sdl_audio_device = 0;
|
|
}
|
|
|
|
LxAudioGeneratorInfo *generator = audio_generators;
|
|
audio_generators = NULL;
|
|
while (generator) {
|
|
LxAudioGeneratorInfo *next = generator->next;
|
|
free(generator);
|
|
generator = next;
|
|
}
|
|
|
|
for (int i = 0; i < (sizeof (GLoaderState.disks) / sizeof (GLoaderState.disks[0])); i++) {
|
|
free(GLoaderState.disks[i]);
|
|
GLoaderState.disks[i] = NULL;
|
|
}
|
|
|
|
free(GLoaderState.pib.pib_pchenv);
|
|
GLoaderState.pib.pib_pchenv = NULL;
|
|
|
|
pthread_key_delete(tlskey);
|
|
}
|
|
|
|
LX_NATIVE_CONSTRUCTOR(lib2ine)
|
|
{
|
|
if (pthread_key_create(&tlskey, NULL) != 0) {
|
|
fprintf(stderr, "Couldn't create TLS key\n");
|
|
abort();
|
|
}
|
|
|
|
memset(&GLoaderState, '\0', sizeof (GLoaderState));
|
|
GLoaderState.dosExit = dosExit_lib2ine;
|
|
GLoaderState.initOs2Tib = initOs2Tib_lib2ine;
|
|
GLoaderState.setOs2Tib = setOs2Tib_lib2ine;
|
|
GLoaderState.getOs2Tib = getOs2Tib_lib2ine;
|
|
GLoaderState.deinitOs2Tib = deinitOs2Tib_lib2ine;
|
|
GLoaderState.allocSegment = allocSegment_lib2ine;
|
|
GLoaderState.freeSegment = freeSegment_lib2ine;
|
|
GLoaderState.findSelector = findSelector_lib2ine;
|
|
GLoaderState.freeSelector = freeSelector_lib2ine;
|
|
GLoaderState.convert1616to32 = convert1616to32_lib2ine;
|
|
GLoaderState.convert32to1616 = convert32to1616_lib2ine;
|
|
GLoaderState.loadModule = loadModule_lib2ine;
|
|
GLoaderState.makeUnixPath = makeUnixPath_lib2ine;
|
|
GLoaderState.terminate = terminate_lib2ine;
|
|
GLoaderState.registerAudioGenerator = registerAudioGenerator_lib2ine;
|
|
GLoaderState.lib2ine_shutdown = lib2ine_shutdown;
|
|
|
|
// VMware emulates the PC Speaker on a sound card _really_ quietly.
|
|
GLoaderState.beep_volume = 0.05f;
|
|
|
|
cfgLoadFiles();
|
|
prepOs2Drives();
|
|
|
|
struct rlimit rlim;
|
|
if (getrlimit(RLIMIT_STACK, &rlim) == -1) {
|
|
rlim.rlim_cur = 8 * 1024 * 1024; // oh well.
|
|
}
|
|
GLoaderState.initOs2Tib(GLoaderState.main_tibspace, &rlim, rlim.rlim_cur, 0); // hopefully that's close enough on the stack address.
|
|
GLoaderState.main_tib_selector = GLoaderState.setOs2Tib(GLoaderState.main_tibspace);
|
|
|
|
// these override config files (for now).
|
|
if (getenv("TRACE_NATIVE")) {
|
|
GLoaderState.trace_native = 1;
|
|
}
|
|
|
|
if (getenv("TRACE_EVENTS")) {
|
|
GLoaderState.trace_events = 1;
|
|
}
|
|
|
|
GLoaderState.subprocess = (getenv("IS_2INE") != NULL);
|
|
|
|
initPib();
|
|
}
|
|
|
|
LX_NATIVE_DESTRUCTOR(lib2ine)
|
|
{
|
|
lib2ine_shutdown();
|
|
}
|
|
|
|
// end of lib2ine.c ...
|
|
|