?? RIGHT := 110 ??
?? NEWTITLE := 'INSTALL_SOFTWARE Utility: RAP$LOAD_PACKING_LIST Interface.' ??
MODULE ram$load_packing_list;

{ PURPOSE:
{   This module contains the interface and procedures that load the packing
{   list from an order.
{
{ DESIGN:
{   The interface is expected to be called by INSTALL_SOFTWARE and
{   PACKAGE_SOFTWARE command interfaces to perform the actual loading work.
{
{   The compiled module resides in RAF$LIBRARY.
{
{ NOTES:
{

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc dme$tape_errors
*copyc rac$local_primary_tape
*copyc rac$packing_list_level
*copyc rac$tape_types
*copyc rae$install_software_cc
*copyc clt$parameter_value
*copyc fst$file_reference
*copyc rat$path
*copyc rat$packing_list_sequence
*copyc rat$sequence_descriptor_types
?? POP ??
*copyc amp$get_file_attributes
*copyc amp$get_segment_pointer
*copyc clp$include_line
*copyc clp$trimmed_string_size
*copyc fsp$close_file
*copyc fsp$convert_fs_structure_to_pf
*copyc fsp$detach_file
*copyc pfp$convert_string_to_fs_path
*copyc pfp$convert_fs_path_to_pf_path
*copyc pfp$define_catalog
*copyc pfp$purge
*copyc osp$append_status_file
*copyc osp$append_status_parameter
*copyc osp$establish_block_exit_hndlr
*copyc osp$disestablish_cond_handler
*copyc osp$generate_error_message
*copyc osp$set_status_abnormal
*copyc rap$open_file
*copyc rap$record_disk_path

?? TITLE := 'Global Declarations Declared by This Module', EJECT ??

?? TITLE := '[XDCL] rap$load_packing_list', EJECT ??

{ PURPOSE:
{   This interface loads the packing list from a tailored product tape or
{   tailored disk file to the loading destination.
{
{ DESIGN:
{   This interface is called by the INSTALL_SOFTWARE and PACKAGE_SOFTWARE
{   LOAD_PACKING_LIST command interfaces.  It controls the process of
{   loading the packing list.  The disk and tape parameter value records
{   are passed through by the calling commmand interface.  The calling
{   command interface did not validate these parameters and expects the
{   validation to be done by this interface.
{
{   The destination path is analyized and any non-existing subcatalogs are
{   created prior to the loading.
{
{   Loading the packing list from tape is somewhat different than loading
{   from disk.  The appropriate loading interface is called to perform the
{   the actual loading based on which set of parameters was specified.
{
{   After loading a disk order packing list, the path to the disk file
{   just loaded from is recorded in the packing list.  This provides
{   an automated way of finding the products during installation (as long
{   as the disk file is not moved).
{
{ NOTES:
{   In the future a block exit handler will be provided to cleanup any file
{   that was loaded into the destination path prior to an abort condition.
{   Conceptually, this should be placed in the LOAD_PACKING_LIST_FROM_TAPE
{   and LOAD_PACKING_LIST_FROM_DISK directly.  But since this is a
{   duplication of code it is suggested that it be placed in this interface
{   around the calls to the disk or tape loading procedures.  The abort
{   procedure should make a call to the DELETE_FILE procedure (found in
{   this module) passing in the LOADING_DESTINATION file reference.
{

  PROCEDURE [XDCL] rap$load_packing_list
    (    external_vsn: clt$parameter_value;
         recorded_vsn: clt$parameter_value;
         tape_type: clt$parameter_value;
         disk_file: clt$parameter_value;
         loading_destination: fst$file_reference;
         unload_volume: boolean;
         removable_media_group: clt$parameter_value;
     VAR status: ost$status);


    status.normal := TRUE;

    validate_parameters (external_vsn, recorded_vsn, tape_type, disk_file, loading_destination, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    prepare_loading_destination (loading_destination, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF (external_vsn.specified OR recorded_vsn.specified) THEN

      load_packing_list_from_tape (external_vsn, recorded_vsn, tape_type,
             loading_destination, unload_volume, removable_media_group, status);

    ELSE {disk_file specified}

      load_packing_list_from_disk (disk_file.value^.file_value^, loading_destination, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;

      rap$record_disk_path (disk_file.value^.file_value^, loading_destination, status);

    IFEND;

  PROCEND rap$load_packing_list;

?? TITLE := 'delete_file', EJECT ??

{ PURPOSE:
{   This procedure deletes the low cycle of the file specified.
{
{ DESIGN:
{   The file path is converted from file reference to PF file format so
{   that PFP$PURGE can be called to delete the file.
{
{ NOTES:
{   DJD is providing a replacement for CLP$EVALUATE_FILE_REFERENCE.
{
{   This may be worthwhile expanding.
{

  PROCEDURE delete_file
    (    file_path: fst$file_reference;
     VAR status: ost$status);


    VAR
      cycle_selector: pft$cycle_selector,
      fs_path: string (fsc$max_path_size),
      ignore_cycle_reference: fst$cycle_reference,
      ignore_cycle_selector: clt$cycle_selector,
      ignore_open_position: fst$open_position,
      ignore_status: ost$status,
      number_of_path_elements: fst$number_of_path_elements,
      password: pft$password,
      path_p: ^pft$path;


    status.normal := TRUE;
    password := '';
    cycle_selector.cycle_option := pfc$lowest_cycle;

{  Convert the file path, which is in file reference format to PF format. }

    pfp$convert_string_to_fs_path (file_path, fs_path, number_of_path_elements, ignore_cycle_reference,
          ignore_open_position, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    PUSH path_p: [1 .. number_of_path_elements];
    pfp$convert_fs_path_to_pf_path (fs_path, path_p, ignore_cycle_reference, ignore_cycle_selector, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

{  Delete the file. }

    pfp$purge (path_p^, cycle_selector, password, status);

  PROCEND delete_file;

?? TITLE := 'load_packing_list_from_disk', EJECT ??

{ PURPOSE:
{   This procedure loads the packing list from a tailored disk file to the
{   loading destination.
{
{ DESIGN:
{   The packing list is expected to be the first file found on the disk
{   file and is blindly loaded off.  Loading is performed using
{   RESTORE_PERMANENT_FILES utility which is only callable through command
{   language.  To accomplish this, the SCL commands must be collected and
{   executed by including the lines.  After the file is successfully
{   loaded, it is verified to be a packing list by checking the sequence
{   descriptor.  When any error occurs cleanup is attempted by deleting the
{   loading destination as a file with ignore status.
{
{   Information about the load will show up in the job log.
{
{   The local procedure called to delete the file deletes only the low
{   cycle.  But in the current usage deleting the low cycle is the same as
{   deleting the file since there is only one cycle.
{
{ NOTES:
{   The SCL commands that are assembled by STRINGREP into the command
{   line have been arranged for readability.  Formating has been turned
{   off to protect these lines from the formater.
{
{   Generally the RESTORE_PERMANENT_FILES subcommands use a warning status
{   to return status with.  This is not picked up by the CLP$INCLUDE_LINE
{   (unless it happens to be the last command).  Because of this, a status
{   must be placed directly on the restore command (which is a bit of
{   work).  However, because RESTORE_FILE returns error level error
{   messages instead, the use of a restore status is not required for
{   loading the packing list here as it is in other INSS situations.
{

  PROCEDURE load_packing_list_from_disk
    (    disk_file: fst$file_reference;
         loading_destination: fst$file_reference;
     VAR status: ost$status);

    VAR
      command_line: string (1000),
      command_line_length: integer,
      ignore_status: ost$status,
      local_status: ost$status;

    status.normal := TRUE;

{  Collect the SCL commands required to load the packing list from tape. }

?? FMT (FORMAT := OFF) ??
    STRINGREP (command_line, command_line_length,
          '$system.osf$builtin_library.restore_permanent_file l=$job_log; ',
          '  restore_file f=$fname($backup_file(', disk_file, ', identifier)) bf=', disk_file,
                   ' nfn=', loading_destination,  '; ',
          'quit');
?? FMT (FORMAT := ON) ??

{  Execute the SCL loading commands and verify that the loaded file is a packing list. }

    clp$include_line (command_line (1, command_line_length), TRUE, osc$null_name, local_status);
    IF local_status.normal THEN
      verify_as_packing_list (loading_destination, status);
    ELSE
      osp$generate_error_message (local_status, ignore_status);
      osp$set_status_abnormal ('RA', rae$pl_loading_error_occurred, '', status);
    IFEND;

    IF NOT status.normal THEN
      delete_file (loading_destination, ignore_status);
    IFEND;

  PROCEND load_packing_list_from_disk;

?? TITLE := 'load_packing_list_from_tape', EJECT ??

{ PURPOSE:
{   This procedure loads the packing list from a tailored product tape to
{   the loading destination.
{
{ DESIGN:
{   The packing list is expected to be the first file found on the disk
{   file and is blindly loaded off.  Loading is performed using
{   RESTORE_PERMANENT_FILES utility which is only callable through command
{   language.  To accomplish this, the SCL commands must be collected and
{   executed by including the lines.  After the file is successfully
{   loaded, it is verified to be a packing list by checking the sequence
{   descriptor.  When any error occurs cleanup is attempted by deleting the
{   loading destination as a file with ignore status.
{
{   Information about the load will show up in the job log.
{
{   The naming the of the local file associated with the tape is rigidly
{   controlled to allow for tapes to be left mounted through a series of
{   command calls.  The local file path is created by taking constant path
{   value and appending the primary tape vsn for the tape.  If specified
{   the RVSN is used, otherwise the EVSN is taken (see notes).  When the tape
{   file is detected as existing upon setting up to load, the tape file
{   will not be returned after loading has completed.  Otherwise it will be
{   returned.
{
{   The DELETE_FILE procedure called to delete the file deletes only the low
{   cycle.  But in the current usage deleting the low cycle is the same as
{   deleting the file since there is only one cycle.
{
{ NOTES:
{   The SCL commands that are assembled by STRINGREP into the command
{   line have been arranged for readability.  Formating has been turned
{   off to protect these lines from the formater.
{
{   Generally the RESTORE_PERMANENT_FILES subcommands use a warning status
{   to return status with.  This is not picked up by the CLP$INCLUDE_LINE
{   (unless it happens to be the last command).  Because of this, a status
{   must be placed directly on the restore command (which is a bit of
{   work).  However, because RESTORE_FILE returns error level error
{   messages instead, the use of a restore status is not required for
{   loading the packing list here as it is in other INSS situations.
{
{   The other interfaces that work off the tape file name use the RVSN
{   as the vsn appended to the tape name.
{
{   The tape file identifier could be checked first before attempting
{   to load packing list.  But it probably isn't worth it since there
{   is no way of reading it until the tape is attached.
{

  PROCEDURE load_packing_list_from_tape
    (    external_vsn: clt$parameter_value;
         recorded_vsn: clt$parameter_value;
         tape_type: clt$parameter_value;
         loading_destination: fst$file_reference;
         unload_volume: boolean;
         removable_media_group: clt$parameter_value;
     VAR status: ost$status);


    VAR
      command_line: string (1000),
      command_line_length: integer,
      detachment_options: ^fst$detachment_options,
      existing_file: boolean,
      ignore_attributes: array [1 .. 1] of amt$get_item,
      ignore_contains_data: boolean,
      ignore_status: ost$status,
      local_file: boolean,
      local_status: ost$status,
      local_evsn : string (6),
      local_rvsn : string (6),
      removable_media_group_name: ost$name,
      return_tape_file: boolean,
      tape_file: rat$path,
      tape_type_value: string (10),
      vsns_line: string (osc$max_string_size),
      vsns_line_length: integer;

    status.normal := TRUE;

    IF tape_type.specified THEN
      tape_type_value := tape_type.value^.keyword_value;
    ELSE { use default }
      tape_type_value := rac$mt9$6250;
    IFEND;

{  Create the local tape file name and determine if it is currently attached.
{  When the local tape file is already attached the tape will not be returned.

    local_rvsn := ' ';
    local_evsn := ' ';

    IF recorded_vsn.specified THEN
      IF recorded_vsn.value^.kind = clc$string THEN
         local_rvsn := recorded_vsn.value^.string_value^;
      ELSE { name value }
         local_rvsn := recorded_vsn.value^.name_value;
      IFEND;
    IFEND;
    IF external_vsn.specified THEN
      IF external_vsn.value^.kind = clc$string THEN
         local_evsn := external_vsn.value^.string_value^;
      ELSE { name value }
         local_evsn := external_vsn.value^.name_value;
      IFEND;
    IFEND;

    IF recorded_vsn.specified THEN
      STRINGREP (tape_file.path, tape_file.size, rac$local_primary_tape,
            local_rvsn(1,clp$trimmed_string_size(local_rvsn)),'.1');
    ELSE { external_vsn specified }
      STRINGREP (tape_file.path, tape_file.size, rac$local_primary_tape,
            local_evsn(1,clp$trimmed_string_size(local_evsn)),'.1');
    IFEND;

    ignore_attributes [1].key := amc$file_length;
    amp$get_file_attributes (tape_file.path (1, tape_file.size), ignore_attributes, local_file, existing_file,
          ignore_contains_data, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    return_tape_file := NOT (local_file OR existing_file);

{  Create the vsns parameter string.

    IF external_vsn.specified AND recorded_vsn.specified THEN
      STRINGREP (vsns_line, vsns_line_length, ' evsn=''', local_evsn, ''' rvsn=''', local_rvsn, '''');
    ELSEIF external_vsn.specified THEN
      STRINGREP (vsns_line, vsns_line_length, ' evsn=''', local_evsn, '''');
    ELSE {recorded_vsn specified}
      STRINGREP (vsns_line, vsns_line_length, ' rvsn=''', local_rvsn, '''');
    IFEND;

    IF removable_media_group.value^.kind = clc$name THEN
      removable_media_group_name := removable_media_group.value^.name_value;
    ELSE { keyword = NONE }
      removable_media_group_name := 'NONE';
    IFEND;

{  Collect the SCL commands required to load the packing list from tape.

?? FMT (FORMAT := OFF) ??
    STRINGREP (command_line, command_line_length,
          '$system.request_magnetic_tape f=', tape_file.path (1, tape_file.size),
                 vsns_line (1, vsns_line_length), ' t=', tape_type_value,
                 ' rmg=', removable_media_group_name, '; ',
          '$system.osf$builtin_library.restore_permanent_file l=$job_log; ',
          '  restore_file f=$fname($backup_file(', tape_file.path (1, tape_file.size),
                   ', identifier)) bf=', tape_file.path (1, tape_file.size),
                   ' nfn=', loading_destination, '; ',
          'quit');
?? FMT (FORMAT := ON) ??

{  Execute the SCL loading commands and verify that the loaded file is a packing list.

    clp$include_line (command_line (1, command_line_length), TRUE, osc$null_name, local_status);
    IF return_tape_file THEN
      IF unload_volume THEN
        detachment_options := NIL;
      ELSE
        PUSH detachment_options: [1 .. 1];
        detachment_options^ [1].selector := fsc$do_unload_volume;
        detachment_options^ [1].unload_volume := FALSE;
      IFEND;
      fsp$detach_file (tape_file.path (1, tape_file.size), detachment_options,
            ignore_status);
    IFEND;

    IF local_status.normal THEN
      verify_as_packing_list (loading_destination, status);
    ELSE
      IF local_status.condition = dme$tape_not_assigned THEN
        osp$set_status_abnormal ('RA', rae$pl_file_not_returned,
             tape_file.path(1, tape_file.size), local_status);
      IFEND;
      osp$generate_error_message (local_status, ignore_status);
      osp$set_status_abnormal ('RA', rae$pl_loading_error_occurred, '', status);
    IFEND;

    IF NOT status.normal THEN
      delete_file (loading_destination, ignore_status);
    IFEND;

  PROCEND load_packing_list_from_tape;

?? TITLE := 'prepare_loading_destination', EJECT ??

{ PURPOSE:
{   This procedure makes sure the subcatalogs along the destination path
{   are created (if they don't already exist).
{
{ DESIGN:
{   The procedure blindly attempts to create each subcatalog along the
{   path.  The interface to create subcatalogs requires the path format be
{   converted from file reference to PF format, so that is done first.  In
{   an attempt to simplify the dividing of the path into subcatalogs, the
{   PF path array containing the entire path is created in a sequence.
{   This will protect the values of the entire PF path array while subsets
{   of the array are used to create the subcatalogs in order of occurance.
{   In this way assignment to the PF path array is only done once.
{
{ NOTES:
{   This interface could be expanded to be used in creating the subproduct
{   installation paths when the path container is converted to file
{   references.
{

  PROCEDURE prepare_loading_destination
    (    destination_path: fst$file_reference;
     VAR status: ost$status);


    CONST
      file = 1, { The file is the last element of the path. }
      first_subcatalog = 1,
      master_catalog = 2; { The first 2 elements of a path are the master catalog. }


    VAR
      fs_path: string (fsc$max_path_size),
      ignore_cycle_reference: fst$cycle_reference,
      ignore_cycle_selector: clt$cycle_selector,
      ignore_open_position: fst$open_position,
      ignore_status: ost$status,
      number_of_path_elements: fst$number_of_path_elements,
      number_of_subcatalogs: integer,
      path_p: ^pft$path,
      path_sequence_p: ^SEQ ( * ),
      subcatalogs: integer;


    status.normal := TRUE;

{  Determine the number of subcatalogs in the destination path. }

    pfp$convert_string_to_fs_path (destination_path, fs_path, number_of_path_elements, ignore_cycle_reference,
          ignore_open_position, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    number_of_subcatalogs := number_of_path_elements - master_catalog - file;

    IF number_of_subcatalogs > 0 THEN

{  Convert the destination path, which is in file reference format to PF format. }

      PUSH path_sequence_p: [[REP (number_of_path_elements * #SIZE (pft$name)) OF cell]];
      RESET path_sequence_p;
      NEXT path_p: [1 .. number_of_path_elements] IN path_sequence_p;
      pfp$convert_fs_path_to_pf_path (fs_path, path_p, ignore_cycle_reference, ignore_cycle_selector, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;

{  Attempt to create each subcatalog in the path while ignoring status. }

      FOR subcatalogs := first_subcatalog TO number_of_subcatalogs DO

        RESET path_sequence_p;
        NEXT path_p: [1 .. master_catalog + subcatalogs] IN path_sequence_p;

        pfp$define_catalog (path_p^, ignore_status);

      FOREND;
    IFEND;

  PROCEND prepare_loading_destination;

?? TITLE := 'validate_parameters', EJECT ??

{ PURPOSE:
{   This procedure validates the parameters passed into the calling
{   interface.
{
{ DESIGN:
{   The disk and tape parameters are verified that at least one of them has
{   been specified and that they have not been used together.  The
{   destination path is checked to be non-local and that no file or catalog
{   currently exist using that path.
{
{   Validation errors are returned in the status variable.
{
{ NOTES:
{   Currently, we are not testing that the destination path references an
{   existing catalog.  AMP$GET_FILE_ATTRIBUTES (sometime around 1.4.1) will
{   be modified to support this test.  At that time the test will be
{   implemented.  Until that happens the test will go undone.  There should
{   not be any consequences from not testing other than the the error
{   message may not be as nice as it could.  RESTORE_PERMANENT_FILES
{   utility will trap the condition and the subsequent attempt to cleanup
{   an erroneously loaded file will do nothing since the object being
{   deleted would be a catalog and not a file.
{

  PROCEDURE validate_parameters
    (    external_vsn: clt$parameter_value;
         recorded_vsn: clt$parameter_value;
         tape_type: clt$parameter_value;
         disk_file: clt$parameter_value;
         destination_path: fst$file_reference;
     VAR status: ost$status);


    VAR
      existing_file: boolean,
      ignore_attributes: array [1 .. 1] of amt$get_item,
      ignore_contains_data: boolean,
      local_file: boolean;


    status.normal := TRUE;

{  Validate the disk and tape parameters. }

    IF (NOT external_vsn.specified) AND (NOT recorded_vsn.specified) AND (NOT disk_file.specified) THEN
      osp$set_status_abnormal ('RA', rae$disk_or_vsns_params_requird, '', status);
      RETURN;
    IFEND;

    IF (external_vsn.specified OR recorded_vsn.specified OR tape_type.specified)
          AND disk_file.specified THEN
      osp$set_status_abnormal ('RA', rae$loapl_params_specifying_err, '', status);
      RETURN;
    IFEND;

{  Test that the destination path is not under $LOCAL. }

    IF destination_path (1, 8) = ':$LOCAL.' THEN
      osp$set_status_abnormal ('RA', rae$pl_cannot_load_to_local_cat, '', status);
      RETURN;
    IFEND;

{  Test that the destination path does not already define a file or catalog (see note). }

    ignore_attributes [1].key := amc$file_length;

    amp$get_file_attributes (destination_path, ignore_attributes, local_file, existing_file,
          ignore_contains_data, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF local_file OR existing_file THEN
      osp$set_status_abnormal ('RA', rae$pl_path_already_being_used, '', status);
      osp$append_status_file (osc$status_parameter_delimiter, destination_path, status);
      RETURN;
    IFEND;

  PROCEND validate_parameters;

?? TITLE := 'verify_as_packing_list', EJECT ??

{ PURPOSE:
{   This procedure verifies that the file that was loaded is actually a
{   packing list.
{
{ DESIGN:
{   The file is opened as a segment access and a sequence descriptor record
{   is NEXT'd onto the open file.  The sequence type field in the
{   descriptor is read.  The file is not a packing list if the descriptor
{   cannot be NEXT'd on, or the suquence type field is not defined for a
{   packing list.
{
{   Validation errors are returned in the status variable.
{
{ NOTES:
{

  PROCEDURE verify_as_packing_list
    (    packing_list_file_ref: fst$file_reference;
     VAR status: ost$status);


    VAR
      file_opened: boolean,
      local_status: ost$status,
      packing_list_fid: amt$file_identifier,
      packing_list_seq_p: ^rat$packing_list_sequence,
      segment_p: amt$segment_pointer,
      sequence_descriptor_p: ^rat$sequence_descriptor;

?? NEWTITLE := 'abort_handler', EJECT ??

{ PURPOSE:
{   This procedure cleans up when an abort situation occurs within the
{   block structure.
{
{ DESIGN:
{   An attempt is made to close the packing list file with ignore status.
{
{ NOTES:
{

    PROCEDURE abort_handler
      (    condition: pmt$condition;
           condition_information: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR handler_status: ost$status);

      VAR
        ignore_status: ost$status;

      fsp$close_file (packing_list_fid, ignore_status);

    PROCEND abort_handler;

?? OLDTITLE, EJECT ??

    status.normal := TRUE;

    osp$establish_block_exit_hndlr (^abort_handler);

  /main/
    BEGIN

      rap$open_file (^packing_list_file_ref, amc$segment, fsc$read, FALSE, NIL, packing_list_fid, file_opened,
            status);
      IF NOT status.normal THEN
        EXIT /main/;
      IFEND;

      amp$get_segment_pointer (packing_list_fid, amc$sequence_pointer, segment_p, status);
      IF NOT status.normal THEN
        EXIT /main/;
      IFEND;
      packing_list_seq_p := segment_p.sequence_pointer;

      RESET packing_list_seq_p;
      NEXT sequence_descriptor_p IN packing_list_seq_p;
      IF sequence_descriptor_p = NIL THEN
        osp$set_status_abnormal ('RA', rae$access_error_verifying_pl, '', status);
        EXIT /main/;
      IFEND;

      IF sequence_descriptor_p^.sequence_type <> rac$packing_list_sequence THEN
        osp$set_status_abnormal ('RA', rae$not_a_packing_list, '', status);
        osp$append_status_file (osc$status_parameter_delimiter, packing_list_file_ref, status);
        EXIT /main/;
      IFEND;

      IF sequence_descriptor_p^.sequence_level <> rac$packing_list_level THEN
        osp$set_status_abnormal ('RA', rae$incompatible_sequence_level, 'PACKING LIST', status);
        osp$append_status_parameter (osc$status_parameter_delimiter, sequence_descriptor_p^.sequence_level,
              status);
      IFEND;

    END /main/;

    IF file_opened THEN
      fsp$close_file (packing_list_fid, local_status);
      IF status.normal THEN
        status := local_status;
      IFEND;
    IFEND;

    osp$disestablish_cond_handler;

  PROCEND verify_as_packing_list;

MODEND ram$load_packing_list;
