# DOSIOCTL.PL: a Perl file of examples for ioctl() under MS-DOS, # the previous documentation not being of the very finest. # # Author: John Dallman (jgd@cix.compulink.co.uk) # Thanks to Dan Carson (dbc@tc.fluke.com) for getting me started # with IOCTL, and to Larry for creating Perl in the first place. __END__ ################################################################# ##### # Figuring out IOCTL via Perl under DOS. # ################################################################# #### An extract from Len Reed's PERLDOS.MAN: ioctl(FILEHANDLE,FUNCTION,SCALAR) Implements the Unix ioctl(2) function. You'll prob- ably have to say require "ioctl.ph"; # probably /usr/local/lib/perl/ioctl.ph first to get the correct function definitions. If ioctl.ph doesn't exist or doesn't have the correct definitions you'll have to roll your own, based on your C header files such as . (There is a perl script called h2ph that comes with the Unix perl kit which may help you in this.) SCALAR will be read and/or written depending on the FUNCTION--a pointer to the string value of SCALAR will be passed as the third argument of the actual ioctl call. (If SCALAR has no string value but does have a numeric value, that value will be passed rather than a pointer to the string value. To guarantee this to be true, add a 0 to the scalar before using it.) The pack() and unpack() functions are useful for manipulating the values of structures used by ioctl(). The following example sets the erase character to DEL. require 'ioctl.ph'; $sgttyb_t = "ccccs"; # 4 chars and a short if (ioctl(STDIN,$TIOCGETP,$sgttyb)) { @ary = unpack($sgttyb_t,$sgttyb); $ary[2] = 127; $sgttyb = pack($sgttyb_t,@ary); ioctl(STDIN,$TIOCSETP,$sgttyb) || die "Can't ioctl: $!"; } The return value of ioctl (and fcntl) is as follows: if OS returns: perl returns: -1 undefined value 0 string "0 but true" anything else that number Thus perl returns true on success and false on failure, yet you can still easily determine the actual value returned by the operating system: ($retval = ioctl(...)) || ($retval = -1); printf "System returned %d\n", $retval; On MS-DOS, the function code of the ioctl function (the second argument) is encoded as follows: The lowest nibble of the function code goes to AL. The two middle nibbles go to CL. The high nibble goes to CH. The return code is -1 in the case of an error and if successful: for functions AL = 00, 09, 0a the value of the register DX for functions AL = 02 - 08, 0e the value of the register AX for functions AL = 01, 0b - 0f the number 0. The program can distiguish between the return value and the success of ioctl as described for Unix above. Some MS-DOS ioctl functions need a number as the first argument. Provided that no other files have been opened the number can be obtained if ioctl is called with @fdnum[number] as the first argument after executing the following code: @fdnum = ("STDIN", "STDOUT", "STDERR"); $maxdrives = 15; for ($i = 3; $i < $maxdrives; $i++) { open("FD$i", "nul"); @fdnum[$i - 1] = "FD$i"; } Clarifications ============== I had a fair amount of trouble understanding some details of this explaination; some clarification is in order. IOCTL calls INT21H function 44H, with register values specified bythe parameters to the ioctl() function. There isn't an ioctl.phfor DOS, but at least the values that you have to use are consistent for all DOS systems. DOS doesn't use data structures for IOCTL to the same extent as UNIX: for many calls, parameters are passed in registers. Note that all the DOS IOCTL calls are quite unlike UNIX ones: there isn't any portability of code that uses ioctl between DOS and UNIX. ioctl(FILEHANDLE,FUNCTION,SCALAR) FILEHANDLE is the Perl filehandle of the file to be manipulated. This is eventually reduced to an MS-DOS file/device handle, which is passed to DOS in BX. For drive-orientiated functions, trickery is required to supply a Perl filehandle whose MS-DOS filehandle has the right value for the desired drive ID - see below. FUNCTION is the IOCTL sub-function, passed to MS-DOS in AL. If a parameter is required in CX, it is passed in the upper nibbles of FUNCTION. SCALAR is a parameter for DOS, passed in DX or DS:DX. A pointer to its string value is passed in DS:DX; if it has no string value, the integer version of its numeric value is passed in DX. To force this to happen, add 0 to the scalar before using it. For IOCTL functions that return data in SCLAR, you don't have to worry about making space - Perl always allocates space, although it often allocates too much. On most systems other than BSD UNIX, it allocates 256 bytes. The normal way to use IOCTL is to use pack() to create the data structure that MS-DOS wants in a string, and then pass the string as SCALAR. Unpack() is very handy for unpacking such structures whern DOS has handed them back to you. The ioctl function returns underfined if the return code from the ioctl handler is -1 (an error), otherwise DX, AX or zero according to the function used (see above). Zero results return "0 but true". On an error, $! is set. "... need a number ..." ======================= The *really* incomprehensible bit of the manpage was: #=================================================== Some MS-DOS ioctl functions need a number as the first argument. Provided that no other files have been opened the number can be obtained if ioctl is called with @fdnum[number] as the first argument after executing the following code: @fdnum = ("STDIN", "STDOUT", "STDERR"); $maxdrives = 15; for ($i = 3; $i < $maxdrives; $i++) { open("FD$i", "nul"); @fdnum[$i - 1] = "FD$i"; } #=================================================== This actually means "Some MS-DOS ioctl functions need a *drive* number as the first argument". It is trying to say that this can be obtained by calling ioctl() with $fdnum[drive_number] as the first argument. Drive numbers are 0 for the default drive, 1 for A:, 2 for B: and so on. This would pass a Perl filehandle, with a value chosen from "STDIN", "STDOUT", "STDERR", "FD3", "FD4", etc... As MS-DOS filehandles are small positive integers, this tricks Perl into producing the right numbers for the wrong reasons: with no other files open, the open(FD...) calls produce MS-DOS filehandles with the right values for the drive ID numbers. The default open MS-DOS files stdaux and stdprn appear to have been closed before the Perl script starts executing (otherwise, they'd be occupying filehandle numbers 3 and 4). This method seems to be bad technique: - Opening all these files uses up FILES= file numbers, and the program filehandles MS-DOS has allocated to PERL.EXE. You can increase FILES=, but increasing the number of program filehandles could only be done by PERL.EXE, via INT21H Function 67H. This demands DOS 3.30, but that is no great problem. By default, there are only 20 program filehandles.... There were also some errors in the Perl code: here is a version which works correctly: # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++@fdnum = ("STDIN", "STDOUT", "STDERR"); # These have handles 0..2 $maxdrives = 15; for ($i = 3; $i < $maxdrives; $i++) # These will have handles { # 3..15 open("FD$i", "nul") || die "Couldn't open FD$i : $!\n" ; $fdnum[$i] = "FD$i"; } # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +++++++ The main error in the original code was @fdnum[$i - 1] = "FD$i"; ^^^^ This meant that the STDERR value was overwritten in the array, andthat the values put in by the $i loop were out of phase with those inserted by the original LIST assignment to @fdnum. We're also addressing a single array element using @name, which is bad practice. Now, passing a FILEHANDLE of $fdnum[$drive] will work, where $drive is 0 for the default drive, 1 for A:, 2 for B:, 3 for C:, etc... Packing and Unpacking ===================== Example: $set_itt = hex( "545C") sets $set_itt to the value of hex("545C"), which is 21596, decimal. The string value of $set_itt is set up at the same time, so unpacking $set_itt unpacks the string, which contains '2', '1', '5', '9', '6'. Remember that the 80x86 series are ilttle-endian machines: the less significant bytes of values are at lower memory addresses. Thus, $in = "\n\0" is equivalent to $in = pack( "s1", 10) ################################################################# ##### # Examples of using MS-DOS IOCTL functions # ################################################################# #### # ----------------------------------------------------------------- --# Get Device/File Data # # FILEHANDLE is an open filehandle on the file or device we're # interested in. # FUNCTION is zero. # SCALAR is zero. # The ioctl return value is undefined for an error, or the DX # device info value. # ----------------------------------------------------------------- -- $device_data = ioctl( STDIN, 0, 0) ; open( A_FILE, "PERL.EXE") || die "Can't access PERL.EXE: $!\n" ; $file_data = ioctl( A_FILE, 0, 0) ; printf( "Terminal device info = %x\nfile device info = %x\n", $device_data, $file_data) ; # ----------------------------------------------------------------- --# Set Device Data # # FILEHANDLE is an open filehandle on the device (*not* a file) we're # interested in. # FUNCTION is 1 # SCALAR is the value to be written # The return value is undefined for an error, or 0-but-true for success.# # This example sets STDIN to raw mode, so that input is # not line-buffered. This is about the only use for this # subfunction when working with Perl under DOS. # ----------------------------------------------------------------- -- $old_dev = ioctl( STDIN, 0, 0) ; # Get device data $old_dev &= 0xFF ; # Clear top bits, which we can't set. $new_dev = $old_dev | 32 ; # Set bit 5, for raw mode.ioctl( STDIN, 1, $new_dev) ; # Set the mode. $c = '' ; # Create our input buffer.print "press a character to continue "; sysread( STDIN, $c, 1) ; # Read just one byte print " thanks...\n" ; ioctl( STDIN, 1, $old_dev) ; # reset the mode. # ----------------------------------------------------------------- --# Functions 02 to 05 would be much easier to try out if I had a device # driver that used them... # ----------------------------------------------------------------- -- # ----------------------------------------------------------------- --# Is device ready for input? # # This is useful if you're using raw mode terminal i/o via sysread(), # as Perl's EoF function doesn't work under these conditions, and # reading appears to crash Perl. # # FILEHANDLE is the filehandle we're interested in. # FUNCTION is 6 # SCALAR is ignored, and should be zero. # The return value is undefined for an error, 0-but-true for device# not ready (or EoF on an input file), or 255 for input ready.# # This example sets raw i/o for STDIN, loops until you hit a key # and then reads input using sysread(). # ----------------------------------------------------------------- -- $old_dev = ioctl( STDIN, 0, 0) ; # Get device data $old_dev &= 0xFF ; # Clear top bits, which we can't set. $new_dev = $old_dev | 32 ; # Set bit 5, for raw mode.ioctl( STDIN, 1, $new_dev) ; # Set the mode. do { $value = ioctl( STDIN, 6, 0) ; # Get status die "ioctl failed: $!\n" # Handle error return unless $value ; print '.' ; # Prove we're looping } until ($value > 0) ; # Exit when get some input. print "ready to read!\n" ; # Prove we've noticed $line = '' ; # Suppress a warning... sysread( STDIN, $line, 1) ; # Read the character print $line ; # Print it out ioctl( STDIN, 1, $old_dev) ; # Reset to cooked mode. # ----------------------------------------------------------------- --# Is device ready for output? # # Function 07 isn't very useful unless we are driving printers from Perl, # as it always returns "ready for output" for disk files, irrespective # of the actual conditions - even if the disk is full, or there is # no disk in the drive. However, it may let us tell if a printer is # off-line. It isn't much use for screen output as that's always ready.# # FILEHANDLE is a handle on the printer we're interested in. # FUNCTION is 7 # SCALAR is ignored, and should be zero. # The return value is undefined for an error. The low byte of the # return value is 0 for not ready, or 255 for ready. The significance # of the high byte's value is unknown: it is always 1.# # This example tests the name supplied on the command line. A filename# is always ready; an off-line printer with a separate buffer-box returns # ready; a serial port with nothing plugged into it returns not-ready.# ----------------------------------------------------------------- -- open( PRINTER, ">$ARGV[0]") || die "Can't open file/device: $!\n" ; $value = ioctl( PRINTER, 7, 0) ; # Do the ioctl die "ioctl failed: $!\n" unless $value ; # Handle the error$value &= 0xFF ; # Select the AL valueprint "File/device ", # Report it. ($value>0 ? "is" : "isn't"), " ready\n" # ----------------------------------------------------------------- --# Does drive use removable media? # # FILEHANDLE is a drive number obtained from @fdnum, set up as above.# FUNCTION is 8 # SCALAR is ignored, and should be 0. # The return value is undefined for an error, 0-but-true for removable# media or 1 for fixed media. # # This example queries the drive whose number is in $drive (0=default, # 1=A:, 2=B:, etc...) # ----------------------------------------------------------------- -- $value = ioctl( $fdnum[$drive], 8, 0) ; die "ioctl failed: $!\n" unless $value; print "Drive ", ($value ? "doesn't" : "does"), " use removable media\n" ; # Note the negative logic: DOS returns 1 for *fixed* media. # ----------------------------------------------------------------- --# Is drive remote? (plus other attributes for Local drives) # # FILEHANDLE is a drive number obtained from @fdnum, set up as above.# FUNCTION is 9 # SCALAR is ignored, and should be 0 # The return value is undefined for an error or the drive attributes # from the DX register. # # This example queries the drive whose number is in $drive (0=default, # 1=A:, 2=B:, etc...) # ----------------------------------------------------------------- -- $value = ioctl( $fdnum[$drive], 9, 0) ; die "ioctl failed: $!\n" unless $value; printf( "Drive attributes are %x\n", $value) ; # ----------------------------------------------------------------- --# Is file or device remote? # FILEHANDLE = filehandle # FUNCTION = 0x0A # SCALAR is ignored, and should be zero # # The return value is undefined for an error or the attributes # from the DX register. Bit 15 -> remote. # ----------------------------------------------------------------- -- open( INPUT, $ARGV[0]) || die "Can't open $ARGV[0]: $!\n" ; $value = ioctl( INPUT, hex( 'A'), 0) ; die "ioctl failed: $!\n" unless $value; printf( "Device attributes are %x\n", $value) ; # ----------------------------------------------------------------- --# Set sharing retry count # # This sets the number of retries on a sharing clash, and the # interval between them. Note that there is no way to *read* the # current values of these settings. # # FILEHANDLE = Not relevant, but should be a Perl filehandle to keep# Perl from getting upset. We'll use STDIN in this example. # FUNCTION = Low nibble is 0xB, upper three nibbles are the wait between# tries on file operations. This is done via a delay loop and the time # it takes depends on the computer's clock speed. The default value is # 1, and applications that change it should reset it before exiting.# SCALAR = Number of retries to make for file operations before giving # up and returning an error. The default is 3; again, programs that# change it should restore the default value. # # The return value is undefined for an error or defined for success.# <<<< Note that this function should always fail unless SHARE.EXE or # equivalent has been loaded. Currently, it doesn't. # ----------------------------------------------------------------- --$value = ioctl( STDIN, hex( "4B"), 6) ; die "SHARE not available: $!\n" unless $value ; # ----------------------------------------------------------------- --# Get/Set device iteration count (number of retries before assume busy) # # These are the simplest ioctl functions that use data from a buffer # at DS:DX, or write into it ; this example was constructed to find out # how that was handled. # # FILEHANDLE = filehandle # FUNCTION = 0x545C or 0x565C: Category 5 for parallel printer, # subfunctions 45 or 65 of Ioctl function 0xC # SCALAR is the buffer # # The return value is undefined for an error or defined for success.# ----------------------------------------------------------------- -- open( PRINT, ">prn") || die "Can't open printer: $!\n" ; $set_itt = hex( "545C") ; $get_itt = hex( "565C") ; $in = pack( "s1", 60) ; $value = ioctl( PRINT, $set_itt, $in) ; # Set iteration count to 60 die "Ioctl failed: $!\n" unless $value ; # Handle errors $dic = "\0" x 256 ; # Zero the buffer for clarity $dic = '' ; # Count appended to the string $value = ioctl( PRINT, $get_itt, $dic) ; # Read iteration countdie "Ioctl failed: $!\n" unless $value ; # Handle errors $res = unpack( "s1", $dic) ; # Get the value print "Iteration count is $res\n" ; # Display it. # Under DDS' Perl 3.041, this works. Under Eelco's current 4.036, it # always returns zero. <<<<< Functions to write up (partial) examples: 0C Get/set code page and display mode options. 0D Block Device params/format/read/write 0E Get logical drive map 0F Set logical drive map ################################################################# ##### # Bugs # ################################################################# #### <<<< In Eelco's current 4.036, Ioctl returns into a buffer don't work. The technique for getting drive numbers is a HORRIBLE kludge... Obviously, it would be better if numeric FILEHANDLE parameters could produce drive numbers, or if there were some other way of doing it... As a basic patch, using INT21 Function 67H to raise the number of program handles to 40 or thereabouts would help. Ioctl subfunctions 10h and 11h can't be accessed via the Perl interface.These give an integrated way of finding out if devices or drives (respectively) support particular ioctl functions. They were introducedin MS-DOS 5.0