[comp.sys.ibm.pc] MKS related patches for COMMAND.COM

jls@killer.DALLAS.TX.US (Jerome Schneider) (11/14/88)

COMMAND.COM PATCHES for MKS
 
Here are two patches to PC-DOS 3.3 COMMAND.COM to better allow the MKS
toolkit to co-exist with command.com and with many application programs
that shell to DOS or another program via command.com.
 
The first patch improves functionality when an MKS /etc/passwd file entry
specifies command.com as the login shell.  By adding a /L (or -l) option
to command.com for "login", the root directory can be searched for an
AUTOEXEC.BAT file to execute before responding to console commands. 
 
The second patch eliminates the "Specified COMMAND search path bad" error
from programs that call command.com (via the system() or EXEC functions).
Many applications written in MSC 4.x or 5.x use the system() library call
that fails to examine the switch char (- or /) before issuing a hard-wired
exec on ($COMSPEC /c "string to be executed").
 
(As for the legitimacy of hacking DOS files, it seems that once you have
modified COMMAND.COM with the MKS patch script to disable conflicting
internal commands, you already have produced a non-standard copy.  Just
be sure to make these patches to a *copy* :-) of command.com.) 
 
 
SOME BACKGROUND ON COMMAND.COM OPTIONS AND FLAGS.
 
Several undocumented features are present in PC-DOS 3.1 - 3.3 command.com.
A partial list of the command line options includes the following:
 
   /p       -  Makes this copy of command.com permanent in memory by
               trapping the "exit" signal.  The autoexec.bat file, if
               any, is run before accepting commands from the console.
               A new environment, containing only PATH= and COMSPEC=
               entries is built.
 
   /d       -  When present with the /p flag to make the shell permanent,
               /d means "Don't" execute autoexec.bat when starting shell.
               (I can find no application that uses this option.)
 
   /f       -  Always issue a "Fail" response by replying automatically
               to critical errors at the Abort, Retry, Ignore message.
               (This seems to work erratically on anything except
               internal command file errors, so I never use it.)
 
   /c "str" -  spawns a sub-shell of command.com to execute the command
               in string "str", which can be either an internal command
               or an EXE, COM, or BAT file.  If /p or /d options have been
               requested, they are cancelled by this option.
 
   /e:nnn   -  establishes the default environment size when starting up
               a /p (permanent) command.com.  If present when spawning a
               sub-shell and if nnn is greater than the passed environment,
               a new environment nnn bytes (nnn paragraphs in DOS 3.1, 3.2)
               is created and the passed environment is copied into it.
 
   <path>   -  Any string not preceded by the current switch char (- or /)
               is examined to determine whether it is a legal path name or
               a valid dos device.  If it is a legal path, a search is made
               to determine if the file command.com exists in the named
               directory.  If either of these tests fails, the ubiquitous
               "Specified COMMAND search directory bad" error is displayed
               and a new environment with just PATH= and COMSPEC= entries
               is created.  If, however, a command.com was found, the COMSPEC
               entry is modified to point to the new command.com.  (It was
               probably sloppy programming that allowed this option to even
               be used in any shell spawn except the /p (permanent) type.)
 
   <device> -  If the path doesn't point to a legal directory, the path name
               is checked (via ioctrl() test ISDEV) for a legal device, such
               as CON, COM1, AUX, etc.  If a valid device, the command.com
               shell uses that device as the standard in/out.
 
Note that the options are shown with a "/" switch character, but command.com
can properly detect a '-' switch char, if set, on the option flags. 
 
 
ADDING A LOGIN OPTION TO COMMAND.COM
 
The MKS toolkit login command works very nicely for "login" to a specific
applications after setting a HOME directory and running ~/profile.ksh at
/bin/sh startup.  Some applications, for reasons of memory requirements,
incompatibility with "-" switch char or "/" file path character, may need
to start up the DOS command.com as an /etc/passwd login shell instead of ksh. 
 
Because login initializes some of the environment variables using "/" in
the file paths, simply invoking command.com from an /etc/passwd line will
not allow us to run AUTOEXEC.BAT (to redefine variables or the switch char,
for example), before responding to the console for commands. 
 
It is tempting to try "/p" in the /etc/passwd entry to force command.com 
to execute /AUTOEXEC.BAT, but because conventional DOS login shells have
no parent process for the root COMMAND.COM to exit to, the /p option traps
all "exit" commands (SIGTERM) to keep COMMAND.COM "permanent" in memory.
This prohibits any exit back to the MKS inittab to respawn a new login.
 
The patch below allows command.com to be used as a login shell by adding
a new /L ( or -l) login option to provide a search of the root directory
for an AUTOEXEC.BAT file to execute before responding to console commands. 
 
To add this feature to COMMAND.COM, we need two things.  First, we must
add an option flag, consisting of an upper or lower case L immediately
preceded by an occurrence of the current SWITCH char (a - or /), to the
command.com option parser.   Second, we must locate the code that modifies
the undocumented memory location holding the AUTOEXEC.BAT execute flag.
 
Because the rarely used option flag "/d" already exists in COMMAND.COM, it
can be modified to test for "L" instead of "D".  Also, since the existing
/d code already alters the "autoexec" byte when the /d flag is found,  we
only need to change the value inserted into the byte.   The legal values
for the autoexec byte are:
 
     0xff - the initial value when command.com loaded.  Any non-zero value 
            means do not run autoexec.bat when starting shell.
 
     0x01 - When the undocumented "/d" flag is encountered, indicate that
            the "don't" flag was found.  Do not run autoexec.bat. 
 
     0x00 - If zero, the autoexec.bat file, if any, should be run.
 
By locating the code that sets the flag to 0x01 when the /d flag is parsed,
it is rather easy, then, to change the option test from D to L and to change
the execute flag value to 0x00.   This will insure that the default (0xff)
value will not be altered except when the /L is detected.
 
-------------------------- PATCH #1 ---------------------------
 
For IBM's PC-DOS 3.3  (my copy is 25,307 bytes dated 3/17/87). Use debug
(on a copy, of course) to unassemble the following:
 
debug command.com
-u f9d fa9
xxxx:0F9D 3A064D16      CMP   AL,[164D]             ; is option a "d"?
xxxx:0FA1 7507          JNZ   0FAA                  ; skip if not, else
xxxx:0FA3 C6062E1601    MOV   BYTE PTR [162E],01    ; set 0x01 in flag
xxxx:0FA8 EBA6          JMP   0F50                  ; and continue scan.
 
If your display matches this, change the /d flag byte from 01 to 00:
 
-e fa7
xxxx:0FA7   01.00
 
and confirm the change:
 
-u fa3 fa7 
xxxx:0FA3 C6062E1600    MOV   BYTE PTR [162E],00    ; set 0x00 in flag
 
Now, lets confirm the value of the "d" option held in memory by:
 
-d 164c 164e
xxxx:1640    ..... 70 63 61 ....      pdc    ; the p, d, c options
 
If this matches, then lets change the option flag from "d" to "l":
 
-e 164d
60C9:164D  64.6c               ; change byte at 164d from 64 to 6C.
 
-d 164c 164e                   ; and confirm the change.
xxxx:1640     ..... 70 6C 63 ....     plc     ; d becomes l
 
At this point,  write the changed command.com back to disk and quit:
 
-w 
Writing 62DB bytes
-q
 
Your patched version of command.com should now run /AUTOEXEC.BAT first
when called with a -l or /L option.  You may wish to create a temporary
autoexec.bat (in the root dir /) to test this.  Remember that although
login changes to the $HOME directory before starting command.com, the
autoexec.bat file is only looked for in the DOS root (/) directory.  
 
I use the  /autoexec.bat like a global initialize file (similar to the
/etc/profile.ksh) that is executed by all passwd entries with command.com
as the shell.  I use the following as the LAST line of my /autoexec.bat:
 
     IF EXIST PROFILE.BAT  PROFILE
 
This tests for a profile.bat in the $HOME directory and executes it if
one is found.  This combination gives me a global/local init paradigm for
those applications using command.com as a login shell.  Remember that if
your profile.bat or autoexec.bat will set a lot of environment variables,
add an appropriate  "-e:nnnn" to the passwd entry to reserve a larger
environment for the login.
 
Finally, for those who are using a different version of command.com,  I
think most 3.x versions of PC or MS DOS are similar but the locations of
the flag test and the option chars will vary. 
 
For PC-DOS 3.1, the execute flag code is at xxxx:0df2 to xxxx:0dfe, and 
the "d" byte is at 1337.  For other versions, I would suggest you first 
locate the "pdcA" string by using debug's search option on command.com:
 
-s 100 2000 70,64,63          ; search 100 to 2000 for "pdc"
xxxx:1336 
-d 1336                       : display any data found by search
xxxx:1330       .. 70 64-63 41 00 ...        pdcA
 
In this case, the "d" (64) is at offset 1337 hex.  Remember this value
and then search for any references to this address.  Because Intel stores
offsets in reverse byte order, the search should be similar to:
 
-s 100 2000 37,13                ; search from 100 to 2000 for 1337
xxxx:0df4                        ; a reference to 1337 found at this addr.
 
Unassemble starting about 2 bytes BEFORE this and you should find code
similar to the following.  The MOV BYTE PTR [xxxx], 01 is confirmation:
 
xxxx:0DF2 3A063713      CMP   AL,[1337]
xxxx:0DF6 7507          JNZ   0DFF
xxxx:0DF8 C6061C1301    MOV   BYTE PTR [131C],01 
xxxx:0DFD EBA6          JMP   0DA5
 
If this doesn't work, your version is probably too radically different
for this patch.   
 
In summary, the two changes for PC-DOS 3.1 command.com are:
-e 1337
xxxx:1337  64.6c              ; change "d" to "l"
-e dfc
xxxx:0DFC  01.00              ; change execute flag to 00
-w
Writing 5AAA bytes
-q
 
In summary, the two changes for PC-DOS 3.2 command.com are:
-e 149d
xxxx:149D  64.6c
-e 3ea
xxxx:0E3A  01.00
-w
Writing 5CEF bytes
-q
 
----------------------- end PATCH #1 -----------------------------
 
 
DUAL SWITCH CHAR FOR COMMAND.COM
 
This patch allows programs that call command.com via the system() or
EXEC functions to work properly with either a -c or /c for the switch
character.   This should cure most problems when spawning command.com
from within an application, and eliminate the empty environment and
the "Specified COMMAND search path bad" errors.
 
The problem results when the switch char is set to "-" AND command.com
is invoked from within a program using a hard-wired exec call:
 
   $COMSPEC /c "string"
 
Command.com interprets the "/c" (and any identifiers in the "string") as
a possible <path> or <device> options rather than as a sub-shell call
"-c" to execute "string" and return. 
 
The fix is to allow both "/" and "-" to be recognized as a valid switch
character in the parsing of command.com options.  The only trade-off is
that specifying a legitimate command search path from the login or root
(i.e. SHELL= COMMAND.COM  C:/PATH1/PATH2/..../PATHN -L )  must include
a drive letter (C:, for example) in the search path.  (A leading "/" in
the search path would be interpreted as a switch char parameter if the
drive letter was missing.)
 
The actual patch method is to replace the specific memory references
to "white space blank" and the DOS switch character flag with hard-
coded tests.  If done properly, the new code just overlays the old
code with no room to spare.  
 
 
-------------------------- PATCH #2 ---------------------------
 
For IBM's PC-DOS 3.3  (my copy is 25,307 bytes dated 3/17/87),
Use debug (on a copy, of course) and unassemble the following:
 
debug command.com
-u f53 f66
xxxx:0F53 AC            LODSB             ; load next char from cmnd line
xxxx:0F54 3A064B16      CMP   AL,[164B]   ; test if whitespace ( space)
xxxx:0F58 74F6          JZ    0F50        ;    yes, it was a space.
xxxx:0F5A 3C09          CMP   AL,09       ; test if tab
xxxx:0F5C 74F2          JZ    0F50        ;    yest, it was a tab.
xxxx:0F5E 3A06170D      CMP   AL,[0D17]   ; is it current DOS switch char
xxxx:0F62 7403          JZ    0F67        ; switch char found, get option
xxxx:0F64 E9E000        JMP   1047        ; not switch char, rescan line.
 
If this matches your version, assemble new code over the existing tests
for white space and switch char.  (Do not type the comments, though):
 
-a f54                        ; start assembling at location 0f54
xxxx:0F54   cmp   al,20       ; See if char is a blank (space)
xxxx:0F56   jz    0f50        ; Skip if it is.
xxxx:0F58   cmp   al,09       ; See if char is a tab.
xxxx:0F5A   jz    0f50        ; Skip if it is a tab.
xxxx:0F5C   cmp   al,2d       ; See if char is a "-" switch char
xxxx:0F5E   jz    0f67        ; Jump to examine next char if it is.
xxxx:0F60   cmp   al,2f       ; See if char is a "/" switch char.
xxxx:0F62                     ; end assembly with a blank line.
 
Verify the patched code by unassembling again:
 
-u f53 f66
xxxx:0F53 AC            LODSB 
xxxx:0F54 3C20          CMP   AL,20
xxxx:0F56 74F8          JZ    0F50
xxxx:0F58 3C09          CMP   AL,09
xxxx:0F5A 74F4          JZ    0F50
xxxx:0F5C 3C2D          CMP   AL,2D
xxxx:0F5E 7407          JZ    0F67
xxxx:0F60 3C2F          CMP   AL,2F
xxxx:0F62 7403          JZ    0F67
xxxx:0F64 E9E000        JMP   1047
 
Write the patched command.com back to disk and exit from debug:
 
-w 
Writing 62DB bytes
-q
 
Your patched version of command.com should now work properly when
invoked from within applications that hard-wire the switch char to
/c, without regard to the current switch char. 
 
Again, for versions other than IBM PC-DOS 3.3, locating the section of
code that tests for whitespace and switch chars in the command line
parser is required.  The best starting point is the address in the previous
patch that changed the "pdcA" d option flag.  The "whitespace blank" for
testing is (usually) the byte BEFORE the "plcA" string.  Using PC-DOS 3.1
as an example, these bytes are found by searching for the " pld" string:
 
-s 100 2000  20,70,6C,63      ; search 100 to 2000 for " plc"
xxxx:1335
-d 1335                       : display any data found by search
637E:1330    .. 20 70 6C-63 41 00 ...        plcA
 
In this case, the " " (20) is at offset 1335 hex.  Remember this value
and then search for any references to this address.  Because Intel stores
offsets in reverse byte order, the debug search should be similar to:
 
-s 100 2000 35,13             ; search from 100 to 2000 for 1335
xxxx:0DAB                     ; This is probably the area, but,
xxxx:0E62                     ; This looks similar, but doesn't fit.
xxxx:0ED6                     ; ditto
xxxx:0EE5 
 
Unassemble each of these locations, starting about 4 bytes BEFORE each
address.  You are looking for code similar to the following:
 
-u 0da7
xxxx:0DA7 49            DEC   CX         ; count-- length of option line.
xxxx:0DA8 AC            LODSB            ; Load next char into AL
xxxx:0DA9 3A063513      CMP   AL,[1335]  ; This is the test for " "
xxxx:0DAD 74F6          JZ    0DA5       ;   jump to continue scan
xxxx:0DAF 3C09          CMP   AL,09      ; This is the test for TAB
xxxx:0DB1 74F2          JZ    0DA5       ;   jump to continue scan
xxxx:0DB3 3A06E70B      CMP   AL,[0BE7]  ; Is char the current sw char.
xxxx:0DB7 7403          JZ    0DBC       ;   Jump $+5 to parse sw char.
xxxx:0DB9 E99C00        JMP   0E58       ; Jump, char is something else.
xxxx:0DBC E331          JCXZ  0DEF       ; Are there any chars after /
 
The replacement code should overlay the bytes starting just after the
LODSB and must not clobber the JMP xxxx.  Note that the jump addresses
vary with each version, so I can't really give portable patch code.   
In general, the first two JZ addresses stay the same.  The third JZ
in the original code is used in the last two JZ's of the patch.
 
  ----- original code --------    ----------- patched code -------
:0DA7 49         DEC  CX            :0DA7 49         DEC CX
:0DA8 AC         LODSB              :0DA8 AC         LODSB      
:0DA9 3A063513   CMP  AL,[1335]     :0DA9 3C20       CMP   AL,20
:0DAD 74F6       JZ   aaaa          :0DAB 74F8       JZ    aaaa 
:0DAF 3C09       CMP  AL,09         :0DAD 3C09       CMP   AL,09
:0DB1 74F2       JZ   aaaa          :0DAF 74F4       JZ    aaaa 
:0DB3 3A06E70B   CMP  AL,[0BE7]     :0DB1 3C2D       CMP   AL,2D
:0DB7 7403       JZ   bbbb          :0DB3 7407       JZ    bbbb 
                                    :0DB5 3C2F       CMP   AL,2F
                                    :0DB7 7403       JZ    bbbb 
:0DB9 E99C00     JMP  0E58          :0DB9 E9E000     JMP   0E58
:0DBC E331       JCXZ 0DEF          :0DBC E331       JCXZ  0DEF    
 
For PC-DOS 3.1 command.com, the added code is:
-a da9
xxxx:0DA9 3C20          CMP	AL,20                              
xxxx:0DAB 74F8          JZ	0DA5                               
xxxx:0DAD 3C09          CMP	AL,09                              
xxxx:0DAF 74F4          JZ	0DA5                               
xxxx:0DB1 3C2D          CMP	AL,2D                              
xxxx:0DB3 7407          JZ	0DBC                               
xxxx:0DB5 3C2F          CMP	AL,2F                              
xxxx:0DB7 7403          JZ	0DBC                               
-w
 
For PC-DOS 3.21 command.com, the added code is:
 
-a de7
xxxx:0DE7 3C20          CMP	AL,20                              
xxxx:0DE9 74F8          JZ	0DE3                               
xxxx:0DEB 3C09          CMP	AL,09                              
xxxx:0DED 74F4          JZ	0DE3                               
xxxx:0DEF 3C2D          CMP	AL,2D                              
xxxx:0DF1 7407          JZ	0DFA                               
xxxx:0DF3 3C2F          CMP	AL,2F                              
xxxx:0DF5 7403          JZ	0DFA                               
-w
Writing 5CEF bytes
 
Note: the size of the environment of the spawned 3.1/3.2 command.com
limits the alteration of variables if the passed environment is larger
than the minimum size built into command.com.  Because the -e:nnnn
parameter can not be easily added to the invocation, providing a larger
environment must be done by patching command.com.  Microsoft supplies
a utility "SETENV" that modifies most versions to default to a larger
minimum size.   Also, various PD hacks exist for this, so I won't
elaborate here.
 
----------------------------- end of PATCH #2 --------------------------
 
ONE UNRESOLVED SWITCH CHAR "-" BUG:
 
There remains one problem with using command.com and the MKS toolkit 
when the switch char is set to "-".  To demonstrate the problem, 
execute the following line using command.com
 
C:> \BIN\ECHO  THIS STRING IS PRINTED BY THE MKS ECHO CMND.
 
When a leading path using "\" characters is parsed by command.com,
an "EXEC failure" error is generated.  The error only occurs when
the switch char is "-" and DOS-style path names are used.  This is
a problem when ksh spawns command.com to process a batch file and
the batch file has execution requests with the old delimiters. (It
is reasonable to expect such .BAT files exist for applications that
must run under command.com with the switch char set to /.)
 
I have traced through this enough to determine that command.com is
trying to exec the file with the ASCIZ name generated thusly:
 
"/path_specifier/\bin\echo ......"
 
Apparently, the leading "\" is not recognized as a path component,
so command prepends $PATH  strings to the command.  If anyone has
spelunked through this, how about some email.   I plan to nail this
down when time permits and will post a patch when (and if) available.
 
Hope this stuff helps -- I'm _really_ in love with the toolkit (or
do I just hate vanilla DOS and command.com?). 
 
.SIGNATURE=$(cat /dev/null)