# 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 <sys/ioctl.h>.  (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 <STDIN> 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



