?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Device Management' ??
MODULE dmm$reconcile_fmd;
?? RIGHT := 110 ??

{
{ PURPOSE:
{
{  The purpose of this module is to perform the Device Management functions
{  necessary to reconcile the files known to Permanent File Manager with
{  those known to Device Manager.
{
{ DESIGN:
{
{  Permanent File Manager performs the reconciliation process as follows.
{
{    1. Builds a list of all files known to Device Manager
{       (dmp$build_sorted_dfl).
{
{    2. Reports files found in permanent file catalogs (dmp$reconcile_fmd),
{       deleting any not known to Device Manager and updating the FMD for
{       those requiring it (dmp$get_reconciled_fmd).
{
{    3. Deletes any files in the Device Management list that have not been
{       reconciled (dmp$device_file_list_update).  Some files may be marked
{       for deletion when they are reconciled (e.g. catalogs).
{
?? TITLE := '  Common Decks', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc oss$job_paged_literal
*copyc rmc$mass_storage_class
*copyc stt$set_name
*copyc ste$error_condition_codes
*copyc dmt$error_condition_codes
*copyc dmt$fmd_index
*copyc dmt$global_file_name
*copyc dmt$reconcile_info
*copyc dmt$reconcile_locator
*copyc dmt$stored_fmd
*copyc dmt$stored_fmd_size
*copyc dmt$stored_ms_fmd_header
*copyc ost$caller_identifier
*copyc ost$name
*copyc ost$status
*copyc ost$string
?? POP ??
*copyc clp$put_job_output
*copyc dmp$close_dfl_r3
*copyc dmp$destroy_sub_file
*copyc dmp$dev_mgmt_table_update
*copyc dmp$open_dfl_r3
*copyc dmp$save_reconcile_list
*copyc dmp$search_avt_by_lun
*copyc dmp$search_avt_by_vsn
*copyc dmp$update_reconcile_list
*copyc mmp$create_scratch_segment
*copyc mmp$delete_scratch_segment
*copyc osp$append_status_parameter
*copyc osp$clear_job_signature_lock
*copyc osp$set_job_signature_lock
*copyc osp$set_status_abnormal
*copyc pfp$log_ascii
*copyc pfp$log_error
*copyc pmp$convert_binary_unique_name
*copyc pmp$get_legible_date_time
*copyc pmp$get_microsecond_clock
*copyc stp$get_volumes_in_set
*copyc stp$search_ast_by_internal_vsn
*copyc dmv$active_volume_table
*copyc dmv$reconcile_locator
*copyc dmv$reconciliation_lock
*copyc dmv$ds_msg_update_interval
?? TITLE := '  Global Declarations', EJECT ??

  TYPE
    dmt$compare_status = (less, equal, greater),
    switch_values = (switch_sides, switch_areas, switch_compare);

?? TITLE := '  dmp$add_to_sorted_dfl', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$add_to_sorted_dfl
    (    lun: iot$logical_unit;
     VAR status: ost$status);

    VAR
      avt_index: dmt$active_volume_table_index,
      close_status: ost$status,
      dfl_index: dmt$device_file_list_index,
      dfl_entry: dmt$ms_device_file_list_entry,
      p_dfl: ^dmt$ms_device_file_list_table,
      p_sequence: ^SEQ ( * ),
      p_old_reconcile_info,
      p_reconcile_info: ^dmt$reconcile_info,
      added_fmd_count,
      fmd_count: dmt$reconcile_index,
      found: boolean,
      reconcile_entry: dmt$reconcile_entry,
      p_reconcile_entry: ^dmt$reconcile_entry,
      segment_length: ost$segment_length,
      segment_pointer: amt$segment_pointer,
      vsn: rmt$recorded_vsn;

    status.normal := TRUE;
    dmp$search_avt_by_lun (lun, avt_index, found);
    IF NOT found THEN
      RETURN; {----->
    IFEND;

    osp$set_job_signature_lock (dmv$reconciliation_lock);
    dmp$dev_mgmt_table_update;
    mmp$create_scratch_segment (amc$sequence_pointer, mmc$as_random, segment_pointer, status);
    IF NOT status.normal THEN
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;
    p_sequence := segment_pointer.sequence_pointer;

    RESET p_sequence;
    NEXT p_reconcile_info IN p_sequence;

    IF dmv$reconcile_locator <> NIL THEN
      {Copy existing reconcile list to new list
      p_old_reconcile_info := dmv$reconcile_locator;
      fmd_count := UPPERBOUND (p_old_reconcile_info^.p_sorted_reconcile_list^) -
            LOWERBOUND (p_old_reconcile_info^.p_sorted_reconcile_list^) + 1;
      NEXT p_reconcile_info^.p_sorted_reconcile_list: [1 .. fmd_count] IN p_sequence;
      p_reconcile_info^.p_sorted_reconcile_list^ := p_old_reconcile_info^.p_sorted_reconcile_list^;
    ELSE
      fmd_count := 0;
    IFEND;

    vsn := dmv$active_volume_table.table_p^ [avt_index].mass_storage.recorded_vsn;

    dmp$open_dfl_r3 (vsn, p_dfl, status);
    IF NOT status.normal THEN
      mmp$delete_scratch_segment (segment_pointer, close_status);
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;

    added_fmd_count := 0;

  /dfl_open/
    FOR dfl_index := 1 TO UPPERBOUND (p_dfl^.entries) DO
      dfl_entry := p_dfl^.entries [dfl_index];
      IF (dfl_entry.flags = dmc$dfle_assigned_to_file) AND
            ((dfl_entry.file_kind = gfc$fk_job_permanent_file) OR (dfl_entry.file_kind = gfc$fk_catalog)) THEN
        reconcile_entry.global_file_name := dfl_entry.global_file_name;
        reconcile_entry.byte_address := dfl_entry.file_byte_address;
        reconcile_entry.avt_index := avt_index;
        reconcile_entry.dfl_index := dfl_index;
        reconcile_entry.reconciled := FALSE;
        reconcile_entry.purge := TRUE;
        NEXT p_reconcile_entry IN p_sequence;
        p_reconcile_entry^ := reconcile_entry;
        fmd_count := fmd_count + 1;
        added_fmd_count := added_fmd_count + 1;
      IFEND;
    FOREND /dfl_open/;

    dmp$close_dfl_r3 (p_dfl, status);
    IF NOT status.normal THEN
      mmp$delete_scratch_segment (segment_pointer, close_status);
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;

    IF added_fmd_count = 0 THEN
      mmp$delete_scratch_segment (segment_pointer, close_status);
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;

    {Sort the reconcile_list.
    RESET p_sequence;
    NEXT p_reconcile_info IN p_sequence;
    NEXT p_reconcile_info^.p_sorted_reconcile_list: [1 .. fmd_count] IN p_sequence;
    heap_sort (p_reconcile_info^.p_sorted_reconcile_list);
    {Copy the reconcile_list to mainframe_pageable.
    dmp$save_reconcile_list (p_reconcile_info^);
    mmp$delete_scratch_segment (segment_pointer, close_status);

    osp$clear_job_signature_lock (dmv$reconciliation_lock);

  PROCEND dmp$add_to_sorted_dfl;
?? TITLE := '  dmp$build_sorted_dfl', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$build_sorted_dfl
    (    set_name: stt$set_name;
     VAR reconcile_locator: dmt$reconcile_locator;
     VAR status: ost$status);

    CONST
      second = 1000000;

    VAR
      avt_index: dmt$active_volume_table_index,
      base: integer,
      clock: integer,
      close_status: ost$status,
      date: ost$date,
      dfl_index: dmt$device_file_list_index,
      dfl_entry: dmt$ms_device_file_list_entry,
      l: integer,
      local_stat: ost$status,
      msg: string (70),
      p_dfl: ^dmt$ms_device_file_list_table,
      p_sequence: ^SEQ ( * ),
      p_reconcile_info: ^dmt$reconcile_info,
      fmd_count: dmt$reconcile_index,
      reconcile_entry: dmt$reconcile_entry,
      p_reconcile_entry: ^dmt$reconcile_entry,
      segment_length: ost$segment_length,
      segment_pointer: amt$segment_pointer,
      time: ost$time,
      display_update_interval: integer,
      vsn: rmt$recorded_vsn;

    osp$set_job_signature_lock (dmv$reconciliation_lock);
    dmp$dev_mgmt_table_update;
    mmp$create_scratch_segment (amc$sequence_pointer, mmc$as_random, segment_pointer, status);
    IF NOT status.normal THEN
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;
    p_sequence := segment_pointer.sequence_pointer;

    RESET p_sequence;
    NEXT p_reconcile_info IN p_sequence;
    p_reconcile_info^.p_sorted_reconcile_list := NIL;

    {Add an entry to the reconcile list for every dfl entry on every active mass storage device.

    base := 0;
    display_update_interval := dmv$ds_msg_update_interval * second;
    msg (1, * ) := ' ';
    fmd_count := 0;
    FOR avt_index := LOWERBOUND (dmv$active_volume_table.table_p^)
          TO UPPERBOUND (dmv$active_volume_table.table_p^) DO
      IF NOT dmv$active_volume_table.table_p^ [avt_index].entry_available AND
            NOT dmv$active_volume_table.table_p^ [avt_index].mass_storage.volume_unavailable AND
            (dmv$active_volume_table.table_p^ [avt_index].mass_storage.set_name <> osc$null_name) THEN

        vsn := dmv$active_volume_table.table_p^ [avt_index].mass_storage.recorded_vsn;

        pmp$get_microsecond_clock (clock, local_stat);
        IF local_stat.normal AND (clock > (base + display_update_interval)) THEN
          base := clock;
          pmp$get_legible_date_time (osc$mdy_date, date, osc$hms_time, time, local_stat);
          IF local_stat.normal THEN
            STRINGREP (msg, l, '   .. scanning ', vsn, ' ', date.mdy, ' ', time.hms);
            clp$put_job_output (msg (1, l), local_stat);
            msg (1, * ) := ' ';
          IFEND;
        IFEND;

        dmp$open_dfl_r3 (vsn, p_dfl, status);
        IF NOT status.normal THEN
          mmp$delete_scratch_segment (segment_pointer, close_status);
          osp$clear_job_signature_lock (dmv$reconciliation_lock);
          RETURN; {----->
        IFEND;

        FOR dfl_index := 1 TO UPPERBOUND (p_dfl^.entries) DO
          dfl_entry := p_dfl^.entries [dfl_index];

          IF (dfl_entry.flags = dmc$dfle_assigned_to_file) AND
                ((dfl_entry.file_kind = gfc$fk_job_permanent_file) OR (dfl_entry.file_kind = gfc$fk_catalog))
                THEN
            reconcile_entry.global_file_name := dfl_entry.global_file_name;
            reconcile_entry.byte_address := dfl_entry.file_byte_address;
            reconcile_entry.avt_index := avt_index;
            reconcile_entry.dfl_index := dfl_index;
            reconcile_entry.reconciled := FALSE;
            reconcile_entry.purge := TRUE;

            NEXT p_reconcile_entry IN p_sequence;
            p_reconcile_entry^ := reconcile_entry;
            fmd_count := fmd_count + 1;
          IFEND;
        FOREND;

        dmp$close_dfl_r3 (p_dfl, status);
        IF NOT status.normal THEN
          mmp$delete_scratch_segment (segment_pointer, close_status);
          osp$clear_job_signature_lock (dmv$reconciliation_lock);
          RETURN; {----->
        IFEND;
      IFEND;
    FOREND;

    {Sort the reconcile_list.

    IF fmd_count > 0 THEN
      RESET p_sequence;
      NEXT p_reconcile_info IN p_sequence;
      NEXT p_reconcile_info^.p_sorted_reconcile_list: [1 .. fmd_count] IN p_sequence;
      heap_sort (p_reconcile_info^.p_sorted_reconcile_list);

      {Copy the reconcile_list to mainframe_pageable.

      dmp$save_reconcile_list (p_reconcile_info^);
    IFEND;

    mmp$delete_scratch_segment (segment_pointer, close_status);
    osp$clear_job_signature_lock (dmv$reconciliation_lock);

  PROCEND dmp$build_sorted_dfl;
?? TITLE := '  dmp$device_file_list_update', EJECT ??

  PROCEDURE [XDCL] dmp$device_file_list_update
    (    set_name: stt$set_name;
     VAR status: ost$status);

    CONST
      critical_message = TRUE,
      message_origin = pmc$msg_origin_recovery;

    VAR
      avt_index: dmt$active_volume_table_index,
      gfn: dmt$global_file_name,
      vsn: rmt$recorded_vsn,
      dfl_index: dmt$device_file_list_index,
      byte_address: amt$file_byte_address,
      local_status: ost$status,
      p_reconcile_info: ^dmt$reconcile_info,
      p_reconcile_list: dmt$p_reconcile_list,
      reconcile_entry: dmt$reconcile_entry,
      index: dmt$reconcile_index;

    osp$set_job_signature_lock (dmv$reconciliation_lock);
    status.normal := TRUE;
    IF dmv$reconcile_locator = NIL THEN
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;
    p_reconcile_info := dmv$reconcile_locator;
    p_reconcile_list := p_reconcile_info^.p_sorted_reconcile_list;

    FOR index := 1 TO UPPERBOUND (p_reconcile_list^) DO
      reconcile_entry := p_reconcile_list^ [index];
      avt_index := reconcile_entry.avt_index;

      IF reconcile_entry.purge AND (dmv$active_volume_table.table_p^ [avt_index].mass_storage.set_name =
            set_name) THEN
        gfn := reconcile_entry.global_file_name;
        vsn := dmv$active_volume_table.table_p^ [avt_index].mass_storage.recorded_vsn;
        dfl_index := reconcile_entry.dfl_index;
        byte_address := reconcile_entry.byte_address;

        dmp$destroy_sub_file (gfn, vsn, dfl_index, byte_address, local_status);
        IF NOT local_status.normal THEN
          status := local_status;
          pfp$log_error (status, -$pmt$ascii_logset [], message_origin, critical_message);
        IFEND;

        IF NOT reconcile_entry.reconciled THEN
          {Just ignore
        IFEND;
      IFEND;
    FOREND;

    IF NOT status.normal THEN
      pfp$log_ascii ('Previous error(s) from dmp$destroy_sub_file.', -$pmt$ascii_logset [], message_origin,
            critical_message, local_status);
    IFEND;
    osp$clear_job_signature_lock (dmv$reconciliation_lock);
  PROCEND dmp$device_file_list_update;
?? TITLE := '  dmp$get_reconciled_fmd', EJECT ??

  PROCEDURE [XDCL] dmp$get_reconciled_fmd
    (    reconcile_locator: dmt$reconcile_locator;
         gfn: dmt$global_file_name;
         old_fmd: dmt$stored_fmd;
     VAR new_fmd: dmt$stored_fmd;
     VAR status: ost$status);

    VAR
      p_reconcile_info: ^dmt$reconcile_info,
      p_reconcile_list: dmt$p_reconcile_list,
      first_fmd_index: dmt$reconcile_index,
      fmd_count: dmt$reconcile_index;

    osp$set_job_signature_lock (dmv$reconciliation_lock);
    p_reconcile_info := dmv$reconcile_locator;
    p_reconcile_list := p_reconcile_info^.p_sorted_reconcile_list;

    find_file (gfn, p_reconcile_list, first_fmd_index, fmd_count, status);
    IF status.normal THEN
      build_new_fmd (old_fmd, gfn, p_reconcile_list, first_fmd_index, fmd_count, new_fmd, status);
    IFEND;
    osp$clear_job_signature_lock (dmv$reconciliation_lock);
  PROCEND dmp$get_reconciled_fmd;
?? TITLE := '  dmp$reconcile_fmd', EJECT ??

  PROCEDURE [XDCL] dmp$reconcile_fmd
    (    reconcile_locator: dmt$reconcile_locator;
         gfn: dmt$global_file_name;
         fmd: dmt$stored_fmd;
         purge_file: boolean;
     VAR device_class: dmt$class;
     VAR new_fmd_size: dmt$stored_fmd_size;
     VAR resides_on_set_master: boolean;
     VAR status: ost$status);

    VAR
      ast_entry: stt$active_set_entry,
      ast_index: stt$ast_index,
      avt_index: dmt$active_volume_table_index,
      first_fmd_index: dmt$reconcile_index,
      fmd_byte_address: amt$file_byte_address,
      fmd_count: dmt$reconcile_index,
      fmd_dfl_index: dmt$device_file_list_index,
      fmd_fmd_count: dmt$reconcile_index,
      fmd_fmd_index: dmt$fmd_index,
      fmd_index: dmt$reconcile_index,
      fmd_internal_vsn: dmt$internal_vsn,
      fmd_ok: boolean,
      fmd_recorded_vsn: rmt$recorded_vsn,
      fmds_not_reconciled: integer,
      fmds_with_vol_unavailable: integer,
      fmds_with_volume_missing: integer,
      found: boolean,
      internal_vsn_name: ost$name,
      kludge: ^dmt$stored_ms_fmd_header,
      master_info: stt$volume_info,
      member_count: stt$number_of_members,
      member_list: ^stt$volume_list,
      p_fmd_header: ^dmt$stored_ms_fmd_header,
      p_fmd_subfile: ^dmt$stored_ms_fmd_subfile,
      p_fmd_subfile_list: ^dmt$stored_fmd,
      p_reconcile_info: ^dmt$reconcile_info,
      p_reconcile_list: dmt$p_reconcile_list,
      reconcile_entry: dmt$reconcile_entry,
      recorded_vsn: rmt$recorded_vsn;

    osp$set_job_signature_lock (dmv$reconciliation_lock);
    IF dmv$reconcile_locator = NIL THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$unknown_file, 'Unknown file.', status);
      osp$clear_job_signature_lock (dmv$reconciliation_lock);
      RETURN; {----->
    IFEND;
    p_reconcile_info := dmv$reconcile_locator;
    p_reconcile_list := p_reconcile_info^.p_sorted_reconcile_list;
    fmds_with_volume_missing := 0;
    fmds_with_vol_unavailable := 0;
    fmds_not_reconciled := 0;
    device_class := -$dmt$class [];

    validate_fmd (^fmd, gfn, p_fmd_header, p_fmd_subfile_list, status);
    IF status.normal THEN
      find_file (gfn, p_reconcile_list, first_fmd_index, fmd_count, status);
    IFEND;

    IF (NOT status.normal) AND (status.condition = dme$unknown_file) THEN
      NEXT p_fmd_subfile: [dmc$current_fmd_version] IN p_fmd_subfile_list;
      dmp$search_avt_by_vsn (p_fmd_subfile^.version_0_0.internal_vsn, avt_index, found);
      IF NOT found THEN
        { NONE of the fmds are present.
        osp$set_status_abnormal (dmc$device_manager_ident, dme$some_volumes_not_online,
              p_fmd_subfile^.version_0_0.recorded_vsn, status);
      ELSEIF dmv$active_volume_table.table_p^ [avt_index].mass_storage.volume_unavailable THEN
        { The first fmd resides on an unavailable volume.
        osp$set_status_abnormal (dmc$device_manager_ident, dme$volume_unavailable,
              p_fmd_subfile^.version_0_0.recorded_vsn, status);
      ELSE
        stp$search_ast_by_internal_vsn (p_fmd_subfile^.version_0_0.internal_vsn, ast_entry, ast_index, found);
        IF NOT found THEN
          pmp$convert_binary_unique_name (p_fmd_subfile^.version_0_0.internal_vsn, internal_vsn_name, status);
          osp$set_status_abnormal (stc$set_management_id, ste$vol_not_found, internal_vsn_name, status);
        IFEND;
      IFEND;
    IFEND;

    IF status.normal THEN
      FOR fmd_index := first_fmd_index TO first_fmd_index + fmd_count - 1 DO
        dmp$update_reconcile_list (fmd_index, purge_file, {reconcile =} TRUE);
      FOREND;
      PUSH member_list: [1 .. 1];
      {Just need master recorded vsn
      stp$get_volumes_in_set (p_fmd_header^.version_0_0.requested_volume.setname, master_info, member_list^,
            member_count, status);
      IF NOT status.normal THEN
        osp$clear_job_signature_lock (dmv$reconciliation_lock);
        RETURN; {----->
      IFEND;

      PUSH p_fmd_subfile: [dmc$current_fmd_version];
      PUSH kludge: [dmc$current_fmd_version];
      new_fmd_size := #SIZE (dmt$stored_ms_version_number) + #SIZE (kludge^) + (#SIZE (p_fmd_subfile^) *
            fmd_count);
      fmd_fmd_count := p_fmd_header^.version_0_0.number_fmds;

      resides_on_set_master := FALSE;

    /reconcile_fmds/
      FOR fmd_fmd_index := 1 TO fmd_fmd_count DO
        NEXT p_fmd_subfile: [dmc$current_fmd_version] IN p_fmd_subfile_list;

        fmd_byte_address := p_fmd_subfile^.version_0_0.stored_byte_address * dmc$byte_address_converter;
        fmd_dfl_index := p_fmd_subfile^.version_0_0.device_file_list_index;
        fmd_internal_vsn := p_fmd_subfile^.version_0_0.internal_vsn;
        fmd_recorded_vsn := p_fmd_subfile^.version_0_0.recorded_vsn;
        resides_on_set_master := (fmd_recorded_vsn = master_info.recorded_vsn) OR resides_on_set_master;

        FOR fmd_index := first_fmd_index TO first_fmd_index + fmd_count - 1 DO
          reconcile_entry := p_reconcile_list^ [fmd_index];
          avt_index := reconcile_entry.avt_index;
          fmd_ok := (fmd_byte_address = reconcile_entry.byte_address)
{            } AND (fmd_dfl_index = reconcile_entry.dfl_index)
{            } AND (fmd_internal_vsn = dmv$active_volume_table.table_p^ [avt_index].mass_storage.internal_vsn)
{            } AND (fmd_recorded_vsn = dmv$active_volume_table.table_p^ [avt_index].
                mass_storage.recorded_vsn);
          IF fmd_ok THEN
            {Return only the classes common to all subfile volumes
            device_class := dmv$active_volume_table.table_p^ [avt_index].mass_storage.class * device_class;
            CYCLE /reconcile_fmds/; {----->
          IFEND;
        FOREND;
        {Subfile not found - check for missing volume
        dmp$search_avt_by_vsn (fmd_internal_vsn, avt_index, found);
        IF NOT found THEN
          fmds_with_volume_missing := fmds_with_volume_missing + 1;
          recorded_vsn := fmd_recorded_vsn;
        ELSEIF dmv$active_volume_table.table_p^ [avt_index].mass_storage.volume_unavailable THEN
          fmds_with_vol_unavailable := fmds_with_vol_unavailable + 1;
          recorded_vsn := fmd_recorded_vsn;
        ELSE
          fmds_not_reconciled := fmds_not_reconciled + 1;
        IFEND;
      FOREND /reconcile_fmds/;

      IF fmds_with_volume_missing > 0 THEN
        {This takes priority - none of the fmds will be purged, so they
        {can all be reconciled at a later time.
        osp$set_status_abnormal (dmc$device_manager_ident, dme$some_volumes_not_online, recorded_vsn, status);
      ELSEIF fmds_with_vol_unavailable > 0 THEN
        osp$set_status_abnormal (dmc$device_manager_ident, dme$volume_unavailable, recorded_vsn, status);
      ELSEIF (fmds_not_reconciled > 0) OR (fmd_count <> fmd_fmd_count) THEN
        osp$set_status_abnormal (dmc$device_manager_ident, dme$update_fmd, 'Update fmds in FMD.', status);
      IFEND;
    IFEND;
    osp$clear_job_signature_lock (dmv$reconciliation_lock);
  PROCEND dmp$reconcile_fmd;
?? TITLE := '   adjust', EJECT ??

  PROCEDURE [INLINE] adjust
    (    sort_list: dmt$p_reconcile_list;
         i: integer;
         n: 0 .. 0ffffffffffffff(16));

    VAR
      j,
      k: 0 .. 0ffffffffffffff(16),
      r: dmt$reconcile_entry,
      done: boolean;

    done := FALSE;
    r := sort_list^ [i];
    j := 2 * i;

    WHILE ((j <= n) AND NOT done) DO
      IF j < n THEN
        IF less_than (^sort_list^ [j], ^sort_list^ [j + 1]) THEN
          j := j + 1;
        IFEND;
      IFEND;
      IF less_than (^r, ^sort_list^ [j]) THEN
        sort_list^ [j DIV 2] := sort_list^ [j];
        j := 2 * j;
      ELSE
        done := TRUE;
      IFEND;
    WHILEND;
    sort_list^ [j DIV 2] := r;
  PROCEND adjust;
?? TITLE := '  build_new_fmd', EJECT ??

  PROCEDURE build_new_fmd
    (    old_fmd: dmt$stored_fmd;
         gfn: dmt$global_file_name;
         p_reconcile_list: dmt$p_reconcile_list;
         first_fmd_index: dmt$reconcile_index;
         fmd_count: dmt$reconcile_index;
     VAR new_fmd: dmt$stored_fmd;
     VAR status: ost$status);

    VAR
      avt_index: dmt$active_volume_table_index,
      p_new_fmd: ^dmt$stored_fmd,
      p_new_version: ^dmt$stored_ms_version_number,
      p_new_header: ^dmt$stored_ms_fmd_header,
      p_new_subfile: ^dmt$stored_ms_fmd_subfile,
      p_old_subfile_list: ^dmt$stored_fmd,
      p_old_header: ^dmt$stored_ms_fmd_header,
      fmd_index: dmt$reconcile_index;

    validate_fmd (^old_fmd, gfn, p_old_header, p_old_subfile_list, status);
    IF NOT status.normal THEN
      RETURN; {----->
    IFEND;

    p_new_fmd := ^new_fmd;
    RESET p_new_fmd;
    NEXT p_new_version IN p_new_fmd;
    IF p_new_version = NIL THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$fmd_too_small, 'FMD too small.', status);
      RETURN; {----->
    IFEND;

    p_new_version^ := dmc$current_fmd_version;
    NEXT p_new_header: [dmc$current_fmd_version] IN p_new_fmd;
    IF p_new_header = NIL THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$fmd_too_small, 'FMD too small.', status);
      RETURN; {----->
    IFEND;

    p_new_header^.version_0_0 := p_old_header^.version_0_0;
    p_new_header^.version_0_0.number_fmds := fmd_count;

    FOR fmd_index := first_fmd_index TO first_fmd_index + fmd_count - 1 DO
      NEXT p_new_subfile: [dmc$current_fmd_version] IN p_new_fmd;
      IF p_new_subfile = NIL THEN
        osp$set_status_abnormal (dmc$device_manager_ident, dme$fmd_too_small, 'FMD too small.', status);
        RETURN; {----->
      IFEND;

      avt_index := p_reconcile_list^ [fmd_index].avt_index;

      p_new_subfile^.version_0_0.stored_byte_address := p_reconcile_list^ [fmd_index].byte_address DIV
            dmc$byte_address_converter;
      p_new_subfile^.version_0_0.device_file_list_index := p_reconcile_list^ [fmd_index].dfl_index;
      p_new_subfile^.version_0_0.internal_vsn := dmv$active_volume_table.table_p^ [avt_index].mass_storage.
            internal_vsn;
      p_new_subfile^.version_0_0.recorded_vsn := dmv$active_volume_table.table_p^ [avt_index].mass_storage.
            recorded_vsn;
    FOREND;
  PROCEND build_new_fmd;
?? TITLE := '  compare_names', EJECT ??

  PROCEDURE [INLINE] compare_names
    (    name_one: ^dmt$global_file_name;
         name_two: ^dmt$global_file_name;
     VAR compare_status: dmt$compare_status);

    IF (name_one^.sequence_number < name_two^.sequence_number) THEN
      compare_status := less;
    ELSEIF (name_one^.sequence_number > name_two^.sequence_number) THEN
      compare_status := greater;
    ELSEIF (name_one^.second < name_two^.second) THEN
      compare_status := less;
    ELSEIF (name_one^.second > name_two^.second) THEN
      compare_status := greater;
    ELSEIF (name_one^.minute < name_two^.minute) THEN
      compare_status := less;
    ELSEIF (name_one^.minute > name_two^.minute) THEN
      compare_status := greater;
    ELSEIF (name_one^.hour < name_two^.hour) THEN
      compare_status := less;
    ELSEIF (name_one^.hour > name_two^.hour) THEN
      compare_status := greater;
    ELSEIF (name_one^.day < name_two^.day) THEN
      compare_status := less;
    ELSEIF (name_one^.day > name_two^.day) THEN
      compare_status := greater;
    ELSEIF (name_one^.month < name_two^.month) THEN
      compare_status := less;
    ELSEIF (name_one^.month > name_two^.month) THEN
      compare_status := greater;
    ELSEIF (name_one^.year < name_two^.year) THEN
      compare_status := less;
    ELSEIF (name_one^.year > name_two^.year) THEN
      compare_status := greater;
    ELSEIF (name_one^.model_number < name_two^.model_number) THEN
      compare_status := less;
    ELSEIF (name_one^.model_number > name_two^.model_number) THEN
      compare_status := greater;
    ELSEIF (name_one^.serial_number < name_two^.serial_number) THEN
      compare_status := less;
    ELSEIF (name_one^.serial_number > name_two^.serial_number) THEN
      compare_status := greater;
    ELSE
      compare_status := equal;
    IFEND;

  PROCEND compare_names;
?? TITLE := '  convert_gfn_to_string', EJECT ??

  PROCEDURE convert_gfn
    (    gfn: ost$binary_unique_name;
     VAR gfn_string: ost$name);

    VAR
      local_status: ost$status;

    pmp$convert_binary_unique_name (gfn, gfn_string, local_status);

  PROCEND convert_gfn;
?? TITLE := '  find_file', EJECT ??

  PROCEDURE find_file
    (    gfn: dmt$global_file_name;
         p_sorted_reconcile_list: dmt$p_reconcile_list;
     VAR first_fmd_index: dmt$reconcile_index;
     VAR fmd_count: dmt$reconcile_index;
     VAR status: ost$status);

    VAR
      key_greater: boolean,
      key_equal: boolean,
      list_length: dmt$reconcile_index,
      index: dmt$reconcile_index,
      lower_index: dmt$reconcile_index,
      upper_index: dmt$reconcile_index,
      gfn_string: ost$name;

    VAR
      compare_status: dmt$compare_status;

    status.normal := TRUE;
    list_length := UPPERBOUND (p_sorted_reconcile_list^);
    lower_index := 1;
    upper_index := list_length;

  /search_list/
    WHILE lower_index <= upper_index DO
      index := (lower_index + upper_index) DIV 2;
      compare_names (^gfn, ^p_sorted_reconcile_list^ [index].global_file_name, compare_status);
      IF compare_status = greater THEN
        lower_index := index + 1;
      ELSEIF compare_status = equal THEN
        EXIT /search_list/; {----->
      ELSE
        upper_index := index - 1;
      IFEND;
    WHILEND /search_list/;

    IF compare_status = equal THEN
      fmd_count := 1;
      upper_index := index + 1;
      WHILE (upper_index <= list_length) AND (gfn = p_sorted_reconcile_list^ [upper_index].global_file_name)
            DO
        upper_index := upper_index + 1;
        fmd_count := fmd_count + 1;
      WHILEND;

      lower_index := index - 1;
      WHILE (lower_index > 0) AND (gfn = p_sorted_reconcile_list^ [lower_index].global_file_name) DO
        lower_index := lower_index - 1;
        fmd_count := fmd_count + 1;
      WHILEND;
      first_fmd_index := lower_index + 1;
    ELSE
      osp$set_status_abnormal (dmc$device_manager_ident, dme$unknown_file, 'Unknown file.', status);
      convert_gfn (gfn, gfn_string);
      osp$append_status_parameter (' ', gfn_string, status);
    IFEND;
  PROCEND find_file;
?? TITLE := 'HEAP_SORT', EJECT ??

  PROCEDURE heap_sort
    (VAR input_vector {input, output} : dmt$p_reconcile_list);

    CONST
      second = 1000000;

    VAR
      base: integer,
      clock: integer,
      date: ost$date,
      i: integer,
      l: integer,
      last_index: 0 .. 0ffffffffffffff(16),
      msg: string (70),
      status: ost$status,
      temp_record: dmt$reconcile_entry,
      display_update_interval: integer,
      time: ost$time;

{ The following sort algorithm is a heap sort. It is a non-recursive sort algorithm.
{ There are two phases to the algorithm. The first phase converts the list to be sorted
{ into a binary tree representation. The second and main sorting phase of the algorithm
{ iterates through the unsorted portion of the list. The procedure ADJUST is called
{ (number of elements to sort - 1) times during this phase. Each call to the ADJUST
{ procedure scans the right and left subtrees searching for the highest key value.
{ The highest key value becomes the root of the tree. The root of the tree is returned
{ to the main sort procedure in position one of the input/output sorted list. The root is
{ then moved into the position one greater than the current size of the tree (unsorted portion of
{ the list). Each call to the ADJUST procedure is made with a tree with one less node than the
{ previous call.

    base := 0;
    msg (1, * ) := ' ';
    last_index := UPPERBOUND (input_vector^);
    display_update_interval := dmv$ds_msg_update_interval * second;

    FOR i := (last_index DIV 2) DOWNTO 1 DO
      adjust (input_vector, i, last_index);
      pmp$get_microsecond_clock (clock, status);
      IF status.normal AND (clock > (base + display_update_interval)) THEN
        base := clock;
        pmp$get_legible_date_time (osc$mdy_date, date, osc$hms_time, time, status);
        IF status.normal THEN
          STRINGREP (msg, l, '   .. sorting ', date.mdy, ' ', time.hms);
          clp$put_job_output (msg (1, l), status);
          msg (1, * ) := ' ';
        IFEND;
      IFEND;
    FOREND;

    FOR i := (last_index - 1) DOWNTO 1 DO
      temp_record := input_vector^ [i + 1];
      input_vector^ [i + 1] := input_vector^ [1];
      input_vector^ [1] := temp_record;
      adjust (input_vector, 1, i);
      pmp$get_microsecond_clock (clock, status);
      IF status.normal AND (clock > (base + display_update_interval)) THEN
        base := clock;
        pmp$get_legible_date_time (osc$mdy_date, date, osc$hms_time, time, status);
        IF status.normal THEN
          STRINGREP (msg, l, '   .. sorting ', date.mdy, ' ', time.hms);
          clp$put_job_output (msg (1, l), status);
          msg (1, * ) := ' ';
        IFEND;
      IFEND;
    FOREND;

  PROCEND heap_sort;
?? TITLE := '  less_than', EJECT ??

  FUNCTION [INLINE, UNSAFE] less_than
    (    entry_one: ^dmt$reconcile_entry;
         entry_two: ^dmt$reconcile_entry): boolean;

    VAR
      compare_status: dmt$compare_status;

    compare_names (^entry_one^.global_file_name, ^entry_two^.global_file_name, compare_status);
    IF compare_status = less THEN
      less_than := TRUE;
    ELSEIF compare_status = greater THEN
      less_than := FALSE;
    ELSE
      less_than := (entry_one^.byte_address < entry_two^.byte_address);
    IFEND;

  FUNCEND less_than;

?? TITLE := '  validate_fmd', EJECT ??

  PROCEDURE validate_fmd
    (    p_stored_fmd: ^dmt$stored_fmd;
         gfn: dmt$global_file_name;
     VAR p_fmd_header: ^dmt$stored_ms_fmd_header;
     VAR p_subfile_list: ^dmt$stored_fmd;
     VAR status: ost$status);

    VAR
      p_fmd: ^dmt$stored_fmd,
      p_version: ^dmt$stored_ms_version_number,
      fmd_count: dmt$fmd_index,
      fmd_index: dmt$fmd_index,
      p_subfile: ^dmt$stored_ms_fmd_subfile,
      gfn_string: ost$name;

    status.normal := TRUE;
    p_fmd := p_stored_fmd;
    RESET p_fmd;

    NEXT p_version IN p_fmd;
    IF (p_version = NIL) THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$invalid_fmd, 'No FMD version number.', status);
      convert_gfn (gfn, gfn_string);
      osp$append_status_parameter (' ', gfn_string, status);
      RETURN; {----->
    IFEND;

    IF (p_version^ <> dmc$current_fmd_version) THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$unsupported_fmd_version,
            'Unsupported FMD version number.', status);
      convert_gfn (gfn, gfn_string);
      osp$append_status_parameter (' ', gfn_string, status);
      RETURN; {----->
    IFEND;

    NEXT p_fmd_header: [dmc$current_fmd_version] IN p_fmd;
    IF (p_fmd_header = NIL) THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$invalid_fmd, 'No FMD header.', status);
      convert_gfn (gfn, gfn_string);
      osp$append_status_parameter (' ', gfn_string, status);
      RETURN; {----->
    IFEND;

    fmd_count := p_fmd_header^.version_0_0.number_fmds;
    IF (fmd_count < 1) THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$invalid_fmd, 'FMD fmd count less than one.',
            status);
      convert_gfn (gfn, gfn_string);
      osp$append_status_parameter (' ', gfn_string, status);
      RETURN; {----->
    IFEND;

    p_subfile_list := p_fmd;

    FOR fmd_index := 1 TO fmd_count DO
      NEXT p_subfile: [dmc$current_fmd_version] IN p_fmd;
      IF (p_subfile = NIL) THEN
        osp$set_status_abnormal (dmc$device_manager_ident, dme$invalid_fmd, 'FMD too small to hold fmds.',
              status);
        convert_gfn (gfn, gfn_string);
        osp$append_status_parameter (' ', gfn_string, status);
        RETURN; {----->
      IFEND;
    FOREND;
  PROCEND validate_fmd;


MODEND dmm$reconcile_fmd;
