%TITLE 'IOGEN$AUTOCONFIGURE, main module for Alpha/VMS autoconfiguration' MODULE iogen$autoconfigure (IDENT = 'X-14', ENVIRONMENT(NOFP)) = BEGIN ! ! Copyright © Digital Equipment Corporation, 1991, 1995 All Rights Reserved. ! Unpublished rights reserved under the copyright laws of the United States. ! ! The software contained on this media is proprietary to and embodies the ! confidential technology of Digital Equipment Corporation. Possession, use, ! duplication or dissemination of the software and media is authorized only ! pursuant to a valid written license from Digital Equipment Corporation. ! ! RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the U.S. ! Government is subject to restrictions as set forth in Subparagraph ! (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, as applicable. ! !++ ! COMPONENT: ! ! IOGEN ! ! MODULE DESCRIPTION: ! ! This module contains IOGEN$AUTOCONFIGURE and some of its direct support ! routines. ! ! AUTHOR: ! ! Richard W. Critz, Jr. ! ! CREATION DATE: 26-Nov-1991 ! ! ! MODIFICATION HISTORY: ! ! X-14 PWL Peter W. LaPine 06-Mar-1996 ! Added MC* to the sca_devices list so the memory channel device ! driver will be loaded as part of the autoconfigure performed ! of behalf of the staconfig process. Loading sys$mcdriver will ! allow sys$pmdriver to be loaded as well, providing the ability ! to form a cluster in a timely fashion in the absence of the ! Ethernet. ! ! X-13 LSS0358 Leonard S. Szubowicz 30-Jan-1996 ! Fold of generation X-11A1. ! The default ICBM prefix list now includes DECW$ in addition ! to SYS$. Since more than one source module needs to be aware ! of this change, the prefix string is now defined by a macro ! in the AUTOCFG-MACROS.REQ file. ! ! X-12 DMB Dave Bernardo 14-Oct-1995 ! Add ATM devices to LAN_DEVICES. ! ! X-11 RWC149 Richard W. Critz, Jr. 21-Feb-1995 ! Add support for system-independent ICBMs (to assist with ! autoconfiguring non-zero LUNs). ! ! X-10 RES Richard E. Stockdale 15-Dec-1994 ! Add FW device (DEFPA) to LAN table. ! ! X-9 RWC141 Richard W. Critz, Jr. 24-Jan-1994 ! Add support for permanent exclusion lists. ! ! X-8 RAP Rod Payne 9-Sep-1993 ! Add FR device (DEFEA) to LAN table. ! ! X-7 EHL Gene Leache 30-jun-1993 ! Fix usage of imgact buffer ! ! X-6 RWC111 Richard W. Critz, Jr. 21-Dec-1992 ! Integrate IOGEN$_ACTERR message to assist in diagnosing ICBM ! activation problems. ! ! X-5 JWC John W. Croll 19-Oct-1992 ! Apply previous fix in all places. Also, fix generation ! number to correspond to CMS generation. ! ! X-7 JWC John W. Croll 1-Sep-1992 ! Fix to load PNDRIVER instead of PADRIVER when CI device ! found. ! ! X-6 RWC075 Richard W. Critz, Jr. 2-Mar-1992 ! PPA cleanup: ! ! 1) Integrate autoconfigure logging. ! ! 2) Remove PEA0 configuration. The cluster guys have decided ! this causes problems. ! ! 3) The original implementation did not allow the prefix list to ! change without first running down the image. Now that things ! are stable, add an ability to track previously seen prefixes and ! the associated ABM addresses so that the prefix list may change ! between invocations without an intervening image rundown. For ! history's sake, this "problem" occurred because the image ! activator returns unusable information when it is called for an ! image that is already on the image list so it's not possible to ! retrieve the ABM address. ! ! X-5 RWC073 Richard W. Critz, Jr. 31-Jan-1992 ! Configure PEA0 after everything else is done. ! ! X-4 RWC065 Richard W. Critz, Jr. 20-Dec-1991 ! Fix missing index initialization in PROCESS_ADP_LIST (error ! discovered by inspection). BUS_LIST processing won't work ! without this correction. ! ! X-3 RWC059 Richard W. Critz, Jr. 16-Dec-1991 ! Fix build bug. ! ! X-2 RWC057 Richard W. Critz, Jr. 13-Dec-1991 ! Handling of the BUS_LIST parameter is bogus if it is omitted. ! Fix it. ! ! X-1 RWC056 Richard W. Critz, Jr. 26-Nov-1991 ! Original. ! !-- ! ! TABLE OF CONTENTS: ! FORWARD ROUTINE iogen$autoconfigure, activate_icbms, do_activate, parse_list, process_adp_list, process_adp : NOVALUE; ! ! INCLUDE FILES: ! LIBRARY 'sys$library:lib'; REQUIRE 'src$:autocfg-macros'; ! ! MACROS: ! ! ! EQUATED SYMBOLS: ! BIND lan_devices = %ASCID'X*, E*, FX*, FC*, FA*, FR*, FW*, H*', sca_devices = %ASCID'PE*, PN*, PI*, PW*, PU*, MC*', default_prefix = %ASCID icbm_prefix_default_str; ! ! OWN STORAGE: ! OWN acdb_range : VECTOR[2,LONG,SIGNED] INITIAL(0, 0); ! ! EXTERNAL REFERENCES: ! EXTERNAL LITERAL iogen$_icbm_ok, iogen$_recall_icbm, iogen$_prefix, iogen$_activate, iogen$_noicbm, iogen$_acterr; EXTERNAL exe$gq_cputype, exe$gq_systype, ioc$gl_adplist : SIGNED LONG, mmg$gl_page_size : LONG; EXTERNAL ROUTINE iogen$scan_controller, iogen$ac_select, iogen$get_exclusion, iogen$log, iogen$lock, iogen$unlock, sys$load_driver, lib$p0_merge; %SBTTL 'IOGEN$AUTOCONFIGURE, the big cheese!' GLOBAL ROUTINE iogen$autoconfigure( flags : BLOCK[,BYTE], ! r/o, by value prefix_list : REF BLOCK[,BYTE], ! r/o, by descriptor select_list : REF BLOCK[,BYTE], ! r/o, by descriptor exclude_list : REF BLOCK[,BYTE], ! r/o, by descriptor bus_list : REF VECTOR[,LONG], ! r/o, by reference log_callback : LONG SIGNED, ! r/o, procedure value exclude_callback: LONG SIGNED) = ! r/o, procedure value !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine allocates space for the ACDB, initializes it, activates ICBMs, ! parses the select and exclude lists/flags and then walks the ADP list to ! cause system autoconfiguration to occur. ! ! FORMAL PARAMETERS: ! ! flags - control optional behavior in IOGEN$AUTOCONFIGURE ! prefix_list - list of ICBM filename prefixes ! select_list - list of (potentially) wildcarded devices to configure ! exclude_list - list of (potentially) wildcarded device name to exclude ! bus_list - list of bus types to include ! log_callback - routine to process log messages ! exclude_callback- routine to read permanent exclusion list if the default ! routine will fail (e.g., during STACONFIG) ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_NORMAL ! SS$_ACCVIO - if workspace is exhausted ! SS$_NOPRIV (from $CMEXEC) ! any status returned by $EXPREG ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL status, acdb : REF BLOCK[,BYTE], buses : INITIAL(0), next_sel, next_excl, actual_prefixes : INITIAL(default_prefix), buffer : VECTOR[512,BYTE] ALIAS, perm_exclude : VECTOR[2,LONG] ALIAS INITIAL(512, buffer); BUILTIN ACTUALCOUNT, NULLPARAMETER; IF .acdb_range[0] EQLA 0 ! if no ACDB has been allocated yet THEN BEGIN LOCAL page_count; ! Determine the minimum number of pages required to contain the ACDB ! and a reasonable cache of CTRLR blocks. To be absolutely safe, we ! must allocate room for 26² blocks but this seems highly ! unnecessary. Based on existing systems, 256 is probably more than ! enough. page_count = (acdb$s_acdbdef + 256 * ctrlr$s_ctrlrdef + .mmg$gl_page_size) / .mmg$gl_page_size; IF NOT (status = $expreg( pagcnt = .page_count, retadr = acdb_range)) THEN RETURN .status; BLOCK[.acdb_range[0],acdb$ps_end; acdb$s_acdbdef,BYTE] = .acdb_range[1]; BLOCK[.acdb_range[0],$byteoffset(acdb$t_prefix_history),0,32,0] = 0 END; ! Do some basic initialization of the ACDB. Store away input parameters ! that will be needed later. Initialize the controller letter hash buckets ! and mark the whole end of the ACDB space as free for allocation. acdb = .acdb_range[0]; IF NOT NULLPARAMETER(flags) THEN IF .flags[iogen$v_ac_log_all] ! LOG_ALL implies LOG THEN acdb[acdb$l_flags] = .flags OR iogen$m_ac_log ELSE acdb[acdb$l_flags] = .flags ELSE acdb[acdb$l_flags] = 0; IF NOT NULLPARAMETER(log_callback) THEN acdb[acdb$ps_log_callback] = .log_callback ELSE acdb[acdb$ps_log_callback] = 0; acdb[acdb$ps_free] = .acdb + acdb$s_acdbdef; INCR i FROM 0 TO iogen$k_max_ctrl_hash DO BEGIN BIND hashlist = acdb[acdb$t_ctrlr_hash] : VECTOR[,LONG]; hashlist[.i] = 0 END; ! Set up the list of active ICBMs based on the prefix list. IF NOT NULLPARAMETER(prefix_list) THEN IF .prefix_list[dsc$w_length] NEQ 0 THEN actual_prefixes = .prefix_list; IF NOT (status = activate_icbms(.acdb, .actual_prefixes)) THEN RETURN .status; ! Initialize the select and exclude lists. First set both lists to be ! empty. Then add select items based on the flags. Finally add in any ! explicitly specified select items. The exclude list is only that which ! is specified by the caller. (acdb[acdb$t_select])<0,32,0> = (acdb[acdb$t_exclude])<0,32,0> = 0; next_sel = acdb[acdb$t_select]; ! pointer to next slot in select list IF NOT NULLPARAMETER(flags) THEN BEGIN IF .flags[iogen$v_ac_sca] OR .flags[iogen$v_ac_lan] THEN next_sel = parse_list(lan_devices, .next_sel); IF .flags[iogen$v_ac_sca] THEN next_sel = parse_list(sca_devices, .next_sel) END; IF NOT NULLPARAMETER(select_list) THEN parse_list(.select_list, .next_sel); next_excl = acdb[acdb$t_exclude]; IF NOT NULLPARAMETER(exclude_list) THEN next_excl = parse_list(.exclude_list, .next_excl); ! The exclude callback is intended for use ONLY by STACONFIG to provide ! IOGEN$AUTOCONFIGURE with a way to read the disk before the disk is ! mounted. All other uses are UNSUPPORTED and likely to crash the system. ! The primitive filesystem routines (used by STACONFIG) must be called in ! kernel mode. IF ACTUALCOUNT() GEQ 7 THEN IF NOT NULLPARAMETER(exclude_callback) THEN status = kernel_call(.exclude_callback, perm_exclude) ELSE status = iogen$get_exclusion(perm_exclude) ELSE status = iogen$get_exclusion(perm_exclude); IF NOT .status THEN RETURN .status; parse_list(perm_exclude, .next_excl); ! Now, scan all of the bus arrays in the system to initialize the ! controller letter hash table. The remaining activity must be performed ! under the IOGEN lock. IF NOT (status = iogen$lock()) THEN RETURN .status; status = exec_call(iogen$scan_controller, .acdb, .ioc$gl_adplist); ! Now, everything is all set up. Walk the ADP list and "do the right ! thing." IF NOT NULLPARAMETER(bus_list) THEN buses = .bus_list; IF .status THEN status = exec_call(process_adp_list, .acdb, .ioc$gl_adplist, .buses); IF .status ! if PROCESS_ADP_LIST was successful, use THEN ! the status from IOGEN$UNLOCK as our return status = iogen$unlock() ! status. Otherwise, ignore the unlock status ELSE ! and return the more serious preceeding error iogen$unlock(); RETURN .status; END; %SBTTL 'ACTIVATE_ICBMS, find and activate the necessary ICBMs' ROUTINE activate_icbms( acdb : REF BLOCK[,BYTE], prefix_list : REF BLOCK[,BYTE]) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine sets up the active ABM list based on the prefix list it is ! passed. It searches the prefix history list in the ACDB for each prefix ! and, for each one found, copies the associated ABM address into the active ! ABM list. If it does not find the prefix in the list and the list is not ! full, it calls DO_ACTIVATE to cause an attempted merged activation of the ! appropriate ICBM. ! ! FORMAL PARAMETERS: ! ! acdb - address of the ACDB ! prefix_list - address of the prefix list descriptor ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_NORMAL - things went mostly ok ! SS$_BADPARAM - an improperly built ICBM was found ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL status, length, i : INITIAL(0), ! index into requested prefix list j, ! index into abm_history list abmindex : INITIAL(0), ! index into active ABM list end_history : INITIAL(acdb[acdb$t_prefix_history] + acdb$s_prefix_history); BIND abmlist = acdb[acdb$t_abmlist] : VECTOR[,LONG,SIGNED], prefixes = acdb[acdb$t_prefix] : VECTOR[,BYTE], abm_history = acdb[acdb$t_abm_history] : VECTOR[,LONG,SIGNED]; ! First, parse the prefix list into its internal format. parse_list(.prefix_list, prefixes); ! Next, mark the active ABM list as empty. abmlist[0] = 0; ! Next, try to find the requested ICBMs. First scan the prefix history list ! looking for a match. If none is found, try calling DO_ACTIVATE. WHILE (length = .prefixes[.i]) NEQ 0 AND .abmindex LSS iogen$k_maxabm DO BEGIN LOCAL history : REF VECTOR[,BYTE] INITIAL(acdb[acdb$t_prefix_history]), found : INITIAL(false); iogen$log(.acdb, iogen$_prefix, 0, prefixes[.i]); WHILE .history[0] NEQ 0 AND .history LSS .end_history DO IF CH$EQL(.history[0], history[1], .length, prefixes[.i+1]) THEN BEGIN found = true; EXITLOOP END ELSE history = .history + iogen$k_prefix_hist_size; IF .history GEQ .end_history THEN RETURN ss$_badparam; ! Calculate the associated index into the ABM history list. Then, if no ! matching entry was found in the prefix history list, call DO_ACTIVATE. ! Copy the ABM address returned by DO_ACTIVATE into the active ABM list. ! If no ICBM was found, this will be a 0. If it's not zero, rachet down ! to the next entry in the active ABM list. j = (.history - acdb[acdb$t_prefix_history]) / iogen$k_prefix_hist_size; IF NOT .found THEN abm_history[.j] = do_activate(.acdb, prefixes[.i], .history); IF (abmlist[.abmindex] = .abm_history[.j]) NEQ 0 THEN abmindex = .abmindex + 1; i = .i + .length + 1 ! skip to the next prefix END; IF .abmindex LSS iogen$k_maxabm THEN abmlist[.abmindex] = 0; ! insurance that list ends properly RETURN ss$_normal END; %SBTTL 'DO_ACTIVATE, merge activate a single ICBM' ROUTINE do_activate(acdb : REF BLOCK[,BYTE], prefix : REF VECTOR[,BYTE], history : REF VECTOR[,BYTE])= !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine attempts to merge activate an ICBM associated with a single ! prefix. ! ! FORMAL PARAMETERS: ! ! acdb - address of the ACDB ! prefix - address of the ASCIC string for the desired prefix ! history - address of empty history block in which to save prefix ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! Address of ICBM's ABM, or 0 if no ICBM is found ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL abm_address : SIGNED LONG INITIAL(0), hdrbuf : VECTOR[128,LONG,SIGNED] VOLATILE, descrip : VECTOR[2,LONG,SIGNED] INITIAL (acdb$s_buffer, acdb[acdb$t_buffer]), retadr : VECTOR[2,LONG], status; ! First, try a platform/CPU specific ICBM. $fao( %ASCID'!ACICBM_!XB!XB', ! mumbleICBM_xxyy descrip, ! set the length properly descrip, ! put it in this buffer .prefix, ! mumble = a prefix .exe$gq_systype, ! xx = platform id code .exe$gq_cputype); ! yy = CPU type id code iogen$log(.acdb, iogen$_activate, 0, descrip, true); status = lib$p0_merge( descrip, ! filespec %ASCID'SYS$SHARE:.EXE', ! default filespec hdrbuf, ! image header retadr); ! mapped address range (unused) ! If that didn't work, drop the CPU type from the name. IF NOT .status THEN BEGIN descrip[0] = .descrip[0] - 2; ! drop CPU type from name iogen$log(.acdb, iogen$_activate, 0, descrip, true); status = lib$p0_merge( descrip, ! filespec %ASCID'SYS$SHARE:.EXE', ! default filespec hdrbuf, ! image header retadr) ! mapped address range (unused) END; ! If that didn't work, drop the system type from the name. IF NOT .status THEN BEGIN descrip[0] = .descrip[0] - 3; ! drop system type from name iogen$log(.acdb, iogen$_activate, 0, descrip, true); status = lib$p0_merge( descrip, ! filespec %ASCID'SYS$SHARE:.EXE', ! default filespec hdrbuf, ! image header retadr) ! mapped address range (unused) END; IF .status THEN BEGIN LOCAL tfarray : REF BLOCK[,BYTE], tfaddr; ! ICBM was successfully merged. Save the prefix so we don't ACCVIO by ! trying to merge it again. If it's poorly built, we'll never be able ! to use it (assured by setting the ABM address to 0). CH$MOVE(.prefix[0]+1, .prefix, .history); ! Now, locate the transfer address array in the image header and walk ! through that array looking for a non-system space address. If found, ! call it. It'd better return IOGEN$_ICBM_OK or we haven't got a valid ! ICBM. tfarray = .hdrbuf[0]; ! Get address of header tfarray = .tfarray + .tfarray[eihd$l_activoff]; tfaddr = .tfarray[eiha$l_tfradr1]; IF .tfaddr LEQ 0 THEN tfaddr = .tfarray[eiha$l_tfradr2]; IF .tfaddr LEQ 0 THEN tfaddr = .tfarray[eiha$l_tfradr3]; IF .tfaddr GTR 0 ! ICBM is properly built THEN IF (status = (.tfaddr)(abm_address)) NEQ iogen$_icbm_ok THEN abm_address = 0; END ELSE iogen$log(.acdb, iogen$_acterr, .status, 0, 1); IF .abm_address EQLA 0 THEN iogen$log(.acdb, iogen$_noicbm, 0, .prefix); RETURN .abm_address END; %SBTTL 'PARSE_LIST, parses a comma separated list into an ASCIC sequence' ROUTINE parse_list( list : REF BLOCK[,BYTE], ! r/o, by descriptor buffer : REF VECTOR[,BYTE]) = ! w/o, by reference !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine parses a comma separated list, ignoring whitespace. It takes ! the resulting individual strings and stores them as a concatenated list of ! ASCIC strings in the specified buffer. The list is terminated with a 0 ! length ASCIC string. Lowercase characters are translated to uppercase. ! ! FORMAL PARAMETERS: ! ! list - descriptor of character string to parse ! buffer - place to store converted list ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! address of terminator byte (to allow continued concatenation) ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL i, ! index into buffer savedi, j, ! index into list string length : INITIAL(.list[dsc$w_length]), string : REF VECTOR[,BYTE] INITIAL (.list[dsc$a_pointer]); savedi = j = buffer[0] = 0; ! insert a list terminator i = 1; ! first open position in the buffer WHILE .j LSS .length DO ! skip whitespace and commas BEGIN WHILE .j LSS .length AND (.string[.j] EQL %CHAR(32) OR ! space .string[.j] EQL %CHAR(9) OR ! tab .string[.j] EQL %C',') DO ! comma j = .j + 1; WHILE .j LSS .length AND ! copy the string NOT (.string[.j] EQL %CHAR(32) OR ! space .string[.j] EQL %CHAR(9) OR ! tab .string[.j] EQL %C',') DO ! comma BEGIN IF .string[.j] GEQ %C'a' AND .string[.j] LEQ %C'z' THEN buffer[.i] = .string[.j] - %C'a' + %C'A' ELSE buffer[.i] = .string[.j]; i = .i + 1; j = .j + 1 END; buffer[.savedi] = .i - .savedi - 1; ! set the length byte buffer[.i] = 0; ! insert a new terminator savedi = .i; i = .i + 1 END; RETURN buffer[.savedi] ! address of terminator END; %SBTTL 'PROCESS_ADP_LIST, configure an ADP subtree' ROUTINE process_adp_list( acdb : REF BLOCK[,BYTE], root_adp : REF BLOCK[,BYTE], bus_list : REF VECTOR[,LONG]) = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine walks the hierarchical ADP list, selecting ADPs for ! autoconfiguration and invoking the routine PROCESS_ADP to do that ! configuration. The ADP list traversal is a depth first, pre-order ! traversal. This routine uses recursion to descend the tree and iteration ! to cross the tree. ! ! FORMAL PARAMETERS: ! ! acdb - address of the ACDB ! root_adp - ADP that is the root of the current subtree ! bus_list - list of ADP types that the caller of IOGEN$AUTOCONFIGURE ! specified should be processed ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! SS$_NORMAL ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL adp : REF BLOCK[,BYTE] INITIAL(.root_adp), i; BUILTIN NULLPARAMETER; WHILE .adp NEQA 0 DO BEGIN i = 0; IF .adp[adp$v_online] THEN IF NULLPARAMETER(bus_list) THEN exec_call(process_adp, .acdb, .adp) ELSE WHILE .bus_list[.i] NEQ 0 DO IF .adp[adp$l_adptype] EQL .bus_list[.i] THEN BEGIN exec_call(process_adp, .acdb, .adp); EXITLOOP END ELSE i = .i + 1; IF .adp[adp$ps_child_adp] NEQA 0 THEN process_adp_list(.acdb, .adp[adp$ps_child_adp], .bus_list); adp = .adp[adp$ps_peer_adp] END; RETURN ss$_normal END; %SBTTL 'PROCESS_ADP, routine to search ABMs and invoke BSRs' ROUTINE process_adp( acdb : REF BLOCK[,BYTE], adp : REF BLOCK[,BYTE]) : NOVALUE = !++ ! FUNCTIONAL DESCRIPTION: ! ! This routine does the work of searching the ABMs in the ABM list for ! matching ADP types. When it finds one, it calls the associated BSR. It ! allows a single shot recall capability from each BSR. Any unknown status ! value (or a return of IOGEN$_RECALL_ICBM from a BSR called for the second ! time) is treated as though it were SS$_ABORT, thereby terminating ! processing of this particular ADP. ! ! FORMAL PARAMETERS: ! ! acdb - address of the ACDB ! adp - address of the ADP to be autoconfigured ! ! IMPLICIT INPUT PARAMETERS: ! ! None ! ! IMPLICIT OUTPUT PARAMETERS: ! ! None ! ! RETURN VALUE: ! ! None ! ! SIDE EFFECTS: ! ! None ! !-- BEGIN LOCAL recall_list : VECTOR[iogen$k_maxabm,LONG,SIGNED], abmindex : INITIAL(0), recall_index : INITIAL(0), status; BIND abmlist = acdb[acdb$t_abmlist] : VECTOR[,LONG,SIGNED]; WHILE .abmindex LSS iogen$k_maxabm AND .abmlist[.abmindex] NEQ 0 DO BEGIN LOCAL i : INITIAL(0); BIND bsrlist = .abmlist[.abmindex] : BLOCKVECTOR[,iogen$s_abmdef,BYTE]; WHILE .bsrlist[.i, iogen$il_abm_adp] NEQ 0 DO BEGIN IF .bsrlist[.i, iogen$il_abm_adp] EQL .adp[adp$l_adptype] THEN BEGIN status = (.bsrlist[.i, iogen$ps_abm_bsr])(.acdb, .adp); IF .status EQL iogen$_recall_icbm THEN BEGIN recall_list[.recall_index] = .abmlist[.abmindex]; recall_index = .recall_index + 1 END ELSE IF .status NEQ ss$_normal THEN RETURN END; i = .i + 1 END; abmindex = .abmindex + 1 END; abmindex = 0; WHILE .abmindex LSS .recall_index DO BEGIN LOCAL i : INITIAL(0); BIND bsrlist = .recall_list[.abmindex] : BLOCKVECTOR[,iogen$s_abmdef,BYTE]; WHILE .bsrlist[.i, iogen$il_abm_adp] NEQ 0 DO BEGIN IF .bsrlist[.i, iogen$il_abm_adp] EQL .adp[adp$l_adptype] THEN BEGIN status = (.bsrlist[.i, iogen$ps_abm_bsr])(.acdb, .adp); IF .status NEQ ss$_normal THEN RETURN END; i = .i + 1 END; abmindex = .abmindex + 1 END; END; END ! End of module ELUDOM