Article View: gnu.gcc.help
Article #50181Re: gcc equivalent of spawn (MSDOS)
From: Marty Leisner 25733
Date: Thu, 11 Nov 1993 14:22
Date: Thu, 11 Nov 1993 14:22
843 lines
21547 bytes
21547 bytes
I ported gnu make to Ms/Dos a number of years ago...
What I do is:
1) use the small model for everything
2) have a fork which works
3) I stripped out all the builtins to gmake and it fit...
This is for Aztec C...
Let me know if its useful...I may package it and distribute it...
#! /bin/sh
# This is a shell archive. Remove anything before this line, then unpack
# it by saving it into a file and typing "sh file". To overwrite existing
# files, type "sh file -c". You can also feed this as standard input via
# unshar, or by typing "sh <file", e.g.. If this archive is complete, you
# will see the following message at the end:
# "End of shell archive."
# Contents: exec.c execl.c execlp.c execv.c execve.c execvp.c
# frkmagic.asm fork.c
# Wrapped by leisner@gnu on Thu Nov 11 09:20:11 1993
PATH=/bin:/usr/bin:/usr/ucb ; export PATH
if test -f 'exec.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'exec.c'\"
else
echo shar: Extracting \"'exec.c'\" \(1635 characters\)
sed "s/^X//" >'exec.c' <<'END_OF_FILE'
X
X/*
X * If do_spawn, use out DOS exec function, freeing the enviornment on the way
X *out.
X * If execing, massage the environment (i.e. if no environment, free out environment
X * and set psp to use new environment
X * Then call Aztec Exec function.
X * $Log: exec.c $
X * Revision 1.1 88/12/04 14:49:12 marty
X * Initialization
X *
X */
X
X
X#include <dos/exec.h>
X
X
X#define FAR_PTR(seg, off) ((void far *) (((long) seg << 16L) + (long) off))
X
Xint do_spawn = 0;
X
Xsetup_exec(filename, argv, env_segment)
Xchar *filename;
Xchar **argv;
Xshort env_segment;
X{
X
X int result = -1;
X char fcb1[16], fcb2[16];
X char buffer[130];
X char *cp;
X char *xp;
X int fd;
X struct exe exe_header;
X extern unsigned _PSP;
X unsigned far *psp_env;
X
X
X if(do_spawn) {
X result = dos_exec(filename, argv, env_segment);
X do_spawn = 0;
X } else {
X psp_env = FAR_PTR(_PSP, 0x2c);
X
X /* need to prepare for exec */
X if((fd = open(filename, 0)) < 0)
X return -1;
X
X if(read(fd, &exe_header, sizeof exe_header) != sizeof exe_header)
X exe_header.exec_magic = 0;
X
X /* if this environment is not the current one, free the old one and use this */
X if(*psp_env != env_segment) {
X free_dos_memory(*psp_env);
X *psp_env = env_segment;
X }
X
X /* copied this code from aztec execv.c */
X argv++; /* skip first arg */
X cp = buffer + 1;
X while(xp = *argv++) {
X *cp++ = ' ';
X while(*xp) {
X if(cp >= buffer + 128)
X goto done;
X *cp++ = *xp++;
X }
X }
X
Xdone:
X *cp = 0xd; /* need this to be dos compatbile -- marty added */
X buffer[0] = cp - (buffer + 1);
X exec(fd, buffer, fcb1, fcb2, &exe_header);
X
X
X
X
X }
X
X return result;
X
X
X
X
X
X
X}
END_OF_FILE
if test 1635 -ne `wc -c <'exec.c'`; then
echo shar: \"'exec.c'\" unpacked with wrong size!
fi
# end of 'exec.c'
fi
if test -f 'execl.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'execl.c'\"
else
echo shar: Extracting \"'execl.c'\" \(157 characters\)
sed "s/^X//" >'execl.c' <<'END_OF_FILE'
X/* $Log: execl.c $
X * Revision 1.1 88/12/04 14:49:14 marty
X * Initialization
X * */
X
Xexecl(file, args)
Xchar *file, *args;
X{
X return execv(file, &args);
X}
END_OF_FILE
if test 157 -ne `wc -c <'execl.c'`; then
echo shar: \"'execl.c'\" unpacked with wrong size!
fi
# end of 'execl.c'
fi
if test -f 'execlp.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'execlp.c'\"
else
echo shar: Extracting \"'execlp.c'\" \(163 characters\)
sed "s/^X//" >'execlp.c' <<'END_OF_FILE'
X/* $Log: execlp.c $
X * Revision 1.1 88/12/04 14:49:16 marty
X * Initialization
X * */
X
Xexeclp(name, args)
Xchar *name, *args;
X{
X
X return execvp(name, &args);
X}
X
END_OF_FILE
if test 163 -ne `wc -c <'execlp.c'`; then
echo shar: \"'execlp.c'\" unpacked with wrong size!
fi
# end of 'execlp.c'
fi
if test -f 'execv.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'execv.c'\"
else
echo shar: Extracting \"'execv.c'\" \(192 characters\)
sed "s/^X//" >'execv.c' <<'END_OF_FILE'
X
X/* $Log: execv.c $
X * Revision 1.1 88/12/04 14:49:18 marty
X * Initialization
X * */
Xextern char **environ;
X
Xexecv(file, argv)
Xchar *file, **argv;
X{
X return execve(file, argv, environ);
X}
X
END_OF_FILE
if test 192 -ne `wc -c <'execv.c'`; then
echo shar: \"'execv.c'\" unpacked with wrong size!
fi
# end of 'execv.c'
fi
if test -f 'execve.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'execve.c'\"
else
echo shar: Extracting \"'execve.c'\" \(4625 characters\)
sed "s/^X//" >'execve.c' <<'END_OF_FILE'
X
X/*
X * Implmentation of execve for ms/Dos.
X * A segment aligned environment is constructed here and passed on.
X *
X * MKS and Aztec argument expansion (in the environment) are also performed here.
X *
X * $Log: execve.c,v $
X * Revision 1.1 89/09/16 12:13:17 marty
X * Initial revision
X *
X *
X * $Id$
X */
X
X#include <errno.h>
X
X
X#define FAR_PTR(seg, off) ((void far *) (((long) seg << 16L) + (long) off))
X
Xextern int use_mks_cmd;
Xextern int use_aztec_cmd;
X
Xstatic char far *next_env_string; /* where to put next environment string */
X
X/* build mks args adds the MKS argument format (~arg) to the environment, startign a
X * next_env_string (which should be the start of the environment).
X */
Xstatic void build_mks_args(argv)
Xchar **argv;
X{
X register char *cp;
X
X if(use_mks_cmd)
X while(*argv) {
X *next_env_string++ = '~';
X cp = *argv++;
X /* copy whole string with NULL terminator */
X while(*next_env_string++ = *cp++)
X ;
X }
X
X}
X
X/* concatenate the arguments into the Aztec format (=arg0 arg1 arg2 ) */
Xstatic void build_aztec_args(argv)
Xchar **argv;
X{
X register char *cp;
X
X if(use_aztec_cmd) {
X *next_env_string++ = '='; /* aztec starts its environment string with '=' */
X
X while(*argv) {
X cp = *argv++;
X while(*cp)
X *next_env_string++ = *cp++;
X *next_env_string++ = ' ';
X }
X *next_env_string++ = '\0';
X }
X}
X
X/* If envp is non-NULL, copy this into the environment, otherwise just copy the default DOS
X * environment (obtained from the PSP)
X */
Xstatic void copy_environment(envp)
Xchar **envp; /* environment */
X{
X if(envp) {
X char *cp;
X
X while(*envp) {
X cp = *envp++;
X while(*next_env_string++ = *cp++)
X ;
X }
X } else {
X /* copy DOS environment from psp */
X char far *psp_environ_start();
X char far *dos_envp = psp_environ_start();
X
X while(*dos_envp)
X while(*next_env_string++ = *dos_envp++)
X ;
X }
X
X}
X
X/* add up size of argc/argv, adding 2 characters to each length if
X * use_mks_cmd is true (i.e. each string is of form ~argument.
X */
Xstatic int compute_mks_arg_size(argv)
Xchar **argv;
X{
X int count = 0;
X
X if(use_mks_cmd) {
X while(*argv)
X count += strlen(*argv++) + 2;
X }
X
X#ifdef PRINT_SIZES
X printf("MKS arg size = %d\n", count);
X#endif
X return count;
X
X}
X
X/* compute size of Aztec command line -- i.e. add 1 space to each argument, 1 for
X * the = and one for the NULL at the end
X */
Xstatic int compute_aztec_arg_size(argv)
Xregister char **argv;
X{
X register int count = 0;
X
X if(use_aztec_cmd) {
X while(*argv)
X count += strlen(*argv++) + 1;
X count++; /* for = */
X }
X
X#ifdef PRINT_SIZES
X printf("aztec size = %d\n", count);
X
X#endif
X return count;
X}
X
X/* compute size of DOS environment -- if environ is NULL use default environment */
Xstatic int compute_dos_environment_size(environ)
Xchar **environ;
X{
X int count = 0;
X
X if(environ) {
X while(*environ)
X count += strlen(*environ++) + 1;
X } else {
X /* look at DOS environment */
X extern char far *psp_environ_start();
X char far *dos_envp = psp_environ_start();
X
X while(*dos_envp) {
X do {
X count++;
X } while(*dos_envp++);
X }
X }
X
X return count;
X
X
X}
X
X
X
X/* all execs trap through hear to exec with an environment --
X * if envp is NULL, use default environment (via psp).
X * Otherwise dynmaically build environment from environ table.
X * Attach MKS and/or Aztec cmd lines as appropriate to the environment
X */
Xint execve(file, argv, envp)
Xchar *file;
Xchar **argv;
Xchar **envp; /* pointer to environ table */
X{
X extern unsigned _PSP;
X unsigned environ_seg; /* segment to use for environment */
X /* need to set environment here and expand AZtec and MKS command strings,
X * also set MKS file name
X */
X
X if(!envp && !use_aztec_cmd && !use_mks_cmd)
X environ_seg = peekw(0x2c, _PSP); /* don't do anything -- just
X * use the incoming environment
X */
X else {
X /* need to construct environment */
X unsigned environ_size;
X int old_strategy;
X
X environ_size = compute_mks_arg_size(argv) + compute_aztec_arg_size(argv) +
X compute_dos_environment_size(envp) + 1;
X
X old_strategy = get_dos_malloc_strategy();
X set_dos_malloc_strategy(2); /* put environment at top of memory */
X
X environ_seg = malloc_dos_memory((environ_size >> 4) + 3); /* round up in clicks */
X set_dos_malloc_strategy(old_strategy);
X
X if(!environ_seg) {
X errno = ENOMEM;
X return -1;
X }
X
X next_env_string = FAR_PTR(environ_seg, 0);
X
X build_mks_args(argv);
X copy_environment(envp);
X build_aztec_args(argv);
X
X *next_env_string = '\0'; /* add NULL to signify end of environment */
X
X }
X
X return setup_exec(file, argv, environ_seg);
X}
X
END_OF_FILE
if test 4625 -ne `wc -c <'execve.c'`; then
echo shar: \"'execve.c'\" unpacked with wrong size!
fi
# end of 'execve.c'
fi
if test -f 'execvp.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'execvp.c'\"
else
echo shar: Extracting \"'execvp.c'\" \(1868 characters\)
sed "s/^X//" >'execvp.c' <<'END_OF_FILE'
X
X
X/* implementation of execvp for ms/dos -- parses off PATH environment
X * variable.
X * $Log: execvp.c $
X * Revision 1.1 88/12/04 14:49:21 marty
X * Initialization
X *
X */
X#define MAX_PATH 128
X
Xstatic try_exec(name, argv)
Xchar *name;
Xchar **argv;
X{
X char buffer[MAX_PATH];
X register char *end;
X char *last_slash, *last_dot;
X
X strcpy(buffer, name);
X end = buffer + strlen(buffer);
X
X last_slash = rindex(buffer, '/');
X last_dot = rindex(buffer, '.');
X
X if( (last_dot && !last_slash) || /* must be extension without path */
X (last_dot && last_slash && last_dot > last_slash)) {
X /* must be extension at end of a path */
X /* see if this is an exec file */
X return execv(buffer, argv);
X }
X
X /* try to exec exe file */
X strcpy(end, ".exe");
X if(!access(buffer, 1))
X return execv(buffer, argv);
X
X /* .exe didn't work, not try com */
X strcpy(end, ".com");
X if(!access(buffer, 1))
X return execv(buffer, argv);
X return -1; /* nothing to exec */
X
X}
X
X
Xexecvp(name, argv)
Xchar *name, **argv;
X{
X char *path;
X char path_buffer[MAX_PATH*4];
X char buffer[MAX_PATH];
X register char *cp; /* component in path currently working on */
X char *tmp;
X
X /* is full path starting at root specified? */
X if(!(path = getenv("PATH")) || *name == '/' || *name == '.' )
X return try_exec(name, argv);
X
X
X /* now try to chop up path */
X strcpy(path_buffer, path);
X path = &path_buffer;
X while(*path) {
X /* get next path entry */
X cp = index(path, ';');
X if(cp) {
X *cp = '\0'; /* null terminate this component */
X strcpy(&buffer, path);
X path = cp + 1; /* get past ';' */
X } else {
X strcpy(&buffer, path);
X *path = 0;
X }
X
X if(buffer[strlen(buffer)] != '/')
X strcat(buffer, "/");
X strcat(buffer, name);
X
X cp = buffer + strlen(buffer); /* cp points to end passed name */
X if(!try_exec(buffer, argv))
X return 0;
X }
X return -1; /* file not found */
X}
END_OF_FILE
if test 1868 -ne `wc -c <'execvp.c'`; then
echo shar: \"'execvp.c'\" unpacked with wrong size!
fi
# end of 'execvp.c'
fi
if test -f 'frkmagic.asm' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'frkmagic.asm'\"
else
echo shar: Extracting \"'frkmagic.asm'\" \(1799 characters\)
sed "s/^X//" >'frkmagic.asm' <<'END_OF_FILE'
X; $Log: frkmagic.asm $
X; Revision 1.1 88/12/04 14:48:26 marty
X; Initialization
X;
Xmod186
Xdataseg segment public para 'data'
X extrn _PSP_:word
X extrn we_have_forked_:word
Xdataseg ends
X extrn setpid_:near
X include lmacros.h
X
X; define a far return
Xretf macro
X db 0cbH
X endm
X
Xold_ss dw ? ; old stack segment
Xold_sp dw ? ; old stack pointer
Xold_ds dw ? ; parents data segment
Xold_es dw ? ; parents stack segment
Xold_bp dw ? ; parent's bp
Xtarget_jmp dw ? ; target jmp address
X dw ?
X
X; forkmagic(child_pid, new_cs, new_ds) sets up childs return
X; address in the PSP at the appropriate
X; point in frkmagic (to return to the parent with the child parent
X; process id.
X; Will execute a far jmp into the new program after pushing the
X; current return address onto the stack
X; needs to ES, DS and SS in code segment before jumping to child
X procdef forkmagic,<<child_pid,word>,<new_cs,word>,<new_ds,word>>
X ; save parents segment registers
X mov cs:old_ss,ss
X mov cs:old_ds,ds
X mov cs:old_es,es
X mov cs:old_sp,sp
X mov cs:old_bp,bp
X
X ; change process ids
X mov ax,child_pid
X push ax
X call setpid_
X pop ax
X
X ; leave return address in bx (so child can jmp [bx])
X mov bx,[bp+2]
X
X mov ax,new_cs
X mov target_jmp+2,ax
X mov ax,offset child_returns
X mov target_jmp,ax
X
X mov ds,new_ds
X pop bp ; bp is already saved
X mov ax,ds
X mov ss,ax
X mov es,ax
X jmp dword ptr [target_jmp]
X
Xchild_returns:
X mov ds:we_have_forked_,1 ; indicate fork occurred
X mov ax,0
X jmp bx
X
X public parent_returns_
Xparent_returns_:
X ; return from child --restore segment registers
X mov ss,cs:old_ss
X mov sp,cs:old_sp
X mov es,cs:old_es
X mov ds,cs:old_ds
X mov bp,cs:old_bp
X
X ; change id back to child
X mov ax,ds:_PSP_
X push ax
X call setpid_
X pop ax
X
X mov ax,child_pid ; return childs id
X pret
X
X
X pend forkmagic
X finish
X end
END_OF_FILE
if test 1799 -ne `wc -c <'frkmagic.asm'`; then
echo shar: \"'frkmagic.asm'\" unpacked with wrong size!
fi
# end of 'frkmagic.asm'
fi
if test -f 'fork.c' -a "${1}" != "-c" ; then
echo shar: Will not clobber existing file \"'fork.c'\"
else
echo shar: Extracting \"'fork.c'\" \(4967 characters\)
sed "s/^X//" >'fork.c' <<'END_OF_FILE'
X
X/* An implementation of fork for ms/dos
X *
X * $Log: fork.c,v $
X * Revision 1.4 89/12/06 07:36:27 marty
X * played with memory allocation strategy
X *
X * Revision 1.3 89/09/22 16:59:12 marty
X * Various modifications.
X *
X * Revision 1.2 89/02/11 01:04:55 marty
X * Took out fprintfs diagnostics
X *
X * Revision 1.1 88/12/04 14:49:26 marty
X * Initialization
X *
X */
X
X#include <stdio.h>
X#include <regs.h>
X
X
X#define FAR_PTR(seg, off) ((void far *) (((long) seg << 16L) + (long) off))
Xextern char **environ;
X
X
Xextern unsigned _PSP, _dsval, _csval;
Xextern far *psp_environ_start();
X
X/* copy the environment from src to dest, removing any special strings
X * along the way.
X * Null environment strings (begin with '=') are for aztec C.
X * Strings of form _= and ~= are for MKS shell
X */
Xcopy_environment(src, dest)
Xchar far *src, *dest;
X{
X /* copy till end of environment */
X while(*src) {
X switch(*src) {
X case '_':
X case '~':
X case '=':
X /* don't copy -- just reach end of string */
X while(*src++)
X ;
X break;
X default:
X /* copy string */
X while(*dest++ = *src++)
X ;
X break;
X }
X }
X *dest = 0; /* second null to mark end of environment */
X}
X
Xstatic unsigned get_environment_size(penv)
Xregister char far *penv;
X{
X register unsigned num_bytes = 0;
X
X while(*penv) {
X while(*penv++)
X num_bytes++;
X num_bytes++; /* null terminator is one byte */
X }
X return num_bytes;
X}
X
X
X
X/* setup_environment() copies the current environment into a block of memory, removing
X * the '=' variable if present to avoid confusion between the parent and child.
X * Returns 0 for error, otherwise click environment is at
X */
Xstatic int setup_environment()
X{
X char far * old_environ; /* environment address in clicks */
X char far * new_environ; /* dos block for where to put environment --
X * owned by parent process?
X */
X unsigned sizeof_environment; /* size in bytes */
X register char *cp;
X int seg;
X int old_strategy;
X
X /* determine orignal environment address */
X
X old_environ = psp_environ_start();
X
X sizeof_environment = get_environment_size(old_environ);
X
X old_strategy = get_dos_malloc_strategy();
X set_dos_malloc_strategy(2); /* put environment on top of memory */
X seg = malloc_dos_memory(sizeof_environment/16 + 4);
X
X set_dos_malloc_strategy(old_strategy);
X
X if(!seg)
X return 0;
X new_environ = FAR_PTR(seg, 0);
X
X copy_environment(old_environ, new_environ);
X
X return seg;
X
X
X}
X
X
Xsetpid(process_id)
Xint process_id;
X{
X REGS regs;
X
X regs.AX = 0x5000;
X regs.BX = process_id;
X if(dos_interrupt(®s)) {
X return -1;
X }
X else return 0;
X
X}
X
Xstatic int make_child_psp(num_clicks)
Xunsigned num_clicks;
X{
X REGS regs;
X
X regs.AX = 0x5500;
X regs.DX = num_clicks;
X if(dos_interrupt(®s)) {
X return -1;
X
X }
X else {
X /* set up block so it belongs to this process */
X pokew(0x1, num_clicks - 1, num_clicks);
X return 0;
X }
X}
X
X
X/* fork for MS/DOS --
X * Returns 0 to child,
X * Returns segment of childs psp to parent or -1 if it fails
X */
Xfork()
X{
X
X int prog_size_clicks;
X unsigned new_csval;
X unsigned new_dsval;
X extern unsigned _mtop, _Cend;
X unsigned child_memory_block; /* first block alloced from dos */
X unsigned second_memory_block; /* should be equal to child_memory_block */
X unsigned parent_pid;
X extern void parent_returns();
X unsigned new_environ_click;
X
X /* check to see if we want to copy psps environment. */
X new_environ_click = setup_environment(); /* make a copy of the environment */
X
X
X /* allocate enough space for current programs code, data segment, stack and
X * psp
X */
X parent_pid = getpid();
X
X prog_size_clicks = 0x10; /* for psp */
X#ifdef COPYCODE
X prog_size_clicks += ((unsigned) &_Cend/16) + 1;
X#endif
X prog_size_clicks += (_mtop/16) + 1;
X if(!(child_memory_block = malloc_dos_memory(prog_size_clicks))) {
X return -1;
X }
X make_child_psp(child_memory_block);
X
X /* copy code and data (stack/bss/data) segments to child */
X#ifdef COPYCODE
X new_csval = child_memory_block + 0x10;
X new_dsval = new_csval + (_dsval - _csval);
X#else
X new_csval = _csval;
X new_dsval = child_memory_block + 0x10;
X#endif
X
X
X#ifdef COPYCODE
X movblock(0, _csval, 0, new_csval, &_Cend);
X#endif
X movblock(0, _dsval, 0, new_dsval, _mtop);
X
X /* patch child's _PSP, _csval, _dsval */
X pokew(&_dsval, new_dsval, new_dsval);
X pokew(&_csval, new_dsval, new_csval);
X pokew(&_PSP, new_dsval, child_memory_block);
X
X
X /* set up terminate address of child to cs:parent_returns */
X pokew(0xa, child_memory_block, &parent_returns);
X pokew(0xc, child_memory_block, _csval);
X if(new_environ_click) {
X /* change ownership of environment block to child */
X pokew(0x1, new_environ_click - 1, child_memory_block);
X pokew(0x2c, child_memory_block, new_environ_click);
X }
X /* does magic to set up fork -- returns child process id or
X * 0, depending on context.
X */
X return forkmagic(child_memory_block, new_csval, new_dsval);
X
X
X
X}
X
X
END_OF_FILE
if test 4967 -ne `wc -c <'fork.c'`; then
echo shar: \"'fork.c'\" unpacked with wrong size!
fi
# end of 'fork.c'
fi
echo shar: End of shell archive.
exit 0
--
marty
leisner@eso.mc.xerox.com leisner.henr801c@xerox.com
Member of the League for Programming Freedom
"I just know I'm a better manager when I have Joe DiMaggio in center field" -- Casey Stengel
Technical Details
Message-ID:
<1993Nov11.142206.19348@spectrum.xerox.com>Path:
arc1-ca.pugleaf.net!gmd.de!newsserver.jvnc.net!yale.edu!spool.mu.edu!howland.reston.ans.net!noc.near.net!das-news.harvard.edu!honeydew.srv.cs.cmu.edu!rochester!rocksanne!draco!leisnerReferences:
<munk.752354944@prles6b>Show Full Headers
Complete Article Headers
X-Google-Language: ENGLISH,ASCII-7-bit X-Google-Thread: 11318c,95c41416cafe7368 X-Google-Attributes: gid11318c,public X-Google-ArrivalTime: 1993-11-11 17:35:36 PST Newsgroups: gnu.gcc.help Path: arc1-ca.pugleaf.net!gmd.de!newsserver.jvnc.net!yale.edu!spool.mu.edu!howland.reston.ans.net!noc.near.net!das-news.harvard.edu!honeydew.srv.cs.cmu.edu!rochester!rocksanne!draco!leisner From: leisner@draco (Marty Leisner 25733) Subject: Re: gcc equivalent of spawn (MSDOS) Message-ID: <1993Nov11.142206.19348@spectrum.xerox.com> Sender: news@spectrum.xerox.com Reply-To: leisner@eso.mc.xerox.com Organization: Xerox X-Newsreader: TIN [version 1.2 PL2] References: <munk.752354944@prles6b> Date: Thu, 11 Nov 1993 14:22:06 GMT Lines: 843