XXCOPY

DATMAN TECHNICAL BULLETIN #033



From:    Kan Yabumoto           tech@datman.com
To:      DATMAN user
Subject: DATMANIA, A Sample Program of DATMAN HOTAPI
Date:    1999-11-11
====================================================================

DATMANIA,  DATMAN Interface Agent

   DATMANIA is a console utility program that combines the
   write-only file access and the read-only access so that the
   user can enter the entire command text including the necessary
   parameters, with the result text shown immediately.

   In addition, this utility retrieves the numeric values printed
   as the first word in the output text and make it the exit code
   of the program.  This feature allows the program invoked inside
   a batch file to set the ERRORLEVEL so that the batch file may
   use a conditional statement based on the value of ERRORLEVEL. 
   
   It is written in C, ready to be compiled by Borland C++ 3.1.
   We have a 32-bit version that is good for Visual C++ 6.0.



License for DATMAN HOTAPI:

    For personal use and/or non-commercial programming activities,
    you may use the DATMAN HOTAPI protocol free of charge.

    When you incorporate any part of our DATMANIA.C program
    (which is copyrighted) in your program, you must show the same
    copyright notice which appears at the beginning of the DATMANIA.C
    source file.
    
    If you plan to use DATMAN HOTAPI as part of your application
    program, incorporating the whole or in part,  for the purpose
    of distribution outside of your own organization, you need to
    be a licensed DATMAN-PRO user and pay a one-time license fee
    to Pixelab. 
    

/****************************************************************************
*                                                                           *
*     MODULE:   datmania.c                                                  *
*                                                                           *
*    PURPOSE:   the datman interface agent (DATMAN-99 batch enhancer)       *
*                                                                           *
*               This program can be used as a template to develop           *
*               a custom application program which controls DATMAN          *
*               directly.  For example, a small change to this program      *
*               allows a DATMAN-format operation under software control.    *
*                                                                           *
* PROGRAMMER:   Kan Yabumoto  yabumoto@datman.com                           *
*                                                                           *
*   COMPILER:   BC++ 3.1,                                                   *
*                                                                           *
*               This program was written for the 16-bit environment.        *
*               If you compile it in 16-bit mode, the executable file       *
*               can be invoked in either DOS or Win32 console.              *
*                                                                           *
*    HISTORY:   2.2     99-11-11    New HOTAPI compatible                   *
*                                                                           *
*  COPYRIGHT:   (c)1999 copyright  Pixelab, Inc.  All rights reserved.      *
*                                                                           *
*               Please note that you may use this source code in part or    *
*               the whole file for your own application program without     *
*               license fee provided the purpose is limited to personal     *
*               usage and non-commercial distribution.                      *
*               If it is distributed outside your organiation for any       *
*               commercial use, you must be a registered DATMAN-PRO user    *
*               and must pay a one-time license fee to Pixelab.             *
*                                                                           *
*               http://www.datman.com                                       *
*                                                                           *
****************************************************************************/

#include    <stdio.h>
#include    <stdlib.h>
#include    <dos.h>
#include    <string.h>
#include    <fcntl.h>
#include    <io.h>

typedef unsigned int    uint;
typedef void interrupt ISR(void);

//      datman family error_level returned upon termination of the program
 
#define ECODE_OK         0  // Successful operation
#define ECODE_OKRDONLY   1  // tape present, read-only set by software
#define ECODE_OKFULL     2  // tape present, no more room
#define ECODE_OKWRPROT   3  // tape present, write-protect set on cartridge
#define ECODE_MTBUSY     4  // tape present, volume being mounted (busy)
#define ECODE_DRBUSY     5  // tape present, drive busy (e.g., dformatx)
#define ECODE_MTERR      6  // tape present, background mount failed once
#define ECODE_NOTAPE     7  // tape not present

#define ECODE_LDASPI    10  // aspi manager resident (loaded by loadaspi)
#define ECODE_CFASPI    11  // aspi manager resident (loaded by config.sys)
#define ECODE_ENGINE    12  // DATMANFE already present
#define ECODE_DATMAN    13  // DATMAN.EXE already present
#define ECODE_CURRDR    14  // the tape drive is the current DOS default drive.
#define ECODE_WIN32     15  // the switch not valid in the Win32 environment

#define ECODE_NOFILE    20  // can't access the aspi file
#define ECODE_NOASPI    21  // cannot find resident aspi manager
#define ECODE_NOLASP    22  // cannot find resident loadaspi
#define ECODE_NODMAN    23  // cannot find resident DATMANFE
#define ECODE_NODAT     24  // cannot find DAT drive
#define ECODE_NOMEM     25  // insufficient memory
#define ECODE_NODRV     26  // all drive letters currently used
#define ECODE_WRPROT    27  // tape write protected
#define ECODE_INACTIVE  28  // the DATMAN volume is currently inactive

#define ECODE_ASPIER    30  // aspi module returned an error condition
#define ECODE_DMANER    31  // unspecified error by DATMANFE
#define ECODE_UNLOAD    32  // uninstall failed (another TSR on top)
#define ECODE_ABORT     33  // aborted by user
#define ECODE_PARA      34  // illegal commmand parameter
#define ECODE_DOS       35  // Invalid DOS version
#define ECODE_MISC      36  // Miscellaneous cause for error
#define ECODE_VERSION   37  // Resident DATMAN wrong version
#define ECODE_FILEIO    38  // File I/O erorr
#define ECODE_DOSBOX    39  // cannot make a TSR in DOS Box


// function prototypes

int     hapi_IsDatmanDrive(int drvno);
int     hapi_FindDatmanDrive(int drvno);
int     hapi_WriteCommand(char* command,char* argument);
long    hapi_ReadResult(char* buf,int buflen);


#define HOTAPI_DIRNAME      "\\$DATMAN$\\$HOTAPI$"
#define HOTAPI_DIRNAME_LEN  18

char    hotapi_fname[256];
char*   hotapi_fname_ptr;       // when this is 0, it's not initialized

/*

  int   hapi_InitDatman(char* path);

    char*  path;        // the path corresponding to DATMAN root directory 
    returns             0 if successful,  1 if error
    
    This function can tell whether a given drive letter is a DATMAN volume.
    To make it network-ready, it accepts a string which corresponds the
    root directory of the DATMAN volume.  When the path is a valid
    DATMAN volume, it will initialize internal storage used by other
    DATMAN HOTAPI functions.

    Example of path:
    
        "T:\"           // for a local drive letter
        "T:"            // the backslash is optional
        "T"             // one-letter string is treated as a drive letter       
        "\\serverx\tape // the DATMAN volume is networked
        "\\serverx\t\   // for UNC, the trailing backslash is optional

    The official detection method of a DATMAN volume is
    to open the virtual file, "\$DATMAN$\$HOTAPI$\VERSION" and
    read 8 bytes from the file.  You should be able to read at
    least 4 bytes from the virtual file (the version string as a
    4-digit decimal number).

    To exclude a possible case of an existing real filename which
    happens to be identical to this magic filename, we recommend
    a check for the virtual directory "\$DATMAN$\$HOTAPI$".
    If this directory exists, then, it is not a DATMAN volume.
    Don't be confused on this test.  Since it is virtual, it should
    not exist.

    Consider this; someone who doesn't like you may place a dummy
    file on a hard disk with the same filename to confuse the
    VERSION test.

*/

int     hapi_InitDatman(        // returns 0 if ok
char*   path)                   // e.g., "T:" or "\\myserverx\t"
{
    int     fh;
    int     len;
    int     actual;
    uint    attrib;
    char    buf[8];

    hotapi_fname_ptr = 0;       // when this is 0, it's not initialized

    if ((len = strlen(path)) < 1 || len > sizeof(hotapi_fname) - 32)
        return(1);                      // be reasonable!!!

    // preliminary test (the virtual directory must not exist
    if (len == 1 || path[1] == ':') {   // simple drive letter
        sprintf(hotapi_fname,"%c:" HOTAPI_DIRNAME ,path[0]);
        len = 2;
    }
    else {                              // probably an UNC over a network
        if (path[len-1] == '\\')        // if last letter is a backslash...
            --len;                      // knock off the last letter
        strcpy(hotapi_fname,path);
        sprintf(hotapi_fname,"%s" HOTAPI_DIRNAME ,path);
    }
    
    if (_dos_getfileattr(hotapi_fname, &attrib) == 0)
        return(1);                      // a file/dir already exists there

    // this is the formal test of a DATMAN volume

    strcpy((hotapi_fname + HOTAPI_DIRNAME_LEN) + len,"\\VERSION");

    fh = _open(hotapi_fname,O_RDONLY|O_BINARY);
    if (fh == -1)
        return(1);                      // does not respond to it

    actual = _read(fh,buf,sizeof(buf));
    _close(fh);

    if (actual >= 4)        // must be at least 4 bytes
        hotapi_fname_ptr = hotapi_fname + (HOTAPI_DIRNAME_LEN + 1) + len;
    return(actual < 4); // success only when the string is 4 letter or more
}


/*

  int   hapi_FindDatmanDrive(int drvno);

    int     drvno;      // the first drive number to search for DATMAN drive
    returns             the drive number (0 = A:, 1 = B:, etc.), -1 if error

    If you do not know the drive letter, you may try for possible
    drive letter such as "D:", "E:", ... "Z".
    This method works only for a local DATMAN volume.

    Since this function calls hapi_InitDatman() which satisfies the
    internal initialization requirements, you need not call the
    initialization function again if the last call to this function
    returned successfully.

    Normally, A: and B: should be avoided in this test because it may
    cause unwanted floppy disk access which is a nuisance.

*/

int     hapi_FindDatmanDrive(   // first driveno (0-25) found, -1 if error
int     drvno)                  // starting drive (0 = A:, 1 = B:, etc.)
{
    static  char drvletter[4] = "T:";
    int     i;

    for(i = drvno; i < 26; ++i) {
        drvletter[0] = (char)('A' + i);
        if (hapi_InitDatman(drvletter) == 0)
            return(i);      // initialization succeeded (found it)
    }
    return(-1);
}

/*

  int   hapi_WriteCommand(char* command, char* argument);

    char*   command;    // HOTAPI command name string
    char*   argument;   // argument string (may be "" or 0)
    returns             0 if success, -1 if error

    This command sends a HOTAPI command by opening the
    corresponding virtual file (the file name part comes
    from the command name string.  Some commands accept
    an optional argument string (which may be made of many
    parameters separated by blanks).
    
    Whether the command actually succeeds or not after
    execution on the tape drive is not this function's concern.
    As long as the virtual file is opened successfully and
    the file-write action succeeds, this command returns
    a success.  The result of this command can be retrieved
    only by opening the "RESULT" file and read the contents
    from the virtual file.
    
    Note: The DATMAN HOTAPI protocol does not permit you to
    combine the write and read action in one file open.  The
    file must be opened either in write-only or read-only
    mode.   You must close the file after writing the parameter
    bytes.
    
    The actual DATMAN action for the command will not be
    initiated until the file is closed.  This is due to the
    fact that until then, DATMAN file engine would not know
    whether the whole parameter string has arrived or
    not.  As long as the entire parameter bytes are written
    to the virtual file, it does not matter how the bytes
    are delivered to the file engine.  You may even send
    one character at a time. in repeated _write() call.
    
    It is strongly advised to use the lowest level file I/O
    library function for the HOTAPI purposes.
    
      _open(), _write(),  _read(), _close()

    If you use higher level functions such as:
    
      open(),  write(),   read()   close()
      fopen(), fwrite(),  fread(), fclose()
  
    the library functions may perform additional file I/O
    implicitly (for example, checking and adjusting the
    file attributes, date/time, etc.).  Some of these
    functions may also perform data buffering in read
    and write operations which may have adverse side effects. 
    To avoid any possible complications, it is best to use
    the raw functions!
    
*/

int     hapi_WriteCommand(      // 0 if ok,  -1 if error
char*   command,                // command name (8.3 format)
char*   argument)               // argument string
{
    int     len;
    int     fh;
    
    if (hotapi_fname_ptr == 0)
        return(-1);                         // has not been initialized
    
    strncpy(hotapi_fname_ptr,command,13);   // append the command name
    hotapi_fname_ptr[13] = 0;               // make sure it's terminated

    fh = _open(hotapi_fname,O_CREAT|O_TRUNC|O_WRONLY|O_BINARY);
    if (fh == -1)
        return(-1);

    if (argument && (len = strlen(argument)) > 0)
        _write(fh,argument,len);
    _close(fh);
    return(0);
}

/*

  long  hapi_ReadResult(char* buf, int buflen);

    char*   buf;    // buffer to receive text made by last write command
    char*   buflen; // number of bytes in buffer (extra data discarded)
    returns         error code (-1 if error)

    This command may be used immediately after a write action.
    it always use the special "RESULT" file to retrieve the text
    generated by the previous write-command (or possibly a read
    command).
    
    One of the reasons why we need the DATMANIA utility is that
    the direct invocation of DATMAN HOTAPI from a batch file using
    the ECHO and TYPE console command would not set the ERRORLEVEL
    value which reflects the status information returned in the
    text read from a HOTAPI action.  Some elaborate batch file
    script should be able to test the result of DATMAN action
    and take appropriate actions.  The last steps in the
    hapi_ReadResult() function converts the numeric value returned
    by a HOTAPI function into the Exit code of this utility. 

    All DATMAN HOTAPI function create a line of text which always
    starts with a numeric value.  While not all of the returned
    values are useful for testing purpose by a batch file,
    the return code is useful in many occasions.  

*/

long    hapi_ReadResult(    // returns actual byte count (-1L as an error)
char*   buf,                // user buffer
int     buflen)             // buffer size
{
    int     fh;
    int     len;
    long    retval;

    if (hotapi_fname_ptr == 0)
        return(-1);                     // has not been initialized
    
    strcpy(hotapi_fname_ptr,"RESULT");  // get result (a magic file)

    fh = _open(hotapi_fname,O_RDONLY|O_BINARY);
    if (fh == -1)
        return(-1);                     // does not respond to it

    if (buf && buflen > 0)
        len = _read(fh,buf,buflen);
    else
        len = 0;
    _close(fh);

    retval = -1L;
    if (len > 0)
        sscanf(buf,"%ld",&retval);      // get the result value (ERRORLEVEL)
    return(retval);                     // the return code
}

/*

    Additional programming tip:
    
    If you want to create a commonly performed DATMAN function such as
    tape-eject and tape-format operations...

    Make sure that the hapi_InitDatman() is called earlier.

    

int     hapi_FormatTape(
char*   label,          // up to 11 chars
char*   comment)        // comments up to 63 chars (double-quoted string)
{
    int     retval;
    int     len;
    char    argline[128];
    char    buf[256];

    if (!label)
        label = "NO_NAME";
    if (!comment)
        comment = "";

    sprintf(argline,"%s %s",label,comment);
    
    if ((retval = hapi_WriteCommand("FORMAT",argline)) != 0)
        return(retval);
    return((int)hapi_ReadResult(buf,sizeof(buf)));
}


int     hapi_EjecttTape(void)
{
    int     retval;
    char    buf[256];
    
    if ((retval = hapi_WriteCommand("EJECT","")) != 0)
        return(retval);

    return((int)hapi_ReadResult(buf,sizeof(buf)));
}


*/  


//#############################################################################


void    help_and_exit(void)
{
    printf("\nDATMAN Interface Agent  ver 2.2\n"
            "(c)1999 Copyright Pixelab, Inc.\n"
            "        All rights reserved.\n\n"
            "==========  Invocation Syntax  ==========\n\n"
            "DATMANIA  drive:  [ command  [ argument... ] ]\n\n"
            "  where   drive:  DATMAN drive   (e.g., T:  or \\\\server\\mydrive)\n"
            "        command:  HOTAPI command (e.g., format, eject)\n"
            "                  if you skip command, \"STATUS\" is assumed\n"
            "       argument:  optional parameters for the command\n\n");
    exit(ECODE_PARA);    
}

char    textbuf[16384];

ISR far* far old_isr24;
int     fail_fl;                // 1: when isr24 has been called

// the following interrupt handler intercepts the Abort/Retry/Fail chore
// and selects the "Fail" option for you.  You will not see the prompt.

void interrupt isr24(void)      // critical error handler
{
    fail_fl = 1;                    // flag set to let main() know about this
    asm mov byte ptr ss:[bp+16],3   // critical error fail (simulate typing F)
                                    // sets AL register to 3 and returns
}

int     main(
int     argc,
char*   argv[])
{
    int     i;
    int     retval;
    char*   command;

    if (argc < 2 || !strcmp(argv[1],"/?"))
        help_and_exit();

    old_isr24 = getvect(0x24);
    setvect(0x24,(ISR*)isr24);

    if (hapi_InitDatman(argv[1])) {
        if (fail_fl) {      // when DATMANFE exits but DATMANCC is not running
            printf("\n%-8d  The DATMAN volume is currently inactive\n",ECODE_INACTIVE);
            setvect(0x24,old_isr24);
            exit(ECODE_INACTIVE);
        }
        else {              // DATMANFE is not responding
            printf("\n%-8d  The drive is not a DATMAN volume\n",ECODE_NODMAN);
            setvect(0x24,old_isr24);
            exit(ECODE_NODMAN);
        }
    }

    command = (argc >= 3) ? argv[2] : "STATUS";
        
    argv[0] = "STATUS";
    for (i = 3; i < argc; ++i) {
        if (i > 2)
            strcat(textbuf," ");                // separator
        strcat(textbuf,argv[i]);
    }

    retval = hapi_WriteCommand(command,textbuf);
    if (!retval) {
        retval = (int)hapi_ReadResult(textbuf,sizeof(textbuf));
        putchar('\n');
        puts(textbuf);
    }
    setvect(0x24,old_isr24);
    return(retval);
}

// end of the DATMANIC.C  source file =========================================



A general discussion on the DATMAN HOTAPI protocol is available in  TB #031 .


[ More Technical Bulletins ] [ DATMAN Table of Contents ]