Implemented more robust current dir/disk APIs.
authorRyan C. Gordon <icculus@icculus.org>
Tue, 10 Jul 2018 01:30:01 -0400
changeset 148 bc01f4233f39
parent 147 3f612cd411b4
child 149 c0dff550146e
Implemented more robust current dir/disk APIs.
native/doscalls-lx.h
native/doscalls.c
native/doscalls.h
--- a/native/doscalls-lx.h	Tue Jul 10 01:20:08 2018 -0400
+++ b/native/doscalls-lx.h	Tue Jul 10 01:30:01 2018 -0400
@@ -385,6 +385,7 @@
     LX_NATIVE_EXPORT(DosDevConfig, 231),
     LX_NATIVE_EXPORT(DosExit, 234),
     LX_NATIVE_EXPORT(DosResetBuffer, 254),
+    LX_NATIVE_EXPORT(DosSetCurrentDir, 255),
     LX_NATIVE_EXPORT(DosSetFilePtr, 256),
     LX_NATIVE_EXPORT(DosClose, 257),
     LX_NATIVE_EXPORT(DosDelete, 259),
--- a/native/doscalls.c	Tue Jul 10 01:20:08 2018 -0400
+++ b/native/doscalls.c	Tue Jul 10 01:30:01 2018 -0400
@@ -1965,45 +1965,6 @@
     return DosDelete_implementation(pszFile);
 } // 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);
@@ -2418,18 +2379,6 @@
     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);
@@ -2809,6 +2758,194 @@
     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
+
+
 #if !LX_LEGACY
 ULONG DosSelToFlat(VOID) { return 0; }
 #else
@@ -3180,33 +3317,20 @@
 APIRET16 Dos16QCurDisk(PUSHORT drivenum, PULONG drivemap)
 {
     TRACE_NATIVE("Dos16QCurDisk(%p, %p)", drivenum, drivemap);
-    if (drivenum) *drivenum = 3; // FIXME: C: only
-    if (drivemap) *drivemap = 1 << 2;
-    return NO_ERROR;
-}
+    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(%d, %p, %p)", drivenum, dirpath, dirpathlen);
-    if (!dirpathlen || !dirpath) return ERROR_INVALID_PARAMETER; // ???
-    int rlen, len = *dirpathlen;
-    char dirtmp[PATH_MAX];
-    getcwd(dirtmp, PATH_MAX);
-    rlen = strlen(dirtmp);
-    if (dirpathlen) *dirpathlen = rlen + 1;
-    if (rlen >= len) {
-        *dirpathlen = rlen + 1;
-        return ERROR_BUFFER_OVERFLOW;
-    }
-    char *pch = strchr(dirtmp, '/');
-    while (pch) {
-        *pch = '\\';
-        pch = strchr(pch, '/');
-    }
- 
-    strcpy((char *)dirpath, dirtmp + 1);
-    return NO_ERROR;
-}
+    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)
 {
@@ -3280,15 +3404,11 @@
 APIRET16 Dos16ChDir(PSZ pszDir, ULONG res)
 {
     TRACE_NATIVE("Dos16ChDir('%s', %u)", pszDir, res);
-    APIRET err;
-    char *path = makeUnixPath(pszDir, &err);
-    if (!path) return err;
-    int ret = chdir(path);
-    free(path);
-    if (ret && (errno == ENOENT)) return ERROR_PATH_NOT_FOUND;
-    else if (ret) return ERROR_ACCESS_DENIED;
-    return NO_ERROR;
-}
+    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)
 {
--- a/native/doscalls.h	Tue Jul 10 01:20:08 2018 -0400
+++ b/native/doscalls.h	Tue Jul 10 01:30:01 2018 -0400
@@ -618,6 +618,7 @@
 OS2EXPORT APIRET OS2API DosSetProcessCp(ULONG cp) OS2APIINFO(289);
 OS2EXPORT APIRET OS2API DosQueryFSAttach(PSZ pszDeviceName, ULONG ulOrdinal, ULONG ulFSAInfoLevel, PFSQBUFFER2 pfsqb, PULONG pcbBuffLength) OS2APIINFO(277);
 OS2EXPORT APIRET OS2API DosSetFileSize(HFILE hFile, ULONG cbSize) OS2APIINFO(272);
+OS2EXPORT APIRET OS2API DosSetCurrentDir(PSZ pszName) OS2APIINFO(255);
 
 #ifdef __cplusplus
 }