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 .