                        TSR PROGRAMMING WITH C

                            Matthew Probert
                           Servile  Software



Programs which remain running and resident in memory while other 
programs are running are the most exciting line of programming for 
many PC developers. This type of program is known as a "Terminate and 
Stay Resident" or "TSR" program and they are very difficult to program 
sucessfuly. 

The difficulties in programming TSRs comes from the limitations of DOS 
which is not a multi-tasking operating system, and does not react well 
to re-enterant code. That is it's own functions (interrupts) calling 
themselves. 

In theory a TSR is quite simple. It is an ordinary program which 
terminates not through the usual DOS terminate function, but through 
the DOS "keep" function - interrupt 27h. This function reserves an 
area of memory, used by the program so that no other programs will 
overwrite it. This in itself is not a very difficult task, excepting 
that the program needs to tell DOS how much memory to leave it! 

The problems stem mainly from not being able to use DOS function calls 
within the TSR program once it has "gone resident". 

There are a few basic rules which help to clarify the problems 
encountered in programming TSRs: 

1. Avoid DOS function calls

2. Monitor the DOS busy flag, when this flag is nonzero, DOS is 
   executing an interrupt 21h function and MUST NOT be disturbed!

3. Monitor interrupt 28h. This reveals when DOS is busy waiting for 
   console input. At this time you can disturb DOS regardless of the 
   DOS busy flag setting.

4. Provide some way of checking whether the TSR is already loaded to 
   prevent multiple copies occuring in memory.

5. Remember that other TSR programs may be chained to interrupts, and 
   so you must chain any interrupt vectors that your program needs.

6. Your TSR program must use its own stack, and NOT that of the 
   running process.

7. TSR programs must be compiled in a small memory model with stack 
   checking turned off.

8. When control passes to your TSR program, it must tell DOS that the 
   active process has changed.

The following three source code modules describe a complete TSR 
program. This is a useful pop-up address book database which can be 
activated while any other program is running by pressing the key 
combination 'Alt' and '.'. If the address book does not respond to the 
key press, it is probably because DOS cannot be disturbed, and you 
should try to pop-it-up again. 

I accept no credit for the TSR system included in this source code. I 
got it some seven-eight years ago from a BBS....!

/* 
   A practical TSR program (a pop-up address book database)
   Compile in small memory model with stack checking OFF
*/

#include <dos.h>
#include <stdio.h>
#include <string.h>
#include <dir.h>

static union REGS rg;

/* 
   Size of the program to remain resident
   experimentation is required to make this as small as possible
*/
unsigned sizeprogram = 28000/16;

/* Activate with Alt . */
unsigned scancode = 52;        /* . */
unsigned keymask = 8;          /* ALT */

char signature[]= "POPADDR";
char fpath[40];

/*
    Function prototypes 
*/

void curr_cursor(int *x, int *y);
int resident(char *, void interrupt(*)());
void resinit(void);
void terminate(void);
void restart(void);
void wait(void);
void resident_psp(void);
void exec(void);

/*
   Entry point from DOS
*/

main(int argc, char *argv[])
{
    void interrupt ifunc();
    int ivec;

    /* 
       For simplicity, assume the data file is in the root directory
       of drive C:
    */
    strcpy(fpath,"C:\\ADDRESS.DAT");

    if ((ivec = resident(signature,ifunc)) != 0)
    {
        /* TSR is resident */
        if (argc > 1)
        {
            rg.x.ax = 0;
            if (strcmp(argv[1],"quit") == 0)
                rg.x.ax = 1;
            else if (strcmp(argv[1],"restart") == 0)
                rg.x.ax = 2;
            else if (strcmp(argv[1],"wait") == 0)
                rg.x.ax = 3;
            if (rg.x.ax)
            {
                int86(ivec,&rg,&rg);
                return;
            }
        }
        printf("\nPopup Address Book is already resident");
    }
    else
    {
        /* Initial load of TSR program */
        printf("Popup Address Book Resident.\nPress Alt . To Activate....\n");
        resinit();
    }
}

void interrupt ifunc(bp,di,si,ds,es,dx,cx,bx,ax)
{
    if(ax == 1)
        terminate();
    else if(ax == 2)
        restart();
    else if(ax == 3)
        wait();
}

popup()
{
    int x,y;

    curr_cursor(&x,&y);

    /* Call the TSR C program here */
    exec();
    cursor(x,y);
}

/*
    Second source module
*/

#include <dos.h>
#include <stdio.h>

static union REGS rg;
static struct SREGS seg;
static unsigned mcbseg;
static unsigned dosseg;
static unsigned dosbusy;
static unsigned enddos;
char far *intdta;
static unsigned intsp;
static unsigned intss;
static char far *mydta;
static unsigned myss;
static unsigned stack;
static unsigned ctrl_break;
static unsigned mypsp;
static unsigned intpsp;
static unsigned pids[2];
static int pidctr = 0;
static int pp;
static void interrupt (*oldtimer)();
static void interrupt (*old28)();
static void interrupt (*oldkb)();
static void interrupt (*olddisk)();
static void interrupt (*oldcrit)();

void interrupt newtimer();
void interrupt new28();
void interrupt newkb();
void interrupt newdisk();
void interrupt newcrit();

extern unsigned sizeprogram;
extern unsigned scancode;
extern unsigned keymask;

static int resoff = 0;
static int running = 0;
static int popflg = 0;
static int diskflag = 0;
static int kbval;
static int cflag;

void dores(void);
void pidaddr(void);

void resinit()
{
    segread(&seg);
    myss = seg.ss;

    rg.h.ah = 0x34;
    intdos(&rg,&rg);
    dosseg = _ES;
    dosbusy = rg.x.bx;

    mydta = getdta();
    pidaddr();
    oldtimer = getvect(0x1c);
    old28 = getvect(0x28);
    oldkb = getvect(9);
    olddisk = getvect(0x13);

    setvect(0x1c,newtimer);
    setvect(9,newkb);
    setvect(0x28,new28);
    setvect(0x13,newdisk);

    stack = (sizeprogram - (seg.ds - seg.cs)) * 16 - 300;
    rg.x.ax = 0x3100;
    rg.x.dx = sizeprogram;
    intdos(&rg,&rg);
}

void interrupt newdisk(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
    diskflag++;
    (*olddisk)();
    ax = _AX;
    newcrit();
    flgs = cflag;
    --diskflag;
}

void interrupt newcrit(bp,di,si,ds,es,dx,cx,bx,ax,ip,cs,flgs)
{
    ax = 0;
    cflag = flgs;
}

void interrupt newkb()
{
    if (inportb(0x60) == scancode)
    {
        kbval = peekb(0,0x417);
        if (!resoff && ((kbval & keymask) ^ keymask) == 0)
        {
            kbval = inportb(0x61);
            outportb(0x61,kbval | 0x80);
            outportb(0x61,kbval);
            disable();
            outportb(0x20,0x20);
            enable();
            if (!running)
                popflg = 1;
            return;
        }
    }
    (*oldkb)();
}

void interrupt newtimer()
{
    (*oldtimer)();
    if (popflg && peekb(dosseg,dosbusy) == 0)
        if(diskflag == 0)
        {
            outportb(0x20,0x20);
            popflg = 0;
            dores();
        }
}

void interrupt new28()
{
    (*old28)();
    if (popflg && peekb(dosseg,dosbusy) != 0)
    {
        popflg = 0;
        dores();
    }
}

resident_psp()
{
    intpsp = peek(dosseg,*pids);
    for(pp = 0; pp < pidctr; pp++)
        poke(dosseg,pids[pp],mypsp);
}

interrupted_psp()
{
    for(pp = 0; pp < pidctr; pp++)
        poke(dosseg,pids[pp],intpsp);
}

void dores()
{
    running = 1;
    disable();
    intsp = _SP;
    intss = _SS;
    _SP = stack;
    _SS = myss;
    enable();
    oldcrit = getvect(0x24);
    setvect(0x24,newcrit);
    rg.x.ax = 0x3300;
    intdos(&rg,&rg);
    ctrl_break = rg.h.dl;
    rg.x.ax = 0x3301;
    rg.h.dl = 0;
    intdos(&rg,&rg);
    intdta = getdta();
    setdta(mydta);
    resident_psp();
    popup();
    interrupted_psp();
    setdta(intdta);
    setvect(0x24,oldcrit);
    rg.x.ax = 0x3301;
    rg.h.dl = ctrl_break;
    intdos(&rg,&rg);
    disable();
    _SP = intsp;
    _SS = intss;
    enable();
    running = 0;
}

static int avec = 0;
unsigned resident(char *signature,void interrupt(*ifunc)())
{
    char *sg;
    unsigned df;
    int vec;

    segread(&seg);
    df = seg.ds-seg.cs;
    for(vec = 0x60; vec < 0x68; vec++)
    {
        if (getvect(vec) == NULL)
        {
            if (!avec)
                avec = vec;
            continue;
        }
        for(sg = signature; *sg; sg++)
        if (*sg != peekb(peek(0,2+vec*4)+df,(unsigned)sg))
            break;
        if (!*sg)
            return vec;
    }
    if (avec)
        setvect(avec,ifunc);
    return 0;
}

static void pidaddr()
{
    unsigned adr = 0;

    rg.h.ah = 0x51;
    intdos(&rg,&rg);
    mypsp = rg.x.bx;
    rg.h.ah = 0x52;
    intdos(&rg,&rg);
    enddos = _ES;
    enddos = peek(enddos,rg.x.bx-2);
    while(pidctr < 2 && (unsigned)((dosseg<<4) + adr) < (enddos <<4))
    {
        if (peek(dosseg,adr) == mypsp)
        {
            rg.h.ah = 0x50;
            rg.x.bx = mypsp + 1;
            intdos(&rg,&rg);
            if (peek(dosseg,adr) == mypsp + 1)
                pids[pidctr++] = adr;
            rg.h.ah = 0x50;
            rg.x.bx = mypsp;
            intdos(&rg,&rg);
        }
        adr++;
    }
}

static resterm()
{
    setvect(0x1c,oldtimer);
    setvect(9,oldkb);
    setvect(0x28,old28);
    setvect(0x13,olddisk);
    setvect(avec,(void interrupt (*)()) 0);
    rg.h.ah = 0x52;
    intdos(&rg,&rg);
    mcbseg = _ES;
    mcbseg = peek(mcbseg,rg.x.bx-2);
    segread(&seg);
    while(peekb(mcbseg,0) == 0x4d)
    {
        if(peek(mcbseg,1) == mypsp)
        {
            rg.h.ah = 0x49;
            seg.es = mcbseg+1;
            intdosx(&rg,&rg,&seg);
        }
        mcbseg += peek(mcbseg,3) + 1;
    }
}

terminate()
{
    if (getvect(0x13) == (void interrupt (*)()) newdisk)
        if (getvect(9) == newkb)
            if(getvect(0x28) == new28)
                if(getvect(0x1c) == newtimer)
                {
                    resterm();
                    return;
                }
    resoff = 1;
}

restart()
{
    resoff = 0;
}

wait()
{
    resoff = 1;
}

void cursor(int y, int x)
{
    rg.x.ax = 0x0200;
    rg.x.bx = 0;
    rg.x.dx = ((y << 8) & 0xff00) + x;
    int86(16,&rg,&rg);
}

void curr_cursor(int *y, int *x)
{
    rg.x.ax = 0x0300;
    rg.x.bx = 0;
    int86(16,&rg,&rg);
    *x = rg.h.dl;
    *y = rg.h.dh;
}

/* 
   Third module, the simple pop-up address book 
   with mouse support
*/

#include <stdio.h>
#include <stdlib.h>
#include <io.h>
#include <string.h>
#include <fcntl.h>
#include <sys\stat.h>
#include <dos.h>
#include <conio.h>
#include <graphics.h>
#include <bios.h>

/* left cannot be less than 3 */
#define left    4

/* Data structure for records */
typedef struct
{
    char name[31];
    char company[31];
    char address[31];
    char area[31];
    char town[31];
    char county[31];
    char post[13];
    char telephone[16];
    char fax[16];
}
data;

extern char fpath[];

static char scr[4000];

static char sbuff[2000];
char stext[30];
data rec;
int handle;
int recsize;
union REGS inreg,outreg;

/* 
    Function prototypes 
*/
void FATAL(char *);
void OPENDATA(void);
void CONTINUE(void);
void EXPORT_MULTI(void);
void GETDATA(int);
int GETOPT(void);
void DISPDATA(void);
void ADD_REC(void);
void PRINT_MULTI(void);
void SEARCH(void);
void MENU(void);

int GET_MOUSE(int *buttons)
{
    inreg.x.ax = 0;
    int86(0x33,&inreg,&outreg);
    *buttons = outreg.x.bx;
    return outreg.x.ax;
}

void MOUSE_CURSOR(int status)
{
    /* Status = 0 cursor off */
    /*          1 cursor on */

    inreg.x.ax = 2 - status;
    int86(0x33,&inreg,&outreg);
}

int MOUSE_LOCATION(int *x, int *y)
{
    inreg.x.ax = 3;
    int86(0x33,&inreg,&outreg);

    *x = outreg.x.cx / 8;
    *y = outreg.x.dx / 8;

    return outreg.x.bx;
}

int GETOPT()
{
    int result;
    int x;
    int y;

    do
    {
        do
        {
            result = MOUSE_LOCATION(&x,&y);
            if (result & 1)
            {
                if (x >= 52 && x <= 53 && y >= 7 && y <= 15)
                    return y - 7;
                if (x >= 4 && x <= 40 && y >= 7 && y <= 14)
                    return y + 10;

                if (x >= 4 && x <= 40 && y == 15)
                    return y + 10;
            }
        }
        while(!bioskey(1));

        result = bioskey(0);
        x = result & 0xff;
        if (x == 0)
        {
            result = result >> 8;
            result -= 60;
        }
    }
    while(result < 0 || result > 8);
    return result;
}

void setvideo(unsigned char mode)
{
    /* Sets the video display mode    and clears the screen */

    inreg.h.al = mode;
    inreg.h.ah = 0x00;
    int86(0x10, &inreg, &outreg);
}


int activepage(void)
{
    /* Returns the currently selected video display page */

    union REGS inreg,outreg;

    inreg.h.ah = 0x0F;
    int86(0x10, &inreg, &outreg);
    return(outreg.h.bh);
}

void print(char *str)
{
    /* 
       Prints characters only directly to the current display page
       starting at the current cursor position. The cursor is not
       advanced. 
       This function assumes a colour display card. For use with a
       monochrome display card change 0xB800 to read 0xB000
    */

    int page;
    int offset;
    unsigned row;
    unsigned col;
    char far *ptr;

    page = activepage();
    curr_cursor(&row,&col);

    offset = page * 4000 + row * 160 + col * 2;

    ptr = MK_FP(0xB800,offset);

    while(*str)
    {
        *ptr++= *str++;
        ptr++;
    }
}


void TRUESHADE(int lef, int top, int right, int bottom)
{
    int n;

    /* True Shading of a screen block */

    gettext(lef,top,right,bottom,sbuff);
    for(n = 1; n < 2000; n+= 2)
        sbuff[n] = 7;
    puttext(lef,top,right,bottom,sbuff);
}

void DBOX(int l, int t, int r, int b)
{
    /* Draws a double line box around the described area */

    int n;

    cursor(t,l);
    print("");
    for(n = 1; n < r - l; n++)
    {
        cursor(t,l + n);
        print("");
    }
    cursor(t,r);
    print("");

    for (n = t + 1; n < b; n++)
    {
        cursor(n,l);
        print("");
        cursor(n,r);
        print("");
    }
    cursor(b,l);
    print("");
    for(n = 1; n < r - l; n++)
    {
        cursor(b,l+n);
        print("");
    }
    cursor(b,r);
    print("");
}

int INPUT(char *text,unsigned length)
{
    /* Receive a string from the operator */

    unsigned key_pos;
    int key;
    unsigned start_row;
    unsigned start_col;
    unsigned end;
    char temp[80];
    char *p;

    curr_cursor(&start_row,&start_col);

    key_pos = 0;
    end = strlen(text);
    for(;;)
    {
        key = bioskey(0);
        if ((key & 0xFF) == 0)
        {
            key = key >> 8;
            if (key == 79)
            {
                while(key_pos < end)
                    key_pos++;
                cursor(start_row,start_col + key_pos);
            }
            else
            if (key == 71)
            {
                key_pos = 0;
                cursor(start_row,start_col);
            }
            else
            if ((key == 75) && (key_pos > 0))
            {
                key_pos--;
                cursor(start_row,start_col + key_pos);
            }
            else
            if ((key == 77) && (key_pos < end))
            {
                key_pos++;
                cursor(start_row,start_col + key_pos);
            }
            else
            if (key == 83)
            {
                p = text + key_pos;
                while(*(p+1))
                {
                    *p = *(p+1);
                    p++;
                }
                *p = 32;
                if (end > 0)
                    end--;
                cursor(start_row,start_col);
                cprintf(text);
                cprintf(" ");
                if ((key_pos > 0) && (key_pos == end))
                    key_pos--;
                cursor(start_row,start_col + key_pos);
            }
        }
        else
        {
            key = key & 0xFF;
            if (key == 13 || key == 27)
                break;
            else
            if ((key == 8) && (key_pos > 0))
            {
                end--;
                key_pos--;
                text[key_pos--] = '\0';
                strcpy(temp,text);
                p = text + key_pos + 2;
                strcat(temp,p);
                strcpy(text,temp);
                cursor(start_row,start_col);
                cprintf("%-*.*s",length,length,text);
                key_pos++;
                cursor(start_row,start_col + key_pos);
            }
            else
            if ((key > 31) && (key_pos < length)  && 
               (start_col + key_pos < 80)) 
            {
                if (key_pos <= end)
                {
                    p = text + key_pos;
                    memmove(p+1,p,end - key_pos);
                    if (end < length)
                        end++;
                    text[end] = '\0';
                }
                text[key_pos++] = (char)key;
                if (key_pos > end)
                {
                    end++;
                    text[end] = '\0';
                }
                cursor(start_row,start_col);
                cprintf("%-*.*s",length,length,text);
                cursor(start_row,start_col + key_pos);
            }
        }
    }
    text[end] = '\0';
    return key;
}


void FATAL(char *error)
{
    /* A fatal error has occured */

    printf("\nFATAL ERROR: %s",error);
    exit(0);
}

void OPENDATA()
{
    /* Check for existence of data file and if not create it */
    /* otherwise open it for reading/writing at end of file */

    handle = open(fpath,O_RDWR,S_IWRITE);

    if (handle == -1)
    {
        handle = open(fpath,O_RDWR|O_CREAT,S_IWRITE);
        if (handle == -1)
            FATAL("Unable to create data file");
    }
    /* Read in first rec */
    read(handle,&rec,recsize);
}

void CLOSEDATA()
{
    close(handle);
}

void GETDATA(int start)
{
    /* Get address data from operator */

    textcolor(BLACK);
    textbackground(GREEN);
    gotoxy(left,8);
    print("Name ");
    gotoxy(left,9);
    print("Company ");
    gotoxy(left,10);
    print("Address ");
    gotoxy(left,11);
    print("Area ");
    gotoxy(left,12);
    print("Town ");
    gotoxy(left,13);
    print("County ");
    gotoxy(left,14);
    print("Post Code ");
    gotoxy(left,15);
    print("Telephone ");
    gotoxy(left,16);
    print("Fax ");

    switch(start)
    {
        case 0: gotoxy(left + 10,8);
                if(INPUT(rec.name,30) == 27)
                    break;
        case 1: gotoxy(left + 10,9);
                if(INPUT(rec.company,30) == 27)
                    break;
        case 2: gotoxy(left + 10,10);
                if(INPUT(rec.address,30) == 27)
                    break;
        case 3: gotoxy(left + 10,11);
                if(INPUT(rec.area,30) == 27)
                    break;

        case 4: gotoxy(left + 10,12);
                if(INPUT(rec.town,30) == 27)
                    break;

        case 5: gotoxy(left + 10,13);
                if(INPUT(rec.county,30) == 27)
                    break;

        case 6: gotoxy(left + 10,14);
                if(INPUT(rec.post,12) == 27)
                    break;

        case 7: gotoxy(left + 10,15);
                if(INPUT(rec.telephone,15) == 27)
                    break;

        case 8: gotoxy(left + 10,16);
                INPUT(rec.fax,15);
                break;

    }
    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left + 23,21);
    print("                                         ");
}

void DISPDATA()
{
    /* Display address data */
    textcolor(BLACK);
    textbackground(GREEN);
    cursor(7,3);
    cprintf("Name      %-30.30s",rec.name);
    cursor(8,3);
    cprintf("Company   %-30.30s",rec.company);
    cursor(9,3);
    cprintf("Address   %-30.30s",rec.address);
    cursor(10,3);
    cprintf("Area      %-30.30s",rec.area);
    cursor(11,3);
    cprintf("Town      %-30.30s",rec.town);
    cursor(12,3);
    cprintf("County    %-30.30s",rec.county);
    cursor(13,3);
    cprintf("Post Code %-30.30s",rec.post);
    cursor(14,3);
    cprintf("Telephone %-30.30s",rec.telephone);
    cursor(15,3);
    cprintf("Fax       %-30.30s",rec.fax);
}

int LOCATE(char *text)
{
    int result;

    do
    {
        /* Read rec into memory */
        result = read(handle,&rec,recsize);
        if (result > 0)
        {
            /* Scan rec for matching data */
            if (strstr(strupr(rec.name),text) != NULL)
                return(1);
            if (strstr(strupr(rec.company),text) != NULL)
                return(1);
            if (strstr(strupr(rec.address),text) != NULL)
                return(1);
            if (strstr(strupr(rec.area),text) != NULL)
                return(1);
            if (strstr(strupr(rec.town),text) != NULL)
                return(1);
            if (strstr(strupr(rec.county),text) != NULL)
                return(1);
            if (strstr(strupr(rec.post),text) != NULL)
                return(1);
            if (strstr(strupr(rec.telephone),text) != NULL)
                return(1);
            if (strstr(strupr(rec.fax),text) != NULL)
                return(1);
        }
    }
    while(result > 0);
    return(0);
}


void SEARCH()
{
    int result;

    gotoxy(left,21);
    textcolor(WHITE);
    textbackground(RED);
    cprintf("Enter data to search for ");
    strcpy(stext,"");
    INPUT(stext,30);
    if (*stext == 0)
    {
        gotoxy(left,21);
        cprintf("%70c",32);
        return;
    }
    gotoxy(left,21);
    textcolor(WHITE);
    textbackground(RED);
    cprintf("Searching for %s Please Wait....",stext);
    strupr(stext);
    /* Locate start of file */
    lseek(handle,0,SEEK_SET);
    result = LOCATE(stext);
    if (result == 0)
    {
        gotoxy(left,21);
        cprintf("%70c",32);
        gotoxy(left + 27,21);
        cprintf("NO MATCHING RECORDS");
        gotoxy(left + 24,22);
        cprintf("Press RETURN to Continue");
        bioskey(0);
        gotoxy(left,21);
        cprintf("%70c",32);
        gotoxy(left,22);
        cprintf("%70c",32);
    }
    else
    {
        lseek(handle,0 - recsize,SEEK_CUR);
        read(handle,&rec,recsize);
        DISPDATA();
    }
    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left,21);
    cprintf("%70c",32);
    textcolor(BLACK);
    textbackground(GREEN);
}

void CONTINUE()
{
    int result;
    long curpos;

    curpos = tell(handle) - recsize;

    result = LOCATE(stext);
    textcolor(WHITE);
    textbackground(RED);
    if (result == 0)
    {
        gotoxy(left + 24,21);
        cprintf("NO MORE MATCHING RECORDS");
        gotoxy(left + 24,22);
        cprintf("Press RETURN to Continue");
        bioskey(0);
        gotoxy(left,21);
        cprintf("%70c",32);
        gotoxy(left,22);
        cprintf("%70c",32);
        lseek(handle,curpos,SEEK_SET);
        read(handle,&rec,recsize);
        DISPDATA();
    }
    else
    {
        lseek(handle,0 - recsize,SEEK_CUR);
        read(handle,&rec,recsize);
        DISPDATA();
    }
    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left,21);
    cprintf("%70c",32);
    gotoxy(left,22);
    cprintf("                                      ");
    textcolor(BLACK);
    textbackground(GREEN);
}



void PRINT_MULTI()
{
    data buffer;
    char destination[60];
    char text[5];
    int result;
    int ok;
    int ok2;
    int blanks;
    int total_lines;
    char *p;
    FILE *fp;

    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left + 23,21);
    cprintf("Enter selection criteria");

    /* Clear existing rec details */
    memset(&rec,0,recsize);

    DISPDATA();
    GETDATA(0);

    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left,21);
    cprintf("Enter report destination PRN");
    strcpy(destination,"PRN");
    gotoxy(left,22);
    cprintf("Enter Address length in lines 18");
    strcpy(text,"18");
    gotoxy(left + 25,21);
    INPUT(destination,40);
    gotoxy(left +30,22);
    INPUT(text,2);
    gotoxy(left,21);
    cprintf("%72c",32);
    gotoxy(left,22);
    cprintf("%72c",32);

    total_lines = atoi(text) - 6;
    if (total_lines < 0)
        total_lines = 0;

    fp = fopen(destination,"w+");
    if (fp == NULL)
    {
        gotoxy(left,21);
        cprintf("Unable to print to %s",destination);
        gotoxy(left,22);
        cprintf("Press RETURN to Continue");
        bioskey(0);
        gotoxy(left,21);
        cprintf("%78c",32);
        gotoxy(left,22);
        cprintf("                        ");
    }

    /* Locate start of file */
    lseek(handle,0,SEEK_SET);

    do
    {
        /* Read rec into memory */
        result = read(handle,&buffer,recsize);
        if (result > 0)
        {
            ok = 1;
            /* Scan rec for matching data */
            if (*rec.name)
                if (stricmp(buffer.name,rec.name))
                    ok = 0;
            if (*rec.company)
                if (stricmp(buffer.company,rec.company))
                    ok = 0;
            if (*rec.address)
                if (stricmp(buffer.address,rec.address))
                    ok = 0;
            if (*rec.area)
                if (stricmp(buffer.area,rec.area))
                    ok = 0;
            if (*rec.town)
                if (stricmp(buffer.town,rec.town))
                    ok = 0;
            if (*rec.county)
                if (stricmp(buffer.county,rec.county))
                    ok = 0;
            if (*rec.post)
                if (stricmp(buffer.post,rec.post))
                ok = 0;
            if (*rec.telephone)
                if (stricmp(buffer.telephone,rec.telephone))
                    ok = 0;
            if (*rec.fax)
                if (stricmp(buffer.fax,rec.fax))
                    ok = 0;
            if (ok)
            {
                blanks = total_lines;
                p = buffer.name;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.name);
                p = buffer.company;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.company);
                p = buffer.address;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.address);
                p = buffer.area;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.area);
                p = buffer.town;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.town);
                p = buffer.county;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.county);
                p = buffer.post;
                ok2 = 0;
                while(*p)
                {
                    if (*p != 32)
                    {
                        ok2 = 1;
                        break;
                    }
                    p++;
                }
                if (!ok2)
                    blanks++;
                else
                    fprintf(fp,"%s\n",buffer.post);
                while(blanks)
                {
                    fprintf(fp,"\n");
                    blanks--;
                }
            }
        }
    }
    while(result > 0);
    fclose(fp);
    lseek(handle,0,SEEK_SET);
    read(handle,&rec,recsize);
    DISPDATA();
}

void EXPORT_MULTI()
{
    data buffer;
    char destination[60];
    int result;
    int ok;
    FILE *fp;

    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left + 23,21);
    cprintf("Enter selection criteria");

    /* Clear existing rec details */
    memset(&rec,0,recsize);

    DISPDATA();
    GETDATA(0);

    textcolor(WHITE);
    textbackground(RED);
    gotoxy(left,21);
    cprintf("Enter export file address.txt");
    strcpy(destination,"address.txt");
    gotoxy(left + 18,21);
    INPUT(destination,59);
    gotoxy(left,21);
    cprintf("%70c",32);

    fp = fopen(destination,"w+");
    if (fp == NULL)
    {
        gotoxy(left,21);
        cprintf("Unable to print to %s",destination);
        gotoxy(left,22);
        cprintf("Press RETURN to Continue");
        bioskey(0);
        gotoxy(left,21);
        cprintf("%78c",32);
        gotoxy(left,22);
        cprintf("                        ");
    }
    /* Locate start of file */
    lseek(handle,0,SEEK_SET);

    do
    {
        /* Read rec into memory */
        result = read(handle,&buffer,recsize);
        if (result > 0)
        {
            ok = 1;
            /* Scan rec for matching data */
            if (*rec.name)
                if (stricmp(buffer.name,rec.name))
                    ok = 0;
            if (*rec.company)
                if (stricmp(buffer.company,rec.company))
                    ok = 0;
            if (*rec.address)
                if (stricmp(buffer.address,rec.address))
                    ok = 0;
            if (*rec.area)
                if (stricmp(buffer.area,rec.area))
                    ok = 0;
            if (*rec.town)
                if (stricmp(buffer.town,rec.town))
                    ok = 0;
            if (*rec.county)
                if (stricmp(buffer.county,rec.county))
                    ok = 0;
            if (*rec.post)
                if (stricmp(buffer.post,rec.post))
                ok = 0;
            if (*rec.telephone)
                if (stricmp(buffer.telephone,rec.telephone))
                    ok = 0;
            if (*rec.fax)
                if (stricmp(buffer.fax,rec.fax))
                    ok = 0;
            if (ok)
            {
                fprintf(fp,"\"%s\",",buffer.name);
                fprintf(fp,"\"%s\",",buffer.company);
                fprintf(fp,"\"%s\",",buffer.address);
                fprintf(fp,"\"%s\",",buffer.area);
                fprintf(fp,"\"%s\",",buffer.town);
                fprintf(fp,"\"%s\",",buffer.county);
                fprintf(fp,"\"%s\",",buffer.post);
                fprintf(fp,"\"%s\",",buffer.telephone);
                fprintf(fp,"\"%s\"\n",buffer.fax);

            }
        }
    }
    while(result > 0);
    fclose(fp);
    lseek(handle,0,SEEK_SET);
    read(handle,&rec,recsize);
    DISPDATA();
}

void MENU()
{
    int option;
    long result;
    long end;
    int new;

    do
    {
        cursor(21,26);
        print("Select option (F2 - F10)");
        cursor(7,52);
        print("F2 Next record");
        cursor(8,52);
        print("F3 Previous record");
        cursor(9,52);
        print("F4 Amend record");
        cursor(10,52);
        print("F5 Add new record");
        cursor(11,52);
        print("F6 Search");
        cursor(12,52);
        print("F7 Continue search");
        cursor(13,52);
        print("F8 Print address labels");
        cursor(14,52);
        print("F9 Export records");
        cursor(15,52);
        print("F10 Exit");
        MOUSE_CURSOR(1);
        option = GETOPT();
        MOUSE_CURSOR(0);

        switch(option)
        {
            case 0 : /* Next rec */
                     result = read(handle,&rec,recsize);
                     if (!result)
                     {
                        lseek(handle,0,SEEK_SET);
                         result = read(handle,&rec,recsize);
                     }
                     DISPDATA();
                     break;

            case 1 : /* Previous rec */
                    result = lseek(handle,0 - recsize * 2,SEEK_CUR);
                    if (result <= -1)
                        lseek(handle,0 - recsize,SEEK_END);
                    result = read(handle,&rec,recsize);
                    DISPDATA();
                    break;

            case 3 : /* Add rec */
                     lseek(handle,0,SEEK_END);
                     memset(&rec,0,recsize);
                     DISPDATA();

            case 2 : /* Amend current rec */
                     new = 1;
                     if (*rec.name)
                        new = 0;
                     else
                     if (*rec.company)
                        new = 0;
                     else
                     if (*rec.address)
                        new = 0;
                     else
                     if (*rec.area)
                        new = 0;
                     else
                     if (*rec.town)
                        new = 0;
                     else
                     if (*rec.county)
                        new = 0;
                     else
                     if (*rec.post)
                        new = 0;
                     else
                     if (*rec.telephone)
                        new = 0;
                     else
                     if (*rec.fax)
                        new = 0;
                     result = tell(handle);
                     lseek(handle,0,SEEK_END);
                     end = tell(handle);

                     /* Back to original position */
                     lseek(handle,result,SEEK_SET);

                     /* If not at end of file, && !new rewind one rec */
                     if (result != end || ! new)
                        result = lseek(handle,0 - recsize,SEEK_CUR);
                     result = tell(handle);
                     gotoxy(left + 22,21);
                     print(" Enter address details  ");
                     GETDATA(0);
                     if (*rec.name || *rec.company)
                        result =  write(handle,&rec,recsize);
                     break;

            case 4 : /* Search */
                     gotoxy(left + 22,21);
                     print("                        ");
                     SEARCH();
                     break;

            case 5 : /* Continue */
                     gotoxy(left + 22,21);
                     print("                        ");
                     CONTINUE();
                     break;

            case 6 : /* Print */
                     gotoxy(left + 22,21);
                     print("                        ");
                     PRINT_MULTI();
                     break;

            case 7 : /* Export */
                     gotoxy(left + 22,21);
                     print("                        ");
                     EXPORT_MULTI();
                     break;

            case 8 : /* Exit */
                     break;

            default: /* Amend current rec */
                     new = 1;
                     if (*rec.name)
                        new = 0;
                     else
                     if (*rec.company)
                        new = 0;
                     else
                     if (*rec.address)
                        new = 0;
                     else
                     if (*rec.area)
                        new = 0;
                     else
                     if (*rec.town)
                        new = 0;
                     else
                     if (*rec.county)
                        new = 0;
                     else
                     if (*rec.post)
                        new = 0;
                     else
                     if (*rec.telephone)
                        new = 0;
                     else
                     if (*rec.fax)
                        new = 0;
                     result = tell(handle);
                     lseek(handle,0,SEEK_END);
                     end = tell(handle);

                     /* Back to original position */
                     lseek(handle,result,SEEK_SET);

                     /* If not at end of file, && !new rewind one rec */
                     if (result != end || ! new)
                        result = lseek(handle,0 - recsize,SEEK_CUR);
                     result = tell(handle);
                     gotoxy(left + 22,21);
                     print(" Enter address details  ");
                     GETDATA(option - 17);
                     if (*rec.name || *rec.company)
                        result = write(handle,&rec,recsize);
                     option = -1;
                     break;

        }
    }
    while(option != 8);
}

void exec()
{
    gettext(1,1,80,25,scr);
    setvideo(3);
    textbackground(WHITE);
    textcolor(BLACK);
    clrscr();
    recsize = sizeof(data);

    OPENDATA();

    TRUESHADE(left,3,79,5);
    window(left - 2,2 ,78, 4);
    textcolor(YELLOW);
    textbackground(MAGENTA);
    clrscr();
    DBOX(left - 3, 1, 77, 3);
    gotoxy(3,2);
    print("Servile Software             PC ADDRESS BOOK 5.2                 
           (c) 1994");

    TRUESHADE(left,8,left + 43,18);
    window(left - 2,7 , left + 42, 17);
    textcolor(BLACK);
    textbackground(GREEN);
    clrscr();
    DBOX(left - 3, 6, left + 41, 16);

    TRUESHADE(left + 48,8,79,18);
    window(left + 46, 7 , 78, 17);
    textbackground(BLUE);
    textcolor(YELLOW);
    clrscr();
    DBOX(left + 45,6,77,16);

    TRUESHADE(left ,21,79,24);
    window(left - 2, 20 , 78, 23);
    textbackground(RED);
    textcolor(WHITE);
    clrscr();
    DBOX(left - 3,19,77,22);

    window(1,1,80,25);
    textcolor(BLACK);
    textbackground(GREEN);
    DISPDATA();

    MENU();

    CLOSEDATA();
    puttext(1,1,80,25,scr);
    return;
}

