Files
2ine-tms34010/native/doscalls.c
T
Ryan C. Gordon cbffb2ff60 Initialize the PIB at startup.
Otherwise, it won't be available to DLL init code when run before the
main executable starts.

Fixes Watcom C's wlink.exe crashing because it gets a NULL command line.
2016-10-17 17:00:32 -04:00

2681 lines
83 KiB
C

#define _POSIX_C_SOURCE 199309
#define _BSD_SOURCE
#define _GNU_SOURCE
#include <unistd.h>
#include <limits.h>
#include <time.h>
#include <dirent.h>
#include <fcntl.h>
#include <pthread.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 "os2native.h"
#include "doscalls.h"
static LxLoaderState *GLoaderState = NULL;
static pthread_mutex_t GMutexDosCalls;
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;
ULONG type;
ULONG attr;
} HFileInfo;
static HFileInfo *HFiles = NULL;
static uint32 MaxHFiles = 0;
typedef struct Thread
{
pthread_t thread;
size_t stacklen;
PFNTHREAD fn;
ULONG fnarg;
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;
LX_NATIVE_MODULE_DEINIT({
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 != -1)
close(HFiles[i].fd);
} // for
free(HFiles);
HFiles = NULL;
MaxHFiles = 0;
pthread_mutex_destroy(&GMutexDosCalls);
GLoaderState = NULL;
})
static int initDoscalls(LxLoaderState *lx_state)
{
GLoaderState = lx_state;
if (pthread_mutex_init(&GMutexDosCalls, NULL) == -1) {
fprintf(stderr, "pthread_mutex_init failed!\n");
return 0;
} // if
MaxHFiles = 20; // seems to be OS/2's default.
HFiles = (HFileInfo *) malloc(sizeof (HFileInfo) * MaxHFiles);
if (!HFiles) {
fprintf(stderr, "Out of memory!\n");
return 0;
} // if
HFileInfo *info = HFiles;
for (uint32 i = 0; i < MaxHFiles; i++, info++) {
info->fd = -1;
info->type = 0;
info->attr = 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.
for (int i = 0; i <= 2; i++) {
HFiles[i].fd = i;
HFiles[i].type = 1; // !!! FIXME: could be a pipe or file.
HFiles[i].attr = DAW_STDIN | DAW_STDOUT | DAW_LEVEL1 | DAW_CHARACTER;
} // for
return 1;
} // initDoscalls
LX_NATIVE_MODULE_INIT({ if (!initDoscalls(lx_state)) return NULL; })
LX_NATIVE_EXPORT(DosSetPathInfo, 219),
LX_NATIVE_EXPORT(DosQueryPathInfo, 223),
LX_NATIVE_EXPORT(DosQueryHType, 224),
LX_NATIVE_EXPORT(DosScanEnv, 227),
LX_NATIVE_EXPORT(DosGetDateTime, 230),
LX_NATIVE_EXPORT(DosDevConfig, 231),
LX_NATIVE_EXPORT(DosExit, 234),
LX_NATIVE_EXPORT(DosResetBuffer, 254),
LX_NATIVE_EXPORT(DosSetFilePtr, 256),
LX_NATIVE_EXPORT(DosClose, 257),
LX_NATIVE_EXPORT(DosDelete, 259),
LX_NATIVE_EXPORT(DosFindClose, 263),
LX_NATIVE_EXPORT(DosFindFirst, 264),
LX_NATIVE_EXPORT(DosFindNext, 265),
LX_NATIVE_EXPORT(DosOpen, 273),
LX_NATIVE_EXPORT(DosQueryCurrentDir, 274),
LX_NATIVE_EXPORT(DosQueryCurrentDisk, 275),
LX_NATIVE_EXPORT(DosQueryFileInfo, 279),
LX_NATIVE_EXPORT(DosWaitChild, 280),
LX_NATIVE_EXPORT(DosRead, 281),
LX_NATIVE_EXPORT(DosWrite, 282),
LX_NATIVE_EXPORT(DosExecPgm, 283),
LX_NATIVE_EXPORT(DosQueryCp, 291),
LX_NATIVE_EXPORT(DosExitList, 296),
LX_NATIVE_EXPORT(DosAllocMem, 299),
LX_NATIVE_EXPORT(DosFreeMem, 304),
LX_NATIVE_EXPORT(DosSetMem, 305),
LX_NATIVE_EXPORT(DosCreateThread, 311),
LX_NATIVE_EXPORT(DosGetInfoBlocks, 312),
LX_NATIVE_EXPORT(DosLoadModule, 318),
LX_NATIVE_EXPORT(DosQueryModuleHandle, 319),
LX_NATIVE_EXPORT(DosQueryModuleName, 320),
LX_NATIVE_EXPORT(DosQueryProcAddr, 321),
LX_NATIVE_EXPORT(DosQueryAppType, 323),
LX_NATIVE_EXPORT(DosCreateEventSem, 324),
LX_NATIVE_EXPORT(DosCloseEventSem, 326),
LX_NATIVE_EXPORT(DosResetEventSem, 327),
LX_NATIVE_EXPORT(DosPostEventSem, 328),
LX_NATIVE_EXPORT(DosWaitEventSem, 329),
LX_NATIVE_EXPORT(DosQueryEventSem, 330),
LX_NATIVE_EXPORT(DosCreateMutexSem, 331),
LX_NATIVE_EXPORT(DosRequestMutexSem, 334),
LX_NATIVE_EXPORT(DosReleaseMutexSem, 335),
LX_NATIVE_EXPORT(DosSubSetMem, 344),
LX_NATIVE_EXPORT(DosSubAllocMem, 345),
LX_NATIVE_EXPORT(DosSubFreeMem, 346),
LX_NATIVE_EXPORT(DosQuerySysInfo, 348),
LX_NATIVE_EXPORT(DosSetExceptionHandler, 354),
LX_NATIVE_EXPORT(DosSetSignalExceptionFocus, 378),
LX_NATIVE_EXPORT(DosEnterMustComplete, 380),
LX_NATIVE_EXPORT(DosExitMustComplete, 381),
LX_NATIVE_EXPORT(DosSetRelMaxFH, 382),
LX_NATIVE_EXPORT(DosFlatToSel, 425),
LX_NATIVE_EXPORT(DosOpenL, 981)
LX_NATIVE_MODULE_INIT_END()
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 PTIB2 getTib2(void)
{
// just read the FS register, since we have to stick it there anyhow...
PTIB2 ptib2;
__asm__ __volatile__ ( "movl %%fs:0xC, %0 \n\t" : "=r" (ptib2) );
return ptib2;
} // getTib2
static PTIB getTib(void)
{
// we store the TIB2 struct right after the TIB struct on the stack,
// so get the TIB2's linear address from %fs:0xC, then step back
// to the TIB's linear address.
uint8 *ptib2 = (uint8 *) getTib2();
return (PTIB) (ptib2 - sizeof (TIB));
} // getTib
APIRET DosGetInfoBlocks(PTIB *pptib, PPIB *pppib)
{
TRACE_NATIVE("DosGetInfoBlocks(%p, %p)", pptib, pppib);
if (pptib != NULL)
*pptib = 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.0
case QSV_VERSION_MINOR: *(buf++) = 40; break; // OS/2 Warp 4.0
case QSV_VERSION_REVISION: *(buf++) = 0; break; // OS/2 Warp 4.0
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
APIRET DosWrite(HFILE h, PVOID buf, ULONG buflen, PULONG actual)
{
TRACE_NATIVE("DosWrite(%u, %p, %u, %p)", (uint) h, buf, (uint) buflen, 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
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);
fn(why);
} // for
} // runDosExitList
VOID DosExit(ULONG action, ULONG exitcode)
{
TRACE_NATIVE("DosExit(%u, %u)", (uint) action, (uint) exitcode);
// !!! FIXME: what does a value other than 0 or 1 do here?
if (action == EXIT_THREAD) {
// !!! FIXME: terminate thread. If last thread: terminate process.
FIXME("DosExit(0) should terminate thread, not process");
} // if
// terminate the process.
runDosExitList(TC_EXIT);
// !!! FIXME: finalize OS/2 DLLs before killing the process?
// OS/2's docs say this only keeps the lower 16 bits of exitcode.
// !!! FIXME: ...but Unix only keeps the lowest 8 bits. Will have to
// !!! FIXME: tapdance to pass larger values back to OS/2 parent processes.
if (exitcode > 255)
FIXME("deal with process exit codes > 255. We clamped this one!");
_exit((int) (exitcode & 0xFF));
} // 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 != 1) && (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
ULONG DosFlatToSel(PVOID ptr)
{
TRACE_NATIVE("DosFlatToSel(%p)", ptr);
return ((ULONG) ((size_t)ptr)) >> 16; // !!! FIXME
} // DosFlatToSel
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;
for (LONG i = 0; i < incr; i++, info++) {
info->fd = -1;
info->type = 0;
info->attr = 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 = malloc(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
APIRET DosQueryHType(HFILE hFile, PULONG pType, PULONG pAttr)
{
TRACE_NATIVE("DosQueryHType(%u, %p, %p)", (uint) hFile, pType, 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
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
// based on case-insensitive search code from PhysicsFS:
// https://icculus.org/physfs/
// It's also zlib-licensed, plus I wrote it. :) --ryan.
// !!! FIXME: this doesn't work as-is for UTF-8 case folding, since string
// !!! FIXNE: length can change!
static int locateOneElement(char *buf)
{
if (access(buf, F_OK) == 0)
return 1; // quick rejection: exists in current case.
DIR *dirp;
char *ptr = strrchr(buf, '/'); // find entry at end of path.
if ((ptr == NULL) || (ptr == buf)) {
dirp = opendir("/");
ptr = buf;
} else {
*ptr = '\0';
dirp = opendir(buf);
*ptr = '/';
ptr++; // point past dirsep to entry itself.
} // else
for (struct dirent *dent = readdir(dirp); dent; dent = readdir(dirp)) {
if (strcasecmp(dent->d_name, ptr) == 0) {
strcpy(ptr, dent->d_name); // found a match. Overwrite with this case.
closedir(dirp);
return 1;
} // if
} // for
// no match at all...
closedir(dirp);
return 0;
} // locateOneElement
static int locatePathCaseInsensitive(char *buf)
{
int rc;
char *ptr = buf;
if (*ptr == '\0')
return 0; // Uh...I guess that's success?
if (access(buf, F_OK) == 0)
return 0; // quick rejection: exists in current case.
while ( (ptr = strchr(ptr + 1, '/')) != NULL ) {
*ptr = '\0'; // block this path section off
rc = locateOneElement(buf);
*ptr = '/'; // restore path separator
if (!rc)
return -2; // missing element in path.
} // while
// check final element...
return locateOneElement(buf) ? 0 : -1;
} // locatePathCaseInsensitive
static char *makeUnixPath(const char *os2path, APIRET *err)
{
if ((strcasecmp(os2path, "NUL") == 0) || (strcasecmp(os2path, "\\DEV\\NUL") == 0))
os2path = "/dev/null";
// !!! FIXME: emulate other OS/2 device names (CON, etc).
//else if (strcasecmp(os2path, "CON") == 0)
else {
char drive = os2path[0];
if ((drive >= 'a') && (drive <= 'z'))
drive += 'A' - 'a';
if ((drive >= 'A') && (drive <= 'Z')) {
if (os2path[1] == ':') { // it's a drive letter.
if (drive == 'C')
os2path += 2; // skip "C:" if it's there.
else {
*err = ERROR_NOT_DOS_DISK;
return NULL;
} // else
} // if
} // if
} // else
const size_t len = strlen(os2path);
char *retval = (char *) malloc(len + 1);
if (!retval) {
*err = ERROR_NOT_ENOUGH_MEMORY;
return NULL;
} // else
strcpy(retval, os2path);
for (char *ptr = strchr(retval, '\\'); ptr; ptr = strchr(ptr + 1, '\\'))
*ptr = '/'; // convert to Unix-style path separators.
locatePathCaseInsensitive(retval);
return retval;
} // 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
info->fd = fd;
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
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
APIRET DosSetFilePtr(HFILE hFile, LONG ib, ULONG method, PULONG ibActual)
{
TRACE_NATIVE("DosSetFilePtr(%u, %d, %u, %p)", (uint) hFile, (int) ib, (uint) method, 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
APIRET DosRead(HFILE hFile, PVOID pBuffer, ULONG cbRead, PULONG pcbActual)
{
TRACE_NATIVE("DosRead(%u, %p, %u, %p)", (uint) hFile, pBuffer, (uint) cbRead, 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
APIRET DosClose(HFILE hFile)
{
TRACE_NATIVE("DosClose(%u)", (uint) 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
APIRET DosEnterMustComplete(PULONG pulNesting)
{
TRACE_NATIVE("DosEnterMustComplete(%p)", pulNesting);
PTIB2 tib2 = 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 = 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)
return ERROR_NOT_ENOUGH_MEMORY; // !!! FIXME: no
if ((strlen(real) + 3) < cbInfoBuf) {
free(real);
return ERROR_BUFFER_OVERFLOW;
} // if
char *dst = (char *) pInfoBuf;
*(dst++) = 'C';
*(dst++) = ':';
strcpy(dst, real);
free(real);
while (*dst) {
if (*dst == '/')
*dst = '\\';
dst++;
} // while
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);
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 os2ThreadEntry2(uint8 *tibspace, Thread *thread)
{
void *esp = NULL; // close enough.
const uint16 selector = GLoaderState->initOs2Tib(tibspace, &esp, thread->stacklen, (TID) thread);
thread->fn(thread->fnarg);
GLoaderState->deinitOs2Tib(selector);
grabLock(&GMutexDosCalls);
thread->prev = NULL;
thread->next = GDeadThreads;
GDeadThreads = thread;
ungrabLock(&GMutexDosCalls);
} // 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];
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);
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
APIRET DosExecPgm(PCHAR pObjname, LONG cbObjname, ULONG execFlag, PSZ pArg, PSZ pEnv, PRESULTCODES pRes, PSZ pName)
{
TRACE_NATIVE("DosExecPgm(%p, %u, %u, %p, %p, %p, '%s')", pObjname, (uint) cbObjname, (uint) execFlag, pArg, pEnv, pRes, 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
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 OS2API 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 OS2API 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
APIRET DosDelete(PSZ pszFile)
{
TRACE_NATIVE("DosDelete('%s')", pszFile);
APIRET err;
char *unixpath = makeUnixPath(pszFile, &err);
if (!unixpath)
return err;
if (unlink(unixpath) == -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
APIRET DosQueryCurrentDir(ULONG disknum, PBYTE pBuf, PULONG pcbBuf)
{
TRACE_NATIVE("DosQueryCurrentDir(%u, %p, %p)", (uint) disknum, pBuf, pcbBuf);
if ((disknum != 0) && (disknum != 3)) // C: (or "current")
return ERROR_INVALID_DRIVE;
// !!! FIXME: this is lazy.
char *cwd = getcwd(NULL, 0);
if (!cwd)
return ERROR_NOT_ENOUGH_MEMORY; // this doesn't ever return this error on OS/2.
const size_t len = strlen(cwd);
if ((len + 1) > *pcbBuf) {
*pcbBuf = len + 1;
free(cwd);
return ERROR_BUFFER_OVERFLOW;
} // if
char *ptr = (char *) pBuf;
char *src = cwd;
while (*src == '/')
src++; // skip initial '/' char.
while (*src) {
const char ch = *src;
*(ptr++) = ((ch == '/') ? '\\' : ch);
src++;
} // while
*ptr = '\0';
free(cwd);
return NO_ERROR;
} // DosQueryCurrentDir
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 (ULONG) * 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 (ULONG) * 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 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)
{
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 (!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 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;
for (i = 0; i < maxents; i++) {
if (!findNextOne(dirf, &pfindbuf, &cbBuf))
break;
} // for
*pcFileNames = i;
if ((i == 0) && (maxents > 0)) {
closedir(dirf->dirp);
dirf->dirp = NULL;
} // if
return NO_ERROR;
} // findNext
static inline ULONG findNextSizeNeeded(const ULONG ulInfoLevel, const ULONG pcFileNames)
{
switch (ulInfoLevel) {
case FIL_STANDARD: return sizeof (FILEFINDBUF3) * pcFileNames;
case FIL_QUERYEASIZE: return sizeof (FILEFINDBUF4) * pcFileNames;
case FIL_QUERYEASFROMLIST: FIXME("write me"); return 0xFFFFFFFF;
default: assert(!"Shouldn't hit this."); return 0xFFFFFFFF;
} // switch
__builtin_unreachable();
} // findNextSizeNeeded;
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))
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) {
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
*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 DosQueryCurrentDisk(PULONG pdisknum, PULONG plogical)
{
TRACE_NATIVE("DosQueryCurrentDisk(%p, %p)", pdisknum, plogical);
// we only offer a "C:" drive at the moment.
if (pdisknum)
*pdisknum = 3; // C:
if (plogical)
*plogical = (1 << 2); // just C:
return NO_ERROR;
} // DosQueryCurrentDisk
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 OS2API 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
// end of doscalls.c ...