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

{ PURPOSE:
{   This module contains the interface and procedures that perform the step
{   to reconcile cycle conflicts.
{
{ DESIGN:
{   The compiled module resides in RAF$LIBRARY.
{
{ NOTES:
{

?? NEWTITLE := 'Global Declarations Referenced by This Module', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc rac$installation_cycles
*copyc rae$install_software_cc
*copyc rat$installation_control_record
?? POP ??
*copyc osp$generate_log_message
*copyc osp$append_status_file
*copyc osp$append_status_parameter
*copyc osp$set_status_abnormal
*copyc pfp$change
*copyc pfp$convert_fs_path_to_pf_path
*copyc pfp$convert_string_to_fs_path
*copyc rap$convert_path_to_str
*copyc rap$get_cycle_data
*copyc rap$record_step_status
*copyc rap$record_subproduct_status
*copyc rap$verify_catalog_exists

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

  TYPE
    rat#cycle_map = array [1 .. rac$max_active_cycle] of boolean;

  TYPE
    rat#reconcile_options = (rac#reconcile_for_install, rac#validate_for_activation);

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

{ PURPOSE:
{   This interface controls the step to reconcile cycle conflicts.
{
{ DESIGN:
{   There are two forms of cycle reconcile:  One, to reconcile subproducts
{   for installation.  The second, to validate subproducts for activation.
{   (The rules for reconcile and validate are found within RECONCILE_FILE
{   and VALIDATE_FILE_DEFERRED procedures, respectively.)
{
{   The reconcile option is choosen based on the task set.  When the load
{   files task is in the set, reconcile for installation is choosen.  When
{   activate files task is in the task set and load files task is not,
{   validate for activation is selected.
{
{   In the event a file fails reconciling, continue processing every file,
{   return with general error message.  The subproduct associated with the
{   file will be pulled from further processing.
{
{   Any bad status encountered at lower levels has been displayed to the
{   job log before returning from  reconcile_subproduct.  A general errors
{   encountered boolean is returned to help set the task status.
{
{ NOTES:
{   Shifting files has an effect on other jobs/tasks accessing the files at
{   the time the shift is performed.  If a job/task has the file opened and
{   the cycle number is changed, that job/task will continue to access the
{   same file as long as it remains opened.  But if a job/task is accessing
{   a specific file cycle with multiple opens around the time of a cycle
{   shift, the file would become lost to the job/task.
{
{   After 1.4.1 the need to shift on a properly maintained system should be
{   very rare.  Also installations are usually done during or around
{   deadstarts when the system is closed to production events.  Therefore,
{   the occurance of the just mentioned problem should be close to nil.
{   But this situation needs to be understood by the users of INSS.
{
{   If the installation catalog (or any lower level catalog) is found to
{   not exist, processing of the remaining element list under that catalog
{   will be skipped.
{
{   The SUBPRODUCTS_FAILED_PROCESSING boolean has been initialized outside of
{   this interface and should never be re-initialized here.
{

  PROCEDURE [XDCL] rap$reconcile_cycle_conflicts
    (VAR installation_control_record {input, output} : rat$installation_control_record;
     VAR subproducts_failed_processing: boolean;
     VAR status: ost$status);


    VAR
      catalog_exists: boolean,
      errors_encountered: boolean,
      ignore_status: ost$status,
      local_status: ost$status,
      processing_record: rat$subp_processing_record,
      reconcile_option: rat#reconcile_options,
      subproduct_index: rat$subproduct_count,
      subproduct_info_seq_p: ^rat$subproduct_info_sequence;


    status.normal := TRUE;

    IF NOT (rac$reconcile_subproducts_step IN installation_control_record.processing_header_p^.step_set) THEN
      RETURN;
    IFEND;

    rap$record_step_status (rac$reconcile_subproducts_step, rac$step_started, installation_control_record,
          status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

  /main/
    FOR subproduct_index := 1 TO UPPERBOUND (installation_control_record.subproduct_processing_records_p^) DO
      processing_record := installation_control_record.subproduct_processing_records_p^ [subproduct_index];

      IF (installation_control_record.job_identifier = processing_record.job_identifier) AND
            (rac$reconcile_file_cycles_task IN processing_record.task_set) AND
            (processing_record.task_status <> rac$task_failed) THEN

        rap$record_subproduct_status (rac$reconcile_file_cycles_task, rac$task_started, subproduct_index,
              installation_control_record, ignore_status);

        catalog_exists := TRUE;
        errors_encountered := FALSE;

        IF rac$load_files_task IN processing_record.task_set THEN
          reconcile_option := rac#reconcile_for_install;
        ELSE {rac$activate_files_task IN processing_record.task_set}
          reconcile_option := rac#validate_for_activation;
        IFEND;

        IF reconcile_option = rac#reconcile_for_install THEN
          rap$verify_catalog_exists (processing_record.installation_catalog_p^,
                installation_control_record.scratch_seq_p, catalog_exists);
        IFEND;

        IF catalog_exists THEN
          reconcile_subproduct (reconcile_option, processing_record.installation_catalog_p^,
                processing_record.subproduct_info_pointers.element_list_p,
                processing_record.subproduct_info_pointers.subproduct_info_seq_p,
                installation_control_record.scratch_seq_p, errors_encountered);
        IFEND;

        IF (NOT catalog_exists) OR (NOT errors_encountered) THEN
          rap$record_subproduct_status (rac$reconcile_file_cycles_task, rac$task_completed, subproduct_index,
                installation_control_record, ignore_status);
        ELSE {subproduct failed processing}
          rap$record_subproduct_status (rac$reconcile_file_cycles_task, rac$task_failed, subproduct_index,
                installation_control_record, ignore_status);
          subproducts_failed_processing := TRUE;
        IFEND;

      IFEND;
    FOREND /main/;

    rap$record_step_status (rac$reconcile_subproducts_step, rac$step_completed, installation_control_record,
          local_status);
    IF status.normal AND (NOT local_status.normal) THEN
      status := local_status;
    IFEND;

  PROCEND rap$reconcile_cycle_conflicts;

?? TITLE := 'compress_to_first_active_cycle', EJECT ??

{ PURPOSE:
{   This procedure compresses all cycles down, starting at the first active
{   cycle in sequential order.
{
{ DESIGN:
{   As an example of cycle compression, assume a file contains the cycles
{   5, 6, 15 and 999.  Also assume that the first active cycle is 3.  After
{   compression the file would contain cycles 3, 4, 5, and  6.
{
{   The first active cycle is one greater than the staging cycle.
{
{ NOTES:
{

  PROCEDURE compress_to_first_active_cycle
    (    file_path: pft$path;
     VAR cycle_exists {input, output} : rat#cycle_map;
     VAR status: ost$status);


    VAR
      current_cycle: pft$cycle_selector,
      cycle_number: 0 .. rac$max_active_cycle,
      first_free_cycle: 0 .. rac$max_active_cycle,
      free_cycle: 0 .. rac$max_active_cycle,
      new_cycle: array [1 .. 1] of pft$change_descriptor,
      password: pft$password;


    status.normal := TRUE;
    current_cycle.cycle_option := pfc$specific_cycle;
    new_cycle [1].change_type := pfc$cycle_number_change;
    password := '';

    { Locate the first free cycle starting after the first active cycle.  A
    { free cycle is guarenteed because of a check by calling procedure.

    cycle_number := rac$staging_cycle + 1;
    WHILE cycle_exists [cycle_number] DO
      cycle_number := cycle_number + 1;
    WHILEND;
    first_free_cycle := cycle_number;
    free_cycle := first_free_cycle;

    { Compress all the cycles that follow the first free cycle.  The cycle map
    { must be updated to reflect the change.  The next free cycle is the cycle
    { following the current free cycle.

    FOR cycle_number := (first_free_cycle + 1) TO rac$max_active_cycle DO
      IF cycle_exists [cycle_number] THEN
        current_cycle.cycle_number := cycle_number;
        new_cycle [1].cycle_number := free_cycle;
        pfp$change (file_path, current_cycle, password, new_cycle, status);
        IF NOT status.normal THEN
          RETURN;
        IFEND;
        cycle_exists [cycle_number] := FALSE;
        cycle_exists [free_cycle] := TRUE;

        free_cycle := free_cycle + 1;
      IFEND;
    FOREND;

  PROCEND compress_to_first_active_cycle;

?? TITLE := 'reconcile_file', EJECT ??

{ PURPOSE:
{   This procedure reconciles cycle conflicts for a file when they exist.
{
{ DESIGN:
{   The file cycles are checked for the availability of file cycles to
{   install into.  This means cycles 1, 2 and 999.
{
{   If files currently reside in any of these cycles they are shifted out of
{   the way, more precisely:
{
{     a.  If cycles 1 and/or 2 exist, they and any cycles sequentially
{     in the way are shifted up, starting at cycle 3.
{
{     b.  If a cycle 999 exists, all cycles are shifted down so that
{     the existing cycles will now occupy cycles 3 and up
{     (sequentially).
{
{     c.  If both case (a) and (b) exist, (a) is performed first and
{     then (b).  This causes the cycles to be compressed.
{
{   When there are not enough empty cycles to perform the shift, an error
{   will be reported and the affected subproduct will be dropped from any
{   further processing.
{
{ NOTES:
{   The type CYCLE_MAP is an array of booleans, one for each cycle
{   possible.  The index into the array represents a possible cycle number
{   and the boolean value defines whether or not that cycle actually
{   exists.  The use of the variable name CYCLE_EXISTS helps convey this
{   meaning when an array index, such as CYCLE_NUMBER is used.
{
{   The scratch sequence is used by GET_CYCLE_DATA as temporary storage
{   for file information.
{

  PROCEDURE reconcile_file
    (    file_path: pft$path;
     VAR scratch_seq_p {input} : ^SEQ ( * );
     VAR status: ost$status);


    VAR
      conflicting_cycles_count: 0 .. rac$staging_cycle,
      cycle_exists_p: ^rat#cycle_map,
      cycles_p: pft$p_cycle_array,
      file_fs: rat$path,
      i: 0 .. rac$max_active_cycle,
      number_of_cycles: 0 .. rac$max_active_cycle;


    status.normal := TRUE;

    rap$get_cycle_data (file_path, scratch_seq_p, cycles_p, status);
    IF NOT status.normal THEN
      IF (status.condition = pfe$unknown_permanent_file) OR
            (status.condition = pfe$unknown_last_subcatalog) OR
            (status.condition = pfe$unknown_nth_subcatalog) OR
            (status.condition = pfe$unknown_master_catalog) OR (status.condition = pfe$unknown_family) THEN
        status.normal := TRUE;
      IFEND;
      RETURN;
    IFEND;

    number_of_cycles := UPPERBOUND (cycles_p^);

    IF number_of_cycles > rac$max_active_cycle - 3 THEN

      { This means that cycle conflicts exist and that there are not enough
      { free cycles available to move them out of the way.

      rap$convert_path_to_str (file_path, file_fs);
      osp$set_status_abnormal ('RA', rae$unable_to_reconcile_cycles, '', status);
      osp$append_status_file (osc$status_parameter_delimiter, file_fs.path (1, file_fs.size), status);
      RETURN;
    IFEND;

    { Initialize the cycle map and set cycles that exist.

    PUSH cycle_exists_p;
    FOR i := 1 TO UPPERBOUND (cycle_exists_p^) DO
      cycle_exists_p^ [i] := FALSE;
    FOREND;
    FOR i := 1 TO number_of_cycles DO
      cycle_exists_p^ [cycles_p^ [i].cycle_number] := TRUE;
    FOREND;

    { Check for cycle conflicts and correct any found.

    conflicting_cycles_count := 0;
    IF cycle_exists_p^ [rac$loading_cycle] THEN
      conflicting_cycles_count := conflicting_cycles_count + 1;
    IFEND;
    IF cycle_exists_p^ [rac$staging_cycle] THEN
      conflicting_cycles_count := conflicting_cycles_count + 1;
    IFEND;

    IF conflicting_cycles_count > 0 THEN
      shift_to_first_active_cycle (file_path, conflicting_cycles_count, cycle_exists_p^, status);
    IFEND;

    IF cycle_exists_p^ [rac$max_active_cycle] THEN
      compress_to_first_active_cycle (file_path, cycle_exists_p^, status);
    IFEND;


  PROCEND reconcile_file;

?? TITLE := 'reconcile_subproduct', EJECT ??

{ PURPOSE:
{   This procedure reconciles any cycle conflicts a subproduct.
{
{ DESIGN:
{   The element list for each subproduct to be processed is used.  Each
{   element in the list describes a file or catalog belonging to that
{   subproduct.
{
{   The element list is traversed.  The traverse is performed recursively.
{   Each call to RECONCILE_SUBPRODUCT processes the next level subcatalog
{   of the subproduct.
{
{   The processing of active and inactive elements is handled differently
{   than in the other steps.  Inactive elements are excluded only when
{   validating that the files are deferred.  An inactive catalog element
{   means that all elements associated with that catalog are also inactive.
{
{ NOTES:
{   When an element turns out to be a catalog it is checked to verify that
{   it exists.  If the catalog does not exist there is no need to check the
{   elements under it.
{
{   The scratch sequence is used by a subsequent procedure as temporary
{   storage for file cycle information.
{
{   The state of the scratch sequence pointers and content is not retained.
{

  PROCEDURE reconcile_subproduct
    (    reconcile_option: rat#reconcile_options;
         element_path: pft$path;
     VAR element_p {input} : ^rat$element;
     VAR subproduct_info_seq_p {input} : ^rat$subproduct_info_sequence;
     VAR scratch_seq_p {input} : ^SEQ ( * );
     VAR errors_encountered {input, output} : boolean);


    VAR
      catalog_exists: boolean,
      current_element_path_p: ^pft$path,
      first_element_down_p: ^rat$element,
      i: integer,
      ignore_status: ost$status,
      local_status: ost$status;


    { The element_path parameter is the path for the current catalog.  Create
    { a PF format path array that is 1 larger than the size of the element
    { path.  This array will be used to construct the PF paths for the files
    { and subcatalogs that reside in the current catalog.

    PUSH current_element_path_p: [1 .. UPPERBOUND (element_path) + 1];
    FOR i := 1 TO UPPERBOUND (element_path) DO
      current_element_path_p^ [i] := element_path [i];
    FOREND;

    { Process the files and subcatalogs at the current catalog level.

    WHILE element_p <> NIL DO

      current_element_path_p^ [UPPERBOUND (current_element_path_p^)] := element_p^.name;

      IF (element_p^.active_element) OR (reconcile_option = rac#reconcile_for_install) THEN

        IF element_p^.element_type = rac$file THEN

          IF reconcile_option = rac#reconcile_for_install THEN
            reconcile_file (current_element_path_p^, scratch_seq_p, local_status);
          ELSE { reconcile option is rac#validate_for_activation }
            validate_file_deferred (current_element_path_p^, scratch_seq_p, local_status);
          IFEND;
          IF NOT local_status.normal THEN
            osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], local_status, ignore_status);
            errors_encountered := TRUE;
          IFEND;

        ELSEIF (element_p^.element_type = rac$catalog) AND (element_p^.element_count <> 0) THEN

          catalog_exists := TRUE;
          IF reconcile_option = rac#reconcile_for_install THEN
            rap$verify_catalog_exists (current_element_path_p^, scratch_seq_p, catalog_exists);
          IFEND;

          IF catalog_exists THEN
            first_element_down_p := #PTR (element_p^.first_element_down_p, subproduct_info_seq_p^);

            reconcile_subproduct (reconcile_option, current_element_path_p^, first_element_down_p,
                  subproduct_info_seq_p, scratch_seq_p, errors_encountered);
          IFEND;
        IFEND;
      IFEND;

      element_p := #PTR (element_p^.next_element_across_p, subproduct_info_seq_p^);
    WHILEND;

  PROCEND reconcile_subproduct;

?? TITLE := 'shift_to_first_active_cycle', EJECT ??

{ PURPOSE:
{   This procedure shifts any conflicting cycles found in the loading
{   and staging cycles up to the first active cycle.
{
{ DESIGN:
{   Any files in the way are themselves shifted up out of the way.  For
{   example, assume a file contains the cycles 1, 2, 4, 5, and 6.  Also
{   assume the first active cycle is 3.  After shifting the file will
{   contain cycles 3, 4, 5, 6, 7 and 8.
{
{   The first active cycle is one greater than the staging cycle.
{
{ NOTES:
{

  PROCEDURE shift_to_first_active_cycle
    (    file_path: pft$path;
         conflicting_cycles_count: 0 .. rac$max_active_cycle;
     VAR cycle_exists {input, output} : rat#cycle_map;
     VAR status: ost$status);


    VAR
      current_cycle: pft$cycle_selector,
      cycle_number: 0 .. rac$max_active_cycle,
      first_free_cycle: 0 .. rac$max_active_cycle,
      free_cycle: 0 .. rac$max_active_cycle,
      free_cycles_count: 0 .. rac$max_active_cycle,
      new_cycle: array [1 .. 1] of pft$change_descriptor,
      password: pft$password;


    status.normal := TRUE;
    current_cycle.cycle_option := pfc$specific_cycle;
    new_cycle [1].change_type := pfc$cycle_number_change;
    password := '';

    { Find enough free cycles starting with the first active cycle to shift
    { the conflicting cycles out of the loading and/or staging cycles.  The
    { required number of free cycles are guarenteed because of a check by
    { calling procedure.

    free_cycles_count := 0;
    cycle_number := rac$staging_cycle + 1;
    WHILE free_cycles_count < conflicting_cycles_count DO
      IF NOT cycle_exists [cycle_number] THEN
        free_cycles_count := free_cycles_count + 1;
      IFEND;
      cycle_number := cycle_number + 1;
    WHILEND;
    first_free_cycle := cycle_number - 1;
    free_cycle := first_free_cycle;

    { Shift all cycles that preceed the first free cycle.  The cycle map must
    { be updated to reflect the change.  The next free cycle is the cycle
    { preceeding the current free cycle.

    FOR cycle_number := (first_free_cycle - 1) DOWNTO 1 DO
      IF cycle_exists [cycle_number] THEN
        current_cycle.cycle_number := cycle_number;
        new_cycle [1].cycle_number := free_cycle;
        pfp$change (file_path, current_cycle, password, new_cycle, status);
        IF NOT status.normal THEN
          RETURN;
        IFEND;
        cycle_exists [cycle_number] := FALSE;
        cycle_exists [free_cycle] := TRUE;

        free_cycle := free_cycle - 1;
      IFEND;
    FOREND;

  PROCEND shift_to_first_active_cycle;

?? TITLE := 'validate_file_deferred', EJECT ??

{ PURPOSE:
{   This procedure verifies that the file is deferred and that there is an
{   open active cycle.  Any fault is returned in the status variable.
{
{ DESIGN:
{   An error is returned when the staging cycle does not exist.
{
{   If a cycle 999 exists, all cycles are shifted down so that the existing
{   cycles will now occupy cycles 3 and up (sequentially).
{
{   When there are not enough empty cycles to perform the shift, an error
{   will be reported and the affected subproduct will be dropped from any
{   further processing.
{
{ NOTES:
{   The type CYCLE_MAP is an array of booleans, one for each cycle
{   possible.  The index into the array represents a possible cycle number
{   and the boolean value defines whether or not that cycle actually
{   exists.  The use of the variable name CYCLE_EXISTS helps convey this
{   meaning when an array index, such as CYCLE_NUMBER is used.
{
{   The scratch sequence is used by GET_CYCLE_DATA as temporary storage
{   for file information.
{

  PROCEDURE validate_file_deferred
    (    file_path: pft$path;
     VAR scratch_seq_p {input} : ^SEQ ( * );
     VAR status: ost$status);


    VAR
      cycle_exists_p: ^rat#cycle_map,
      cycles_p: pft$p_cycle_array,
      file_fs: rat$path,
      i: 0 .. rac$max_active_cycle,
      number_of_cycles: 0 .. rac$max_active_cycle;


    status.normal := TRUE;

    rap$get_cycle_data (file_path, scratch_seq_p, cycles_p, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;

    IF UPPERBOUND (cycles_p^) > rac$max_active_cycle - 2 THEN
      { This means that cycle conflicts exist and that there are not enough
      { free cycles available to move them out of the way.

      rap$convert_path_to_str (file_path, file_fs);
      osp$set_status_abnormal ('RA', rae$unable_to_reconcile_cycles, '', status);
      osp$append_status_file (osc$status_parameter_delimiter, file_fs.path (1, file_fs.size), status);
      RETURN;
    IFEND;

    number_of_cycles := UPPERBOUND (cycles_p^);

    { Initialize the cycle map and set cycles that exist.

    PUSH cycle_exists_p;
    FOR i := 1 TO UPPERBOUND (cycle_exists_p^) DO
      cycle_exists_p^ [i] := FALSE;
    FOREND;
    FOR i := 1 TO number_of_cycles DO
      cycle_exists_p^ [cycles_p^ [i].cycle_number] := TRUE;
    FOREND;

    { Check for the existence of the staging cycle and then a free max active cycle.

    IF NOT cycle_exists_p^ [rac$staging_cycle] THEN
      rap$convert_path_to_str (file_path, file_fs);
      osp$set_status_abnormal ('RA', rae$file_not_deferred, '', status);
      osp$append_status_file (osc$status_parameter_delimiter, file_fs.path (1, file_fs.size), status);

    ELSEIF cycle_exists_p^ [rac$max_active_cycle] THEN
      compress_to_first_active_cycle (file_path, cycle_exists_p^, status);
    IFEND;

  PROCEND validate_file_deferred;
MODEND ram$reconcile_cycle_conflicts;
