I failed to achieve this via a batch file but I succeeded using C and ol’ int86. (assembly is the way to go but I am ignorant as to how to structure and compile an assembly program).
So, here is a sample which compiled with open-watcom (cross-compiled on Linux and run both on dosbox and VirtualBox emulators, MSDOS 6.22). It succeeds in changing drive
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <process.h>
#include <i86.h>
#include <errno.h>
extern int errno;
/* program to emulate Unix's pushd command by
saving current dir AND drive to environment
and then change to new, user specified, dir AND drive.
Similarly a popd command would read environment variable
and change back to original dir.
It uses a hack to save environment variables to calling "shell"
It re-spawns a shell (command.com) which will inherit all environment
from calling process, that's us. A neat hack and that's how you
turn a pragmatically useless (ok, limited) MSDOS to magic Unix.
I did not make any effort to free allocated memory.
Compiled using open-watcom (THANK YOU (open-)Watcom!) on Linux for MSDOS
wcc pushd.c -oh -ok -ot -s -oa -ei -ob -ol+
wlink system dos option stack=4096 option map option eliminate name pushd.exe file pushd.o
(based on a Makefile by Mike Brutman)
Author: Andreas Hadjiprocopis (https://github.com/hadjiprocopis)
Date: 31/01/2020
Licence: free to copy, modify and share
*/
#define ISALPHALOWER(c) ((c) >= 'a' && (c) <= 'z')
#define ISALPHAUPPER(c) ((c) >= 'A' && (c) <= 'Z')
#define ISALPHA(c) (ISALPHALOWER(c) || ISALPHAUPPER(c))
#define TOUPPER(c) (ISALPHALOWER(c) ? ((c)+'A'-'a') : (c))
#define PATHSEPARATOR '\\'
int main(int argc, char *argv[])
{
char *newdir = argv[1], *apath, *cwd,
*initialDir, initialDrive, *envs, *envstr;
unsigned char newdrive = 255;
union REGS inr;
union REGS outr;
struct SREGS s;
if( argc != 2 ){ fprintf(stderr, "Usage : %s newdir\n", argv[0]); exit(1); }
if( ISALPHA(newdir[0]) &&
(newdir[1] == ':') &&
(newdir[2] == PATHSEPARATOR)
){
newdrive = TOUPPER(newdir[0]);
//newdir += 3;
printf("%s : newdir contains a drive spec '%c'.\n", argv[0], newdrive);
}
// find current drive and path using int86x
// same thing can be achieved using getcwd()
inr.h.ah = 0x19; /* get current drive, A=0x0, B=0x1 etc. */
int86x( 0x21, &inr, &outr, &s ); // 0x21, inregs, outregs, insegregs
printf( "current drive : '%c'\n", outr.h.al+'A');
apath = (char *)malloc(64*sizeof(char));
strcpy(apath, "<empty>");
/* get current dir without drive letters or preceding slash, it is an absolute path */
inr.h.ah = 0x47;
inr.h.dl = 0x03; // for drive
inr.w.si = FP_OFF(apath);
s.ds = FP_SEG(apath);
int86x( 0x21, &inr, &outr, &s ); // 0x21, inregs, outregs, insegregs
printf( "current path (if you are on \ this will be empty string): '%s'\n", apath);
if( outr.x.cflag != 0 ){
printf("got error, doserrno=%d, %d\n", _doserrno, outr.w.ax);
}
/* same thing with getcwd() */
initialDir = getcwd(NULL, 0); // must free cwd
if( initialDir == NULL ){ fprintf(stderr, "%s : call to getcwd() has failed: (%d), %s\n", argv[0], errno, strerror(errno)); exit(1); }
initialDrive = initialDir[0];
printf("using getcwd() : dir '%s' and drive '%c'\n", initialDir, initialDrive);
/* now save to an env variable */
// sets env var CWD to "c:\\abc\xyz"
envstr = (char *)malloc(100*sizeof(char));
sprintf(envstr, "CWDIR=%s", initialDir);
printf("setting env '%s'\n", envstr);
if( 0 != putenv(envstr) ){ fprintf(stderr, "%s : putenv() failed for '%s' : (%d) %s\n", argv[0], envstr, errno, strerror(errno)); exit(1); }
envstr = (char *)malloc(100*sizeof(char));
sprintf(envstr, "CWDRIVE=%c", initialDrive);
printf("setting env '%s'\n", envstr);
if( 0 != putenv(envstr) ){ fprintf(stderr, "%s : putenv() failed for '%s' : (%d) %s\n", argv[0], envstr, errno, strerror(errno)); exit(1); }
/* and change dir to what the user asked */
// set drive
if( (newdrive >= 'A') && (newdrive <= 'Z') ){
inr.h.ah = 0x0E; // change drive
inr.h.dl = TOUPPER(newdrive)-'A';
int86x( 0x21, &inr, &outr, &s);
printf("drive set to %c\n", newdrive);
}
// chdir
inr.h.ah = 0x3B; // change dir (not drive!)
s.ds = FP_SEG(newdir);
inr.w.dx = FP_OFF(newdir);
int86x( 0x21, &inr, &outr, &s ); // 0x21, inregs, outregs, insegregs
if( outr.x.cflag != 0 ){
printf("got error chdir to '%s', doserrno=%d, code=%d\n", newdir, _doserrno, outr.w.ax);
}
cwd = getcwd(NULL, 0);
fprintf(stderr, "My working directory is now '%s'\n", cwd);
free(cwd);
envs = getenv("CWDRIVE");
if( envs == NULL ){ fprintf(stderr, "%s : call to getenv() has failed for 'CWDRIVE'.\n", argv[0]); }
fprintf(stdout, "%s : CWDRIVE='%s'\n", argv[0], envs);
//free(envs);
envs = getenv("CWDIR");
if( envs == NULL ){ fprintf(stderr, "%s : call to getenv() has failed for 'CWDIR'.\n", argv[0]); }
fprintf(stdout, "%s : CWD='%s'\n", argv[0], envs);
/* now these env vars were set only for our own process and not for the surrounding command.com
process. What we can do is to spawn a new command.com and this new process will get a
copy of our environment, neat trick.
*/
if( -1 == execl("c:\\command.com", "c:\\command.com", NULL) ){
fprintf(stderr, "%s : execl failed : (%d) %s\n", argv[0], errno, strerror(errno));
exit(1);
}
// if execl was successful nothing beyond this point will be executed
}
bw, Andreas