Files
2ine-tms34010/native/doscalls.c
T
opencode.energia.arti.ee 68f873784b
Build / Linux (push) Waiting to run
tms34010: run TI TMS34010 OS/2 tools (gspa/gsplnk/gsprom) under 2ine
Fixes needed to run the 1990 TI 'TMS340 Family Code Generation Tools r5.01'
(16-bit OS/2 1.x NE, DOSCALLS-only) on 2ine:

* CMake: fall back to the system SDL2 headers when /usr/local/include/SDL2
  is absent; the hard-coded path broke the build on stock distros.

* doscalls16: implement the entry points these tools import
  - Dos16GetDateTime (33) with the OS/2 1.x *hours-first* 10-byte layout
    (the year-first 8-byte layout crashes gspa in asctime/localtime)
  - Dos16NewSize (68)
  - Dos16GetHugeShift (135) / Dos16HugeIncr (136)
  - Dos16CreateCSAlias (43), Dos16DevConfig (52), Dos16Move (67)

* lx_loader: walk NE fixup chains to the segment's 64K virtual size instead
  of its file size, so fixups pointing into the BSS tail are applied. gspa
  segfaulted in a lcall to its CRT exit handler because of this.

* doscalls: do not assert that the host PID fits in 16 bits (pid_max is
  4194304 on current kernels); the 16-bit API truncates anyway.

Verified: gspa assembles a 2,412-line TMS34010 source with 'No Errors,
No Warnings', gsplnk links it with rts.lib, gsprom converts to Tek-hex.
The C compiler child (gspac via gspcl) still hits the lxConvert1616to32
selector assertion; see TMS34010.md.
2026-10-31 20:50:11 +00:00

3871 lines
124 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.
*/
#include "os2native16.h"
#include "doscalls.h"
#include <unistd.h>
#include <limits.h>
#include <time.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include "doscalls-lx.h"
static pthread_mutex_t GMutexDosCalls;
static pthread_mutex_t GMutexDosBeep;
typedef struct ExitListItem
{
PFNEXITLIST fn;
uint32 priority;
struct ExitListItem *next;
} ExitListItem;
static ExitListItem *GExitList = NULL;
// Flags returned for character devices from DosQueryHType()...
enum {
DAW_STDIN = (1 << 0),
DAW_STDOUT = (1 << 1),
DAW_NUL = (1 << 2),
DAW_CLOCK = (1 << 3),
DAW_SPEC = (1 << 4),
DAW_ADD_ON = (1 << 5),
DAW_GIOCTL = (1 << 6),
DAW_LEVEL0 = 0,
DAW_LEVEL1 = (1 << 7),
DAW_LEVEL2 = (1 << 8),
DAW_LEVEL3 = (1 << 7) | (1 << 8),
DAW_FCNLEV = (1 << 7) | (1 << 8) | (1 << 9),
// ((1 << 10) is reserved, I think.)
DAW_OPENCLOSE = (1 << 11),
DAW_SHARE = (1 << 12),
DAW_NONIBM = (1 << 13),
DAW_IDC = (1 << 14),
DAW_CHARACTER = (1 << 15)
};
typedef struct HFileInfo
{
int fd; // unix file descriptor.
ULONG type; // file, pipe, device, etc.
ULONG attr; // for character devices, DAW_*
ULONG flags; // OPEN_FLAGS_*
} HFileInfo;
static HFileInfo *HFiles = NULL;
static uint32 MaxHFiles = 0;
typedef struct Thread
{
pthread_t thread;
size_t stacklen;
PFNTHREAD fn;
ULONG fnarg;
uint16 selector;
struct Thread *prev;
struct Thread *next;
} Thread;
static Thread *GDeadThreads = NULL;
typedef struct
{
pthread_mutex_t mutex;
pthread_cond_t condition;
volatile int posted;
volatile int waiting;
} EventSem;
typedef struct
{
DIR *dirp;
ULONG attr;
ULONG level;
char pattern[CCHMAXPATHCOMP];
} DirFinder;
static DirFinder GHDir1;
static GINFOSEG *ginfo;
static SEL ginfosel;
static LINFOSEG *linfo;
static SEL linfosel;
static HDIR *hdir16 = 0;
static int grabLock(pthread_mutex_t *lock)
{
return (pthread_mutex_lock(lock) == 0);
} // grabLock
static void ungrabLock(pthread_mutex_t *lock)
{
pthread_mutex_unlock(lock);
} // ungrabLock
static void timespecNowPlusMilliseconds(struct timespec *waittime, const ULONG ulTimeout)
{
clock_gettime(CLOCK_REALTIME, waittime);
waittime->tv_sec += ulTimeout / 1000;
waittime->tv_nsec += (ulTimeout % 1000) * 1000000;
if (waittime->tv_nsec >= 1000000000) {
waittime->tv_sec++;
waittime->tv_nsec -= 1000000000;
assert(waittime->tv_nsec < 1000000000);
} // if
} // timespecNowPlusMilliseconds
static void initHFileInfoFromUnixFd(const int fd, HFileInfo *info)
{
struct stat statbuf;
if ((fd != -1) && (fstat(fd, &statbuf) == 0)) {
ULONG type = 0;
switch (statbuf.st_mode & S_IFMT) {
case S_IFREG:
type = 0;
break;
case S_IFBLK:
case S_IFCHR:
type = 1;
break;
case S_IFIFO:
case S_IFSOCK:
default:
type = 2;
break;
} // switch
info->fd = fd;
info->type = type;
FIXME("What should this be?");
info->attr = DAW_STDIN | DAW_STDOUT | DAW_LEVEL1 | DAW_CHARACTER;
info->flags = 0;
} else {
info->fd = -1;
info->type = 0;
info->attr = 0;
info->flags = 0;
} // else
} // initHFileInfoFromUnixFd
APIRET DosGetInfoBlocks(PTIB *pptib, PPIB *pppib)
{
TRACE_NATIVE("DosGetInfoBlocks(%p, %p)", pptib, pppib);
if (pptib != NULL)
*pptib = (PTIB) LX_GETTIB();
if (pppib != NULL)
*pppib = (PPIB) &GLoaderState.pib;
return 0;
} // DosGetInfoBlocks
APIRET DosQuerySysInfo(ULONG first, ULONG last, PVOID _buf, ULONG buflen)
{
TRACE_NATIVE("DosQuerySysInfo(%u, %u, %p, %u)", (uint) first, (uint) last, _buf, (uint) buflen);
uint32 *buf = (uint32 *) _buf;
if (last < first) return ERROR_INVALID_PARAMETER;
if ( (buflen / sizeof (uint32)) < ((last - first) + 1) ) return ERROR_BUFFER_OVERFLOW;
for (uint32 varid = first; varid <= last; varid++) {
switch (varid) {
case QSV_MAX_PATH_LENGTH: *(buf++) = PATH_MAX; break;
case QSV_MAX_TEXT_SESSIONS: *(buf++) = 999999; break;
case QSV_MAX_PM_SESSIONS: *(buf++) = 999999; break;
case QSV_MAX_VDM_SESSIONS: *(buf++) = 0; break;
case QSV_BOOT_DRIVE: *(buf++) = 3; break; // "C:"
case QSV_DYN_PRI_VARIATION: *(buf++) = 1; break;
case QSV_MAX_WAIT: *(buf++) = 1; break;
case QSV_MIN_SLICE: *(buf++) = 1; break;
case QSV_MAX_SLICE: *(buf++) = 10; break;
case QSV_PAGE_SIZE: *(buf++) = 4096; break;
// !!! FIXME: change the version number in some way so apps can know this isn't actually OS/2.
case QSV_VERSION_MAJOR: *(buf++) = 20; break; // OS/2 Warp 4.5
case QSV_VERSION_MINOR: *(buf++) = 45; break; // OS/2 Warp 4.5
case QSV_VERSION_REVISION: *(buf++) = 0; break; // OS/2 Warp 4.5
case QSV_MS_COUNT: {
static long startoffset = 0;
if (startoffset == 0) {
struct timespec ts;
struct sysinfo info;
sysinfo(&info);
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
startoffset = info.uptime - ((long) ts.tv_sec);
} // if
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
*(buf++) = (uint32) (((long) ts.tv_sec) - startoffset);
break;
} // case
case QSV_TIME_LOW: *(buf++) = (uint32) time(NULL); break;
case QSV_TIME_HIGH: *(buf++) = 0; break; // !!! FIXME: 32-bit Linux time_t is 32-bits!
case QSV_TOTPHYSMEM: {
struct sysinfo info;
sysinfo(&info);
*(buf++) = (uint32) info.totalram;
break;
} // case
case QSV_TOTRESMEM: {
struct sysinfo info;
sysinfo(&info);
*(buf++) = (uint32) info.totalram - info.freeram; // !!! FIXME: probably not right?
break;
} // case
case QSV_TOTAVAILMEM: {
struct sysinfo info;
sysinfo(&info);
*(buf++) = (uint32) info.freeram;
break;
} // case
case QSV_MAXPRMEM: *(buf++) = 0x7FFFFFFF; break; // !!! FIXME: should I list all 4 gigs? Also: what about machines with < 2 gigs?
case QSV_MAXSHMEM: *(buf++) = 0x7FFFFFFF; break; // !!! FIXME: should I list all 4 gigs? Also: what about machines with < 2 gigs?
case QSV_TIMER_INTERVAL: *(buf++) = 10; break;
case QSV_MAX_COMP_LENGTH: *(buf++) = NAME_MAX; break;
case QSV_FOREGROUND_FS_SESSION: *(buf++) = 1; break; // !!! FIXME
case QSV_FOREGROUND_PROCESS: *(buf++) = 1; break; // !!! FIXME
default: return ERROR_INVALID_PARAMETER;
} // switch
} // for
return NO_ERROR;
} // DosQuerySysInfo
APIRET DosQueryModuleName(HMODULE hmod, ULONG buflen, PCHAR buf)
{
TRACE_NATIVE("DosQueryModuleName(%u, %u, %p)", (uint) hmod, (uint) buflen, buf);
const LxModule *lxmod = (LxModule *) hmod;
// !!! FIXME: error 6 ERROR_INVALID_HANDLE
if (strlen(lxmod->os2path) >= buflen)
return ERROR_BAD_LENGTH;
strcpy(buf, lxmod->os2path);
return NO_ERROR;
} // DosQueryModuleName
APIRET DosScanEnv(PSZ name, PSZ *outval)
{
TRACE_NATIVE("DosScanEnv('%s', %p)", name, outval);
char *env = GLoaderState.pib.pib_pchenv;
const size_t len = strlen(name);
while (*env) {
if ((strncmp(env, name, len) == 0) && (env[len] == '=')) {
*outval = env + len + 1;
return NO_ERROR;
} // if
env += strlen(env) + 1;
} // while
return ERROR_ENVVAR_NOT_FOUND;
} // DosScanEnv
static int getHFileUnixDescriptor(HFILE h)
{
grabLock(&GMutexDosCalls);
const int fd = (h < MaxHFiles) ? HFiles[h].fd : -1;
ungrabLock(&GMutexDosCalls);
return fd;
} // getHFileUnixDescriptor
static APIRET DosWrite_implementation(HFILE h, PVOID buf, ULONG buflen, PULONG actual)
{
const int fd = getHFileUnixDescriptor(h);
if (fd == -1)
return ERROR_INVALID_HANDLE;
// !!! FIXME: writing to a terminal should probably convert CR/LF to LF.
const int rc = write(fd, buf, buflen);
if (rc < 0) {
*actual = 0;
return ERROR_DISK_FULL; // !!! FIXME: map these errors.
} // if
*actual = (uint32) rc;
return NO_ERROR;
} // DosWrite_implementation
APIRET DosWrite(HFILE h, PVOID buf, ULONG buflen, PULONG actual)
{
TRACE_NATIVE("DosWrite(%u, %p, %u, %p)", (uint) h, buf, (uint) buflen, actual);
return DosWrite_implementation(h, buf, buflen, actual);
} // DosWrite
static void runDosExitList(const uint32 why)
{
ExitListItem *next = NULL;
for (ExitListItem *item = GExitList; item; item = next) {
// don't run any of these more than once:
// http://www.verycomputer.com/3_c1f6e02c06ed108e_1.htm
PFNEXITLIST fn = item->fn;
next = item->next;
GExitList = next;
free(item);
//printf("calling dosexitlist item %p\n", fn); fflush(stdout);
fn(why);
//printf("dosexitlist item %p returned!\n", fn); fflush(stdout);
} // for
} // runDosExitList
static VOID DosExit_implementation(ULONG action, ULONG exitcode)
{
// !!! FIXME: what does a value other than 0 or 1 do here?
if (action == EXIT_THREAD) {
// !!! FIXME: If last thread: terminate process.
FIXME("if last thread, treat as DosExit(1)"); // right now it will terminate process like exit(0), without running the exit list or reporting exitcode.
pthread_exit(NULL); // DosWaitThread doesn't report exit codes.
} // if
// terminate the process.
runDosExitList(TC_EXIT);
GLoaderState.terminate(exitcode);
} // DosExit_implementation
VOID DosExit(ULONG action, ULONG exitcode)
{
TRACE_NATIVE("DosExit(%u, %u)", (uint) action, (uint) exitcode);
DosExit_implementation(action, exitcode);
} // DosExit
APIRET DosExitList(ULONG ordercode, PFNEXITLIST fn)
{
TRACE_NATIVE("DosExitList(%u, %p)", (uint) ordercode, fn);
if (fn == NULL)
return ERROR_INVALID_FUNCTION;
const uint8 cmd = ordercode & 0xFF;
const uint8 arg = (ordercode >> 8) & 0xFF;
ExitListItem *prev = NULL;
ExitListItem *item = NULL;
// !!! FIXME: docs say this is illegal, but have to check what OS/2 actually does here.
if ((cmd != EXLST_ADD) && (arg != 0))
return ERROR_INVALID_DATA;
switch (cmd) {
case EXLST_ADD: {
ExitListItem *newitem = (ExitListItem *) malloc(sizeof (ExitListItem));
if (!newitem)
return ERROR_NOT_ENOUGH_MEMORY;
newitem->fn = fn;
for (item = GExitList; item; item = item->next) {
if (item->priority >= ((uint32) arg))
break;
prev = item;
} // for
if (prev) {
prev->next = newitem;
newitem->next = item;
} else {
newitem->next = GExitList;
GExitList = newitem;
} // else
return NO_ERROR;
} // case
case EXLST_REMOVE: {
for (item = GExitList; item; item = item->next) {
if (item->fn == fn) {
if (prev)
prev->next = item->next;
else
GExitList = item->next;
free(item);
return NO_ERROR;
} // if
prev = item;
} // for
return ERROR_INVALID_FUNCTION; // !!! FIXME: yeah?
} // case
case EXLST_EXIT:
return NO_ERROR; // ... just treat this as a no-op, I guess...?
default: return ERROR_INVALID_DATA;
} // switch
return NO_ERROR;
} // DosExitList
APIRET DosCreateEventSem(PSZ name, PHEV phev, ULONG attr, BOOL32 state)
{
TRACE_NATIVE("DosCreateEventSem('%s', %p, %u, %u)", name, phev, (uint) attr, (uint) state);
if (name != NULL) {
FIXME("implement named semaphores");
return ERROR_NOT_ENOUGH_MEMORY;
} // if
if (attr & DC_SEM_SHARED) {
FIXME("implement shared semaphores");
} // if
assert(sizeof (HEV) == sizeof (EventSem *));
EventSem *sem = (EventSem *) malloc(sizeof (EventSem));
if (sem == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
if (pthread_mutex_init(&sem->mutex, NULL) == -1) {
free(sem);
return ERROR_NOT_ENOUGH_MEMORY;
} // if
if (pthread_cond_init(&sem->condition, NULL) == -1) {
pthread_mutex_destroy(&sem->mutex);
free(sem);
return ERROR_NOT_ENOUGH_MEMORY;
} // if
sem->posted = state ? 1 : 0;
*phev = (HEV) sem;
return NO_ERROR;
} // DosCreateEventSem
APIRET DosCreateMutexSem(PSZ name, PHMTX phmtx, ULONG attr, BOOL32 state)
{
TRACE_NATIVE("DosCreateMutexSem('%s', %p, %u, %u)", name, phmtx, (uint) attr, (uint) state);
if (name != NULL) {
FIXME("implement named mutexes");
return ERROR_NOT_ENOUGH_MEMORY;
} // if
if (attr & DC_SEM_SHARED) {
FIXME("implement shared mutexes");
} // if
assert(sizeof (HMTX) == sizeof (pthread_mutex_t *));
pthread_mutex_t *mtx = (pthread_mutex_t *) malloc(sizeof (pthread_mutex_t));
if (mtx == NULL)
return ERROR_NOT_ENOUGH_MEMORY;
// OS/2 mutexes are recursive.
pthread_mutexattr_t muxattr;
pthread_mutexattr_init(&muxattr);
pthread_mutexattr_settype(&muxattr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutexattr_setrobust(&muxattr, PTHREAD_MUTEX_ROBUST);
const int rc = pthread_mutex_init(mtx, &muxattr);
pthread_mutexattr_destroy(&muxattr);
if (rc == -1) {
free(mtx);
return ERROR_NOT_ENOUGH_MEMORY;
} // if
if (state)
pthread_mutex_lock(mtx);
*phmtx = (HMTX) mtx;
return NO_ERROR;
} // DosCreateMutexSem
// !!! FIXME: this is obviously not correct.
APIRET DosSetExceptionHandler(PEXCEPTIONREGISTRATIONRECORD rec)
{
TRACE_NATIVE("DosSetExceptionHandler(%p)", rec);
return NO_ERROR;
} // DosSetExceptionHandler
// !!! FIXME: this is obviously not correct.
APIRET DosUnsetExceptionHandler(PEXCEPTIONREGISTRATIONRECORD rec)
{
TRACE_NATIVE("DosUnsetExceptionHandler(%p)", rec);
return NO_ERROR;
} // DosSetExceptionHandler
#if !LX_LEGACY
ULONG DosFlatToSel(VOID) { return 0; }
#else
ULONG _DosFlatToSel(PVOID ptr)
{
TRACE_NATIVE("DosFlatToSel(%p)", ptr);
return GLoaderState.convert32to1616(ptr);
} // _DosFlatToSel
// DosFlatToSel() passes its argument in %eax, so a little asm to bridge that...
// Note that this isn't syscall calling conventions at all; CSet/2 generates
// thunking code that expects this function to preserve %ecx, so we'll just
// go ahead and save a bunch of stuff, just in case.
__asm__ (
".globl DosFlatToSel \n\t"
".type DosFlatToSel, @function \n\t"
"DosFlatToSel: \n\t"
" pushl %ebx \n\t" // save off a bunch of stuff. Better safe than sorry.
" pushl %ecx \n\t"
" pushl %edx \n\t"
" pushl %edi \n\t"
" pushl %esi \n\t"
" pushl %eax \n\t" // the actual argument to DosFlatToSel.
" call _DosFlatToSel \n\t"
" addl $4, %esp \n\t" // clear argument to DosFlatToSel.
" popl %esi \n\t"
" popl %edi \n\t"
" popl %edx \n\t" // save off a bunch of stuff.
" popl %ecx \n\t"
" popl %ebx \n\t"
" ret \n\t"
".size _DosFlatToSel, .-_DosFlatToSel \n\t"
);
#endif
APIRET DosSetSignalExceptionFocus(BOOL32 flag, PULONG pulTimes)
{
TRACE_NATIVE("DosSetSignalExceptionFocus(%u, %p)", (uint) flag, pulTimes);
if (flag == 0) {
if (GLoaderState.main_module->signal_exception_focus_count == 0)
return ERROR_ALREADY_RESET;
GLoaderState.main_module->signal_exception_focus_count--;
} else if (flag == 1) {
if (GLoaderState.main_module->signal_exception_focus_count == 0xFFFFFFFF)
return ERROR_NESTING_TOO_DEEP;
GLoaderState.main_module->signal_exception_focus_count++;
} else {
// !!! FIXME: does OS/2 do something if flag != 0 or 1?
} // else
if (pulTimes)
*pulTimes = GLoaderState.main_module->signal_exception_focus_count;
// !!! FIXME: I guess enable/disable SIGINT handler here?
return NO_ERROR;
} // DosSetSignalExceptionFocus
APIRET DosSetRelMaxFH(PLONG pincr, PULONG pcurrent)
{
TRACE_NATIVE("DosSetRelMaxFH(%p, %p)", pincr, pcurrent);
#if 0
struct rlimit rlim;
int rc = getrlimit(RLIMIT_NOFILE, &rlim);
assert(rc == 0); // this shouldn't fail...right?
if (pcurrent)
*pcurrent = (ULONG) rlim.rlim_cur;
if (pincr) {
rlim.rlim_cur = (rlim_t) (((LONG) rlim.rlim_cur) + *pincr);
if (setrlimit(RLIMIT_NOFILE, &rlim) == 0) {
if (pcurrent)
*pcurrent = (ULONG) rlim.rlim_cur;
} // if
} // if
#endif
if (pincr != NULL) {
const LONG incr = *pincr;
// OS/2 API docs say reductions in file handles are advisory, so we
// ignore them outright. Fight me.
if (incr > 0) {
grabLock(&GMutexDosCalls);
HFileInfo *info = (HFileInfo *) realloc(HFiles, sizeof (HFileInfo) * (MaxHFiles + incr));
if (info != NULL) {
HFiles = info;
FIXME("this needs to initialize the end of the array, not the front");
for (LONG i = 0; i < incr; i++, info++) {
info->fd = -1;
info->type = 0;
info->attr = 0;
info->flags = 0;
} // for
MaxHFiles += incr;
} // if
ungrabLock(&GMutexDosCalls);
} // if
} // if
if (pcurrent != NULL) {
grabLock(&GMutexDosCalls);
*pcurrent = MaxHFiles;
ungrabLock(&GMutexDosCalls);
} // if
return NO_ERROR; // always returns NO_ERROR even if it fails.
} // DosSetRelMaxFH
APIRET DosAllocMem(PPVOID ppb, ULONG cb, ULONG flag)
{
TRACE_NATIVE("DosAllocMem(%p, %u, %u)", ppb, (uint) cb, (uint) flag);
// !!! FIXME: this API is actually much more complicated than this.
*ppb = calloc(1, cb);
if (!*ppb)
return ERROR_NOT_ENOUGH_MEMORY;
return NO_ERROR;
} // DosAllocMem
APIRET DosSubSetMem(PVOID pbBase, ULONG flag, ULONG cb)
{
TRACE_NATIVE("DosSubSetMem(%p, %u, %u)", pbBase, (uint) flag, (uint) cb);
return NO_ERROR; // !!! FIXME: obviously wrong
} // DosSubSetMem
APIRET DosSubAllocMem(PVOID pbBase, PPVOID ppb, ULONG cb)
{
TRACE_NATIVE("DosSubAllocMem(%p, %p, %u)", pbBase, ppb, (uint) cb);
*ppb = malloc(cb); // !!! FIXME: obviously wrong
if (!*ppb)
return ERROR_NOT_ENOUGH_MEMORY;
return NO_ERROR;
} // DosSubAllocMem
static APIRET DosQueryHType_implementation(HFILE hFile, PULONG pType, PULONG pAttr)
{
APIRET retval = NO_ERROR;
grabLock(&GMutexDosCalls);
if ((hFile >= MaxHFiles) || (HFiles[hFile].fd == -1))
retval = ERROR_INVALID_HANDLE;
else {
// OS/2 will dereference a NULL pointer here, but I can't do it...!!!
if (pType) *pType = HFiles[hFile].type;
if (pAttr) *pAttr = HFiles[hFile].attr;
} // else
ungrabLock(&GMutexDosCalls);
return retval;
} // DosQueryHType_implementation
APIRET DosQueryHType(HFILE hFile, PULONG pType, PULONG pAttr)
{
TRACE_NATIVE("DosQueryHType(%u, %p, %p)", (uint) hFile, pType, pAttr);
return DosQueryHType_implementation(hFile, pType, pAttr);
} // DosQueryHType
APIRET DosSetMem(PVOID pb, ULONG cb, ULONG flag)
{
TRACE_NATIVE("DosSetMem(%p, %u, %u)", pb, (uint) cb, (uint) flag);
return NO_ERROR; // !!! FIXME: obviously wrong.
} // DosSetMem
APIRET DosGetDateTime(PDATETIME pdt)
{
TRACE_NATIVE("DosGetDateTime(%p)", pdt);
// we can't use time() for this, since we need sub-second resolution.
struct timeval tv;
gettimeofday(&tv, NULL);
struct tm ltm;
localtime_r(&tv.tv_sec, &ltm);
pdt->hours = ltm.tm_hour;
pdt->minutes = ltm.tm_min;
pdt->seconds = (ltm.tm_sec == 60) ? 59 : ltm.tm_sec; // !!! FIXME: I assume OS/2 doesn't support leap seconds...
pdt->hundredths = tv.tv_usec / 10000000; // microseconds to hundreths.
pdt->day = ltm.tm_mday;
pdt->month = ltm.tm_mon + 1;
pdt->year = ltm.tm_year + 1900;
pdt->timezone = -(ltm.tm_gmtoff / 60); // OS/2 is minutes west of GMT, Unix is seconds east. Convert and flip!
pdt->weekday = ltm.tm_wday;
return NO_ERROR; // never returns failure.
} // DosGetDateTime
static char *makeUnixPath(const char *os2path, APIRET *err)
{
return GLoaderState.makeUnixPath(os2path, (uint32 *) err);
} // makeUnixPath
static APIRET doDosOpen(PSZ pszFileName, PHFILE pHf, PULONG pulAction, LONGLONG cbFile, ULONG ulAttribute, ULONG fsOpenFlags, ULONG fsOpenMode, PEAOP2 peaop2)
{
int isReadOnly = 0;
int flags = 0;
const ULONG access_mask = OPEN_ACCESS_READONLY | OPEN_ACCESS_WRITEONLY | OPEN_ACCESS_READWRITE;
switch (fsOpenMode & access_mask) {
case OPEN_ACCESS_READONLY: isReadOnly = 1; flags |= O_RDONLY; break;
case OPEN_ACCESS_WRITEONLY: flags |= O_WRONLY; break;
case OPEN_ACCESS_READWRITE: flags |= O_RDWR; break;
default: return ERROR_INVALID_PARAMETER;
} // switch
if (isReadOnly && (cbFile != 0))
return ERROR_INVALID_PARAMETER;
else if (ulAttribute & FILE_DIRECTORY)
return ERROR_INVALID_PARAMETER; // !!! FIXME: I assume you can't create a directory with DosOpen...right?
else if (isReadOnly && ((fsOpenFlags & 0xF0) == OPEN_ACTION_CREATE_IF_NEW))
return ERROR_INVALID_PARAMETER; // !!! FIXME: is this invalid on OS/2?
else if (isReadOnly && ((fsOpenFlags & 0x0F) == OPEN_ACTION_REPLACE_IF_EXISTS))
return ERROR_INVALID_PARAMETER; // !!! FIXME: is this invalid on OS/2?
else if (fsOpenFlags & 0xfffffe00) // bits (1<<8) through (1<<31) are reserved, must be zero.
return ERROR_INVALID_PARAMETER;
else if (fsOpenFlags & OPEN_FLAGS_DASD) // no good is going to come of this.
return ERROR_OPEN_FAILED;
else if (fsOpenMode & 0xffff0000) // bits (1<<16) through (1<<31) are reserved, must be zero.
return ERROR_INVALID_PARAMETER;
if (peaop2 != NULL) {
FIXME("EAs not yet implemented");
return ERROR_OPEN_FAILED;
}
switch (fsOpenFlags & 0xF0) {
case OPEN_ACTION_FAIL_IF_NEW:
break; // just don't O_CREAT and you're fine.
case OPEN_ACTION_CREATE_IF_NEW:
flags |= O_CREAT | O_EXCL; // the O_EXCL is intentional! see below.
break;
} // switch
int mustNotExist = 0;
int isExclusive = 0;
int isReplacing = 0;
switch (fsOpenFlags & 0x0F) {
case OPEN_ACTION_FAIL_IF_EXISTS:
if (isReadOnly)
mustNotExist = 1;
else {
isExclusive = 1;
flags |= O_CREAT | O_EXCL;
} // else
break;
case OPEN_ACTION_OPEN_IF_EXISTS:
break; // nothing to do here.
case OPEN_ACTION_REPLACE_IF_EXISTS: // right now, we already failed above if readonly.
isReplacing = 1;
flags |= O_TRUNC; // have the open call nuke it, and then we'll ftruncate to grow it if necessary.
break;
} // switch
if (fsOpenMode & OPEN_FLAGS_WRITE_THROUGH)
flags |= O_SYNC; // !!! FIXME: this flag is supposed to drop when inherited by children, but fcntl() can't do that on Linux.
if (fsOpenMode & OPEN_FLAGS_NOINHERIT)
flags |= O_CLOEXEC;
if (fsOpenMode & OPEN_FLAGS_FAIL_ON_ERROR)
FIXME("need other file i/o APIs to do a system dialog box if this handle has i/o failures!");
// O_DIRECT isn't supported on ZFS-on-Linux, so I'm already not on board,
// but also: do we care if programs intended to run on a system with a
// few megabytes of RAM--on an OS that didn't have a resizeable file
// cache!--writes into a multi-gigabyte machine's flexible cache? I don't!
//if (fsOpenMode & OPEN_FLAGS_NO_CACHE)
// flags |= O_DIRECT;
FIXME("There are fsOpenMode flags we currently ignore (like sharing support)");
const mode_t mode = S_IRUSR | ((ulAttribute & FILE_READONLY) ? 0 : S_IWUSR);
FIXME("Most of the file attributes don't make sense on Unix, but we could stuff them in EAs");
HFileInfo *info = HFiles;
uint32 i;
if (!grabLock(&GMutexDosCalls))
return ERROR_SYS_INTERNAL;
for (i = 0; i < MaxHFiles; i++, info++) {
if (info->fd == -1) { // available?
info->fd = -2;
break;
} // if
} // for
ungrabLock(&GMutexDosCalls);
if (i == MaxHFiles) {
return ERROR_TOO_MANY_OPEN_FILES;
}
const HFILE hf = i;
APIRET err = NO_ERROR;
char *unixpath = makeUnixPath(pszFileName, &err);
if (!unixpath) {
info->fd = -1;
return err;
} // if
//if (strcmp(pszFileName, unixpath) != 0) { fprintf(stderr, "DosOpen: converted '%s' to '%s'\n", pszFileName, unixpath); }
int fd = open(unixpath, flags, mode);
// if failed trying exclusive-create because file already exists, but we
// didn't explicitly _need_ exclusivity, retry without O_EXCL. We always
// try O_EXCL at first when using O_CREAT, so we can tell atomically if
// we were the one that created the file, to satisfy pulAction.
int existed = 0;
if ((fd == -1) && (flags & (O_CREAT|O_EXCL)) && (errno == EEXIST) && !isExclusive) {
existed = 1;
fd = open(unixpath, flags & ~O_EXCL, mode);
} else if (fd != -1) {
existed = 1;
} // else if
free(unixpath);
if (fd != -1) {
if (mustNotExist) {
close(fd);
info->fd = -1;
return ERROR_OPEN_FAILED; // !!! FIXME: what error does OS/2 return for this?
} else if ( (!existed || isReplacing) && (cbFile > 0) ) {
if (ftruncate(info->fd, cbFile) == -1) {
const int e = errno;
close(fd);
info->fd = -1;
errno = e; // let the switch below sort out the OS/2 error code.
} // if
} // else if
} // if
if (fd != -1) {
if (isReplacing)
FIXME("Replacing a file should delete all its EAs, too");
} // if
initHFileInfoFromUnixFd(fd, info);
if (fd == -1) {
switch (errno) {
case EACCES: return ERROR_ACCESS_DENIED;
case EDQUOT: return ERROR_CANNOT_MAKE;
case EEXIST: return ERROR_CANNOT_MAKE;
case EISDIR: return ERROR_ACCESS_DENIED;
case EMFILE: return ERROR_TOO_MANY_OPEN_FILES;
case ENFILE: return ERROR_TOO_MANY_OPEN_FILES;
case ENAMETOOLONG: return ERROR_FILENAME_EXCED_RANGE;
case ENOSPC: return ERROR_DISK_FULL;
case ENOMEM: return ERROR_NOT_ENOUGH_MEMORY;
case ENOENT: return ERROR_FILE_NOT_FOUND; // !!! FIXME: could be PATH_NOT_FOUND too, depending on circumstances.
case ENOTDIR: return ERROR_PATH_NOT_FOUND;
case EPERM: return ERROR_ACCESS_DENIED;
case EROFS: return ERROR_ACCESS_DENIED;
case ETXTBSY: return ERROR_ACCESS_DENIED;
default: return ERROR_OPEN_FAILED; // !!! FIXME: debug logging about missin errno case.
} // switch
__builtin_unreachable();
} // if
info->flags = fsOpenFlags;
if (isReplacing)
*pulAction = FILE_TRUNCATED;
else if (existed)
*pulAction = FILE_EXISTED;
else
*pulAction = FILE_CREATED;
*pHf = hf;
return NO_ERROR;
} // doDosOpen
APIRET DosOpen(PSZ pszFileName, PHFILE pHf, PULONG pulAction, ULONG cbFile, ULONG ulAttribute, ULONG fsOpenFlags, ULONG fsOpenMode, PEAOP2 peaop2)
{
TRACE_NATIVE("DosOpen('%s', %p, %p, %u, %u, %u, %u, %p)", pszFileName, pHf, pulAction, (uint) cbFile, (uint) ulAttribute, (uint) fsOpenFlags, (uint) fsOpenMode, peaop2);
return doDosOpen(pszFileName, pHf, pulAction, (LONGLONG) cbFile, ulAttribute, fsOpenFlags, fsOpenMode, peaop2);
} // DosOpen
APIRET DosRequestMutexSem(HMTX hmtx, ULONG ulTimeout)
{
TRACE_NATIVE("DosRequestMutexSem(%u, %u)", (uint) hmtx, (uint) ulTimeout);
if (!hmtx)
return ERROR_INVALID_HANDLE;
pthread_mutex_t *mutex = (pthread_mutex_t *) hmtx;
int rc;
if (ulTimeout == SEM_IMMEDIATE_RETURN) {
rc = pthread_mutex_trylock(mutex);
} else if (ulTimeout == SEM_INDEFINITE_WAIT) {
rc = pthread_mutex_lock(mutex);
} else {
struct timespec waittime;
timespecNowPlusMilliseconds(&waittime, ulTimeout);
rc = pthread_mutex_timedlock(mutex, &waittime);
} // else
switch (rc) {
case 0: return NO_ERROR;
case EAGAIN: return ERROR_TOO_MANY_SEM_REQUESTS;
case EOWNERDEAD: return ERROR_SEM_OWNER_DIED;
case ETIMEDOUT: return ERROR_TIMEOUT;
default: break;
} // switch
return ERROR_INVALID_HANDLE; // oh well.
} // DosRequestMutexSem
APIRET DosReleaseMutexSem(HMTX hmtx)
{
TRACE_NATIVE("DosReleaseMutexSem(%u)", (uint) hmtx);
if (!hmtx)
return ERROR_INVALID_HANDLE;
pthread_mutex_t *mutex = (pthread_mutex_t *) hmtx;
const int rc = pthread_mutex_unlock(mutex);
if (rc == 0)
return NO_ERROR;
else if (rc == EPERM)
return ERROR_NOT_OWNER;
return ERROR_INVALID_HANDLE;
} // DosReleaseMutexSem
static APIRET DosSetFilePtr_implementation(HFILE hFile, LONG ib, ULONG method, PULONG ibActual)
{
int whence;
switch (method) {
case FILE_BEGIN: whence = SEEK_SET; break;
case FILE_CURRENT: whence = SEEK_CUR; break;
case FILE_END: whence = SEEK_END; break;
default: return ERROR_INVALID_FUNCTION;
} // switch
const int fd = getHFileUnixDescriptor(hFile);
if (fd == -1)
return ERROR_INVALID_HANDLE;
const off_t pos = lseek(fd, (off_t) ib, whence);
if (pos == -1) {
if (errno == EINVAL)
return ERROR_NEGATIVE_SEEK;
return ERROR_INVALID_FUNCTION; // !!! FIXME: ?
} // if
if (ibActual)
*ibActual = (ULONG) pos;
return NO_ERROR;
} // DosSetFilePtr_implementation
APIRET DosSetFilePtr(HFILE hFile, LONG ib, ULONG method, PULONG ibActual)
{
TRACE_NATIVE("DosSetFilePtr(%u, %d, %u, %p)", (uint) hFile, (int) ib, (uint) method, ibActual);
return DosSetFilePtr_implementation(hFile, ib, method, ibActual);
} // DosSetFilePtr
static APIRET DosRead_implementation(HFILE hFile, PVOID pBuffer, ULONG cbRead, PULONG pcbActual)
{
const int fd = getHFileUnixDescriptor(hFile);
if (fd == -1)
return ERROR_INVALID_HANDLE;
const ssize_t br = read(fd, pBuffer, cbRead);
if (br == -1)
return ERROR_INVALID_FUNCTION; // !!! FIXME: ?
if (pcbActual)
*pcbActual = (ULONG) br;
return NO_ERROR;
} // DosRead_implementation
APIRET DosRead(HFILE hFile, PVOID pBuffer, ULONG cbRead, PULONG pcbActual)
{
TRACE_NATIVE("DosRead(%u, %p, %u, %p)", (uint) hFile, pBuffer, (uint) cbRead, pcbActual);
return DosRead_implementation(hFile, pBuffer, cbRead, pcbActual);
} // DosRead
static APIRET DosClose_implementation(HFILE hFile)
{
const int fd = getHFileUnixDescriptor(hFile);
if (fd == -1)
return ERROR_INVALID_HANDLE;
if (fd <= 2) { FIXME("remove me"); return NO_ERROR; } // for debugging purposes, remove me later.
const int rc = close(fd);
if (rc == -1)
return ERROR_ACCESS_DENIED; // !!! FIXME: ?
grabLock(&GMutexDosCalls);
HFiles[hFile].fd = -1;
HFiles[hFile].type = 0;
HFiles[hFile].attr = 0;
ungrabLock(&GMutexDosCalls);
return NO_ERROR;
} // DosClose_implementation
APIRET DosClose(HFILE hFile)
{
TRACE_NATIVE("DosClose(%u)", (uint) hFile);
return DosClose_implementation(hFile);
} // DosClose
APIRET DosEnterMustComplete(PULONG pulNesting)
{
TRACE_NATIVE("DosEnterMustComplete(%p)", pulNesting);
PTIB2 tib2 = (PTIB2) LX_GETTIB2();
if (tib2->tib2_usMCCount == 0xFFFF) {
return ERROR_NESTING_TOO_DEEP;
} else if (tib2->tib2_usMCCount == 0) {
FIXME("block signals here");
}
tib2->tib2_usMCCount++;
if (pulNesting)
*pulNesting = tib2->tib2_usMCCount;
return NO_ERROR;
} // DosEnterMustComplete
APIRET DosExitMustComplete(PULONG pulNesting)
{
TRACE_NATIVE("DosExitMustComplete(%p)", pulNesting);
PTIB2 tib2 = (PTIB2) LX_GETTIB2();
if (tib2->tib2_usMCCount == 0) {
return ERROR_ALREADY_RESET;
} else if (tib2->tib2_usMCCount == 1) {
FIXME("unblock signals here");
}
tib2->tib2_usMCCount--;
if (pulNesting)
*pulNesting = tib2->tib2_usMCCount;
return NO_ERROR;
} // DosExitMustComplete
static void unixTimeToOs2(const time_t unixtime, FDATE *os2date, FTIME *os2time)
{
struct tm lt;
localtime_r(&unixtime, &lt);
os2date->day = (USHORT) lt.tm_mday;
os2date->month = (USHORT) (lt.tm_mon + 1);
os2date->year = (USHORT) (lt.tm_year - 80); // wants years since 1980, apparently.
os2time->twosecs = (USHORT) (lt.tm_sec / 2);
os2time->minutes = (USHORT) lt.tm_min;
os2time->hours = (USHORT) lt.tm_hour;
} // unixTimeToOs2
static inline void initFileCreationDateTime(FDATE *fdate, FTIME *ftime)
{
// we don't store creation date on Unix. OS/2 zeroes out fields that aren't
// appropriate to FAT file systems, but I don't want to risk apps using
// zeroes to decide a file in on a FAT disk and reducing features,
// shrinking filenames to 8.3, etc.
if (fdate) {
fdate->day = 1;
fdate->month = 1;
fdate->year = 0; // years since 1980, apparently.
} // if
if (ftime) {
ftime->twosecs = 0;
ftime->minutes = 0;
ftime->hours = 0;
} // if
} // initFileCreationDateTime
static APIRET queryFileInfoStandardFromStat(const struct stat *statbuf, PVOID pInfoBuf, ULONG cbInfoBuf)
{
FILESTATUS3 *st = (FILESTATUS3 *) pInfoBuf;
memset(st, '\0', sizeof (*st));
initFileCreationDateTime(&st->fdateCreation, &st->ftimeCreation);
unixTimeToOs2(statbuf->st_atime, &st->fdateLastAccess, &st->ftimeLastAccess);
unixTimeToOs2(statbuf->st_mtime, &st->fdateLastWrite, &st->ftimeLastWrite);
st->cbFile = (ULONG) statbuf->st_size;
st->cbFileAlloc = (ULONG) (statbuf->st_blocks * 512);
if (S_ISDIR(statbuf->st_mode))
st->attrFile |= FILE_DIRECTORY;
if ((statbuf->st_mode & S_IWUSR) == 0) // !!! FIXME: not accurate...?
st->attrFile |= FILE_READONLY;
return NO_ERROR;
} // queryFileInfoStandardFromStat
static APIRET queryPathInfoStandard(PSZ unixPath, PVOID pInfoBuf, ULONG cbInfoBuf)
{
if (cbInfoBuf < sizeof (FILESTATUS3))
return ERROR_BUFFER_OVERFLOW;
struct stat statbuf;
if (stat(unixPath, &statbuf) == -1) {
return ERROR_PATH_NOT_FOUND; // !!! FIXME
}
return queryFileInfoStandardFromStat(&statbuf, pInfoBuf, cbInfoBuf);
} // queryPathInfoStandard
static APIRET queryPathInfoEaSize(PSZ unixPath, PVOID pInfoBuf, ULONG cbInfoBuf)
{
if (cbInfoBuf < sizeof (FILESTATUS4))
return ERROR_BUFFER_OVERFLOW;
const APIRET rc = queryPathInfoStandard(unixPath, pInfoBuf, cbInfoBuf);
if (rc != NO_ERROR)
return rc;
FILESTATUS4 *st = (FILESTATUS4 *) pInfoBuf;
FIXME("write me");
st->cbList = 0;
return NO_ERROR;
} // queryPathInfoEaSize
static APIRET queryPathInfoFullName(PSZ unixPath, PVOID pInfoBuf, ULONG cbInfoBuf)
{
char *real = realpath(unixPath, NULL);
if (real == NULL) {
// !!! FIXME: what _should_ this do if file doesn't exist? If parent directory doesn't?
real = strdup(unixPath); // could be file doesn't exist, but this still needs to return something...
if (real == NULL) {
return ERROR_NOT_ENOUGH_MEMORY; // !!! FIXME: no
}
}
if ((strlen(real) + 3) >= cbInfoBuf) {
free(real);
return ERROR_BUFFER_OVERFLOW;
}
// !!! FIXME: this needs to map to drive letters defined in a config
// !!! FIXME: eventually.
char *dst = (char *) pInfoBuf;
*(dst++) = 'C';
*(dst++) = ':';
strcpy(dst, real);
free(real);
while (*dst) {
if (*dst == '/')
*dst = '\\';
dst++;
}
return NO_ERROR;
} // queryPathInfoFullName
static APIRET queryPathInfoEasFromList(PSZ unixPath, PVOID pInfoBuf, ULONG cbInfoBuf)
{
FIXME("write me");
return ERROR_INVALID_LEVEL;
} // queryPathInfoEasFromList
static APIRET queryPathInfo(PSZ unixPath, ULONG ulInfoLevel, PVOID pInfoBuf, ULONG cbInfoBuf)
{
switch (ulInfoLevel) {
case FIL_STANDARD: return queryPathInfoStandard(unixPath, pInfoBuf, cbInfoBuf);
case FIL_QUERYEASIZE: return queryPathInfoEaSize(unixPath, pInfoBuf, cbInfoBuf);
case FIL_QUERYEASFROMLIST: return queryPathInfoEasFromList(unixPath, pInfoBuf, cbInfoBuf);
// OS/2 has an undocumented info level, 7, that appears to return a case-corrected version
// of the path. (FIL_QUERYFULLNAME doesn't correct the case of what the app queries on
// OS/2). Since we have to case-correct for a Unix filesystem anyhow, we just use the
// usual FIL_QUERYFULLNAME to handle the undocumented level. Java uses this level.
case 7:
case FIL_QUERYFULLNAME: return queryPathInfoFullName(unixPath, pInfoBuf, cbInfoBuf);
default: break;
} // switch
return ERROR_INVALID_LEVEL;
} // queryPathInfo
APIRET DosQueryPathInfo(PSZ pszPathName, ULONG ulInfoLevel, PVOID pInfoBuf, ULONG cbInfoBuf)
{
TRACE_NATIVE("DosQueryPathInfo('%s', %u, %p, %u)", pszPathName, (uint) ulInfoLevel, pInfoBuf, (uint) cbInfoBuf);
APIRET err = NO_ERROR;
char *unixPath = makeUnixPath(pszPathName, &err);
if (!unixPath)
return err;
err = queryPathInfo(unixPath, ulInfoLevel, pInfoBuf, cbInfoBuf);
free(unixPath);
return err;
} // DosQueryPathInfo
static APIRET queryFileInfoStandard(const struct stat *statbuf, PVOID pInfoBuf, ULONG cbInfoBuf)
{
if (cbInfoBuf < sizeof (FILESTATUS3))
return ERROR_BUFFER_OVERFLOW;
return queryFileInfoStandardFromStat(statbuf, pInfoBuf, cbInfoBuf);
} // queryFileInfoStandard
static APIRET queryFileInfoEaSize(const struct stat *statbuf, PVOID pInfoBuf, ULONG cbInfoBuf)
{
if (cbInfoBuf < sizeof (FILESTATUS4))
return ERROR_BUFFER_OVERFLOW;
const APIRET rc = queryFileInfoStandard(statbuf, pInfoBuf, cbInfoBuf);
if (rc != NO_ERROR)
return rc;
FILESTATUS4 *st = (FILESTATUS4 *) pInfoBuf;
FIXME("write me");
st->cbList = 0;
return NO_ERROR;
} // queryFileInfoEaSize
static APIRET queryFileInfoEasFromList(const struct stat *statbuf, PVOID pInfoBuf, ULONG cbInfoBuf)
{
FIXME("write me");
return ERROR_INVALID_LEVEL;
} // queryFileInfoEasFromList
APIRET DosQueryFileInfo(HFILE hf, ULONG ulInfoLevel, PVOID pInfo, ULONG cbInfoBuf)
{
TRACE_NATIVE("DosQueryFileInfo(%u, %u, %p, %u)", (uint) hf, (uint) ulInfoLevel, pInfo, (uint) cbInfoBuf);
const int fd = getHFileUnixDescriptor(hf);
if (fd == -1)
return ERROR_INVALID_HANDLE;
struct stat statbuf;
if (fstat(fd, &statbuf) == -1)
return ERROR_INVALID_HANDLE; // !!! FIXME: ...?
switch (ulInfoLevel) {
case FIL_STANDARD: return queryFileInfoStandard(&statbuf, pInfo, cbInfoBuf);
case FIL_QUERYEASIZE: return queryFileInfoEaSize(&statbuf, pInfo, cbInfoBuf);
case FIL_QUERYEASFROMLIST: return queryFileInfoEasFromList(&statbuf, pInfo, cbInfoBuf);
default: break;
} // switch
return ERROR_INVALID_LEVEL;
} // DosQueryFileInfo
static void os2ThreadCleanup(void *arg)
{
Thread *thread = (Thread *) arg;
GLoaderState.deinitOs2Tib(thread->selector);
grabLock(&GMutexDosCalls);
thread->prev = NULL;
thread->next = GDeadThreads;
GDeadThreads = thread;
ungrabLock(&GMutexDosCalls);
} // os2ThreadCleanup
static void os2ThreadEntry2(uint8 *tibspace, Thread *thread)
{
void *esp = NULL; // close enough.
GLoaderState.initOs2Tib(tibspace, &esp, thread->stacklen, (TID) thread);
thread->selector = GLoaderState.setOs2Tib(tibspace);
pthread_cleanup_push(os2ThreadCleanup, thread);
thread->fn(thread->fnarg);
pthread_cleanup_pop(1);
} // os2ThreadEntry2
static void *os2ThreadEntry(void *arg)
{
// put our thread's TIB structs on the stack and call that the top of the stack.
uint8 tibspace[LXTIBSIZE];
memset(tibspace, '\0', LXTIBSIZE); // make sure TLS slots are clear, etc.
os2ThreadEntry2(tibspace, (Thread *) arg);
return NULL; // OS/2 threads don't return a value here.
} // os2ThreadEntry
APIRET DosCreateThread(PTID ptid, PFNTHREAD pfn, ULONG param, ULONG flag, ULONG cbStack)
{
TRACE_NATIVE("DosCreateThread(%p, %p, %u, %u, %u)", ptid, pfn, (uint) param, (uint) flag, (uint) cbStack);
if (flag & CREATE_SUSPENDED)
FIXME("Can't start threads in suspended state");
Thread *thread = (Thread *) malloc(sizeof (Thread));
if (!thread)
return ERROR_NOT_ENOUGH_MEMORY;
memset(thread, '\0', sizeof (*thread));
size_t stacksize = cbStack;
if ((stacksize % 4096) != 0)
stacksize += 4096 - (cbStack % 4096);
stacksize += 4096; // leave a page for the TIB and host system libs that use more stack.
thread->stacklen = stacksize;
thread->fn = pfn;
thread->fnarg = param;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setstacksize(&attr, stacksize);
const int rc = pthread_create(&thread->thread, &attr, os2ThreadEntry, thread);
pthread_attr_destroy(&attr);
if (rc != 0) {
free(thread);
return ERROR_MAX_THRDS_REACHED; // I guess...?
} // if
if (ptid)
*ptid = (TID) thread;
return NO_ERROR;
} // DosCreateThread
static void __attribute__((noreturn)) execOs2Child(const char *exe, char * const *argv, char * const *envp)
{
// !!! FIXME: wire up any other things we need here.
execve(exe, argv, envp);
fprintf(stderr, "Failed to exec child process: %s\n", strerror(errno));
_exit(1); // uh oh.
} // execOs2Child
static char *findExePath(const char *exe, APIRET *err)
{
FIXME("eventually this needs to search the path and maybe launch native (non-OS/2) processes too");
char *retval = strdup("/proc/self/exe");
if (!retval)
*err = ERROR_NOT_ENOUGH_MEMORY;
return retval;
} // findExePath
static char **calcEnvp(const char *os2env, const char *exe, APIRET *err)
{
char **retval = NULL;
size_t len = 0;
size_t idx = 0;
const char *src = os2env;
if (*src == '\0') {
retval = (char **) malloc(sizeof (char *));
if (retval)
retval[0] = NULL;
return retval;
} // if
while (1) {
if (*(src++))
len++;
else {
idx++;
len += sizeof (char *) + 1;
if (*src == '\0')
break;
} // else
} // while
len += sizeof (char *); // NULL at the end.
len += strlen(exe) + 16 + sizeof (char *); // IS_2INE environment var.
idx++; // IS_2INE
retval = (char **) malloc(len);
if (!retval) {
*err = ERROR_NOT_ENOUGH_MEMORY;
return NULL;
} // if
char *dst = (char *) (retval + (idx + 1));
char *start = dst;
src = os2env;
idx = 0;
while (1) {
const char ch = *(src++);
*(dst++) = ch;
if (!ch) {
retval[idx++] = start;
start = dst;
if (*src == '\0')
break;
} // if
} // while
strcpy(dst, "IS_2INE=");
dst += strlen(dst);
strcpy(dst, exe);
dst += strlen(dst) + 1;
*(dst++) = '\0';
retval[idx++] = start;
retval[idx] = NULL;
return retval;
} // calcEnvp
static char **calcArgv(const char *os2args, APIRET *err)
{
const size_t argv0len = strlen(os2args);
if (argv0len == 0) {
*err = ERROR_BAD_ENVIRONMENT;
return NULL; // bad.
} // if
size_t len = 0;
size_t idx = 1;
const char *src = os2args + argv0len + 1;
// this can overallocate a little, but that's alright.
while (*src) {
const char ch = *(src++);
if ((ch == ' ') || (ch == '\t'))
idx++;
len++;
} // while
idx++; // one more for NULL terminator element.
len += argv0len + 1 + sizeof (char *);
len += idx * (sizeof (char *) + 1);
void *allocated = malloc(len);
if (!allocated) {
*err = ERROR_NOT_ENOUGH_MEMORY;
return NULL;
} // if
char **retval = (char **) allocated;
char *dst = (char *) (retval + idx + 1);
idx = 0;
retval[idx++] = dst;
strcpy(dst, os2args);
dst += argv0len + 1;
src = os2args + argv0len + 1;
while ((*src == ' ') || (*src == '\t'))
src++; // skip whitespace.
const char *srcstart = src;
while (*src) {
const char ch = *(src++);
if ((ch == ' ') || (ch == '\t')) {
retval[idx++] = dst;
const size_t cpylen = ((size_t) (src - srcstart)) - 1;
memcpy(dst, srcstart, cpylen);
dst += cpylen;
*(dst++) = '\0';
while ((*src == ' ') || (*src == '\t'))
src++;
srcstart = src;
} else if (ch == '"') {
srcstart++;
while ((*src != '"') && (*src != '\0'))
src++;
retval[idx++] = dst;
const size_t cpylen = (size_t) (src - srcstart);
memcpy(dst, srcstart, cpylen);
dst += cpylen;
*(dst++) = '\0';
if (*src == '"')
src++;
while ((*src == ' ') || (*src == '\t'))
src++;
srcstart = src;
} // else if
} // for
const size_t cpylen = (size_t) (src - srcstart);
if (cpylen) {
retval[idx++] = dst;
memcpy(dst, srcstart, cpylen);
dst += cpylen;
*(dst++) = '\0';
} // if
retval[idx] = NULL;
return retval;
} // calcArgv
static void setProcessResultCode(PRESULTCODES pRes, const int status)
{
if (!pRes)
return;
if (WIFEXITED(status)) {
// !!! FIXME: OS/2 processes exit with a ULONG, and returns bottom 16-bits of it for codeResult. Unix only gives you 8, though!
pRes->codeTerminate = TC_EXIT;
pRes->codeResult = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
FIXME("These might not be exactly right");
pRes->codeResult = 0;
switch (WTERMSIG(status)) {
case SIGTERM:
case SIGKILL:
case SIGINT:
case SIGQUIT:
pRes->codeTerminate = TC_KILLPROCESS;
break;
case SIGPIPE:
case SIGABRT:
pRes->codeTerminate = TC_HARDERROR;
break;
default:
pRes->codeTerminate = TC_EXCEPTION;
break;
} // switch
} // else if
} // setProcessResultCode
static APIRET DosExecPgm_implementation(PCHAR pObjname, LONG cbObjname, ULONG execFlag, PSZ pArg, PSZ pEnv, PRESULTCODES pRes, PSZ pName)
{
APIRET retval = NO_ERROR;
if (pObjname) // !!! FIXME: what does it want to put in here for failures?
*pObjname = '\0';
if ((execFlag == EXEC_TRACE) || (execFlag == EXEC_ASYNCRESULTDB)) { // sorry debuggers!
FIXME("tried to launch a process for debugging!");
return ERROR_ACCESS_DENIED;
} // if
if (execFlag == EXEC_LOAD) {
FIXME("What is this, exactly?");
return ERROR_GEN_FAILURE;
} // if
APIRET err;
char *pNameUnix = makeUnixPath(pName, &err);
if (!pNameUnix)
return err;
// calculate the args and environment first, so the child process is literally just calling execve().
char *exe = findExePath(pName, &retval);
if (!exe) {
free(pNameUnix);
return retval;
} // if
char **argv = calcArgv(pArg, &retval);
if (!argv) {
free(exe);
free(pNameUnix);
return retval;
} // if
char **envp = calcEnvp(pEnv, pNameUnix, &retval);
if (!argv) {
free(argv);
free(exe);
free(pNameUnix);
return retval;
} // if
//printf("EXEC'ING! exe='%s'\n", exe);
//for (char * const *ptr = argv; *ptr; ptr++) { printf(" ARGV[%d] = '%s'\n", (int) (ptr - argv), *ptr); }
//for (char * const *ptr = envp; *ptr; ptr++) { printf(" ENVP[%d] = '%s'\n", (int) (ptr - envp), *ptr); }
const int doubleFork = (execFlag == EXEC_ASYNC) || (execFlag == EXEC_BACKGROUND);
int status = 0;
pid_t pid = fork();
if (pid == -1) // failed?
retval = ERROR_NO_PROC_SLOTS;
else if (pid == 0) { // we're the child.
if (doubleFork) { // fork again so we aren't a direct child of the original parent.
pid = fork();
if (pid == -1) { // uhoh.
fprintf(stderr, "Failed to double-fork child process for EXEC_ASYNC: %s\n", strerror(errno));
_exit(1);
} else if (pid != 0) { // we're the parent
_exit(0); // just die now.
}
// we're the double-forked child.
if (setsid() < 0) {
fprintf(stderr, "Failed to setsid(): %s\n", strerror(errno));
_exit(1);
} // if
// !!! FIXME: need to report pid to original parent now via a temporary pipe.
} // if
// run the new program!
execOs2Child(exe, argv, envp);
} else { // we're the parent.
switch (execFlag) {
case EXEC_SYNC: {
const pid_t rc = waitpid(pid, &status, 0);
assert(rc == pid); // !!! FIXME
setProcessResultCode(pRes, status);
break;
} // case
case EXEC_ASYNC:
case EXEC_ASYNCRESULT:
case EXEC_BACKGROUND:
if (pRes) {
pRes->codeResult = 0;
pRes->codeTerminate = (ULONG) pid;
} // if
break;
default:
assert(!"shouldn't hit this code");
retval = ERROR_INVALID_FUNCTION;
break;
} // switch
} // else
free(envp);
free(argv);
free(exe);
return retval;
} // DosExecPgm_implementation
APIRET DosExecPgm(PCHAR pObjname, LONG cbObjname, ULONG execFlag, PSZ pArg, PSZ pEnv, PRESULTCODES pRes, PSZ pName)
{
TRACE_NATIVE("DosExecPgm('%s', %u, %u, '%s', '%s', %p, '%s')", pObjname, (uint) cbObjname, (uint) execFlag, pArg, pEnv, pRes, pName);
return DosExecPgm_implementation(pObjname, cbObjname, execFlag, pArg, pEnv, pRes, pName);
} // DosExecPgm
APIRET DosResetEventSem(HEV hev, PULONG pulPostCt)
{
TRACE_NATIVE("DosResetEventSem(%u, %p)", (uint) hev, pulPostCt);
if (!hev)
return ERROR_INVALID_HANDLE;
APIRET retval = NO_ERROR;
ULONG count = 0;
EventSem *sem = (EventSem *) hev;
pthread_mutex_lock(&sem->mutex);
count = sem->posted;
if (!sem->posted)
retval = ERROR_ALREADY_RESET;
else
sem->posted = 0;
pthread_mutex_unlock(&sem->mutex);
if (pulPostCt)
*pulPostCt = count;
return retval;
} // DosResetEventSem
APIRET DosPostEventSem(HEV hev)
{
TRACE_NATIVE("DosPostEventSem(%u)", (uint) hev);
if (!hev)
return ERROR_INVALID_HANDLE;
APIRET retval = NO_ERROR;
EventSem *sem = (EventSem *) hev;
pthread_mutex_lock(&sem->mutex);
if (sem->posted)
retval = ERROR_ALREADY_POSTED;
else {
sem->posted = 1;
pthread_cond_broadcast(&sem->condition);
} // else
pthread_mutex_unlock(&sem->mutex);
return retval;
} // DosPostEventSem
APIRET DosWaitEventSem(HEV hev, ULONG ulTimeout)
{
TRACE_NATIVE("DosWaitEventSem(%u, %u)", (uint) hev, (uint) ulTimeout);
if (!hev)
return ERROR_INVALID_HANDLE;
EventSem *sem = (EventSem *) hev;
int posted = 0;
if (ulTimeout == SEM_IMMEDIATE_RETURN) {
pthread_mutex_lock(&sem->mutex);
posted = sem->posted;
pthread_mutex_unlock(&sem->mutex);
return posted ? NO_ERROR : ERROR_TIMEOUT;
} else if (ulTimeout == SEM_INDEFINITE_WAIT) {
pthread_mutex_lock(&sem->mutex);
sem->waiting++;
while (1) {
posted = sem->posted;
if (!posted) {
pthread_cond_wait(&sem->condition, &sem->mutex);
posted = sem->posted;
} // if
if (posted) {
sem->waiting--;
pthread_mutex_unlock(&sem->mutex);
return NO_ERROR;
} // if
// spurious wakeup? Block again.
} // while
} else { // wait for X milliseconds.
struct timespec waittime;
timespecNowPlusMilliseconds(&waittime, ulTimeout);
pthread_mutex_lock(&sem->mutex);
sem->waiting++;
while (1) {
int rc = 0;
posted = sem->posted;
if (!posted) {
rc = pthread_cond_timedwait(&sem->condition, &sem->mutex, &waittime);
posted = sem->posted;
} // if
if ((posted) || (rc == ETIMEDOUT)) {
sem->waiting--;
pthread_mutex_unlock(&sem->mutex);
if (posted)
return NO_ERROR; // we're good.
else
return ERROR_TIMEOUT;
} // if
// otherwise, try again...spurious wakeup?
} // while
} // else
return NO_ERROR;
} // DosWaitEventSem
APIRET DosQueryEventSem(HEV hev, PULONG pulPostCt)
{
TRACE_NATIVE("DosQueryEventSem(%u, %p)", (uint) hev, pulPostCt);
if (!hev)
return ERROR_INVALID_HANDLE;
EventSem *sem = (EventSem *) hev;
pthread_mutex_lock(&sem->mutex);
const ULONG count = (ULONG) sem->posted;
pthread_mutex_unlock(&sem->mutex);
if (pulPostCt)
*pulPostCt = count;
return NO_ERROR;
} // DosQueryEventSem
APIRET DosCloseEventSem(HEV hev)
{
TRACE_NATIVE("DosCloseEventSem(%u)", (uint) hev);
if (!hev)
return ERROR_INVALID_HANDLE;
EventSem *sem = (EventSem *) hev;
pthread_mutex_lock(&sem->mutex);
const int waiting = sem->waiting;
pthread_mutex_unlock(&sem->mutex);
if (waiting > 0)
return ERROR_SEM_BUSY;
pthread_mutex_destroy(&sem->mutex);
pthread_cond_destroy(&sem->condition);
free(sem);
return NO_ERROR;
} // DosCloseEventSem
APIRET DosFreeMem(PVOID pb)
{
TRACE_NATIVE("DosFreeMem(%p)", pb);
// !!! FIXME: this API is actually much more complicated than this.
free(pb);
return NO_ERROR;
} // DosFreeMem
APIRET DosWaitChild(ULONG action, ULONG option, PRESULTCODES pres, PPID ppid, PID pid)
{
TRACE_NATIVE("DosWaitChild(%u, %u, %p, %p, %u)", (uint) action, (uint) option, pres, ppid, (uint) pid);
if (action == DCWA_PROCESSTREE) {
// we need a wait to wait on the whole tree (including trees of a
// terminated child we already waited on, which the API reference
// lists as an example.
FIXME("implement me");
return ERROR_INVALID_PARAMETER;
} // if
pid_t unixpid;
if (action == DCWA_PROCESS)
unixpid = (pid == 0) ? (pid_t) -1 : (pid_t) pid;
int waitoptions;
if (option == DCWW_WAIT) {
waitoptions = 0;
} else if (option == DCWW_NOWAIT) {
waitoptions = WNOHANG;
} else {
return ERROR_INVALID_PARAMETER;
} // else
int status = 0;
pid_t rc;
while (1) {
rc = waitpid(unixpid, &status, waitoptions);
if (rc == 0) { // WNOHANG and no children were done.
assert(option == DCWW_NOWAIT);
return ERROR_CHILD_NOT_COMPLETE;
} else if (rc == -1) {
if (errno == EINTR)
continue; // try again, I guess.
else if (errno == ECHILD)
return (pid == 0) ? ERROR_WAIT_NO_CHILDREN : ERROR_INVALID_PROCID;
} else {
break; // we're good.
} // else
assert(!"Shouldn't hit this code");
return ERROR_CHILD_NOT_COMPLETE;
} // while
if (ppid)
*ppid = (PID) rc;
setProcessResultCode(pres, status);
return NO_ERROR;
} // DosWaitChild
APIRET DosWaitThread(PTID ptid, ULONG option)
{
TRACE_NATIVE("DosWaitThread(%p, %u)", ptid, (uint) option);
Thread *thread = (Thread *) *ptid;
void *exitcode = NULL;
if ((option != DCWW_WAIT) && (option != DCWW_NOWAIT))
return ERROR_INVALID_PARAMETER;
if (thread == NULL) { // check our dead thread list for anything.
while (1) {
grabLock(&GMutexDosCalls);
if (GDeadThreads) {
thread = GDeadThreads;
GDeadThreads = thread->next;
if (GDeadThreads)
GDeadThreads->prev = NULL;
assert(!thread->prev);
} // if
ungrabLock(&GMutexDosCalls);
if (thread) {
pthread_join(thread->thread, &exitcode); // reap it.
*ptid = (TID) thread;
free(thread);
return NO_ERROR;
} // if
if (option == DCWW_NOWAIT)
return ERROR_THREAD_NOT_TERMINATED;
usleep(10000); // sleep then try again, for DCWW_WAIT.
} // while
assert(!"shouldn't hit this code");
return ERROR_THREAD_NOT_TERMINATED; // !!! FIXME
} // if
// codepath if we were waiting for a specific thread.
int rc;
if (option == DCWW_NOWAIT) {
rc = pthread_tryjoin_np(thread->thread, &exitcode);
} else { //if (option == DCWW_WAIT)
rc = pthread_join(thread->thread, &exitcode);
} // else
if (rc == EBUSY)
return ERROR_THREAD_NOT_TERMINATED;
else if (rc != 0)
return ERROR_INVALID_THREADID; // oh well.
grabLock(&GMutexDosCalls);
if (thread->prev)
thread->prev->next = thread->next;
else
GDeadThreads = thread->next;
if (thread->next)
thread->next->prev = thread->prev;
ungrabLock(&GMutexDosCalls);
free(thread);
return NO_ERROR;
} // DosWaitThread
APIRET DosSleep(ULONG msec)
{
TRACE_NATIVE("DosSleep(%u)", (uint) msec);
if (msec == 0)
sched_yield();
else
usleep(msec * 1000);
return NO_ERROR;
} // DosSleep
APIRET DosSubFreeMem(PVOID pbBase, PVOID pb, ULONG cb)
{
TRACE_NATIVE("DosSubFreeMem(%p, %p, %u)", pbBase, pb, (uint) cb);
free(pb); // !!! FIXME: obviously wrong
return NO_ERROR;
} // DosSubAllocMem
static APIRET DosDelete_implementation(PSZ pszFile)
{
APIRET err;
if (!pszFile)
return ERROR_INVALID_PARAMETER;
char *unixpath = makeUnixPath(pszFile, &err);
if (!unixpath)
return err;
const int rc = unlink(unixpath);
free(unixpath);
if (rc == -1) {
switch (errno) {
case EACCES: return ERROR_ACCESS_DENIED;
case EBUSY: return ERROR_ACCESS_DENIED;
case EISDIR: return ERROR_ACCESS_DENIED;
case ENAMETOOLONG: return ERROR_FILENAME_EXCED_RANGE;
case ENOENT: return ERROR_FILE_NOT_FOUND; // !!! FIXME: could be PATH_NOT_FOUND too, depending on circumstances.
case ENOTDIR: return ERROR_PATH_NOT_FOUND;
case EPERM: return ERROR_ACCESS_DENIED;
case EROFS: return ERROR_ACCESS_DENIED;
case ETXTBSY: return ERROR_ACCESS_DENIED;
default: return ERROR_INVALID_PARAMETER; // !!! FIXME: debug logging about missin errno case.
} // switch
__builtin_unreachable();
} // if
return NO_ERROR;
} // DosDelete_implementation
APIRET DosDelete(PSZ pszFile)
{
TRACE_NATIVE("DosDelete('%s')", pszFile);
return DosDelete_implementation(pszFile);
} // DosDelete
APIRET DosSetPathInfo(PSZ pszPathName, ULONG ulInfoLevel, PVOID pInfoBuf, ULONG cbInfoBuf, ULONG flOptions)
{
TRACE_NATIVE("DosSetPathInfo('%s', %u, %p, %u, %u)", pszPathName, (uint) ulInfoLevel, pInfoBuf, (uint) cbInfoBuf, (uint) flOptions);
FIXME("write me");
return NO_ERROR;
} // DosSetPathInfo
APIRET DosQueryModuleHandle(PSZ pszModname, PHMODULE phmod)
{
TRACE_NATIVE("DosQueryModuleHandle('%s', %p)", pszModname, phmod);
grabLock(&GMutexDosCalls);
LxModule *lxmod;
for (lxmod = GLoaderState.loaded_modules; lxmod; lxmod = lxmod->next) {
if (strcasecmp(lxmod->name, pszModname) == 0)
break;
} // for
ungrabLock(&GMutexDosCalls);
if (phmod)
*phmod = (HMODULE) lxmod;
return lxmod ? NO_ERROR : ERROR_INVALID_NAME;
} // DosQueryModuleHandle
APIRET DosQueryProcAddr(HMODULE hmod, ULONG ordinal, PSZ pszName, PFN* ppfn)
{
TRACE_NATIVE("DosQueryProcAddr(%u, %u, '%s', %p)", (uint) hmod, (uint) ordinal, pszName, ppfn);
const LxModule *lxmod = (LxModule *) hmod;
const LxExport *exports = lxmod->exports;
const uint32 num_exports = lxmod->num_exports;
if (ordinal > 65533) {
return ERROR_INVALID_ORDINAL; // according to the docs.
} else if (ordinal != 0) {
for (uint32 i = 0; i < num_exports; i++, exports++) {
if (exports->ordinal == ordinal) {
if (ppfn)
*ppfn = (PFN) exports->addr;
return NO_ERROR;
} // if
} // for
return ERROR_INVALID_ORDINAL;
} else {
// the docs explicitly say that you can't do name lookups in
// the DOSCALLS module, but whatever, I allow it.
for (uint32 i = 0; i < num_exports; i++, exports++) {
if (exports->name && (strcmp(exports->name, pszName) == 0)) {
if (ppfn)
*ppfn = (PFN) exports->addr;
return NO_ERROR;
} // if
} // for
return ERROR_INVALID_NAME;
} // else
__builtin_unreachable();
} // DosQueryProcAddr
APIRET DosQueryCp(ULONG cb, PULONG arCP, PULONG pcCP)
{
TRACE_NATIVE("DosQueryCp(%u, %p, %p)", (uint) cb, arCP, pcCP);
FIXME("This is mostly a stub");
if (cb < sizeof (*arCP) * 2)
return ERROR_CPLIST_TOO_SMALL;
// 437 == United States codepage.
arCP[0] = 437; // current process codepage
arCP[1] = 437; // prepared system codepage
if (pcCP)
*pcCP = sizeof (*arCP) * 2;
return NO_ERROR;
} // DosQueryCp
APIRET DosOpenL(PSZ pszFileName, PHFILE pHf, PULONG pulAction, LONGLONG cbFile, ULONG ulAttribute, ULONG fsOpenFlags, ULONG fsOpenMode, PEAOP2 peaop2)
{
TRACE_NATIVE("DosOpenL('%s', %p, %p, %llu, %u, %u, %u, %p)", pszFileName, pHf, pulAction, (unsigned long long) cbFile, (uint) ulAttribute, (uint) fsOpenFlags, (uint) fsOpenMode, peaop2);
return doDosOpen(pszFileName, pHf, pulAction, cbFile, ulAttribute, fsOpenFlags, fsOpenMode, peaop2);
} // DosOpenL
static inline ULONG findNextSizeNeeded(const ULONG ulInfoLevel, const ULONG pcFileNames)
{
switch (ulInfoLevel) {
case -1: return (sizeof (FILEFINDBUF) - CCHMAXPATHCOMP) * pcFileNames;
case FIL_STANDARD: return (sizeof (FILEFINDBUF3) - CCHMAXPATHCOMP) * pcFileNames;
case FIL_QUERYEASIZE: return (sizeof (FILEFINDBUF4) - CCHMAXPATHCOMP) * pcFileNames;
case FIL_QUERYEASFROMLIST: FIXME("write me"); return 0xFFFFFFFF;
default: assert(!"Shouldn't hit this."); return 0xFFFFFFFF;
} // switch
__builtin_unreachable();
} // findNextSizeNeeded;
static int wildcardMatch(const char *str, const char *pattern)
{
// !!! FIXME: the way OS/2 matches files is sort of complex, and I'm not sure this is 100% right.
int ext = 0;
while (1) {
const char pat = *(pattern++);
if (pat == '\0') {
return (*str == '\0') ? 1 : 0;
} else if (pat == '?') {
if (*str) // also matches end of string, but don't advance.
str++;
} else if ((pat == '.') && (!ext)) {
ext = 1;
if (*str == '.')
str++;
else if (*str != '\0')
return 0;
} else if (pat == '*') {
const char next = *pattern;
while (*str && (*str != next))
str++;
} else {
const char ch = *str;
const char a = ((ch >= 'A') && (ch <= 'Z')) ? (ch - ('A' - 'a')) : ch;
const char b = ((pat >= 'A') && (pat <= 'Z')) ? (pat - ('A' - 'a')) : pat;
if (a == b)
str++;
else
return 0;
} // else
} // while
__builtin_unreachable();
} // wildcardMatch
static int findNextOne(DirFinder *dirf, PVOID *ppfindbuf, PULONG pcbBuf, int *err)
{
const ULONG attr = dirf->attr;
const ULONG musthave = (attr & 0xFF00) >> 8;
const ULONG mayhave = (attr & 0x00FF);
// these attributes mean nothing on Unix, so if they are Must Haves, return nothing.
if (musthave & (FILE_HIDDEN | FILE_SYSTEM | FILE_ARCHIVED))
return 0;
struct dirent *dent;
const int fd = dirfd(dirf->dirp);
const char *pattern = dirf->pattern;
while ((dent = readdir(dirf->dirp)) != NULL) {
const char *name = dent->d_name;
// !!! FIXME: OS/2 doesn't enumerate ".." for the drive root!
//if (strcmp(name, "..") == 0) { if drive_root continue; }
const size_t namelen = strlen(name);
if (namelen >= CCHMAXPATHCOMP) continue;
if (namelen + findNextSizeNeeded(dirf->level, 1) >= *pcbBuf) continue; // FIXME??
if (!wildcardMatch(name, pattern)) continue;
// we stat(), not lstat(), because OS/2 doesn't understand symlinks,
// so we pretend the real file/dir is there. This could cause
// problems with broken links and infinite loops, though.
// For lstat-like behaviour, if we change our minds, the last arg
// of fstatat() needs the AT_SYMLINK_NOFOLLOW flag.
struct stat statbuf;
if (fstatat(fd, name, &statbuf, 0) == -1) continue;
const int isfile = S_ISREG(statbuf.st_mode);
const int isdir = S_ISDIR(statbuf.st_mode);
if (!isfile && !isdir) continue; // OS/2 didn't have fifos, devs, sockets, etc.
assert(isfile != isdir);
if (isdir) {
if ((mayhave & FILE_DIRECTORY) == 0) continue;
} else {
if (musthave & FILE_DIRECTORY) continue;
} // else
const int readonly = ((statbuf.st_mode & S_IWUSR) == 0); // !!! FIXME: not accurate...?
if (readonly) {
if ((mayhave & FILE_READONLY) == 0) continue;
} else {
if (musthave & FILE_READONLY) continue;
} // else
// okay! Definitely take this one!
switch (dirf->level) {
case -1: {
PFILEFINDBUF st = (PFILEFINDBUF) *ppfindbuf;
initFileCreationDateTime(&st->fdateCreation, &st->ftimeCreation);
unixTimeToOs2(statbuf.st_atime, &st->fdateLastAccess, &st->ftimeLastAccess);
unixTimeToOs2(statbuf.st_mtime, &st->fdateLastWrite, &st->ftimeLastWrite);
st->cbFile = (ULONG) statbuf.st_size; // !!! FIXME: > 2gig files?
st->cbFileAlloc = (ULONG) (statbuf.st_blocks * 512);
if (isdir) st->attrFile |= FILE_DIRECTORY;
if (readonly) st->attrFile |= FILE_READONLY;
st->cchName = (UCHAR) namelen;
strcpy(st->achName, name);
return 1;
} // case
case FIL_STANDARD: {
PFILEFINDBUF3 st = (PFILEFINDBUF3) *ppfindbuf;
memset(st, '\0', sizeof (*st));
*ppfindbuf = st + 1;
*pcbBuf -= sizeof (*st);
st->oNextEntryOffset = sizeof (*st);
initFileCreationDateTime(&st->fdateCreation, &st->ftimeCreation);
unixTimeToOs2(statbuf.st_atime, &st->fdateLastAccess, &st->ftimeLastAccess);
unixTimeToOs2(statbuf.st_mtime, &st->fdateLastWrite, &st->ftimeLastWrite);
st->cbFile = (ULONG) statbuf.st_size; // !!! FIXME: > 2gig files?
st->cbFileAlloc = (ULONG) (statbuf.st_blocks * 512);
if (isdir) st->attrFile |= FILE_DIRECTORY;
if (readonly) st->attrFile |= FILE_READONLY;
st->cchName = (UCHAR) namelen;
strcpy(st->achName, name);
return 1;
} // case
case FIL_QUERYEASIZE: {
PFILEFINDBUF4 st = (PFILEFINDBUF4) *ppfindbuf;
memset(st, '\0', sizeof (*st));
*ppfindbuf = st + 1;
*pcbBuf -= sizeof (*st);
st->oNextEntryOffset = sizeof (*st);
initFileCreationDateTime(&st->fdateCreation, &st->ftimeCreation);
unixTimeToOs2(statbuf.st_atime, &st->fdateLastAccess, &st->ftimeLastAccess);
unixTimeToOs2(statbuf.st_mtime, &st->fdateLastWrite, &st->ftimeLastWrite);
st->cbFile = (ULONG) statbuf.st_size; // !!! FIXME: > 2gig files?
st->cbFileAlloc = (ULONG) (statbuf.st_blocks * 512);
if (isdir) st->attrFile |= FILE_DIRECTORY;
if (readonly) st->attrFile |= FILE_READONLY;
st->cbList = 0; FIXME("write me: EA support");
st->cchName = (UCHAR) namelen;
strcpy(st->achName, name);
return 1;
} // case
case FIL_QUERYEASFROMLIST:
FIXME("write me");
break;
} // switch
} // while
return 0; // found nothing else that matches our needs.
} // findNextOne
static APIRET findNext(DirFinder *dirf, PVOID pfindbuf, ULONG cbBuf, PULONG pcFileNames)
{
if (!dirf->dirp) {
*pcFileNames = 0;
return ERROR_NO_MORE_FILES;
} // if
const ULONG maxents = *pcFileNames;
ULONG i;
int err = NO_ERROR;
memset(pfindbuf, 0, cbBuf);
for (i = 0; i < maxents; i++) {
if (!findNextOne(dirf, &pfindbuf, &cbBuf, &err))
break;
} // for
*pcFileNames = i;
if ((i == 0) && (maxents > 0)) {
closedir(dirf->dirp);
dirf->dirp = NULL;
err = ERROR_NO_MORE_FILES;
} // if
return err;
} // findNext
APIRET DosFindFirst(PSZ pszFileSpec, PHDIR phdir, ULONG flAttribute, PVOID pfindbuf, ULONG cbBuf, PULONG pcFileNames, ULONG ulInfoLevel)
{
TRACE_NATIVE("DosFindFirst('%s', %p, %u, %p, %u, %p, %u)", pszFileSpec, phdir, (uint) flAttribute, pfindbuf, (uint) cbBuf, pcFileNames, (uint) ulInfoLevel);
if (flAttribute & 0xffffc8c8) // reserved bits that must be zero.
return ERROR_INVALID_PARAMETER;
else if (cbBuf > 0xFFFF) // !!! FIXME: Control Program API Reference says this fails if > 64k, although that was obviously a 16-bit limitation.
return ERROR_INVALID_PARAMETER;
else if ((ulInfoLevel != FIL_STANDARD) && (ulInfoLevel != FIL_QUERYEASIZE) && (ulInfoLevel != FIL_QUERYEASFROMLIST) && (ulInfoLevel != -1)) // -1 for 16 call
return ERROR_INVALID_PARAMETER;
else if (!pcFileNames)
return ERROR_INVALID_PARAMETER;
else if (!pfindbuf && (*pcFileNames != 0))
return ERROR_INVALID_PARAMETER;
else if (!phdir)
return ERROR_INVALID_PARAMETER;
else if (!*phdir)
return ERROR_INVALID_HANDLE;
else if (cbBuf < findNextSizeNeeded(ulInfoLevel, *pcFileNames))
return ERROR_BUFFER_OVERFLOW;
if ((ulInfoLevel != FIL_STANDARD) && (ulInfoLevel != -1)) {
FIXME("implement extended attribute support");
return ERROR_INVALID_PARAMETER;
} // if
if (flAttribute == FILE_NORMAL)
flAttribute = FILE_READONLY | FILE_HIDDEN | FILE_SYSTEM | FILE_ARCHIVED; // most of the "may-have" bits.
APIRET err = NO_ERROR;
char *path = makeUnixPath(pszFileSpec, &err);
if (!path)
return err;
char *pattern = strrchr(path, '/');
if (pattern)
*(pattern++) = '\0';
else
pattern = path;
if (strlen(pattern) >= CCHMAXPATHCOMP) {
free(path);
return ERROR_FILENAME_EXCED_RANGE;
} // if
DirFinder *dirf = NULL;
if (*phdir == HDIR_SYSTEM) {
dirf = &GHDir1;
} else if (*phdir == HDIR_CREATE) {
dirf = (DirFinder *) malloc(sizeof (DirFinder));
if (!dirf) {
free(path);
return ERROR_NOT_ENOUGH_MEMORY;
} // if
memset(dirf, '\0', sizeof (*dirf));
} else {
dirf = (DirFinder *) *phdir;
} // else
DIR *dirp = opendir((path == pattern) ? "." : path);
if (!dirp) {
const int rc = errno;
if (*phdir == HDIR_CREATE)
free(dirf);
free(path);
switch (rc) {
case EACCES: return ERROR_ACCESS_DENIED;
case EMFILE: return ERROR_NO_MORE_SEARCH_HANDLES;
case ENFILE: return ERROR_NO_MORE_SEARCH_HANDLES;
case ENOMEM: return ERROR_NOT_ENOUGH_MEMORY;
case ENOENT: return ERROR_PATH_NOT_FOUND; // !!! FIXME: should this be FILE_NOT_FOUND if the parent dir exists...?
case ENAMETOOLONG: return ERROR_FILENAME_EXCED_RANGE;
default: break;
} // switch
return ERROR_PATH_NOT_FOUND; // oh well.
} // if
if (dirf->dirp)
closedir(dirf->dirp);
dirf->dirp = dirp;
dirf->attr = flAttribute;
dirf->level = ulInfoLevel;
strcpy(dirf->pattern, pattern);
free(path);
err = findNext(dirf, pfindbuf, cbBuf, pcFileNames);
// From the Control Program API reference:
// "Any nonzero return code, except ERROR_EAS_DIDNT_FIT, indicates that no handle has been allocated. This includes such nonerror
// indicators as ERROR_NO_MORE_FILES."
if ((err != NO_ERROR) && (err != ERROR_EAS_DIDNT_FIT)) {
closedir(dirf->dirp);
dirf->dirp = NULL;
if (*phdir == HDIR_CREATE)
free(dirf);
return err;
} // if
if (err == ERROR_NO_MORE_FILES)
return ERROR_FILE_NOT_FOUND;
*phdir = (HDIR) dirf;
return NO_ERROR;
} // DosFindFirst
APIRET DosFindNext(HDIR hDir, PVOID pfindbuf, ULONG cbfindbuf, PULONG pcFilenames)
{
TRACE_NATIVE("DosFindNext(%u, %p, %u, %p)", (uint) hDir, pfindbuf, (uint) cbfindbuf, pcFilenames);
if ((hDir == 0) || (hDir == HDIR_SYSTEM) || (hDir == HDIR_CREATE))
return ERROR_INVALID_HANDLE;
else if (cbfindbuf > 0xFFFF) // !!! FIXME: Control Program API Reference says this fails if > 64k, although that was obviously a 16-bit limitation.
return ERROR_INVALID_PARAMETER;
else if (!pcFilenames)
return ERROR_INVALID_PARAMETER;
else if (!pfindbuf && (*pcFilenames != 0))
return ERROR_INVALID_PARAMETER;
else if (cbfindbuf < findNextSizeNeeded(((DirFinder *) hDir)->level, *pcFilenames))
return ERROR_BUFFER_OVERFLOW;
return findNext((DirFinder *) hDir, pfindbuf, cbfindbuf, pcFilenames);
} // DosFindNext
APIRET DosFindClose(HDIR hDir)
{
TRACE_NATIVE("DosFindClose(%u)", (uint) hDir);
if ((hDir == 0) || (hDir == HDIR_SYSTEM) || (hDir == HDIR_CREATE))
return ERROR_INVALID_HANDLE;
DirFinder *dirf = (DirFinder *) hDir;
if (dirf->dirp)
closedir(dirf->dirp);
if (dirf == &GHDir1)
memset(dirf, '\0', sizeof (*dirf));
else
free(dirf);
return NO_ERROR;
} // DosFindClose
APIRET DosDevConfig(PVOID pdevinfo, ULONG item)
{
TRACE_NATIVE("DosDevConfig(%p, %u)", pdevinfo, (uint) item);
if (pdevinfo == NULL)
return ERROR_INVALID_PARAMETER;
BYTE *info = (BYTE *) pdevinfo;
switch (item) {
case DEVINFO_PRINTER: *info = 0; break; // we don't report any printers.
case DEVINFO_RS232: *info = 0; break; // we don't report any serial ports.
case DEVINFO_FLOPPY: *info = 0; break; // we don't report any floppies.
case DEVINFO_COPROCESSOR: *info = 1; break; // We could check for this, but c'mon.
case DEVINFO_SUBMODEL: *info = 1; break; // OS/2 Warp 4 under Vmware returns this, but I don't know what this means.
case DEVINFO_MODEL: *info = 252; break; // OS/2 Warp 4 under Vmware returns this, but I don't know what this means.
case DEVINFO_ADAPTER: *info = 1; break; // sure, you have a real display from the last 30+ years.
default: return ERROR_INVALID_PARAMETER;
} // switch
return NO_ERROR;
} // DosDevConfig
APIRET DosLoadModule(PSZ pszName, ULONG cbName, PSZ pszModname, PHMODULE phmod)
{
TRACE_NATIVE("DosLoadModule(%p, %u, '%s', %p)", pszName, (uint) cbName, pszModname, phmod);
FIXME("improve this");
*pszName = 0;
FIXME("there's no mutex on this global state at the moment!");
LxModule *lxmod = GLoaderState.loadModule(pszModname);
if (!lxmod)
return ERROR_BAD_FORMAT;
*phmod = (HMODULE) lxmod;
return NO_ERROR;
} // DosLoadModule
static APIRET resetOneBuffer(const int fd)
{
if (fsync(fd) == -1) {
switch (errno) {
case EBADF: return ERROR_INVALID_HANDLE;
case EIO: return ERROR_ACCESS_DENIED;
case EROFS: return ERROR_ACCESS_DENIED;
case EINVAL: return ERROR_INVALID_HANDLE;
default: break;
} // switch
return ERROR_INVALID_HANDLE;
} // if
return NO_ERROR;
} // resetOneBuffer
APIRET DosResetBuffer(HFILE hFile)
{
TRACE_NATIVE("DosResetBuffer(%u)", (uint) hFile);
if (hFile == 0xFFFFFFFF) { // flush all files.
// !!! FIXME: this is holding the lock the whole time...
APIRET err = NO_ERROR;
grabLock(&GMutexDosCalls);
for (uint32 i = 0; i < MaxHFiles; i++) {
const int fd = HFiles[hFile].fd;
if (fd != -1) {
const APIRET thiserr = resetOneBuffer(fd);
if (err == NO_ERROR)
err = thiserr;
} // if
} // for
ungrabLock(&GMutexDosCalls);
return err;
} // if
// flush just one file.
const int fd = getHFileUnixDescriptor(hFile);
if (fd == -1)
return ERROR_INVALID_HANDLE;
return resetOneBuffer(fd);
} // DosResetBuffer
APIRET DosQueryAppType(PSZ pszName, PULONG pFlags)
{
TRACE_NATIVE("DosQueryAppType('%s', %p)", pszName, pFlags);
if (!pFlags)
return ERROR_INVALID_PARAMETER;
APIRET err = NO_ERROR;
char *path = makeUnixPath(pszName, &err);
if (!path)
return err;
const int fd = open(path, O_RDONLY, 0);
free(path);
if (fd == -1) {
switch (errno) { // !!! FIXME: copy/paste, put this in a function.
case EACCES: return ERROR_ACCESS_DENIED;
case EISDIR: return ERROR_ACCESS_DENIED;
case EMFILE: return ERROR_TOO_MANY_OPEN_FILES;
case ENFILE: return ERROR_TOO_MANY_OPEN_FILES;
case ENAMETOOLONG: return ERROR_FILENAME_EXCED_RANGE;
case ENOSPC: return ERROR_DISK_FULL;
case ENOMEM: return ERROR_NOT_ENOUGH_MEMORY;
case ENOENT: return ERROR_FILE_NOT_FOUND; // !!! FIXME: could be PATH_NOT_FOUND too, depending on circumstances.
case ENOTDIR: return ERROR_PATH_NOT_FOUND;
case EPERM: return ERROR_ACCESS_DENIED;
case EROFS: return ERROR_ACCESS_DENIED;
case ETXTBSY: return ERROR_ACCESS_DENIED;
default: break;
} // switch
return ERROR_OPEN_FAILED; // !!! FIXME: debug logging about missing errno case.
} // if
// !!! FIXME: I guess we should parse NE/LE files here too?
uint32 lxoffset = 0;
LxHeader lx;
uint8 mz[2];
if (read(fd, mz, 2) != 2) goto queryapptype_iofailed;
if ((mz[0] != 'M') || (mz[1] != 'Z')) goto queryapptype_iofailed;
if (lseek(fd, 0x3C, SEEK_SET) == -1) goto queryapptype_iofailed;
if (read(fd, &lxoffset, sizeof (lxoffset)) != sizeof (lxoffset)) goto queryapptype_iofailed;
if (lseek(fd, lxoffset, SEEK_SET) == -1) goto queryapptype_iofailed;
if (read(fd, &lx, sizeof (lx)) != sizeof (lx)) goto queryapptype_iofailed;
close(fd);
if ((lx.magic_l != 'L') || (lx.magic_x != 'X')) return ERROR_INVALID_EXE_SIGNATURE;
if (lx.module_flags & 0x2000) return ERROR_EXE_MARKED_INVALID; // "not loadable" ... I guess this is it?
const uint32 module_type = lx.module_flags & 0x00038000;
ULONG flags = 0;
// !!! FIXME: can you be both compat and incompat?
if (lx.module_flags & 0x100) flags |= FAPPTYP_NOTWINDOWCOMPAT;
if (lx.module_flags & 0x200) flags |= FAPPTYP_WINDOWCOMPAT;
if (lx.module_flags & 0x300) flags |= FAPPTYP_WINDOWAPI;
// !!! FIXME: FAPPTYP_BOUND = 0x0008
if (module_type & 0x8000) flags |= FAPPTYP_DLL;
if (module_type & 0x20000) flags |= FAPPTYP_PHYSDRV;
if (module_type & 0x28000) flags |= FAPPTYP_VIRTDRV;
if (module_type & 0x18000) flags |= FAPPTYP_PROTDLL;
// !!! FIXME: FAPPTYP_DOS = 0x0020
// !!! FIXME: FAPPTYP_WINDOWSREAL = 0x0200
// !!! FIXME: FAPPTYP_WINDOWSPROT = 0x0400
// !!! FIXME: FAPPTYP_WINDOWSPROT31 =0x1000
flags |= FAPPTYP_32BIT; // !!! FIXME: currently always true.
return NO_ERROR;
queryapptype_iofailed:
close(fd);
return ERROR_INVALID_EXE_SIGNATURE;
} // DosQueryAppType
static uint32 *initTLSPage(void)
{
const int pagesize = getpagesize();
void *addr = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (addr == ((void *) MAP_FAILED))
return NULL;
// Fill in the page with a debug info string.
char *dst = (char *) addr;
const char *str = "This is the fake OS/2 TLS page. ";
assert(strlen(str) == 32);
for (int i = 0; i < 127; i++, dst += 32)
strcpy(dst, str);
memcpy(dst, str, 31);
dst[31] = '\0';
if (mprotect(addr, pagesize, PROT_NONE) == -1) {
munmap(addr, pagesize);
return NULL;
} // if
printf("allocated magic OS/2 TLS page at %p\n", addr); fflush(stdout);
return (uint32 *) addr;
} // initTLSPage
APIRET DosAllocThreadLocalMemory(ULONG cb, PULONG *p)
{
TRACE_NATIVE("DosAllocThreadLocalMemory(%u, %p)", (uint) cb, p);
if (cb > 8) // this is a limitation in OS/2. This whole API is weird.
return ERROR_INVALID_PARAMETER;
else if (!p)
return ERROR_INVALID_PARAMETER;
APIRET retval = NO_ERROR;
uint32 mask = 0xFF >> (8 - cb);
grabLock(&GMutexDosCalls);
// this is probably expensive to do with the mutex held, but honestly,
// is there a lot of thread contention at the point where your app is
// allocating TLS?
if (GLoaderState.tlspage == NULL) {
if ((GLoaderState.tlspage = initTLSPage()) == NULL) {
ungrabLock(&GMutexDosCalls);
return ERROR_NOT_ENOUGH_MEMORY;
} // if
} // if
int i;
for (i = 0; i < 32; i++, mask <<= 1) {
if (((GLoaderState.tlsmask ^ mask) & mask) == mask)
break;
} // for
if (i == 32)
retval = ERROR_NOT_ENOUGH_MEMORY; // there are only 32 slots, couldn't find anything contiguous.
else {
assert(GLoaderState.tlsallocs[i] == 0);
GLoaderState.tlsallocs[i] = (uint8) cb;
GLoaderState.tlsmask |= mask;
*p = GLoaderState.tlspage + i;
printf("allocated %u OS/2 TLS slot%s at %p\n", (uint) cb, (cb == 1) ? "" : "s", *p); fflush(stdout);
} // else
ungrabLock(&GMutexDosCalls);
return retval;
} // DosAllocThreadLocalMemory
APIRET DosFreeThreadLocalMemory(ULONG *p)
{
TRACE_NATIVE("DosFreeThreadLocalMemory(%p)", p);
APIRET retval = ERROR_INVALID_PARAMETER;
grabLock(&GMutexDosCalls);
if (GLoaderState.tlspage) {
const uint32 slot = (uint32) (p - GLoaderState.tlspage);
if (slot < 32) {
const uint8 slots = GLoaderState.tlsallocs[slot];
if (slots > 0) {
const uint32 mask = ((uint32) (0xFF >> (8 - slots))) << slot;
assert((GLoaderState.tlsmask & mask) == mask);
GLoaderState.tlsmask &= ~mask;
printf("freed %u OS/2 TLS slot%s at %p\n", (uint) slots, (slots == 1) ? "" : "s", p); fflush(stdout);
GLoaderState.tlsallocs[slot] = 0;
retval = NO_ERROR;
} // if
} // if
} // if
ungrabLock(&GMutexDosCalls);
return retval;
} // DosFreeThreadLocalMemory
APIRET DosQueryFHState(HFILE hFile, PULONG pMode)
{
TRACE_NATIVE("DosQueryFHState(%u, %p)", (uint) hFile, pMode);
APIRET retval = NO_ERROR;
ULONG tmp = 0;
if (!pMode)
pMode = &tmp;
grabLock(&GMutexDosCalls);
if ((hFile < MaxHFiles) && (HFiles[hFile].fd != -1))
*pMode = HFiles[hFile].flags;
else
retval = ERROR_INVALID_HANDLE;
ungrabLock(&GMutexDosCalls);
return retval;
} // DosQueryFHState
APIRET DosQueryHeaderInfo(HMODULE hmod, ULONG ulIndex, PVOID pvBuffer, ULONG cbBuffer, ULONG ulSubFunction)
{
TRACE_NATIVE("DosQueryHeaderInfo(%u, %u, %p, %u, %u)", (uint) hmod, (uint) ulIndex, pvBuffer, (uint) cbBuffer, (uint) ulSubFunction);
switch (ulSubFunction) {
//case QHINF_EXEINFO:
//case QHINF_READRSRCTBL:
//case QHINF_READFILE:
case QHINF_LIBPATHLENGTH:
if (cbBuffer < sizeof (ULONG))
return ERROR_BUFFER_OVERFLOW;
*((ULONG *) pvBuffer) = GLoaderState.libpathlen;
return NO_ERROR;
case QHINF_LIBPATH:
if (cbBuffer < GLoaderState.libpathlen)
return ERROR_BUFFER_OVERFLOW;
strcpy((char *) pvBuffer, GLoaderState.libpath);
return NO_ERROR;
//case QHINF_FIXENTRY:
//case QHINF_STE:
//case QHINF_MAPSEL:
default: FIXME("I don't know what this query wants"); break;
} // switch
return ERROR_INVALID_PARAMETER;
} // DosQueryHeaderInfo
APIRET DosQueryExtLIBPATH(PSZ pszExtLIBPATH, ULONG flags)
{
TRACE_NATIVE("DosQueryExtLIBPATH('%s', %u)", pszExtLIBPATH, (uint) flags);
// !!! FIXME: this is mostly a stub for now.
if ((flags != BEGIN_LIBPATH) && (flags != END_LIBPATH))
return ERROR_INVALID_PARAMETER;
if (pszExtLIBPATH)
*pszExtLIBPATH = '\0';
return NO_ERROR;
} // DosQueryExtLIBPATH
APIRET DosSetMaxFH(ULONG cFH)
{
TRACE_NATIVE("DosSetMaxFH(%u)", (uint) cFH);
grabLock(&GMutexDosCalls);
if (cFH < MaxHFiles) {
ungrabLock(&GMutexDosCalls);
return ERROR_INVALID_PARAMETER; // strictly speaking, we could shrink, but I'm not doing it.
} // if
if (cFH == MaxHFiles) {
ungrabLock(&GMutexDosCalls);
return NO_ERROR;
} // if
HFileInfo *info = (HFileInfo *) realloc(HFiles, sizeof (HFileInfo) * (cFH));
if (info != NULL) {
HFiles = info;
info += MaxHFiles;
for (ULONG i = MaxHFiles; i < cFH; i++, info++) {
info->fd = -1;
info->type = 0;
info->attr = 0;
info->flags = 0;
} // for
MaxHFiles = cFH;
} // if
ungrabLock(&GMutexDosCalls);
return NO_ERROR;
} // DosSetMaxFH
APIRET DosQuerySysState(ULONG func, ULONG arg1, ULONG pid, ULONG _res_, PVOID buf, ULONG bufsz)
{
TRACE_NATIVE("DosQuerySysState(%u, %u, %u, %u, %p, %u)", (uint) func, (uint) arg1, (uint) pid, (uint) _res_, buf, (uint) bufsz);
FIXME("implement me");
return ERROR_INVALID_PARAMETER;
} // DosQuerySysState
APIRET DosR3ExitAddr(void)
{
TRACE_NATIVE("DosR3ExitAddr()");
FIXME("I have no idea what this is"); // but...JAVA USES IT.
return ERROR_INVALID_PARAMETER;
} // DosR3ExitAddr
APIRET DosQueryThreadContext(TID tid, ULONG level, PCONTEXTRECORD pcxt)
{
TRACE_NATIVE("DosQueryThreadContext(%u, %u, %p)", (uint) tid, (uint) level, pcxt);
FIXME("Need to be able to suspend threads first");
return ERROR_INVALID_PARAMETER;
} // DosQueryThreadContext
static APIRET DosQueryCurrentDir_implementation(ULONG disknum, PBYTE pBuf, PULONG pcbBuf)
{
if (disknum > (sizeof (GLoaderState.disks) / sizeof (GLoaderState.disks[0]))) {
return ERROR_INVALID_DRIVE;
}
const char *cdir = GLoaderState.current_dir[disknum ? (disknum-1) : GLoaderState.current_disk];
if (cdir == NULL) {
FIXME("should this return ERROR_INVALID_DRIVE?");
return ERROR_NOT_DOS_DISK;
}
const ULONG pathlen = (ULONG) strlen(cdir);
if (*pcbBuf <= pathlen) {
*pcbBuf = pathlen;
return ERROR_BUFFER_OVERFLOW;
}
strcpy((char *) pBuf, cdir);
*pcbBuf = pathlen;
return NO_ERROR;
} // DosQueryCurrentDir_implementation
APIRET DosQueryCurrentDir(ULONG disknum, PBYTE pBuf, PULONG pcbBuf)
{
TRACE_NATIVE("DosQueryCurrentDir(%u, %p, %p)", (uint) disknum, pBuf, pcbBuf);
return DosQueryCurrentDir_implementation(disknum, pBuf, pcbBuf);
} // DosQueryCurrentDir
static APIRET DosQueryCurrentDisk_implementation(PULONG pdisknum, PULONG pdiskmap)
{
*pdisknum = GLoaderState.current_disk;
*pdiskmap = GLoaderState.diskmap;
return NO_ERROR;
} // DosQueryCurrentDisk_implementation
APIRET DosQueryCurrentDisk(PULONG pdisknum, PULONG pdiskmap)
{
TRACE_NATIVE("DosQueryCurrentDisk(%p, %p)", pdisknum, pdiskmap);
return DosQueryCurrentDisk_implementation(pdisknum, pdiskmap);
} // DosQueryCurrentDisk
static APIRET DosSetCurrentDir_implementation(PSZ pszName)
{
// OS/2 reports failure for changing to a path that ends in '\\' even if
// the rest of the path is a valid dir. Linux does not, so we explicitly
// check it here. However, "\\" by itself is valid (as the absolute path
// to the disk's root dir).
if (*pszName == '\0') {
return NO_ERROR; // I guess this is a no-op...?
} else if ((pszName[0] == '\\') && (pszName[1] == '\0')) {
// this is okay.
} else if (pszName[strlen(pszName) - 1] == '\\') {
return ERROR_PATH_NOT_FOUND;
}
FIXME("This function needs a lock"); // in case the cwd changes from another thread.
uint32 err = 0;
char *path = makeUnixPath(pszName, &err);
if (!path) return (APIRET) err;
// generally we don't use the Unix process's cwd, but this might be
// helpful for subprocesses or native builds, but mostly, it checks
// all the stuff we care about (missing dir, not a dir, etc).
APIRET retval = NO_ERROR;
if (chdir(path) == -1) {
switch (errno) {
// OS/2 treats a chdir to a regular file as "Path not found", even though it _is_ found. :/
case ENOTDIR: retval = ERROR_PATH_NOT_FOUND; break;
case ENOENT: retval = ERROR_PATH_NOT_FOUND; break;
case EACCES: retval = ERROR_ACCESS_DENIED; break;
case EIO: retval = ERROR_DRIVE_LOCKED; break;
case ENAMETOOLONG: retval = ERROR_FILENAME_EXCED_RANGE; break;
case ENOMEM: retval = ERROR_NOT_ENOUGH_MEMORY; break;
default: retval = ERROR_PATH_NOT_FOUND; break;
}
}
free(path);
if (retval == NO_ERROR) {
char drive = pszName[0];
if ((drive >= 'a') && (drive <= 'z')) {
drive += 'A' - 'a';
}
if (((drive >= 'A') && (drive <= 'Z')) && (pszName[1] == ':')) { // it's a drive letter.
pszName += 2; // skip drive letter and colon.
} else {
drive = (GLoaderState.current_disk-1) + 'A';
}
const int driveidx = drive - 'A';
char *oldpath = (*pszName == '\\') ? "" : GLoaderState.current_dir[driveidx];
const size_t buflen = strlen(pszName) + strlen(oldpath) + 2;
char *newpath = (char *) malloc(buflen);
if (!newpath) {
return ERROR_NOT_ENOUGH_MEMORY;
}
// set up the string we'll use for the cwd. Normalize out "." and ".." chars, append to existing path
// if we're relative, etc.
snprintf(newpath, buflen, "%s%s%s", oldpath, *oldpath ? "\\" : "", pszName);
char *ptr = newpath;
// strip out double '\\' chars.
while ( (ptr = strstr(ptr, "\\\\")) != NULL ) {
memmove(ptr, ptr + 1, strlen(ptr));
}
// trim '\\' char off the start of the string if it exists.
if (*newpath == '\\') {
memmove(newpath, newpath + 1, strlen(newpath));
}
// strip out "." and ".." dirs.
char *element = newpath;
ptr = newpath;
while ( (ptr = strchr(ptr, '\\')) != NULL ) {
*ptr = '\0';
if (strcmp(element, ".") == 0) { // dump "." subdir.
memmove(element, ptr + 1, strlen(ptr + 1) + 1);
ptr = element;
} else if (strcmp(element, "..") == 0) { // dump ".." subdir and parent.
if (element == newpath) { // no parent? Just dump "..".
memmove(newpath, ptr + 1, strlen(ptr + 1) + 1);
ptr = newpath;
} else {
assert(((int)(element - newpath)) >= 2); // shouldn't be "\\" at start of string.
char *prev = element - 2;
while ((prev != newpath) && (*prev != '\\')) {
prev--;
}
if (prev != newpath) {
prev++;
}
memmove(prev, ptr + 1, strlen(ptr + 1) + 1);
ptr = prev;
}
} else {
*ptr = '\\'; // just a normal dir, restore it.
ptr++;
}
element = ptr;
}
// check last element, when strchr fails because there wasn't any more '\\' chars...
if (strcmp(element, ".") == 0) {
if (element != newpath) {
element--; // so we eat the '\\' char
}
*element = '\0';
} else if (strcmp(element, "..") == 0) {
if (element == newpath) { // no parent? Just dump the whole string.
*newpath = '\0';
} else {
assert(((int)(element - newpath)) >= 2); // shouldn't be "\\" at start of string.
char *prev = element - 2;
while ((prev != newpath) && (*prev != '\\')) {
prev--;
}
*prev = '\0'; // dump parent (if any) and ".."
}
}
ptr = (char *) realloc(newpath, strlen(newpath) + 1); // shrink if possible.
if (ptr) {
newpath = ptr;
}
//printf("CHDIR: Normalized '%s' + '%s' to '%s'\n", oldpath, pszName, newpath);
free(GLoaderState.current_dir[driveidx]);
GLoaderState.current_dir[driveidx] = newpath;
}
return retval;
} // DosSetCurrentDir_implementation
APIRET DosSetCurrentDir(PSZ pszName)
{
TRACE_NATIVE("DosSetCurrentDir('%s')", pszName);
return DosSetCurrentDir_implementation(pszName);
} // DosSetCurrentDir
static APIRET DosCreateDir_implementation(PSZ pszDirName, PEAOP2 peaop2)
{
if (peaop2 != NULL) {
FIXME("EAs not yet implemented");
return ERROR_EA_LIST_INCONSISTENT;
}
uint32 err = 0;
char *path = makeUnixPath(pszDirName, &err);
if (!path) return (APIRET) err;
const int rc = mkdir(path, 0755);
free(path);
if (rc == -1) {
switch (errno) {
case EACCES: return ERROR_ACCESS_DENIED;
case EBUSY: return ERROR_ACCESS_DENIED;
case EISDIR: return ERROR_ACCESS_DENIED;
case ENAMETOOLONG: return ERROR_FILENAME_EXCED_RANGE;
case ENOENT: return ERROR_FILE_NOT_FOUND; // !!! FIXME: could be PATH_NOT_FOUND too, depending on circumstances.
case ENOTDIR: return ERROR_PATH_NOT_FOUND;
case EPERM: return ERROR_ACCESS_DENIED;
case EROFS: return ERROR_ACCESS_DENIED;
case ETXTBSY: return ERROR_ACCESS_DENIED;
default: return ERROR_INVALID_PARAMETER; // !!! FIXME: debug logging about missin errno case.
}
__builtin_unreachable();
}
return NO_ERROR;
} // DosCreateDir_implementation
APIRET DosCreateDir(PSZ pszDirName, PEAOP2 peaop2)
{
TRACE_NATIVE("DosCreateDir('%s', %p)", pszDirName, peaop2);
return DosCreateDir_implementation(pszDirName, peaop2);
} // DosCreateDir
#if !LX_LEGACY
ULONG DosSelToFlat(VOID) { return 0; }
#else
ULONG _DosSelToFlat(void *ptr)
{
TRACE_NATIVE("DosSelToFlat(%p)", ptr);
return (ULONG) GLoaderState.convert1616to32((uint32) ptr);
} // _DosSelToFlat
// DosSelToFlat() passes its argument in %eax, so a little asm to bridge that...
__asm__ (
// !!! FIXME: this needs to protect more registers.
".globl DosSelToFlat \n\t"
".type DosSelToFlat, @function \n\t"
"DosSelToFlat: \n\t"
" pushl %eax \n\t"
" call _DosSelToFlat \n\t"
" addl $4, %esp \n\t"
" ret \n\t"
".size _DosSelToFlat, .-_DosSelToFlat \n\t"
);
#endif
static inline int trySpinLock(int *lock)
{
return (__sync_lock_test_and_set(lock, 1) == 0);
} // trySpinLock
APIRET16 DosSemRequest(PHSEM16 sem, LONG ms)
{
TRACE_NATIVE("DosSemRequest(%p, %u)", sem, (uint) ms);
if (ms == 0) {
return !trySpinLock(sem) ? ERROR_TIMEOUT : NO_ERROR;
} else if (ms < 0) {
while (!trySpinLock(sem))
usleep(1000);
return NO_ERROR;
}
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
const uint64 end = (((uint64) ts.tv_sec) * 1000) + (((uint64) ts.tv_nsec) / 1000000) + ms;
while (1) {
if (trySpinLock(sem))
return NO_ERROR;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
const uint64 now = (((uint64) ts.tv_sec) * 1000) + (((uint64) ts.tv_nsec) / 1000000);
if (now >= end)
return ERROR_TIMEOUT;
usleep(1000);
} // while
return NO_ERROR;
} // DosSemRequest
APIRET16 DosSemClear(PHSEM16 sem)
{
TRACE_NATIVE("DosSemClear(%p)", sem);
__sync_lock_release(sem);
return NO_ERROR;
} // DosSemClear
APIRET16 DosSemWait(PHSEM16 sem, LONG ms)
{
TRACE_NATIVE("DosSemWait(%p, %u)", sem, (uint) ms);
if (ms == 0) {
return __sync_bool_compare_and_swap(sem, 0, 1) ? NO_ERROR : ERROR_TIMEOUT;
} else if (ms < 0) {
while (!__sync_bool_compare_and_swap(sem, 0, 1))
usleep(1000);
return NO_ERROR;
}
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
const uint64 end = (((uint64) ts.tv_sec) * 1000) + (((uint64) ts.tv_nsec) / 1000000) + ms;
while (1) {
if (__sync_bool_compare_and_swap(sem, 0, 1))
return NO_ERROR;
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
const uint64 now = (((uint64) ts.tv_sec) * 1000) + (((uint64) ts.tv_nsec) / 1000000);
if (now >= end)
return ERROR_TIMEOUT;
usleep(1000);
} // while
return NO_ERROR;
} // DosSemWait
APIRET16 DosSemSet(PHSEM16 sem)
{
TRACE_NATIVE("DosSemSet(%p)", sem);
__sync_bool_compare_and_swap(sem, 1, 0);
return NO_ERROR;
} // DosSemSet
APIRET DosCloseMutexSem(HMTX hmtx)
{
TRACE_NATIVE("DosCloseMutexSem(%u)", (uint) hmtx);
if (hmtx) {
pthread_mutex_t *mutex = (pthread_mutex_t *) hmtx;
const int rc = pthread_mutex_destroy(mutex);
if (rc == EBUSY) {
return ERROR_SEM_BUSY;
} else if (rc == 0) {
free(mutex);
return NO_ERROR;
} // else if
} // if
return ERROR_INVALID_HANDLE;
} // DosCloseMutexSem
APIRET DosSetProcessCp(ULONG cp)
{
TRACE_NATIVE("DosSetProcessCp(%u)", (unsigned int) cp);
if (cp != 437) { // United States
FIXME("non-US codepages unsupported at the moment");
return ERROR_INVALID_CODE_PAGE;
}
return NO_ERROR;
} // DosSetProcessCp
APIRET DosQueryFSAttach(PSZ pszDeviceName, ULONG ulOrdinal, ULONG ulFSAInfoLevel, PFSQBUFFER2 pfsqb, PULONG pcbBuffLength)
{
TRACE_NATIVE("DosQueryFSAttach('%s', %u, %u, %p, %p)", pszDeviceName, (unsigned int) ulOrdinal, (unsigned int) ulFSAInfoLevel, pfsqb, pcbBuffLength);
return ERROR_INVALID_FUNCTION;
} // DosQueryFSAttach
APIRET DosSetFileSize(HFILE h, ULONG len)
{
TRACE_NATIVE("DosSetFileSize(%u, %u)", (unsigned int) h, (unsigned int) len);
const int fd = getHFileUnixDescriptor(h);
if (fd == -1)
return ERROR_INVALID_HANDLE;
if (ftruncate(fd, len) == -1) {
FIXME("Set a better error based on errno");
return ERROR_DISK_FULL;
}
return NO_ERROR;
} // DosSetFileSize
static APIRET DosDupHandle_implementation(HFILE hFile, PHFILE pHfile)
{
if (*pHfile == -1) {
HFileInfo *info = HFiles;
uint32 i;
if (!grabLock(&GMutexDosCalls))
return ERROR_SYS_INTERNAL;
for (i = 0; i < MaxHFiles; i++, info++) {
if (info->fd == -1) { // available?
info->fd = -2;
break;
} // if
} // for
ungrabLock(&GMutexDosCalls);
if (i == MaxHFiles) {
return ERROR_TOO_MANY_OPEN_FILES;
}
memcpy(&HFiles[i], &HFiles[hFile], sizeof(HFileInfo));
HFiles[i].fd = dup(HFiles[hFile].fd);
*pHfile = i;
} else {
if (HFiles[*pHfile].fd != -1)
return ERROR_INVALID_TARGET_HANDLE;
memcpy(&HFiles[*pHfile], &HFiles[hFile], sizeof(HFileInfo));
HFiles[*pHfile].fd = dup(HFiles[hFile].fd);
}
return NO_ERROR;
} // DosDupHandle_implementation
APIRET DosDupHandle(HFILE hFile, PHFILE pHfile)
{
TRACE_NATIVE("DosDupHandle(%u, %p)", hFile, pHfile);
return DosDupHandle_implementation(hFile, pHfile);
} // DosDupHandle
typedef struct DosBeepGeneratorInfo
{
ULONG samples_remaining;
ULONG positive;
ULONG square_remaining;
ULONG square_length;
ULONG square_remainder;
ULONG accumulator;
ULONG next_freq;
ULONG next_duration;
} DosBeepGeneratorInfo;
static DosBeepGeneratorInfo dos_beep_info;
static int DosBeepGenerator(void *userdata, float *stream, int samples, int freq)
{
const float beep_volume = GLoaderState.beep_volume;
DosBeepGeneratorInfo *info = &dos_beep_info;
assert(userdata == &dos_beep_info);
while (samples > 0) {
if (!info->samples_remaining) { // current beep command is finished?
if (!info->next_freq) {
return 0; // out of things to play, remove ourselves from the mixer.
}
// set up next beep command.
info->square_length = ((ULONG) freq) / info->next_freq;
info->square_remainder = ((ULONG) freq) % info->next_freq;
info->samples_remaining = ((ULONG) ((info->next_duration / 1000.0) * ((ULONG) freq))) * 1;
info->accumulator = 0;
info->next_freq = 0;
info->next_duration = 0;
} else if (!info->square_remaining) { // this part of the square wave is finished, flip it and start the next part.
info->positive = !info->positive;
info->square_remaining = info->square_length;
info->accumulator += info->square_remainder;
if (info->accumulator >= info->square_length) {
info->accumulator -= info->square_length;
info->square_remaining++;
}
} else { // still generating the current piece of the square wave, mix it into the stream.
// VMware emulates the PC Speaker on a sound card _really_ quietly.
const float val = info->positive ? beep_volume : -beep_volume;
int total = info->square_remaining;
if (info->samples_remaining < total) total = info->samples_remaining;
if (samples < total) total = samples;
FIXME("good candidate for SSE");
for (int i = 0; i < total; i++) {
stream[i] += val;
}
samples -= total;
info->samples_remaining -= total;
info->square_remaining -= total;
stream += total;
}
}
return 1; // remain in the mixer to keep generating audio.
} // DosBeepGenerator
static APIRET DosBeep_implementation(ULONG freq, ULONG duration)
{
if ((freq < 0x25) || (freq > 0x7FFF)) {
return ERROR_INVALID_FREQUENCY;
} else if (duration == 0) {
return NO_ERROR;
}
// Either OS/2 or Vmware seems to be doubling the frequency requested, I
// think, as this sounds closer to what DosBeep() generates there.
//FIXME("sanity check this");
//freq *= 2;
// assuming OS/2 ran the PC Speaker off the BIOS timer, firing 18.2 times
// a second (about 55 milliseconds), we'll round up the requested
// duration to match.
FIXME("sanity check this");
const ULONG duration_diff = duration % 55;
const ULONG mix_duration = duration + (duration_diff ? (55 - duration_diff) : 0);
// we chain beep commands together and return a little before they'll
// end, so the caller can queue another without a gap in playback if
// they're making multiple calls to DosBeep to make a cute little tune.
// OS/2's implementation appears to hold a mutex while driving the PC
// speaker; if you DosBeep() from two threads at once, one will block
// until the other finishes playing.
grabLock(&GMutexDosBeep);
FIXME("should be atomic");
while (*((volatile ULONG *) &dos_beep_info.next_freq) != 0) {
usleep(1000);
}
dos_beep_info.next_duration = mix_duration;
dos_beep_info.next_freq = freq;
// wait about the time this was requested to play, but since there is
// variance on when the audio thread will run again, and we padded out
// the play time to 55 milliseconds, chances are good that the next
// DosBeep call will queue more sound to play seamlessly.
// If less than 50 milliseconds, don't sleep at all; if they call DosBeep
// again, it'll block until it can queue the command anyhow.
if (duration > 55) {
usleep((duration-55) * 1000);
}
const int rc = GLoaderState.registerAudioGenerator(DosBeepGenerator, &dos_beep_info, 1);
if (!rc) {
memset(&dos_beep_info, '\0', sizeof (dos_beep_info)); // oh well.
}
ungrabLock(&GMutexDosBeep);
return NO_ERROR;
} // DosBeep_implementation
APIRET DosBeep(ULONG freq, ULONG duration)
{
TRACE_NATIVE("DosBeep(%u, %u)", freq, duration);
return DosBeep_implementation(freq, duration);
} // DosBeep
APIRET16 Dos16GetVersion(PUSHORT pver)
{
TRACE_NATIVE("Dos16GetVersion(%p)", pver);
uint8 *bytes = (uint8 *) pver;
bytes[0] = 45; // OS/2 Warp 4.52 reports this.
bytes[1] = 20;
return NO_ERROR;
} // Dos16GetVersion
APIRET16 Dos16CreateCSAlias(USHORT sel, PUSHORT pnewsel)
{
TRACE_NATIVE("Dos16CreateCSAlias(%u, %p)", (uint) sel, pnewsel);
// We hand back a selector for the same linear memory (our 16-bit segments
// are already mapped read/write/execute, so this is writable in practice).
const uint32 addr32 = GLoaderState.ldt[sel >> 3];
if (!addr32)
return ERROR_INVALID_SELECTOR;
uint16 newsel = 0xFFFF;
uint16 offset = 0;
if (!GLoaderState.findSelector(addr32, &newsel, &offset, 1))
return ERROR_NOT_ENOUGH_MEMORY;
if (pnewsel) *pnewsel = (USHORT) ((newsel << 3) | 7);
return NO_ERROR;
} // Dos16CreateCSAlias
APIRET16 Dos16DevConfig(PUSHORT pcb, PBYTE pb)
{
TRACE_NATIVE("Dos16DevConfig(%p, %p)", pcb, pb);
FIXME("returning an empty device config");
if (pcb) *pcb = 0;
return NO_ERROR;
} // Dos16DevConfig
APIRET16 Dos16Move(PVOID pdst, PVOID psrc, USHORT cb)
{
TRACE_NATIVE("Dos16Move(%p, %p, %u)", pdst, psrc, (uint) cb);
memmove(pdst, psrc, cb);
return NO_ERROR;
} // Dos16Move
APIRET16 Dos16GetDateTime(PDATETIME16 pdt)
{
TRACE_NATIVE("Dos16GetDateTime(%p)", pdt);
// the 1.x 16-bit DATETIME layout is identical to the 32-bit one.
return (APIRET16) DosGetDateTime((PDATETIME) pdt);
} // Dos16GetDateTime
APIRET16 Dos16NewSize(USHORT sel, USHORT newsize)
{
TRACE_NATIVE("Dos16NewSize(%u, %u)", (uint) sel, (uint) newsize);
// we always map a full 64k segment and never discard, so resizing is a
// no-op. Just make sure the selector is one of ours.
if (!GLoaderState.ldt[sel >> 3])
return ERROR_INVALID_SELECTOR;
FIXME("this implementation is severely lacking"); // sharing concerns, etc.
return NO_ERROR;
} // Dos16NewSize
USHORT Dos16HugeShift(void)
{
// Huge segments are made of consecutive 64k selectors, so each one is 8
// higher than the last (1 << 3).
TRACE_NATIVE("Dos16HugeShift()");
return 3;
} // Dos16HugeShift
USHORT Dos16HugeIncr(USHORT sel, USHORT incr)
{
TRACE_NATIVE("Dos16HugeIncr(%u, %u)", (uint) sel, (uint) incr);
// !!! FIXME: this assumes the same selector math as DOS16HugeShift, and
// !!! FIXME: that we actually made a huge allocation with consecutive
// !!! FIXME: selectors (we don't, currently).
return (USHORT) (sel + (incr << 3));
} // Dos16HugeIncr
APIRET16 Dos16GetMachineMode(PBYTE pmode)
{
TRACE_NATIVE("Dos16GetMachineMode(%p)", pmode);
*pmode = 1; // 1==OS/2 mode (as opposed to zero for DOS mode).
return NO_ERROR;
} // Dos16GetMachineMode
APIRET16 Dos16GetHugeShift(PUSHORT pcount)
{
TRACE_NATIVE("Dos16GetHugeShift(%p)", pcount);
*pcount = 0; FIXME("maybe?");
return NO_ERROR;
} // Dos16GetHugeShift
APIRET16 Dos16GetPID(PPIDINFO16 ppidinfo)
{
TRACE_NATIVE("Dos16GetPID(%p)", ppidinfo);
PTIB2 tib2 = (PTIB2) LX_GETTIB2();
// note that these happen to fit Linux default PID ranges in 2018, which
// tend to be 32768, but can be much higher by writing to
// /proc/sys/kernel/pid_max
// NOTE: host PIDs routinely exceed 16 bits (pid_max is 4194304 here), so
// these asserts fire on any modern Linux; the 16-bit API truncates below.
// assert(GLoaderState.pib.pib_ulpid <= 0xFFFF);
// assert(GLoaderState.pib.pib_ulppid <= 0xFFFF);
// assert(tib2->tib2_ultid <= 0xFFFF);
ppidinfo->pid = (USHORT) GLoaderState.pib.pib_ulpid;
ppidinfo->tid = (USHORT) tib2->tib2_ultid;
ppidinfo->pidParent = (USHORT) GLoaderState.pib.pib_ulppid;
return NO_ERROR;
} // Dos16GetPID
VOID Dos16Exit(USHORT action, USHORT exitcode)
{
TRACE_NATIVE("Dos16Exit(%u, %u)", (uint) action, (uint) exitcode);
DosExit_implementation(action, exitcode);
} // Dos16Exit
APIRET16 Dos16QHandType(USHORT handle, PUSHORT ptype, PUSHORT pflags)
{
TRACE_NATIVE("Dos16QHandType(%u, %p, %p)", (uint) handle, ptype, pflags);
ULONG ultype, ulattr;
const APIRET rc = DosQueryHType_implementation((HFILE) handle, &ultype, &ulattr);
if (rc == NO_ERROR) {
if (ptype) *ptype = (USHORT) ultype;
if (pflags) *pflags = (USHORT) ulattr;
} // if
return (APIRET16) rc;
} // Dos16QHandType
APIRET16 Dos16AllocSeg(USHORT size, PUSHORT psel, USHORT flags)
{
TRACE_NATIVE("Dos16AllocSeg(%u, %p, %u)", (uint) size, psel, (uint) flags);
if (psel == NULL) {
return ERROR_INVALID_PARAMETER;
} else if ((flags & 0xFFF0) != 0) { // these bits are reserved and must be zero.
return ERROR_INVALID_PARAMETER;
}
// we don't care about the size, we just allocate a whole 64k segment. Oh well.
uint16 sel = 0xFFFF;
//void *addr = GLoaderState.allocSegment(&sel, 1); // we mark these as code in case they want to do self-modifying magic, etc.
void *addr = GLoaderState.allocSegment(&sel, 0);
if (!addr) {
assert(sel == 0xFFFF);
return ERROR_NOT_ENOUGH_MEMORY;
}
assert(sel != 0xFFFF);
if (flags & (1 << 0)) {
FIXME("segment is meant to be shareable through Dos16GiveSeg");
}
if (flags & (1 << 1)) {
FIXME("segment is meant to be shareable through Dos16GetSeg");
}
// documentation says this is a selector (specifically: a segment only on
// DOS in the family mode API), but it looks like OS/2 wants a segment too.
//*psel = (USHORT) sel;
*psel = (USHORT) ((sel << 3) | 7);
return NO_ERROR;
} // Dos16AllocSeg
APIRET16 Dos16ReallocSeg(USHORT size, USHORT sel)
{
TRACE_NATIVE("Dos16ReallocSeg(%u, %u)", (uint) size, (uint) sel);
// we don't ever discard segments, and we always allocate the full 64k,
// so we don't have to worry about resizing an existing segment.
// But there are sharing concerns, etc.
FIXME("this implementation is severely lacking");
// (we should probably make sure the selector is valid...)
return NO_ERROR;
} // Dos16ReallocSeg
APIRET16 Dos16FreeSeg(USHORT sel)
{
TRACE_NATIVE("Dos16FreeSeg(%u)", (uint) sel);
FIXME("this needs to deal with reference counting and shared segments");
// documentation says this is a selector (specifically: a segment only on
// DOS in the family mode API), but it looks like OS/2 wants a segment too.
GLoaderState.freeSegment(sel >> 3);
return NO_ERROR;
} // Dos16FreeSeg
APIRET16 Dos16GetCp(USHORT cb, PUSHORT arCP, PUSHORT pcCP)
{
TRACE_NATIVE("Dos16GetCp(%u, %p, %p)", (uint) cb, arCP, pcCP);
FIXME("This is mostly a stub");
FIXME("...and mostly copy/paste from DosQueryCp()...");
if (cb < sizeof (*arCP) * 2)
return ERROR_CPLIST_TOO_SMALL;
// 437 == United States codepage.
arCP[0] = 437; // current process codepage
arCP[1] = 437; // prepared system codepage
if (pcCP)
*pcCP = sizeof (*arCP) * 2;
return NO_ERROR;
} // Dos16GetCp
APIRET16 Dos16GetEnv(PUSHORT psel, PUSHORT pcmdoffset)
{
TRACE_NATIVE("Dos16GetEnv(%p, %p)", psel, pcmdoffset);
const uint32 cmd1616 = GLoaderState.convert32to1616(GLoaderState.pib.pib_pchcmd);
if (pcmdoffset) *pcmdoffset = (USHORT) (cmd1616 & 0xFFFF);
if (psel) *psel = (USHORT) ((cmd1616 >> 16) & 0xFFFF);
return NO_ERROR;
} // Dos16GetEnv
APIRET16 Dos16Write(USHORT h, PVOID buf, USHORT buflen, PUSHORT actual)
{
TRACE_NATIVE("Dos16Write(%u, %p, %u, %p)", (uint) h, buf, (uint) buflen, actual);
ULONG ulactual = actual ? *actual : 0;
APIRET rc = DosWrite_implementation(h, buf, buflen, &ulactual);
if (actual) *actual = (USHORT) ulactual;
return (APIRET16) rc;
} // Dos16Write
APIRET16 Dos16SetVec(USHORT vecnum, PFN routine, PVOID prevaddress)
{
TRACE_NATIVE("Dos16SetVec(%u, %p, %p)", (uint) vecnum, routine, prevaddress);
FIXME("do something");
if (prevaddress) *(PFN *)prevaddress = 0;
return NO_ERROR;
}
APIRET16 Dos16SetSigHandler(PFN routine, PVOID prevaddress, PUSHORT prevaction, USHORT action, USHORT signumber)
{
TRACE_NATIVE("Dos16SetSigHandler(%p, %p, %p, %u, %u)", routine, prevaddress, prevaction, action, signumber);
FIXME("do something");
if (prevaddress) *(PFN *)prevaddress = 0;
if (prevaction) *prevaction = 0;
return NO_ERROR;
}
APIRET16 Dos16GetInfoSeg(PSEL globalseg, PSEL localseg)
{
TRACE_NATIVE("Dos16GetInfoSeg(%p, %p)", globalseg, localseg);
if (globalseg) *globalseg = ginfosel << 3 | 7;
if (localseg) *localseg = linfosel << 3 | 7;
return NO_ERROR;
}
APIRET16 Dos16QCurDisk(PUSHORT drivenum, PULONG drivemap)
{
TRACE_NATIVE("Dos16QCurDisk(%p, %p)", drivenum, drivemap);
ULONG uldrivenum;
const APIRET rc = DosQueryCurrentDisk_implementation(&uldrivenum, drivemap);
*drivenum = (USHORT) uldrivenum;
return (APIRET16) rc;
} // Dos16QCurDisk
APIRET16 Dos16QCurDir(USHORT drivenum, PBYTE dirpath, PUSHORT dirpathlen)
{
TRACE_NATIVE("Dos16QCurDir(%u, %p, %p)", (uint) drivenum, dirpath, dirpathlen);
ULONG uldirpathlen = (ULONG) *dirpathlen;
const APIRET rc = DosQueryCurrentDir_implementation(drivenum, dirpath, &uldirpathlen);
*dirpathlen = (USHORT) uldirpathlen;
return (APIRET16) rc;
} // Dos16QCurDir
APIRET16 Dos16HoldSignal(USHORT action)
{
TRACE_NATIVE("Dos16HoldSignal(%d)", action);
FIXME("stub");
return NO_ERROR;
}
APIRET16 Dos16QFsInfo(USHORT drivenum, USHORT fslevel, PBYTE infobuf, USHORT infobufsize)
{
TRACE_NATIVE("Dos16QFsInfo(%d, %d, %p, %d)", drivenum, fslevel, infobuf, infobufsize);
const char label[] = "linux";
if (fslevel == 1)
FIXME("fslevel 1");
else if (fslevel == 2)
{
infobuf[4] = sizeof(label);
strcpy((char *)&infobuf[5], label);
}
return NO_ERROR;
}
APIRET16 Dos16QFileMode(PCHAR filepath, PUSHORT attr, ULONG res)
{
TRACE_NATIVE("Dos16QFileMode('%s', %p, %d)", filepath, attr, res);
APIRET err;
char *path = makeUnixPath(filepath, &err);
if (!path) return err;
struct stat sbuf;
int ret = stat(path, &sbuf);
free(path);
if (ret) return ERROR_FILE_NOT_FOUND;
*attr = 0;
if (S_ISDIR(sbuf.st_mode))
*attr |= (1 << 4);
return NO_ERROR;
}
APIRET16 Dos16Open(PSZ pszFileName, PUSHORT pHf, PULONG pulAction, ULONG cbFile, USHORT ulAttribute, USHORT fsOpenFlags, USHORT fsOpenMode, PEAOP2 peaop2)
{
TRACE_NATIVE("Dos16Open('%s', %p, %p, %u, %u, %u, %u, %p)", pszFileName, pHf, pulAction, (uint) cbFile, (uint) ulAttribute, (uint) fsOpenFlags, (uint) fsOpenMode, peaop2);
HFILE hf;
APIRET ret = doDosOpen(pszFileName, &hf, pulAction, (LONGLONG) cbFile, ulAttribute, fsOpenFlags, fsOpenMode, peaop2);
*pHf = hf;
return ret;
} // DosOpen
APIRET16 Dos16FindFirst(PSZ pszFileSpec, PSHORT phdir, USHORT flAttribute, PVOID pfindbuf, USHORT cbBuf, PUSHORT pcFileNames, ULONG res)
{
if (!hdir16)
hdir16 = calloc(10, sizeof(HDIR)); // 10 dir handles
ULONG hdir32 = *phdir;
ULONG cfn = *pcFileNames;
APIRET ret = DosFindFirst(pszFileSpec, &hdir32, flAttribute, pfindbuf, cbBuf, &cfn, -1);
if (ret && (ret != ERROR_EAS_DIDNT_FIT))
return ret;
int i;
for (i = 0; i < 10; i++)
{
if(!hdir16[i])
break;
}
if (i == 10)
return ERROR_NO_MORE_FILES;
hdir16[i] = hdir32;
*phdir = i;
*pcFileNames = cfn;
return ret;
}
APIRET16 Dos16ChDir(PSZ pszDir, ULONG res)
{
TRACE_NATIVE("Dos16ChDir('%s', %u)", pszDir, res);
if (res != 0) { // reserved, and must be zero, or return error.
return ERROR_INVALID_PARAMETER;
}
return (APIRET16) DosSetCurrentDir_implementation(pszDir);
} // Dos16ChDir
APIRET16 Dos16Close(USHORT hFile)
{
TRACE_NATIVE("Dos16Close(%u)", (uint) hFile);
return DosClose_implementation(hFile);
} // Dos16Close
APIRET16 Dos16FindNext(USHORT hDir, PVOID pfindbuf, USHORT cbfindbuf, PUSHORT pcFilenames)
{
APIRET ret;
ULONG pcfn = *pcFilenames;
ret = DosFindNext(hdir16[hDir], pfindbuf, cbfindbuf, &pcfn);
*pcFilenames = pcfn;
return ret;
}
APIRET16 Dos16FindClose(USHORT hDir)
{
if (hDir > 10)
return ERROR_INVALID_HANDLE;
HDIR hdir32 = hdir16[hDir];
hdir16[hDir] = 0;
return DosFindClose(hdir32);
}
APIRET16 Dos16Delete(PSZ pszFile, ULONG res)
{
TRACE_NATIVE("Dos16Delete('%s', %u)", pszFile, (uint) res);
return DosDelete_implementation(pszFile);
} // Dos16Delete
APIRET16 Dos16DupHandle(USHORT hFile, PUSHORT pHfile)
{
TRACE_NATIVE("Dos16DupHandle(%u, %p)", (uint) hFile, pHfile);
HFILE phf = *(int16_t *)pHfile;
APIRET ret = DosDupHandle_implementation(hFile, &phf);
*pHfile = phf;
return ret;
} // Dos16DupHandle
APIRET16 Dos16Read(USHORT hFile, PVOID pBuffer, USHORT cbRead, PUSHORT pcbActual)
{
TRACE_NATIVE("Dos16Read(%u, %p, %u, %p)", (uint) hFile, pBuffer, (uint) cbRead, pcbActual);
ULONG actual;
APIRET ret = DosRead_implementation(hFile, pBuffer, cbRead, &actual);
*pcbActual = (USHORT) actual;
return ret;
} // Dos16Read
APIRET16 Dos16ExecPgm(PCHAR pObjname, SHORT cbObjname, USHORT execFlag, PSZ pArg, PSZ pEnv, PRESULTCODES16 pRes, PSZ pName)
{
TRACE_NATIVE("Dos16ExecPgm('%s', %d, %u, '%s', '%s', %p, '%s')", pObjname, (int) cbObjname, (uint) execFlag, pArg, pEnv, pRes, pName);
RESULTCODES res;
const APIRET ret = DosExecPgm_implementation(pObjname, cbObjname, execFlag, pArg, pEnv, &res, pName);
pRes->codeTerminate = (USHORT) res.codeTerminate;
pRes->codeResult = (USHORT) res.codeResult;
return ret;
} // Dos16ExecPgm
APIRET16 Dos16CWait(USHORT action, USHORT option, PRESULTCODES16 pres, PUSHORT ppid, USHORT pid)
{
TRACE_NATIVE("Dos16CWait(%u, %u, %p, %p, %u)", (uint) action, (uint) option, pres, ppid, (uint) pid);
RESULTCODES res;
PID cpid;
APIRET ret = DosWaitChild(action, option, &res, &cpid, pid);
pres->codeTerminate = res.codeTerminate;
pres->codeResult = res.codeResult;
*ppid = cpid;
return ret;
} // Dos16CWait
APIRET16 Dos16ChgFilePtr(USHORT handle, LONG distance, USHORT whence, PULONG newoffset)
{
TRACE_NATIVE("Dos16ChgFlePtr(%u, %u, %u, %p)", (uint) handle, (uint) distance, (uint) whence, newoffset);
return (APIRET16) DosSetFilePtr_implementation(handle, distance, whence, newoffset);
} // Dos16ChgFilePtr
APIRET16 Dos16Beep(USHORT freq, USHORT duration)
{
TRACE_NATIVE("Dos16Beep(%u, %u)", freq, duration);
return DosBeep_implementation(freq, duration);
} // Dos16Beep
APIRET16 Dos16MkDir(PSZ pszDirName, PEAOP2 peaop2)
{
TRACE_NATIVE("Dos16MkDir('%s', %p)", pszDirName, peaop2);
return DosCreateDir_implementation(pszDirName, peaop2);
}
APIRET16 Dos16MkDir2(PSZ pszDirName, PEAOP2 peaop2) // !!! FIXME: check this.
{
TRACE_NATIVE("Dos16MkDir('%s', %p)", pszDirName, peaop2);
return DosCreateDir_implementation(pszDirName, peaop2);
}
LX_NATIVE_CONSTRUCTOR(doscalls)
{
GLoaderState.dosExit = DosExit;
if (pthread_mutex_init(&GMutexDosCalls, NULL) == -1) {
fprintf(stderr, "pthread_mutex_init failed!\n");
abort();
} // if
if (pthread_mutex_init(&GMutexDosBeep, NULL) == -1) {
pthread_mutex_destroy(&GMutexDosCalls);
fprintf(stderr, "pthread_mutex_init failed!\n");
abort();
} // if
MaxHFiles = 20; // seems to be OS/2's default.
HFiles = (HFileInfo *) malloc(sizeof (HFileInfo) * MaxHFiles);
if (!HFiles) {
pthread_mutex_destroy(&GMutexDosCalls);
pthread_mutex_destroy(&GMutexDosBeep);
fprintf(stderr, "Out of memory!\n");
abort();
} // if
HFileInfo *info = HFiles;
for (uint32 i = 0; i < MaxHFiles; i++, info++) {
info->fd = -1;
info->type = 0;
info->attr = 0;
info->flags = 0;
} // for
// launching a Hello World program from CMD.EXE seems to inherit several
// file handles. 0, 1, 2 seem to map to stdin, stdout, stderr (and will
// be character devices (maybe CON?) by default, unless you redirect
// to a file in which case they're physical files, and using '|' in
// CMD.EXE will make handle 1 into a Pipe, of course.
// Handles, 4, 6 and 9 were also in use (all character devices, attributes
// 51585, 51392, 51392), but I don't know what these are, if they are
// inherited from CMD.EXE or supplied by OS/2 for whatever purpose.
// For now, we just wire up stdio.
FIXME("We can iterate /proc/self/fd to get all currently-open files");
for (int i = 0; i <= 2; i++) {
initHFileInfoFromUnixFd(i, &HFiles[i]);
} // for
FIXME("Maybe set this up in lx_loader and make it NULL in lib2ine");
FIXME("ginfo's time fields update in a timer interrupt! :O");
ginfo = (GINFOSEG *) GLoaderState.allocSegment(&ginfosel, 0);
linfo = (LINFOSEG *) GLoaderState.allocSegment(&linfosel, 0);
if ((!ginfo) || (!linfo)) {
pthread_mutex_destroy(&GMutexDosCalls);
pthread_mutex_destroy(&GMutexDosBeep);
fprintf(stderr, "Failed to allocate info segments!\n");
abort();
}
memset(ginfo, '\0', sizeof (GINFOSEG));
memset(linfo, '\0', sizeof (LINFOSEG));
ginfo->time = time(NULL);
struct tm *gtime = gmtime((time_t *)&ginfo->time);
ginfo->hour = gtime->tm_hour;
ginfo->minutes = gtime->tm_min;
ginfo->seconds = gtime->tm_sec;
ginfo->timezone = 0;
ginfo->cusecTimerInterval = 100;
ginfo->day = gtime->tm_mday;
ginfo->month = gtime->tm_mon;
ginfo->year = gtime->tm_year;
ginfo->weekday = gtime->tm_wday;
ginfo->uchMajorVersion = 20; FIXME("are these backwards?");
ginfo->uchMinorVersion = 40; FIXME("are these backwards?");
ginfo->sgCurrent = 1;
ginfo->sgMax = 99;
ginfo->cHugeShift = 0;
ginfo->fProtectModeOnly = 1;
ginfo->pidForeground = GLoaderState.pib.pib_ulpid;
ginfo->csecMaxWait = 1;
ginfo->cmsecMinSlice = 1;
ginfo->cmsecMaxSlice = 10;
ginfo->bootdrive = 3;
ginfo->csgWindowableVioMax = 99;
ginfo->csgPMMax = 99;
linfo->pidCurrent = GLoaderState.pib.pib_ulpid;
linfo->pidParent = GLoaderState.pib.pib_ulppid;
}
LX_NATIVE_DESTRUCTOR(doscalls)
{
GLoaderState.dosExit = NULL;
ExitListItem *next = GExitList;
GExitList = NULL;
for (ExitListItem *item = next; item; item = next) {
next = item->next;
free(item);
} // for
for (uint32 i = 0; i < MaxHFiles; i++) {
if (HFiles[i].fd > 2) // don't close -1, or any stdio handles.
close(HFiles[i].fd);
} // for
free(HFiles);
HFiles = NULL;
MaxHFiles = 0;
GLoaderState.freeSelector(ginfosel);
GLoaderState.freeSelector(linfosel);
pthread_mutex_destroy(&GMutexDosCalls);
pthread_mutex_destroy(&GMutexDosBeep);
}
// end of doscalls.c ...