/* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** Copyright (C) 1993 by ** DIGITAL EQUIPMENT CORPORATION, Maynard, MA. ** ** This software is furnished under a license and may be used and copied ** only in accordance with the terms of such license and with the inclusion ** of the above copyright notice. This software or any other copies there- ** of may not be provided or otherwise made available to any other person. ** No title to and ownership of the software is hereby transferred. ** ** The information in this software is subject to change without notice ** and should not be construed as a commitment by DIGITAL EQUIPMENT ** CORPORATION. ** ** DIGITAL assumes no responsibility for the use or reliability of its ** software on equipment which is not supplied by DIGITAL. **------------------------------------------------------------------------ */ /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FACILITY: DISKMOUNT ** ** MODULE DESCRIPTION: ** ** Diskmount parent process. This module reads data from the data ** file, formats the data, creates subprocesses, sends disk mount ** data to the subprocesses, and reads status messages back from ** each subprocess. ** ** AUTHORS: ** ** Philip Dickerson (with much assistance from the VAXC Notes ** conference and individuals within Digital) ** ** CREATION DATE: March, 1992 ** ** MODIFICATION HISTORY: ** ** March,1992 P. Dickerson ** ** X-4 Joseph A. Dziedzic 21-Sep-1993 ** Final cleanup of comments, correct database file record ** format descriptions, etc. ** ** X-3 Joseph A. Dziedzic 6-Jul-1993 ** Cleaned up comments, etc. ** ** X-2 Joseph Dziedzic 24-May-1993 ** Rename main program entry point to eliminate confusion ** in UTIL32.OLB. ** **------------------------------------------------------------------------ */ /* ** ** Prototype Diskmount utility data file ** ------------------------------------- ** Data file will contain: ** - (anywhere in file) comment lines indicated by "!" ** - (starting with the first "*" delimiter), multi-line entries ** for each disk containing one item per line in order ** (missing optional items still need a line containing "-") ** [It is intended that each multi-line entry for mounting a disk may ** contain additional lines of data (delimited in some way, such as "\") ** that would be ignored by the diskmount program, but could be used by ** another program (such as one to schedule backups) that reads the ** same data file.] ** ** The actions of this program for any disk will be controlled by several ** items on the first line of data for the disk, as follows: ** &Disk_name:[M,D,R][.Class][.Condition] ** - Disk_name is a unique "name" for the disk entry. ** - M-mount, D-dismount, R-reserve {M is currently the only valid choice} ** - Class is a user-defined "category" of disks (such as system, work-disks, ** pagedisk, critical, phase-I, etc, etc), which can be used by telling ** the program to mount all disks with a specific class. This could be ** used, for example, to mount different groups of disks at different ** stages of system startup. ** - Condition is an optional test (such as whether this node is a CI node ** or a workstation) used to determine whether or not to mount this ** particular disk on this system. (Not yet implemented.) ** ** The format of each entry is as follows: ** (items in [] are optional) ** &Disk_name:[M,D,R][.class][.condition] !comments allowed here ** Disk-data !comments allowed here ** DSA2005($11$DUAn,...) ** DAD|ESS_ZK34_COM ** Label !comments allowed here ** may be "*" or "Start-of-label*" ** [Logical ] !comments allowed here ** [Additional-logicals] !comments allowed here ** [Preferred-path ] !comments allowed here ** [Qualifiers ] !comments allowed here ** [DFS_access_point] !comments allowed here ** \secondary data (such as backup schedule for this disk) ** ** Examples of disk entries in data file: ** -------------------------------------- ** &NOTESD:M.Critical.CI_NODE: !Notes disk (mount on CI node only) ** DSA2005($11$dua111,$11$dua112) !Shadow, and physical device names ** notes_disk !Label ** NOTESD$ !Logical ** - !additional logicals ** HSC08 !Preferred path ** rebuild !other qualifiers ** DEC:.XXX.YYY.ZZZ.STAR_NOTESD$ !DFS access point (served on net) ** \SLS data (ignored by diskmount utility) ** **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** ** INCLUDE FILES ** **------------------------------------------------------------------------ */ #include "src$:diskmount.h" /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** ** GLOBAL DECLARATIONS ** **------------------------------------------------------------------------ */ /* in_ variables are for INput TO subprocess */ /* out_ variables are for OUTput FROM subprocess */ unsigned short in_mbx_chan[MAX_SUBPROCESSES];/* mbx channel numbers */ unsigned short out_mbx_chan[MAX_SUBPROCESSES]; unsigned int status, /* return status(es) */ subprocess_pid, proc_nbr, curr_nbr, max_subp; QIO_SB in_mbx_iosb[MAX_SUBPROCESSES]; /* I/O status block for QIO */ QIO_SB out_mbx_iosb[MAX_SUBPROCESSES]; /* I/O status block for QIO */ OUT_QIO_BUFFER output_data[MAX_SUBPROCESSES]; unsigned short efn[MAX_SUBPROCESSES]; /* Event flag number for subprocess */ char input_data[IN_MBX_MAXMSG]; char severity; int quiet; #define MAX_LINE 512 /* Maximum line length in data file */ /* Function prototype definitions */ void help_syntax (); /* Display syntax on -help */ void display_output_msg (); /* Output message from subprocess */ long edit_collapse (char *buff); /* Collapse character string */ int queue_qio (); /* Write & queue read QIO to subproc */ int create_subp (); /* Create subprocess */ /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FUNCTIONAL DESCRIPTION: ** ** Main function ... ** ** FORMAL PARAMETERS: ** ** ** RETURN VALUE: ** ** **------------------------------------------------------------------------ */ diskmount_main (int argc, char **argv[]) MAIN_PROGRAM { FILE * fp, *fopen (); int efn_mask, curr_mask, i; int arg_count; unsigned char r_name[64], r_action[9], r_class[64], r_cond[64], class[64], action[64], line[MAX_LINE], rec[IN_MBX_MAXMSG]; /* for command string */ char *endpos; char space[] = { ' ', 0 }; char ign_char[] = { '\n', '!', '\\', 0 }; char entry_tag = '&'; /* delimiter for disk entry */ /* Print some informational messages */ printf (COPYRIGHT_STR); printf (VERSION_STR); printf ("\nDISKMOUNT-I-Image: %s\n\n", argv[0]); /* Initialize some variables */ line[0] = '\0'; rec[0] = '\0'; proc_nbr = 0; curr_nbr = 0; efn_mask = 0; quiet = 0; max_subp = MAX_SUBPROCESSES; class[0] = '\0'; action[0] = '\0'; /* Command line arguments: example - class="category" would only mount those disks marked with that class. */ /* Obtain command line parameters */ for (arg_count = 1; arg_count < argc; arg_count++) { if ((strcmp ("-class", argv[arg_count]) == 0) && (arg_count < (argc - 1))) strcpy (class, argv[++arg_count]); else if ((strcmp ("-action", argv[arg_count]) == 0) && (arg_count < (argc - 1))) strcpy (action, argv[++arg_count]); else if (strcmp ("-help", argv[arg_count]) == 0) help_syntax (); else if (strcmp ("-quiet", argv[arg_count]) == 0) quiet = 1; } /* UPCASE class and action fields for later comparisons */ for (i = 0; class[i]; i++) class[i] = _toupper (class[i]); for (i = 0; action[i]; i++) action[i] = _toupper (action[i]); /* Debugging aid printf ("Class is: %s\n", class); printf ("Action is: %s\n", action); */ /* Open data file */ if ((fp = fopen (DATA_FILE, "r")) == NULL) { fprintf (stderr, "Can't open data file: %s\n", DATA_FILE); exit (1); } /* Process initial entries in data file. */ /* Currently, just print entries which are not recognized. */ do { fgets (line, MAX_LINE, fp); if (line[0] != '!' && line[0] != entry_tag) printf (" ***** Initial entry: %s \n", line); } while (line[0] != entry_tag); /* At this point, have read first line from file starting with delimiter */ /* Outer loop, reading until end-of-file */ do { /* Initialize strings each time through outer loop, in case some of these fields are not supplied in record read from file */ rec[0] = '\0'; r_name[0] = '\0'; r_action[0] = '\0'; r_class[0] = '\0'; r_cond[0] = '\0'; /* Inner loop, reading until next delimiter */ do { /* Remove trailing comments and/or newline character. Also remove non-diskmount entries (starting with "\"). There may not be a newline char if record was longer than 256 bytes. No recovery for this case - characters beyond 256 are lost! */ /* Remove white space and UPCASE */ if (endpos = strpbrk (line, ign_char)) *endpos = '\0'; edit_collapse (line); for (i = 0; line[i]; i++) line[i] = _toupper (line[i]); /* If this line has data, add this line to complete string, and add space character as separator */ if (line[0] != '\0') { if (line[0] == entry_tag) /* Separate individual items from first record */ { sscanf (line, "%*c%[^:]%*c%[^.]%*c%[^.]%*c%[^.]", r_name, r_action, r_class, r_cond); strcat (rec, r_name); } else { strcat (rec, line); } /* Check for continuation character */ if (rec[strlen (rec) - 1] == '+') rec[strlen (rec) - 1] = '\0'; else strcat (rec, space); } fgets (line, MAX_LINE, fp); } while (line[0] != entry_tag && line[0] != NULL); /* END-inner loop */ /* Now have one complete entry for a disk - process it, check conditions, and if appropriate, spawn the mount */ strcpy (input_data, "3 \0"); strcat (input_data, rec); /* If class specified on command line, check whether it matches this record's class field */ if ((class[0] != '\0') && (strcmp (class, r_class) != 0)) { printf ("Class field NOT matched - no mount for %s\n", r_name); } else { if (proc_nbr < max_subp) { output_data[proc_nbr].buffer[0] = STS$M_SEVERITY; status = create_subp (); if (status & STS$M_SUCCESS) { proc_nbr++; sys$setef (proc_nbr); efn_mask = efn_mask | (1 << proc_nbr); } else { printf ("Subprocess creation failed - STATUS: %x\n\n", status); max_subp = proc_nbr; } if (proc_nbr == 0) { printf ("DISKMOUNT-F-Unable to create any subprocesses"); exit (4); } } /* Now wait for the event flag associated with some subprocess to set; this will be true immediately after creating the subprocess, or when I/O has been received from the subprocess. At that point display any output message from the subprocess, and send another mount request. */ sys$wflor (1, efn_mask); sys$readef (1, &curr_mask); for (curr_nbr = 0; ( !(1 << (curr_nbr+1) & curr_mask)); curr_nbr++) ; /* Null body */ sys$clref (curr_nbr + 1); display_output_msg (); if ( !(quiet) ) printf ( "DISKMOUNT-I-Sending mount request for %s to subprocess %d\n", r_name, curr_nbr); status = queue_qio (); if ( !(status & STS$M_SUCCESS) ) { printf ( "DISKMOUNT-F-Process communication failed - STATUS: %x\n\n", status); } } } while (line[0] != NULL); /* END-outer loop */ /* Finished reading data from file - close file */ fclose (fp); /* Need to wait at this point until all subprocesses have completed */ if (efn_mask != 0) { sys$wfland (1, efn_mask); for (curr_nbr = 0; curr_nbr < proc_nbr; curr_nbr++) { /* Debugging aid printf ("IOSB: %x, Status: %s\n", out_mbx_iosb[curr_nbr].status,output_data[curr_nbr].buffer); */ /* Display output message from subprocess */ display_output_msg (); /* Write EOF to subprocess mailbox */ status = sys$qiow ( 0, /* EFN */ in_mbx_chan[curr_nbr], /* I/O channel */ IO$_WRITEOF | IO$M_NOW, /* I/O function */ &in_mbx_iosb[curr_nbr], /* I/O status block */ 0, /* Completion AST routine */ 0, /* Completion AST parameter */ 0, /* P1 - buffer address */ 0, /* P2 - buffer length */ 0, /* P3 - Not used */ 0, /* P4 - Not used */ 0, /* P5 - Not used */ 0); /* P6 - Not used */ status = sys$qio ( (curr_nbr + 1), /* EFN */ out_mbx_chan[curr_nbr],/* I/O channel */ IO$_READVBLK, /* I/O function */ &out_mbx_iosb[curr_nbr],/* I/O status block */ 0, /* Completion AST routine */ 0, /* Completion AST parameter */ output_data[curr_nbr].buffer,/* P1 - buffer address */ sizeof (output_data[curr_nbr].buffer),/* P2 - buffer length */ 0, /* P3 - Not used */ 0, /* P4 - Not used */ 0, /* P5 - Not used */ 0); /* P6 - Not used */ } printf ("\n\nDISKMOUNT-I-WAIT Waiting for all subprocesses to complete\n"); sys$wfland (1, efn_mask); for (curr_nbr = 0; curr_nbr < proc_nbr; curr_nbr++) { severity = output_data[curr_nbr].buffer[0]; if (severity != STS$M_SEVERITY) { if (!(quiet) || (severity != STS$K_SUCCESS)) { output_data[curr_nbr].buffer[0] = ' '; printf ("Subprocess number %d%s\n", curr_nbr, output_data[curr_nbr].buffer); } } } } else printf ("No subprocesses were created\n"); } /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FUNCTIONAL DESCRIPTION: ** ** Function help_syntax displays several lines of text ** that show the available options for the module. ** ** FORMAL PARAMETERS: ** ** None ** ** RETURN VALUE: ** ** None - EXITS program at end of function. ** **------------------------------------------------------------------------ */ void help_syntax () { fprintf (stderr, "\n Command usage is:\n"); fprintf (stderr, " [-help] Display this help\n"); fprintf (stderr, " [-class classname] Mount devices with this class\n"); fprintf (stderr, " [-quiet] Inhibit SUCCESS messages\n"); fprintf (stderr, "\n"); fprintf (stderr, "Following are NOT YET implemented .......... \n"); fprintf (stderr, "-------------------------------------------- \n"); fprintf (stderr, "Current action is to SET, FORCE, and MOUNT \n"); fprintf (stderr, " [-name diskname] Mount device with this name\n"); fprintf (stderr, " [-list] List devices in data file\n"); fprintf (stderr, " [-find] Find a device in data file\n"); fprintf (stderr, " [-action [mount] Mount device\n"); fprintf (stderr, " [set] Set preferred path\n"); fprintf (stderr, " [force] Force use of preferred path\n"); fprintf (stderr, " [rebuild] Perform rebuild\n"); fprintf (stderr, "\n"); /* Future possibilities follow */ /* fprintf (stderr, " [?remount?]\n"); fprintf (stderr, " [?]\n"); fprintf (stderr, " [-format] ???\n"); fprintf (stderr, " ???\n"); fprintf (stderr, "\n"); */ /* EXIT program from help - do not perform any action other than displaying help */ exit (1); } /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FUNCTIONAL DESCRIPTION: ** ** Function display_output_msg displays any message which has ** returned from a subprocess. ** ** FORMAL PARAMETERS: ** ** None ** ** RETURN VALUE: ** ** None ** **------------------------------------------------------------------------ */ void display_output_msg () { /* Severity is returned as a single character in the first byte of the data message returned from the subprocess, using the standard VAX severity codes. STS$M_SEVERITY is returned if no message has been returned. */ severity = output_data[curr_nbr].buffer[0]; if (severity != STS$M_SEVERITY) { if (!(quiet) || (severity != STS$K_SUCCESS)) { output_data[curr_nbr].buffer[0] = ' '; switch (severity) { case STS$K_WARNING: printf ("\nDISKMOUNT-W-WARNING, Warnings for device: "); break; case STS$K_SUCCESS: printf ("\nDISKMOUNT-S-SUCCESS, Successful mount of device: "); break; case STS$K_ERROR: printf ("\nDISKMOUNT-E-ERROR, Errors for device: "); break; case STS$K_INFO: printf ("\nDISKMOUNT-I-INFO, Information for device: "); break; case STS$K_SEVERE: printf ("\nDISKMOUNT-F-FATAL, Fatal Errors for device: "); break; } printf (" %s", output_data[curr_nbr].buffer); } } } /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FUNCTIONAL DESCRIPTION: ** ** Function edit_collapse removes leading, trailing, and ** embedded white space from string. This function was ** obtained from a note in the VAXC Notes conference. ** ** FORMAL PARAMETERS: ** ** RETURN VALUE: ** **------------------------------------------------------------------------ */ long edit_collapse (char *buff) { long wpos; /* white position */ long xpos; /* non-white position */ char white[] = /* white space chars */ { '\r', '\n', '\t', ' ', '\0' }; char temp[MAX_LINE]; /* working storage */ strcpy (temp, buff); /* move to working */ xpos = strspn (temp, white); /* look for non-white */ strcpy (temp, &temp[xpos]); /* shift left */ wpos = strcspn (temp, white); /* find first white */ while (wpos < strlen (temp)) /* begin loop */ { xpos = strspn (&temp[wpos], white); /* look for non-white */ xpos += wpos; /* add offset value */ strcpy (&temp[wpos], &temp[xpos]); /* shift left */ wpos = strcspn (temp, white); /* find next white */ } strcpy (buff, temp); /* move to return */ } /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FUNCTIONAL DESCRIPTION: ** ** Function queue_qio submits a write QIO to the specified subprocess ** and queues a read QIO to the same subprocess with an event-flag ** to be set when the read QIO completes. The write QIO is performed ** as a QIOW with the IO$M_NOW modifier, so that this function waits ** until the QIO is successfully queued, but doesn't wait for the ** subprocess to read the QIO. ** ** FORMAL PARAMETERS: ** ** RETURN VALUE: ** ** **------------------------------------------------------------------------ */ int queue_qio () { /* Send input data to subprocess */ status = sys$qiow ( 0, /* EFN */ in_mbx_chan[curr_nbr], /* I/O channel */ IO$_WRITEVBLK | IO$M_NOW,/* I/O function */ &in_mbx_iosb[curr_nbr], /* I/O status block */ 0, /* Completion AST routine */ 0, /* Completion AST parameter */ input_data, /* P1 - buffer address */ sizeof (input_data), /* P2 - buffer length */ 0, /* P3 - Not used */ 0, /* P4 - Not used */ 0, /* P5 - Not used */ 0); /* P6 - Not used */ if ( !(status & STS$M_SUCCESS) ) { sys$dassgn (in_mbx_chan[curr_nbr]); sys$dassgn (out_mbx_chan[curr_nbr]); return status; } /* Read data back from subprocess */ status = sys$qio ( (curr_nbr + 1), /* EFN */ out_mbx_chan[curr_nbr], /* I/O channel */ IO$_READVBLK, /* I/O function */ &out_mbx_iosb[curr_nbr],/* I/O status block */ 0, /* Completion AST routine */ 0, /* Completion AST parameter */ output_data[curr_nbr].buffer,/* P1 - buffer address */ sizeof (output_data[curr_nbr].buffer),/* P2 - buffer length */ 0, /* P3 - Not used */ 0, /* P4 - Not used */ 0, /* P5 - Not used */ 0); /* P6 - Not used */ if ( !(status & STS$M_SUCCESS) ) { sys$dassgn (in_mbx_chan[curr_nbr]); sys$dassgn (out_mbx_chan[curr_nbr]); return status; } return status; } /* **++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ** FUNCTIONAL DESCRIPTION: ** ** Function create_subp creates the next subprocess (if possible) ** and also creates mailboxes for inter-process communication ** and assigns channels to the mailboxes. Each subprocess deducts ** quotas (in particular, BYTLM) from the parent process' quotas. ** ** FORMAL PARAMETERS: ** ** ** RETURN VALUE: ** ** **------------------------------------------------------------------------ */ int create_subp () { ITMDSC item_list[2]; struct dsc$descriptor_s in_mbxnam_desc; struct dsc$descriptor_s out_mbxnam_desc; struct dsc$descriptor_s command_desc; char command_buf[] = { "RUN SYS$MANAGER:DISKMOUNT_CHILD" }; unsigned int spawn_flag = CLI$M_NOWAIT; char in_mbxnam_buf[32]; /* Receives input mailbox name */ char out_mbxnam_buf[32]; /* Receives output mailbox name */ DSC_INIT_BUF (command_desc, command_buf); printf ("DISKMOUNT-I-Creating subprocess number: %d\n", proc_nbr); /* Create the mailboxes to send/receive information to the subprocess */ status = sys$crembx ( /* Create a temporary mailbox */ 0, /* Perm/temp flag */ &in_mbx_chan[proc_nbr], /* Receives I/O channel */ IN_MBX_MAXMSG, /* Max message size provided by VMS */ IN_MBX_BUFQUO, /* Buffer quota provided by VMS */ OWNER_ONLY_ACCESS, /* Protection mask */ 0, /* Access mode - default to user */ 0); /* Mailbox logical name */ if ( !(status & STS$M_SUCCESS) ) return status; status = sys$crembx ( /* Create a temporary mailbox */ 0, /* Perm/temp flag */ &out_mbx_chan[proc_nbr],/* Receives I/O channel */ OUT_MBX_MAXMSG, /* Max message size provided by VMS */ OUT_MBX_BUFQUO, /* Buffer quota provided by VMS */ OWNER_ONLY_ACCESS, /* Protection mask */ 0, /* Access mode - default to user */ 0); /* Mailbox logical name */ if ( !(status & STS$M_SUCCESS) ) { sys$dassgn (in_mbx_chan[proc_nbr]); return status; } /* Use SYS$GETDVI to obtain the names of the mailboxes just created for use in the LIB$SPAWN call. This was originally done because when the SYS$CREPRC call was made, it executes without a CLI and therefore doesn't have access to the logical name table LNM$JOB in which the logical names for the mailboxes are stored. This is no longer necessary since the SYS$CREPRC was changed to a LIB$SPAWN call, but it has been retained in case there is a future need for SYS$CREPRC (for example, if the subprocesses are changed to detached processes). */ DSC_INIT_BUF (in_mbxnam_desc, in_mbxnam_buf); DSC_INIT_BUF (out_mbxnam_desc, out_mbxnam_buf); ITM_INIT (item_list[0], DVI$_DEVNAM, in_mbxnam_buf, in_mbxnam_desc.dsc$w_length); ITM_TERM (item_list[1]); status = sys$getdviw ( 0, /* Event flag */ in_mbx_chan[proc_nbr], /* I/O channel */ 0, /* Device name */ &item_list, /* Item list */ &in_mbx_iosb[proc_nbr], /* I/O status block */ 0, /* AST address */ 0, /* AST parameter */ 0); /* Reserved */ if ( !(status & STS$M_SUCCESS) ) { sys$dassgn (in_mbx_chan[proc_nbr]); sys$dassgn (out_mbx_chan[proc_nbr]); return status; } ITM_INIT (item_list[0], DVI$_DEVNAM, out_mbxnam_buf, out_mbxnam_desc.dsc$w_length); ITM_TERM (item_list[1]); status = sys$getdviw ( 0, /* Event flag */ out_mbx_chan[proc_nbr], /* I/O channel */ 0, /* Device name */ &item_list, /* Item list */ &out_mbx_iosb[proc_nbr],/* I/O status block */ 0, /* AST address */ 0, /* AST parameter */ 0); /* Reserved */ if ( !(status & STS$M_SUCCESS) ) { sys$dassgn (in_mbx_chan[proc_nbr]); sys$dassgn (out_mbx_chan[proc_nbr]); return status; } /* Spawn the subprocess */ status = lib$spawn ( &command_desc, /* Command string */ &in_mbxnam_desc, /* Input file */ &out_mbxnam_desc, /* Output file */ &spawn_flag, /* Don't wait for subprocess */ 0, /* Process name - let VMS choose it */ &subprocess_pid, /* Subprocess PID */ 0, /* Completion status */ 0, /* Completion EFN */ 0, /* Completion AST routine */ 0, /* Completion AST parameter */ 0, /* Prompt string - default to parent */ 0); /* CLI - default to parent's */ if ( !(status & STS$M_SUCCESS) ) { sys$dassgn (in_mbx_chan[proc_nbr]); sys$dassgn (out_mbx_chan[proc_nbr]); return status; } return status; }