1
/* @(#)ospsystem.c 19.1 (ES0-DMD) 02/25/03 13:56:13 */
2
/*===========================================================================
3
Copyright (C) 1995 European Southern Observatory (ESO)
5
This program is free software; you can redistribute it and/or
6
modify it under the terms of the GNU General Public License as
7
published by the Free Software Foundation; either version 2 of
8
the License, or (at your option) any later version.
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
15
You should have received a copy of the GNU General Public
16
License along with this program; if not, write to the Free
17
Software Foundation, Inc., 675 Massachusetss Ave, Cambridge,
20
Corresponding concerning ESO-MIDAS should be addressed as follows:
21
Internet e-mail: midas@eso.org
22
Postal address: European Southern Observatory
23
Data Management Division
24
Karl-Schwarzschild-Strasse 2
25
D 85748 Garching bei Muenchen
27
===========================================================================*/
29
/*--------------------------ospsystem--------------------------------------
32
.MODULE osp (operating system independant interface, processes)
35
.AUTHOR Trond Melen (IPG-ESO Garching)
37
Execute a command in a shell (The Borne shell under UNIX) or to a
38
command line interpreter (DCL under VMS).
40
VMS: The "system()" C runtime library call could have been used but is
41
not for two reasons. First, it is slow because spawning a process for
42
each command is very expensive under VMS. Second, a command cannot be
43
run in the background (using SPAWN/NOWAIT) since a VMS subprocess dies
46
Instead a subprocess is created when "ospsystem()" is called for the first
47
time, and it is kept alive for the rest of the execution period. To
48
run a process in the background, a SPAWN/NOWAIT command is executed in
49
this subprocess. Therfore, a background command will be aborted if the
50
program that issued the command terminates or the subprocess is
51
otherwise killed. The same holds if the user is logged out.
53
To simulate the behaviour of a UNIX shell, the command string is
54
parsed and the symbols ';', '&', '(' and ')' are given special meanings.
55
Commands separated by semicolons are run in sequence. If a command is
56
followed by an ampersant, its run in the background. Several commands
57
enclosed in paranthesis are treated as one. This is implemented by
58
creating a command file in the SYS$SCRATCH directory holding the simple
59
commands. The command file is executed using the '@file.com' construct.
61
Nesting is not admissable. It is only really usefull for running a
62
sequence of commands in the background, and spawning a process from a
63
spawned process is unsafe since the parent process might die before
64
the child and thereby kill it.
66
The parser is documented in connection with the relevant routines.
69
Error status of the command (UNIX only).
70
----------------------------------------------------------------------*/
74
int ospdebug = 0; /* flag controlling debug printings */
77
/* The fairly complicated VMS solution */
79
#include <descrip.h> /* character descriptor definitions */
80
#include <dvidef.h> /* SYS$GETDVI definitions */
81
#include <iodef.h> /* SYS$QIO definitions */
84
#define COMMAND_LEN 256
87
#define BIT_0(x) ((x) & 1) /* mask returning bit zero (used for
88
checking return status of system calls */
91
/* Type definitions. */
93
typedef struct dsc$descriptor_s descriptor;
95
typedef struct /* structure used by SYS$GETDVI */
97
unsigned short buf_len;
98
unsigned short item_code;
105
long channel; /* channel assigned to mailbox
106
assosiated with the subprocess */
107
long pid; /* process identifier for the subprocess */
110
typedef enum /* tokens for the parser */
111
{T_WORD, T_SEMI, T_LEFT, T_RIGHT, T_AMP, T_EOS, T_ERROR, T_TOOLONG}
115
/* Variables global to ospsystem related code. */
117
static proc_record sub_proc = {0, 0}; /* info about the subprocess */
118
static int sub_proc_created = 0; /* flag, initially false */
119
static int exit_handler_installed = 0; /* flag, initially false */
120
static char ospword[WORD_LEN + 1]; /* value only valid when
121
get_token returnes T_WORD */
124
/* A string handling routine. */
126
static char *strprecat(s1, s2)
127
/* Workes as strcat, but s2 is inserted at the beginning of s1. */
137
to = &s1[l1 + l2 - 1];
138
for (i = 0; i < l1; i++)
147
/* routines to simplify access to VMS services */
149
static descriptor dsc(str)
150
/* Create a descriptor from a string. */
151
/* Returning structures is not portable, but neigther are descriptors. */
152
/* May cause program abortion if str is not a null terminated string. */
157
dsc.dsc$w_length = (strlen(str) <= 256) ? strlen(str) : 256 ;
158
dsc.dsc$a_pointer = str;
159
dsc.dsc$b_class = DSC$K_CLASS_S;
160
dsc.dsc$b_dtype = DSC$K_DTYPE_T;
166
static void error_msg(str, msg_id)
167
/* Print string and error message identified by msg_id. */
176
msg_dsc = dsc(buffer);
177
msg_dsc.dsc$w_length = 256;
178
flags = 0; /* use process default */
179
/* flags = 15; /* get all information available */
181
if (BIT_0(status = SYS$GETMSG(msg_id, &msg_len, &msg_dsc, flags, NULL)))
183
buffer[msg_len] = '\0';
184
fprintf(stderr, "%s: %s\n", str, buffer);
189
"%s: error_msg: SYS$GETMSG failed for msg_id %d\n", str, msg_id);
191
msg_dsc.dsc$w_length = 256;
192
if (BIT_0(status = SYS$GETMSG(status, &msg_len, &msg_dsc, flags, NULL)))
194
buffer[msg_len] = '\0';
195
fprintf(stderr, "error_msg: %s\n", buffer);
199
"error_msg: unable to find out why (status = %d)\n", status);
204
static int write_channel(channel, buffer)
205
/* Write null-terminated string to VMS-type channel. */
206
/* Returns 0 on success, 1 on falure. */
210
long event_flag, function, buf_size;
211
short int iosb[4]; /* not used */
214
event_flag = 0; /* not used */
215
function = IO$_WRITEVBLK | IO$M_NOW; /* write virtual block */
216
buf_size = strlen(buffer);
218
status = SYS$QIOW(event_flag, channel, function, iosb, NULL, 0,
219
buffer, buf_size, 0, 0, 0, 0);
222
error_msg("SYS$QIOW", status);
229
static char *get_dev_name(channel, str, max_len)
230
/* Get name of the device which the channel is assigned to. */
231
/* Returnes str on success, NULL on falure. */
236
/* parameters for SYS$GETDVI */
237
item_dsc item_list[2];
243
item_list[0].buf_len = max_len - 1;
244
item_list[0].item_code = DVI$_DEVNAM;
245
item_list[0].buffer = str;
246
item_list[0].return_len = &str_len;
247
item_list[1].buf_len = 0;
248
item_list[1].item_code = 0;
250
status = SYS$GETDVIW(event_flag, channel, NULL, item_list,
251
NULL, NULL, 0, NULL);
254
error_msg("SYS$GETDVI", status);
263
/* Asyncronous system traps (ASTs) end exit handler (EXH) */
265
static int ready_ast()
266
/* Executes in parent process when subprosses is ready for input, */
267
/* i.e. when previous command has compleated. */
271
status = SYS$WAKE(NULL, NULL);
274
error_msg("SYS$WAKE", status);
275
fprintf(stderr, "osp/ready_ast: cannot recover\n");
282
static long termination_ast()
283
/* Executes in parent process when a subprocess terminates */
287
fprintf(stderr, "[subprocess terminated]\n");
289
sub_proc_created = 0;
291
status = SYS$WAKE(NULL, NULL);
292
if (!BIT_0(status)) error_msg("SYS$WAKE", status);
298
static int cleanup_exh()
299
/* Kill the subprocess if one has been created. */
300
/* Executes in parent process when this is exiting. */
304
if (sub_proc_created)
306
status = SYS$DELPRC(&sub_proc.pid, NULL);
307
if (!BIT_0(status)) error_msg("SYS$DELPRC", status);
314
static int install_exit_handler()
315
/* Install the exithandler and set the assosiated flag */
317
static long control_block[4];
318
static long condition_value;
321
control_block[1] = (long) cleanup_exh;
322
control_block[2] = 1;
323
control_block[3] = (long) &condition_value;
325
status = SYS$DCLEXH(control_block);
328
error_msg("SYS$DCLEXH", status);
332
exit_handler_installed = 1;
338
/* Routines to create and initialize the subprocess. */
340
static int create_sub_proc()
341
/* Create a subprocess and let it take its input from a mailbox. */
342
/* Returns 0 on success, 1 on falure. */
344
char mbx_dev_name[10];
347
/* Parameters for SYS$CREMBX. */
350
/* Parameters for LIB$SPAWN. */
352
long flags, spawned_pid, (* c_ast)(), c_ast_param;
355
status = SYS$CREMBX(permanent_flag, &mbx_channel, max_msg_size,
356
buffer_size, protection_mask, access_mode, &mbx_name); */
358
fprintf(stderr, "osp/create_sub_proc: creating mailbox\n");
359
status = SYS$CREMBX(0, &mbx_chan, 0, 0, 0, 0, 0);
362
error_msg("SYS$CREMBX", status);
366
/* Get devive name of mailbox. */
367
if (ospdebug) fprintf(stderr,
368
"osp/create_sub_proc: asking system for mailbox' device name\n");
369
(void) get_dev_name(mbx_chan, mbx_dev_name, sizeof(mbx_dev_name));
371
/* Spawn a process, not waiting for it to complete.
372
status = LIB$SPAWN(&command_str, &in_file, &out_file, &flags, &proc_name,
373
&spawned_pid, &c_status, &c_efn, c_ast, c_ast_param); */
375
fprintf(stderr, "osp/create_sub_proc: spawning a subprocess\n");
376
in_file = dsc(mbx_dev_name); /* take input from the mailbox */
377
flags = 1; /* NOWAIT */
378
c_ast = termination_ast; /* rutine to be run upon completion */
379
status = LIB$SPAWN(0, &in_file, 0, &flags, 0, &spawned_pid, 0, 0, c_ast, 0);
382
error_msg("LIB$SPAWN", status);
386
/* Initialize process record. */
387
sub_proc.channel = mbx_chan;
388
sub_proc.pid = spawned_pid;
390
/* Print information about the subprocess. */
394
"osp/create_sub_proc: Process identifier %d\n", sub_proc.pid);
396
"osp/create_sub_proc: Channel number %d\n", sub_proc.channel);
402
static int init_sub_proc()
403
/* Initialize subprocess using DCL commands */
404
/* Returns 0 on success, 1 on falure. */
407
char user_terminal[10], command[80];
411
/* Get channel assigned to mailbox associated with subprocess. */
412
channel = sub_proc.channel;
414
/* Assign terminal device name to I/O logical names. */
416
write_channel(channel, "SET NOVERIFY") ||
417
write_channel(channel, "SET NOON") ||
418
write_channel(channel,
419
"DEFINE/NOLOG TT 'F$TRNLNM(\"SYS$OUTPUT\")") ||
420
write_channel(channel,
421
"DEFINE/NOLOG SYS$COMMAND 'F$TRNLNM(\"SYS$OUTPUT\")") ||
422
write_channel(channel,
423
"DEFINE/NOLOG SYS$INPUT 'F$TRNLNM(\"SYS$OUTPUT\")") ||
424
write_channel(channel,
425
"DEFINE/NOLOG FOR$INPUT 'F$TRNLNM(\"SYS$OUTPUT\")"));
431
static TOKEN get_token(string, pos_ptr, look_for_T_RIGHT)
432
/* Return the token at current position and */
433
/* increment position to the beginning of next token. */
434
/* If token is T_WORD, ospword will hold the characters. */
435
/* '(' is only interpreted as a token at the beginning */
436
/* of a line or after a ';' or a '&'. ')' is only */
437
/* interpreted if the flag look_for_T_RIGHT is set. */
439
char *string; /* string to be parsed */
440
int *pos_ptr; /* pointer to variable holding position within string */
441
int look_for_T_RIGHT; /* 1 if ')' should be interpreted as a token, else 0 */
443
enum {NEUTRAL, INWORD} state = NEUTRAL;
447
while (i < sizeof(ospword))
449
ch = string[(*pos_ptr)++];
460
prev_ch = string[(*pos_ptr) - 1];
461
if (prev_ch == ';' || prev_ch == '&')
470
if (look_for_T_RIGHT)
502
if (look_for_T_RIGHT)
523
static TOKEN make_simple_command(look_ahead, string,
524
pos_ptr, dcl_command, look_for_T_RIGHT)
525
/*------------------------------------------------------------------
526
Make_simple_command builds a command using tokens from string
527
and places it in dcl_command.
528
The routine is called with look_ahead holding the first
529
token of the simple command, which must be T_WORD. This
530
word and the following are collected and copied into dcl_command.
531
If look_ahead is not T_WORD, dcl_command becomes the null string.
532
Make_simple_command returnes the first token not used in building
534
---------------------------------------------------------------------*/
535
TOKEN look_ahead; /* first token to be used */
536
char string[]; /* the string being parsed */
537
int *pos_ptr; /* ponter to integer holding
538
current position whithin string */
539
char dcl_command[]; /* string to hold the command being build */
540
int look_for_T_RIGHT; /* 1 if ')' should be interpreted as a token, else 0 */
542
dcl_command[0] = '\0';
544
/* Check for empty command. */
545
if (look_ahead != T_WORD) return(look_ahead);
547
/* loop over the next tokens */
553
if ((strlen(dcl_command) + strlen(ospword) + 1) > COMMAND_LEN)
555
strcat(dcl_command, ospword);
556
strcat(dcl_command, " ");
557
look_ahead = get_token(string, pos_ptr, look_for_T_RIGHT);
559
default: /* token was a command terminator */
566
static TOKEN make_command(look_ahead, string, pos_ptr,
568
/*-------------------------------------------------------------------
569
Make_command builds a command using tokens from string
570
and places it in dcl_command.
571
The routine is called with look_ahead holding the first
572
token of the command.
573
* If look_ahead is T_WORD, the simple exercice of building
574
the command is left to make_simple command.
575
* If look_ahead is T_LEFT, a command file is created and the
576
commands enclosed in paranthesis are included in the file.
577
* If look_ahead is a command separator or terminator, an
578
empty command is build.
579
* For other values of look_ahead som error handling is performed.
580
Some effords are made to avoid creating files for a single or empty
582
Make_command returnes the first token not used building dcl_command.
583
dcl_command will hold a simple command, or the command to run the
585
---------------------------------------------------------------------*/
586
TOKEN look_ahead; /* first token to be used */
587
char string[]; /* the string being parsed */
588
int *pos_ptr; /* ponter to integer holding
589
current position whithin string */
590
char dcl_command[]; /* string to hold the command being build */
591
int action; /* flag, if 0 only check syntax,
592
else create the necessary files */
594
FILE *fd; /* file descriptor of file created */
595
char file_name[64]; /* name of file created */
596
int com_file_created = 0; /* flag, initially false */
597
int look_for_T_RIGHT = 0; /* flag, initially false */
598
char last_dcl[COMMAND_LEN + 1]; /* temporary storage for dcl_command */
603
/* switch on first token of the command */
609
dcl_command[0] = '\0';
612
look_ahead = make_simple_command(look_ahead, string, pos_ptr,
613
dcl_command, look_for_T_RIGHT);
616
fprintf(stderr, "Unexpected ')'\n");
622
/* create a com-file to execute commands in paranthesis in sequence */
623
look_for_T_RIGHT = 1;
625
/* not all tokens are admissable after T_LEFT */
626
look_ahead = get_token(string, pos_ptr, look_for_T_RIGHT);
630
dcl_command[0] = '\0';
631
look_for_T_RIGHT = 0;
632
look_ahead = get_token(string, pos_ptr, look_for_T_RIGHT);
635
fprintf(stderr, "Nested paranthesis not allowed\n");
638
fprintf(stderr, "Unexpexted '&'\n");
641
fprintf(stderr, "Unbalanced parenthesis\n");
646
default: /* T_WORD or T_SEMI */
650
/* loop over commands to be included in command file */
653
look_ahead = make_simple_command(look_ahead, string, pos_ptr,
654
dcl_command, look_for_T_RIGHT);
656
/* switch on token following the command */
660
fprintf(stderr, "osp/parser: BUG! Error in parser\n");
663
fprintf(stderr, "Nested paranthesis not allowed\n");
666
fprintf(stderr, "'&' not allowed in paranthesis\n");
669
fprintf(stderr, "Unbalanced parenthesis\n");
676
if (strlen(dcl_command) > 0)
678
/* if this is not the first nonempty command */
679
if (strlen(last_dcl) > 0)
681
/* if command file not yet created, do it */
682
if (!com_file_created)
684
sprintf(file_name, "sys$scratch:midas_%s.com",
688
fd = fopen(file_name, "w");
689
fprintf(fd, "$ SET NOON\n");
691
com_file_created = 1; /* true */
694
/* Write last command to file */
696
fprintf(fd, "$ %s\n", last_dcl);
699
/* copy dcl_commnad to last_dcl */
700
strcpy(last_dcl, dcl_command);
703
/* if command was terminated with ';', build next command */
704
if (look_ahead == T_SEMI)
706
/* get the next token and loop to next command */
707
look_ahead = get_token(string, pos_ptr,look_for_T_RIGHT);
711
/* command was terminated with ')', close the com-file */
712
if (com_file_created)
716
fprintf(fd, "$ %s\n", last_dcl);
718
"$ IF f$process() .nes. f$getjpi(\"%x\", \"prcnam\") THEN \
719
WRITE sys$error \"Process \" + f$process() + \" terminated\"\n", sub_proc.pid);
720
fprintf(fd, "$ DELETE/NOLOG %s;\n", file_name); */
723
sprintf(dcl_command, "@%s", file_name);
726
strcpy(dcl_command, last_dcl);
727
/* get the next token */
728
look_for_T_RIGHT = 0;
729
look_ahead = get_token(string, pos_ptr, look_for_T_RIGHT);
737
static int run_command(command, action)
738
/*----------------------------------------------------------------------
739
run_command execute the command(s) in the command string if the flag
740
action is set. It the flag is not set, only syntax checking will be
741
performed. Commands are separated with ';' or '&'. If a command
742
is terminated with an '&', dcl_command is concatenated with the string
743
"SPAWN/NOWAIT " so that it will be run in the background.
744
run_command retuns 1 on error, 0 if command was ok.
745
----------------------------------------------------------------------*/
746
char command[]; /* the command to be parsed and executed */
747
int action; /* flag, if 0 only check syntax,
748
else create the necessary files */
750
TOKEN look_ahead; /* first token not yet used */
751
char dcl_command[COMMAND_LEN + 1]; /* string to hold the
752
command being build */
753
int pos = 0; /* current position whithin string */
755
/* Get first token in command. */
756
look_ahead = get_token(command, &pos, 0);
758
/* loop over commands separated by semicolons or ampersent */
761
look_ahead = make_command(look_ahead, command, &pos, dcl_command,action);
765
if (action && strlen(dcl_command) > 0)
767
write_channel(sub_proc.channel, dcl_command);
768
write_channel(sub_proc.channel,
769
"DEFINE/NOLOG SYS$INPUT 'F$TRNLNM(\"TT\")");
771
look_ahead = get_token(command, &pos, 0);
774
if (strlen(dcl_command) > 0)
775
if (strlen("SPAWN/NOWAIT/NOLOG ") +
776
strlen(dcl_command) > COMMAND_LEN)
778
fprintf(stderr, "Token or command too long\n");
783
strprecat(dcl_command, "SPAWN/NOWAIT/NOLOG ");
785
write_channel(sub_proc.channel, dcl_command);
787
look_ahead = get_token(command, &pos, 0);
790
if (action && strlen(dcl_command) > 0)
792
write_channel(sub_proc.channel, dcl_command);
793
write_channel(sub_proc.channel,
794
"DEFINE/NOLOG SYS$INPUT 'F$TRNLNM(\"TT\")");
798
fprintf(stderr, "Unexpected left parenthesis\n");
801
fprintf(stderr, "Unexpected right parenthesis\n");
804
fprintf(stderr, "';' expected\n");
809
fprintf(stderr, "Token or command too long\n");
816
/* The entrypoint for the VMS implementation */
818
long ospsystem(command)
819
/* Routine simulating the UNIX "system()" library call under VMS. */
820
char *command; /* command to be executed */
822
/* parameters for SYS$QIOW */
823
long event_flag, channel, function, acc_mode, status;
825
/* check the command syntax */
826
if (run_command(command, 0)) /* if syntax error */
829
/* If subprocess not yet created, do it. */
830
/* Should ask system for the existance of the process, but ... */
831
if (!sub_proc_created)
834
fprintf(stderr, "ospsystem: Creating a subprocess\n");
835
if (create_sub_proc() != 0)
837
fprintf(stderr, "Unable to create subprocess\n");
841
/* Initialize subprocess. */
843
fprintf(stderr, "osp/create_sub_proc: initializing subprocess\n");
845
printf(stderr, "Subprocess may not be properly initialized\n");
847
/* Intstall exit handler. */
849
fprintf(stderr, "osp/create_sub_proc: installing exit handler\n");
850
if (!exit_handler_installed && install_exit_handler())
851
printf(stderr, "Subprocess may not be deleted on exit\n");
854
fprintf(stderr, "ospsystem: Subprocess created\n");
855
sub_proc_created = 1; /* true */
858
/* Run the command in the subprocess. */
859
(void) run_command(command, 1);
861
/* enable mailbox to run ready_ast when dcl command has compleated */
862
event_flag = 0; /* not used */
863
channel = sub_proc.channel; /* channel assigned to mailbox */
864
function = IO$_SETMODE | IO$M_READATTN; /* read attention ast */
866
status = SYS$QIOW(event_flag, channel, function, NULL,
867
NULL, 0, ready_ast, NULL, 0, 0, 0, 0);
870
error_msg("SYS$QIOW", status);
872
"Unable to enable ready_ast, skipping the hibernation\n");
876
/* wait for dcl command to complete, ready_ast will wake us up */
878
fprintf(stderr, "ospsystem: caller is hibernated\n");
880
status = SYS$HIBER();
883
error_msg("SYS$HIBER", status);
884
fprintf(stderr, "Hibernation failed\n");
888
fprintf(stderr, "ospsystem: Hibernated caller waked up\n");