?? RIGHT := 110 ??
*copy osd$default_pragmats
?? NEWTITLE := 'NOS/VE Device Management' ??
MODULE dmm$space_manager;
?? RIGHT := 110 ??

?? PUSH (LISTEXT := ON) ??
?? NEWTITLE := 'Global Declarations Referenced By This Module', EJECT ??
*copyc oss$mainframe_paged_literal
*copyc dmt$active_volume_table_index
*copyc dmt$allocation_info
*copyc dmt$allocation_size
*copyc dmt$device_allocation_unit
*copyc dmt$device_file_list_index
*copyc dmt$device_log_entries
*copyc dmt$device_position
*copyc dmt$error_condition_codes
*copyc dmt$mainframe_allocation_table
*copyc dmt$mainframe_device_file_list
*copyc dmt$mat_change_request
*copyc dmt$ms_device_allocation_table
?? POP ??
*copyc dmp$search_avt_by_rvsn
*copyc dmv$null_vsn
*copyc dmp$close_file
*copyc dmp$get_mat_pointer
*copyc dmp$get_mfl_pointer
*copyc dmp$get_physical_attributes
*copyc dmp$lock_avt_entry
*copyc dmp$open_dat
*copyc dmp$open_dflt
*copyc dmp$search_avt_by_lun
*copyc dmp$unlock_avt_entry
*copyc dmp$verify_access
*copyc dpp$put_critical_message
*copyc mmp$write_modified_pages
*copyc osp$begin_system_activity
*copyc osp$clear_mainframe_sig_lock
*copyc osp$end_system_activity
*copyc osp$fatal_system_error
*copyc osp$fetch_locked_variable
*copyc osp$file_access_condition
*copyc osp$set_locked_variable
*copyc osp$set_mainframe_sig_lock
*copyc osp$set_status_abnormal
*copyc osp$test_set_main_sig_lock
*copyc pmp$get_legible_date_time
*copyc pmp$zero_out_table
*copyc syp$continue_to_cause
*copyc syp$cycle
*copyc syp$disestablish_cond_handler
*copyc syp$establish_condition_handler
*copyc dmv$active_volume_table
*copyc dmv$internal_task_delay_values
*copyc dmv$internal_task_exec_counts
*copyc dmv$mat_change_count_max
*copyc dmv$mat_change_list
*copyc osv$mainframe_wired_heap
*copyc osv$page_size
*copyc syv$job_recovery_option
*copyc i#call_monitor
*copyc i#move

  VAR
    dmv$space_messages_to_console: [XREF] boolean;

?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared By This Module', EJECT ??

  VAR
    v$zero_dau_status_counts: [READ, oss$mainframe_paged_literal] dmt$dau_status_counts := [
{ DMC$DAU_USABLE                       } 0,
{ DMC$DAU_HARDWARE_FLAWED              } 0,
{ DMC$DAU_SOFTWARE_FLAWED              } 0,
{ DMC$DAU_ASSIGNED_TO_MAINFRAME        } 0,
{ DMC$DAU_ASSIGNED_TO_FILE             } 0,
{ DMC$DAU_ASS_TO_MF_SWR_FLAWED         } 0,
{ DMC$DAU_ASS_TO_FILE_SWR_FLAWED       } 0];

?? OLDTITLE ??
?? NEWTITLE := '[xdcl, #gate] DMP$ANALYZE_DAT_POSITION', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$analyze_dat_position
    (    p_dat: ^dmt$ms_device_allocation_table;
         position: dmt$device_position;
     VAR allocation_style: dmt$allocation_styles;
     VAR dau_status_counts: dmt$dau_status_counts);

    VAR
      daus_per_allocation: dmt$daus_per_allocation,
      daus_per_position: dmt$daus_per_position,
      first_dau: dmt$dau_address,
      dau: dmt$dau_address,
      dau_status: dmt$dau_status,
      flawed_daus: dmt$dau_address;

?? NEWTITLE := 'HANDLER', EJECT ??

    PROCEDURE handler
      (    mf: ost$monitor_fault;
           p_msa: ^ost$minimum_save_area;
       VAR continue: syt$continue_option);

      VAR
        process_condition: boolean,
        p_scc: ^syt$system_core_condition,
        p_sac: ^mmt$segment_access_condition;

      process_condition := FALSE;
      IF mf.identifier = mmc$segment_fault_processor_id THEN
        p_sac := #LOC (mf.contents);
        process_condition := p_sac^.identifier = mmc$sac_io_read_error

      ELSEIF mf.identifier = syc$system_core_condition THEN
        p_scc := #LOC (mf.system_core_condition);
        process_condition := (p_scc^.condition = syc$user_defined_condition) AND
              (p_scc^.user_defined_condition = syc$udc_volume_unavailable)
      IFEND;

      IF process_condition THEN
        dau_status_counts := v$zero_dau_status_counts;
        allocation_style := UPPERVALUE (allocation_style);
        EXIT dmp$analyze_dat_position; {----->
      ELSE
        syp$continue_to_cause (mf, p_msa, syc$condition_ignored, continue);
      IFEND;

    PROCEND handler;
?? OLDTITLE ??
?? EJECT ??

    syp$establish_condition_handler (^handler);

    daus_per_allocation := 0;
    daus_per_position := p_dat^.header.daus_per_position;
    first_dau := position * daus_per_position;
    dau_status_counts := v$zero_dau_status_counts;

    FOR dau := first_dau TO (first_dau + daus_per_position - 1) DO
      dau_status := p_dat^.body [dau].dau_status;
      IF (dau_status <= UPPERVALUE (dau_status)) THEN
        dau_status_counts [dau_status] := dau_status_counts [dau_status] + 1;
      IFEND;

      IF (dau_status = dmc$dau_assigned_to_file) OR (dau_status = dmc$dau_ass_to_file_swr_flawed) THEN
        IF (p_dat^.body [dau].allocation_chain_position = dmc$part_of_allocation_unit) THEN
          daus_per_allocation := daus_per_allocation + 1;
        ELSE
          daus_per_allocation := 1;
        IFEND;
      IFEND;
    FOREND;

    flawed_daus := dau_status_counts [dmc$dau_hardware_flawed] +
{                } dau_status_counts [dmc$dau_software_flawed] +
{                } dau_status_counts [dmc$dau_ass_to_mf_swr_flawed] +
{                } dau_status_counts [dmc$dau_ass_to_file_swr_flawed];

    IF (daus_per_allocation = 0) OR (flawed_daus <> 0) THEN
      IF (dau_status_counts [dmc$dau_usable] = daus_per_position) THEN
        daus_per_allocation := daus_per_position;
      ELSE
        daus_per_allocation := dmc$default_req_alloc_size DIV p_dat^.header.bytes_per_dau;
      IFEND;
    IFEND;

    allocation_style := UPPERVALUE (allocation_style);
    WHILE (daus_per_allocation <> p_dat^.header.daus_per_allocation_style [allocation_style]) AND
          (allocation_style > LOWERVALUE (allocation_style)) DO
      allocation_style := PRED (allocation_style);
    WHILEND;
    syp$disestablish_cond_handler;

  PROCEND dmp$analyze_dat_position;
?? TITLE := '  dmp$calculate_device_capacity', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$calculate_device_capacity
    (    product_id: cmt$product_identification;
     VAR capacity: integer;
     VAR status: ost$status);

    VAR
      attributes: array [1 ..3] of dmt$physical_device_attribute;

    status.normal := TRUE;
    capacity := 0;

    attributes [1].keyword := dmc$bytes_per_mau;
    attributes [2].keyword := dmc$cylinders_per_device;
    attributes [3].keyword := dmc$maus_per_cylinder;

    dmp$get_physical_attributes (product_id, ^attributes, status);
    IF status.normal THEN
      capacity := attributes [1].bytes_per_mau * attributes [2].
            cylinders_per_device * attributes [3].maus_per_cylinder;
    IFEND;

  PROCEND dmp$calculate_device_capacity;

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

  PROCEDURE [XDCL, #GATE] dmp$calculate_remaining_space
    (    logical_unit_number: iot$logical_unit;
     VAR remaining_bytes: integer;
     VAR status: ost$status);

    VAR
      info: dmt$allocation_info,
      vsn: rmt$recorded_vsn,
      avt_index: dmt$active_volume_table_index,
      volume_active: boolean;

    status.normal := TRUE;
    remaining_bytes := 0;
    dmp$search_avt_by_lun (logical_unit_number, avt_index, volume_active);
    IF NOT volume_active THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$volume_not_online,
            'specified logical unit number is not active device - dmp$calculate_remaining_space', status);
      RETURN; {----->
    IFEND;

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

    dmp$get_allocation_info (vsn, avt_index, info, status);
    IF status.normal THEN
      remaining_bytes := (info.available_mat_space + info.available_dat_space) * info.bytes_per_dau;
    IFEND;

  PROCEND dmp$calculate_remaining_space;
?? TITLE := '  decrease_mat_space', EJECT ??

  PROCEDURE decrease_mat_space
    (    p_mat: ^dmt$mainframe_allocation_table;
         p_dat: ^dmt$ms_device_allocation_table);

    VAR
      p_mat_changes: ^dmt$mat_changes,
      mat_change_count: dmt$mat_change_count,
      mat_change_index: dmt$mat_change_count,
      mat_change_request: dmt$mat_change_request,
      daus_per_allocation_unit: dmt$daus_per_position,
      daus_returned: dmt$daus_per_position,
      dau: dmt$dau_address,
      style: dmt$allocation_styles,
      dat_size: 0 .. 07fffffff(16),
      status: ost$status;

    osp$set_mainframe_sig_lock (dmv$mat_change_list.lock);

    p_mat_changes := ^dmv$mat_change_list.values;

    dat_size := #OFFSET (#LOC (p_dat^.body)) + #SIZE (p_dat^.body);
    mat_change_request.request_code := syc$rc_apply_mat_changes;
    mat_change_request.avt_index := p_mat^.avt_index;
    mat_change_request.mat_change_type := dmc$remove_mat_space;
    mat_change_request.p_mat_changes := p_mat_changes;

    REPEAT
      mat_change_request.available_dat_space := p_dat^.header.available;
      i#call_monitor (^mat_change_request, #SIZE (mat_change_request));

      mat_change_count := mat_change_request.mat_change_count;

{Update the statistic if necessary (this code may be removed when we know more about how big we get)
      IF mat_change_count > dmv$mat_change_list.list_size_used_max THEN
        dmv$mat_change_list.list_size_used_max := mat_change_count;
      IFEND;

      FOR mat_change_index := 1 TO mat_change_count DO
        style := p_mat_changes^ [mat_change_index].style;
        dau := p_mat_changes^ [mat_change_index].dau_address;
        daus_per_allocation_unit := p_mat^.daus_per_allocation_unit [style];
        daus_returned := daus_per_allocation_unit;

        FOR dau := dau TO (dau + daus_per_allocation_unit - 1) DO
          IF (p_dat^.body [dau].dau_status = dmc$dau_ass_to_mf_swr_flawed) THEN
            p_dat^.body [dau].dau_status := dmc$dau_software_flawed;
            daus_returned := daus_returned - 1;
          ELSE
            p_dat^.body [dau].dau_status := dmc$dau_usable;
          IFEND;
        FOREND;

        p_dat^.header.available := p_dat^.header.available + daus_returned;
      FOREND;

      IF (mat_change_count <> 0) THEN
        mmp$write_modified_pages (p_dat, dat_size, osc$wait, status);
        IF NOT status.normal THEN
          IF osp$file_access_condition (status) THEN
            status.normal := TRUE;
          ELSE
            osp$fatal_system_error ('Unable to flush DAT.', ^status);
          IFEND;
        IFEND;
      IFEND;
    UNTIL (mat_change_count = 0);

    osp$clear_mainframe_sig_lock (dmv$mat_change_list.lock);

  PROCEND decrease_mat_space;
?? TITLE := '  determine_mat_changes', EJECT ??

  PROCEDURE determine_mat_changes
    (    p_mat: ^dmt$mainframe_allocation_table;
     VAR additional_space: dmt$dau_address);

    VAR
      usable_space: dmt$dau_address,
      target_space: dmt$dau_address,
      usable_dat_space: integer,
      daus_per_position: dmt$daus_per_position,
      no_free_cylinders: boolean;

    IF NOT dmv$active_volume_table.table_p^ [p_mat^.avt_index].mass_storage.allocation_allowed THEN
      additional_space := 0;
      RETURN;
    IFEND;

    usable_space := p_mat^.available_space;

    IF (usable_space < p_mat^.minimum_space) THEN
      target_space := (p_mat^.maximum_space + p_mat^.minimum_space) DIV 2;
      additional_space := target_space - usable_space;
    ELSE
      additional_space := 0;
    IFEND;

    { Get at least a cylinder of space if there are no free cylinders currently
    { in the MAT.

    no_free_cylinders := p_mat^.available_allocation_units [dmc$acyl] = 0;

    IF no_free_cylinders THEN
      daus_per_position := p_mat^.daus_per_position;
      IF (additional_space < daus_per_position) THEN
        additional_space := daus_per_position;
      IFEND;
    IFEND;

    { Fill MAT only until the DAT reaches the DAT threshold.

    usable_dat_space := p_mat^.available_dat_space - p_mat^.dat_threshold;
    IF (additional_space > usable_dat_space) THEN
      IF (usable_dat_space > 0) THEN
        additional_space := usable_dat_space;
      ELSE
        additional_space := 0;
      IFEND;
    IFEND;

  PROCEND determine_mat_changes;
?? TITLE := '  display_informative_message', EJECT ??

  PROCEDURE display_informative_message
    (    volume_out_count: integer;
         volume_low_count: integer);

    VAR
      date: ost$date,
      integer_length: integer,
      integer_string: string (6),
      message_length: integer,
      message: string (80),
      status: ost$status,
      time: ost$time;

    IF (volume_out_count = 0) AND (volume_low_count = 0) THEN
      message := ' 0 VOLUMES LOW, 0 VOLUMES OUT'
    ELSE
      message (1, * ) := ' ';
      message_length := 1;
      IF (volume_low_count <> 0) THEN
        STRINGREP (integer_string, integer_length, volume_low_count);
        message (message_length, integer_length) := integer_string;
        message_length := message_length + integer_length;
        message (message_length, 12) := ' VOLUMES LOW';
        message_length := message_length + 12;
      IFEND;

      IF (volume_out_count <> 0) THEN
        IF (volume_low_count <> 0) THEN
          message (message_length, 1) := ',';
          message_length := message_length + 1;
        IFEND;
        STRINGREP (integer_string, integer_length, volume_out_count);
        message (message_length, integer_length) := integer_string;
        message_length := message_length + integer_length;
        message (message_length, 12) := ' VOLUMES OUT';
      IFEND;
    IFEND;

    { add date to end of message.

    pmp$get_legible_date_time (osc$mdy_date, date, osc$hms_time, time, {ignore} status);
    message (40, 8) := date.mdy;
    dpp$put_critical_message (message, status);

  PROCEND display_informative_message;
?? TITLE := '  increase_mat_space', EJECT ??

  PROCEDURE increase_mat_space
    (    p_mat: ^dmt$mainframe_allocation_table;
         p_dat: ^dmt$ms_device_allocation_table;
         space_required: dmt$dau_address;
         skip_cylinders: boolean);

    VAR
      p_mat_changes: ^dmt$mat_changes,
      mat_change_count: dmt$mat_change_count,
      mat_change: dmt$mat_change,
      mainframe_assigned: dmt$mainframe_assigned,
      space_obtained: dmt$dau_address,
      cylinder_end_space: dmt$dau_address,
      dau_status_counts: dmt$dau_status_counts,
      avt_index: dmt$active_volume_table_index,
      allocation_unit_found: boolean,
      all_positions_searched: boolean,
      p_daus: ^array [ * ] of dmt$ms_device_allocation_unit,
      position: dmt$device_position,
      daus_per_position: dmt$daus_per_position,
      dau: dmt$dau_address,
      first_dau: dmt$dau_address,
      next_dau: dmt$dau_address,
      dau_limit: dmt$dau_address,
      allocation_style: dmt$allocation_styles,
      maximum_position: dmt$device_position,
      starting_position_number: dmt$device_position,
      starting_search_position: dmt$device_position,
      size: 0 .. 7fffffff(16),
      daus_per_allocation_unit: dmt$daus_per_position,
      status: ost$status;

    p_daus := ^p_dat^.body;
    size := #OFFSET (#LOC (p_daus^)) + #SIZE (p_daus^);

    osp$set_mainframe_sig_lock (dmv$mat_change_list.lock);

    p_mat_changes := ^dmv$mat_change_list.values;

    mat_change_count := 0;
    avt_index := p_mat^.avt_index;
    mainframe_assigned := dmv$active_volume_table.table_p^ [avt_index].mass_storage.mainframe_assigned;

    maximum_position := p_mat^.positions_per_device - 1;
    starting_position_number := p_mat^.starting_position_number;
    starting_search_position := p_mat^.starting_search_position;
    position := starting_search_position;
    daus_per_position := p_mat^.daus_per_position;

    space_obtained := 0;
    all_positions_searched := FALSE;

  /search_all_positions/
    WHILE (space_obtained < space_required) AND NOT all_positions_searched DO
      dmp$analyze_dat_position (p_dat, position, allocation_style, dau_status_counts);
      IF (allocation_style = dmc$acyl) AND skip_cylinders THEN
        cylinder_end_space := space_obtained;
      ELSE
        cylinder_end_space := space_obtained + dau_status_counts [dmc$dau_usable];
      IFEND;

      { The "search_position" loop takes all the space from a cylinder.  This
      { may result in overfilling the MAT by up to one cylinder minus one dau.
      { The overfilling is allowed to avoid fragmentation that would result
      { from moving partial cylinders to the MAT.

    /search_position/
      WHILE (space_obtained < cylinder_end_space) DO
        mat_change.style := allocation_style;
        daus_per_allocation_unit := p_mat^.daus_per_allocation_unit [allocation_style];
        dau := position * daus_per_position;
        dau_limit := dau + daus_per_position DIV daus_per_allocation_unit * daus_per_allocation_unit;

      /find_allocation_unit/
        WHILE (dau < dau_limit) DO
          first_dau := dau;
          next_dau := first_dau + daus_per_allocation_unit;

          REPEAT
            allocation_unit_found := (p_daus^ [dau].dau_status = dmc$dau_usable);
            dau := dau + 1;
          UNTIL NOT allocation_unit_found OR (dau = next_dau);

          IF allocation_unit_found THEN
            FOR dau := first_dau TO (next_dau - 1) DO
              p_daus^ [dau].dau_status := dmc$dau_assigned_to_mainframe;
              p_daus^ [dau].mainframe_id := mainframe_assigned;
            FOREND;

            mat_change.dau_address := first_dau;
            IF mat_change_count = dmv$mat_change_count_max THEN
              mmp$write_modified_pages (p_dat, size, osc$wait, status);
              IF NOT status.normal THEN
                IF osp$file_access_condition (status) THEN
                  status.normal := TRUE;
                ELSE
                  osp$fatal_system_error ('Unable to flush DAT', ^status);
                IFEND;
              IFEND;

              process_mat_changes (p_mat, mat_change_count, p_mat_changes, p_dat^.header.available);
              mat_change_count := 0;
            IFEND;
            accumulate_mat_change (mat_change, p_mat_changes, mat_change_count);

            space_obtained := space_obtained + daus_per_allocation_unit;
            p_dat^.header.available := p_dat^.header.available - daus_per_allocation_unit;
          IFEND;

          dau := next_dau;
        WHILEND /find_allocation_unit/;

        allocation_style := LOWERVALUE (allocation_style);

      WHILEND /search_position/;

      IF (position < maximum_position) THEN
        position := position + 1;
      ELSE
        position := starting_position_number;
      IFEND;

      all_positions_searched := (position = starting_search_position);
    WHILEND /search_all_positions/;

    mmp$write_modified_pages (p_dat, size, osc$wait, status);
    IF NOT status.normal THEN
      IF osp$file_access_condition (status) THEN
        status.normal := TRUE;
      ELSE
        osp$fatal_system_error ('Unable to flush DAT', ^status);
      IFEND;
    IFEND;

    process_mat_changes (p_mat, mat_change_count, p_mat_changes, p_dat^.header.available);

    osp$clear_mainframe_sig_lock (dmv$mat_change_list.lock);

    p_mat^.starting_search_position := position;

  PROCEND increase_mat_space;
?? TITLE := '  get_dflt_space', EJECT ??

  PROCEDURE get_dflt_space
    (    p_dflt: ^dmt$ms_device_file_list_table;
         avt_index: dmt$active_volume_table_index;
     VAR status: ost$status);

    VAR
      current_value: integer,
      mainframe_assigned: dmt$mainframe_assigned,
      mflt_changes: ^array [1 .. * ] of dmt$ms_mf_file_list_entry,
      number_mflt_changes: integer,
      mflt_change: dmt$device_file_list_index,
      volume_dfl_index: dmt$device_file_list_index,
      mfl_index: dmt$ms_mf_device_file_list_ord,
      size: 0 .. 7fffffff(16),
      p_mfl: ^dmt$ms_mf_device_file_list;

    status.normal := TRUE;

    PUSH mflt_changes: [1 .. 100];

    number_mflt_changes := 0;
    mainframe_assigned := dmv$active_volume_table.table_p^ [avt_index].mass_storage.mainframe_assigned;

    dmp$get_mfl_pointer (avt_index, p_mfl);
    IF p_mfl = NIL THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$nil_mflt_pointer, 'p_mfl = NIL.', status);
      RETURN; {----->
    IFEND;

    size := #SIZE (dmt$ms_device_file_list_header) + (UPPERBOUND (p_dflt^.entries) -
          LOWERBOUND (p_dflt^.entries) + 1) * #SIZE (dmt$ms_device_file_list_entry);

    volume_dfl_index := 1;

  /fill_mf_file_list/
    FOR mfl_index := 1 TO UPPERBOUND (p_mfl^) DO

      osp$fetch_locked_variable (p_mfl^ [mfl_index].ordinal, current_value);

      IF current_value = 0 THEN

        WHILE p_dflt^.entries [volume_dfl_index].flags <> dmc$dfle_available DO
          IF volume_dfl_index + 1 > UPPERBOUND (p_dflt^.entries) THEN
            EXIT /fill_mf_file_list/; {----->
          IFEND;
          volume_dfl_index := volume_dfl_index + 1;
        WHILEND;

        p_dflt^.entries [volume_dfl_index].flags := dmc$dfle_assigned_to_mainframe;
        p_dflt^.entries [volume_dfl_index].mainframe_assigned := mainframe_assigned;
        mflt_change := volume_dfl_index;

        IF (number_mflt_changes = UPPERBOUND (mflt_changes^)) THEN

          mmp$write_modified_pages (p_dflt, size, osc$wait, status);
          IF NOT status.normal THEN
            IF osp$file_access_condition (status) THEN
              status.normal := TRUE;
            ELSE
              RETURN; {----->
            IFEND;
          IFEND;

          apply_changes_to_mfl (mflt_changes, number_mflt_changes, avt_index, status);
          IF NOT status.normal THEN
            RETURN; {----->
          IFEND;
          number_mflt_changes := 0;
        IFEND;

        accumulate_mflt_change (mflt_change, mflt_changes, number_mflt_changes);

        volume_dfl_index := volume_dfl_index + 1;
        IF volume_dfl_index > UPPERBOUND (p_dflt^.entries) THEN
          EXIT /fill_mf_file_list/; {----->
        IFEND;

      IFEND;
    FOREND /fill_mf_file_list/;

    IF number_mflt_changes > 0 THEN

      mmp$write_modified_pages (p_dflt, size, osc$wait, status);
      IF NOT status.normal THEN
        IF osp$file_access_condition (status) THEN
          status.normal := TRUE;
        ELSE
          RETURN; {----->
        IFEND;
      IFEND;

      apply_changes_to_mfl (mflt_changes, number_mflt_changes, avt_index, status);
    IFEND;

  PROCEND get_dflt_space;
?? TITLE := '  accumulate_mat_change', EJECT ??

  PROCEDURE accumulate_mat_change
    (    mat_change: dmt$mat_change;
         p_mat_changes: ^dmt$mat_changes;
     VAR mat_change_count: dmt$mat_change_count);

    VAR
      lower: dmt$mat_change_count,
      upper: dmt$mat_change_count,
      mid: dmt$mat_change_count,
      insertion_index: dmt$mat_change_count,
      replacement_index: dmt$mat_change_count,
      number_to_move: dmt$mat_change_count,
      size: 0 .. 7fffffff(16),
      insertion_address: ^cell,
      replacement_address: ^cell,
      temp: integer,
      temp_buffer: ^array [1 .. *] of dmt$mat_change;

    lower := 0;
    upper := mat_change_count + 1;
    temp := lower + upper;
    mid := temp DIV 2;

    WHILE mid <> lower DO
      IF (p_mat_changes^ [mid].dau_address <= mat_change.dau_address) THEN
        lower := mid;
      ELSE
        upper := mid;
      IFEND;
      temp := lower + upper;
      mid := temp DIV 2;
    WHILEND;

    insertion_index := mid + 1;
    replacement_index := mid + 2;
    number_to_move := mat_change_count - insertion_index + 1;
    size := #SIZE (dmt$mat_change) * number_to_move;

    IF size <> 0 THEN
      insertion_address := ^p_mat_changes^ [insertion_index];
      replacement_address := ^p_mat_changes^ [replacement_index];
      PUSH temp_buffer: [1 .. number_to_move];
      i#move (insertion_address, temp_buffer, size);
      i#move (temp_buffer, replacement_address, size);
    IFEND;

    p_mat_changes^ [insertion_index] := mat_change;
    mat_change_count := mat_change_count + 1;

  PROCEND accumulate_mat_change;

?? TITLE := '  process_mat_changes', EJECT ??

  PROCEDURE process_mat_changes
    (    p_mat: ^dmt$mainframe_allocation_table;
         mat_change_count: dmt$mat_change_count;
         p_mat_changes: ^dmt$mat_changes;
         available_dat_space: dmt$dau_address);

    VAR
      mat_change_request: dmt$mat_change_request,
      current_position: dmt$device_position,
      next_position: dmt$device_position,
      i: dmt$mat_change_count,
      this_position_checked: boolean,
      status: ost$status;

    IF mat_change_count = 0 THEN
      RETURN; {----->
    IFEND;

{Update the statistic if necessary (this code may be removed when we know more about how big we get)
    IF mat_change_count > dmv$mat_change_list.list_size_used_max THEN
      dmv$mat_change_list.list_size_used_max := mat_change_count;
    IFEND;

    this_position_checked := FALSE;
    current_position := p_mat_changes^ [1].dau_address DIV p_mat^.daus_per_position;

    FOR i := 1 TO mat_change_count DO
      next_position := p_mat_changes^ [i].dau_address DIV p_mat^.daus_per_position;
      IF next_position <> current_position THEN
        current_position := next_position;
        this_position_checked := FALSE;
      IFEND;
      IF NOT this_position_checked THEN
        IF (current_position > p_mat^.positions_per_device) OR (current_position < 0) THEN
          osp$fatal_system_error ('unable to locate position in mat - DMMSMAN', ^status);
        IFEND;
        this_position_checked := TRUE;
      IFEND;
    FOREND;

    mat_change_request.request_code := syc$rc_apply_mat_changes;
    mat_change_request.avt_index := p_mat^.avt_index;
    mat_change_request.mat_change_type := dmc$add_mat_space;
    mat_change_request.mat_change_count := mat_change_count;
    mat_change_request.p_mat_changes := p_mat_changes;
    mat_change_request.available_dat_space := available_dat_space;

    i#call_monitor (^mat_change_request, #SIZE (mat_change_request));

  PROCEND process_mat_changes;
?? TITLE := '  apply_changes_to_mfl', EJECT ??

  PROCEDURE apply_changes_to_mfl
    (    p_new_mfl_entries: ^array [1 .. * ] of dmt$ms_mf_file_list_entry;
         number_mfl_changes: integer;
         avt_index: dmt$active_volume_table_index;
     VAR status: ost$status);

    VAR
      mfl_change_index: dmt$ms_mf_device_file_list_ord,
      mf_file_list_index: dmt$ms_mf_device_file_list_ord,
      actual: integer,
      successful: boolean,
      p_mfl: ^dmt$ms_mf_device_file_list;

    status.normal := TRUE;

    dmp$get_mfl_pointer (avt_index, p_mfl);
    IF p_mfl = NIL THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$nil_mflt_pointer, 'p_mfl = NIL.', status);
      RETURN; {----->
    IFEND;

    mf_file_list_index := 0;

    FOR mfl_change_index := 1 TO number_mfl_changes DO

    /insert_entry/
      WHILE TRUE DO
        mf_file_list_index := mf_file_list_index + 1;
        IF mf_file_list_index > UPPERBOUND (p_mfl^) THEN
          osp$set_status_abnormal (dmc$device_manager_ident, dme$overflow_of_mflt,
                'MFL is full - apply_changes_to_mfl.', status);
          RETURN; {----->
        IFEND;
        osp$set_locked_variable (p_mfl^ [mf_file_list_index].
              ordinal, 0, p_new_mfl_entries^ [mfl_change_index].ordinal, actual, successful);
        IF successful THEN
          EXIT /insert_entry/; {----->
        IFEND;
      WHILEND /insert_entry/;

    FOREND;

  PROCEND apply_changes_to_mfl;
?? TITLE := '  accumulate_mflt_change', EJECT ??

  PROCEDURE accumulate_mflt_change
    (    mflt_change: dmt$device_file_list_index;
     VAR mflt_changes: ^array [1 .. * ] of dmt$ms_mf_file_list_entry;
     VAR number_mflt_changes: integer);

    VAR
      lower: integer,
      upper: integer,
      mid: integer,
      insertion_index: integer,
      replacement_index: integer,
      number_to_move: integer,
      size: 0 .. 7fffffff(16),
      insertion_address: ^cell,
      replacement_address: ^cell,
      temp: integer,
      temp_buffer: ^array [1 .. * ] of dmt$ms_mf_file_list_entry;

    lower := 0;
    upper := number_mflt_changes + 1;
    temp := lower + upper;
    mid := temp DIV 2;

    WHILE mid <> lower DO
      IF mflt_changes^ [mid].ordinal <= mflt_change THEN
        lower := mid;
      ELSE
        upper := mid;
      IFEND;
      temp := lower + upper;
      mid := temp DIV 2;
    WHILEND;

    insertion_index := mid + 1;
    replacement_index := mid + 2;
    number_to_move := number_mflt_changes - insertion_index + 1;
    size := #SIZE (dmt$ms_mf_file_list_entry) * number_to_move;

    IF size <> 0 THEN
      insertion_address := ^mflt_changes^ [insertion_index];
      replacement_address := ^mflt_changes^ [replacement_index];
      PUSH temp_buffer: [1 .. number_to_move];
      i#move (insertion_address, temp_buffer, size);
      i#move (temp_buffer, replacement_address, size);
    IFEND;

    mflt_changes^ [insertion_index].ordinal := mflt_change;
    number_mflt_changes := number_mflt_changes + 1;

  PROCEND accumulate_mflt_change;
?? TITLE := '  dmp$get_allocation_info', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$get_allocation_info
    (    vsn: rmt$recorded_vsn;
         avt_index: dmt$active_volume_table_index;
     VAR allocation_info: dmt$allocation_info;
     VAR status: ost$status);

    VAR
      entry_p: ^dmt$ms_active_vol_table_entry,
      i_avt_index: dmt$active_volume_table_index,
      p_mat: ^dmt$mainframe_allocation_table,
      volume_found: boolean;

    status.normal := TRUE;
    IF (avt_index > 0) AND (avt_index <= UPPERBOUND (dmv$active_volume_table.table_p^))
{ } AND ((dmv$active_volume_table.table_p^ [avt_index].mass_storage.recorded_vsn = vsn) OR
          (vsn = dmv$null_vsn)) THEN
      volume_found := NOT dmv$active_volume_table.table_p^ [avt_index].entry_available;
      i_avt_index := avt_index;
    ELSE
      dmp$search_avt_by_rvsn (vsn, i_avt_index, volume_found);
    IFEND;

    IF NOT volume_found THEN
      osp$set_status_abnormal (dmc$device_manager_ident, dme$volume_not_online, vsn, status);
      RETURN; {----->
    IFEND;

    entry_p := ^dmv$active_volume_table.table_p^ [i_avt_index].mass_storage;
    allocation_info.allocation_allowed := entry_p^.allocation_allowed;
    allocation_info.device_log_count := entry_p^.device_log_entry_count;
    allocation_info.no_space := entry_p^.space_gone;
    allocation_info.space_low := entry_p^.space_low;
    allocation_info.no_file_entries := dmc$no_available_dflt_entries IN entry_p^.disk_table_status;

    dmp$get_mat_pointer (i_avt_index, p_mat);
    IF p_mat <> NIL THEN
      allocation_info.available_mat_space := p_mat^.available_space;
      allocation_info.available_dat_space := p_mat^.available_dat_space;
      allocation_info.bytes_per_dau := p_mat^.bytes_per_dau;
    ELSE
      allocation_info.available_mat_space := 0;
      allocation_info.available_dat_space := 0;
      allocation_info.bytes_per_dau := 0;
    IFEND;

  PROCEND dmp$get_allocation_info;
?? TITLE := '  dmp$volume_space_manager', EJECT ??

  PROCEDURE [XDCL] dmp$volume_space_manager
    (    avt_index: dmt$active_volume_table_index;
         full_update: boolean;
     VAR status: ost$status);

    CONST
      c$lock_retry_count = 5,
      c$process_cylinders = FALSE,
      c$skip_cylinders = TRUE;

    VAR
{     able_to_lock_avt_entry: boolean,
      able_to_set_lock: boolean,
      another_dflt_update_required: boolean,
      dflt: ^dmt$ms_device_file_list_table,
      dflt_update_required: boolean,
      disk_table_status: dmt$ms_volume_table_status,
      entry_p: ^dmt$ms_active_vol_table_entry,
      no_available_dflt_space: boolean,
      ok: boolean,
      p_dat: ^dmt$ms_device_allocation_table,
      p_mat: ^dmt$mainframe_allocation_table,
      retry: integer,
      space_required: dmt$dau_address;

    status.normal := TRUE;

    dmp$verify_access (avt_index, ok);
    IF NOT ok THEN
      RETURN; {----->
    IFEND;

{What the fuck should we do in this situation, where we used to exit when we did not get the lock?
    dmp$lock_avt_entry (avt_index{, able_to_lock_avt_entry});
{   IF NOT able_to_lock_avt_entry THEN
{     osp$set_status_abnormal (dmc$device_manager_ident, dme$unable_to_lock_avt_entry,
{           'unable to lock avt entry - DMMSMAN', status);
{     RETURN; {----->
{   IFEND;

    entry_p := ^dmv$active_volume_table.table_p^ [avt_index].mass_storage;
    disk_table_status := entry_p^.disk_table_status;

    entry_p^.disk_table_status := disk_table_status + $dmt$ms_volume_table_status
          [dmc$table_update_in_progress];

    dmp$unlock_avt_entry (avt_index);

    IF (dmc$table_update_in_progress IN disk_table_status) THEN
      RETURN; {----->
    IFEND;

    dflt_update_required := (dmc$dflt_update_required IN disk_table_status);
    no_available_dflt_space := (dmc$no_available_dflt_entries IN disk_table_status);
    another_dflt_update_required := dflt_update_required;

    dmp$get_mat_pointer (avt_index, p_mat);
    determine_mat_changes (p_mat, space_required);

{ DAT update required
    IF (space_required > 0) OR p_mat^.mat_too_full OR full_update THEN
      dmp$open_dat (entry_p^.p_device_allocation_table, osc$os_ring_1, osc$tsrv_ring, mmc$sar_write_extend,
            mmc$as_sequential, p_dat, status);
      IF NOT status.normal THEN
        osp$set_status_abnormal (dmc$device_manager_ident, dme$open_dat_failure,
              'unable to open dat - DMMSMAN', status);
        RETURN; {----->
      IFEND;

      retry := 0;
      REPEAT
        osp$test_set_main_sig_lock (entry_p^.update_lock, able_to_set_lock);
        IF NOT able_to_set_lock THEN
          retry := retry + 1;
          syp$cycle;
        IFEND;
      UNTIL able_to_set_lock or (retry > c$lock_retry_count);

      IF able_to_set_lock THEN
        IF full_update THEN
          space_required := UPPERVALUE (space_required);
          increase_mat_space (p_mat, p_dat, space_required, c$skip_cylinders);
          determine_mat_changes (p_mat, space_required);
        IFEND;

        IF (space_required > 0) THEN
          increase_mat_space (p_mat, p_dat, space_required, c$process_cylinders);
        IFEND;

        IF p_mat^.mat_too_full THEN
          decrease_mat_space (p_mat, p_dat);
        IFEND;

        osp$clear_mainframe_sig_lock (entry_p^.update_lock);
      IFEND;

      dmp$close_file (p_dat, status);
      IF NOT status.normal THEN
        osp$fatal_system_error ('unable to close dat - DMMSMAN', ^status);
      IFEND;
    IFEND;


{ DFLT update required
    IF dflt_update_required THEN
      dmp$open_dflt (entry_p^.p_device_file_list_table, osc$os_ring_1, osc$tsrv_ring, mmc$sar_write_extend,
            mmc$as_sequential, dflt, status);
      IF NOT status.normal THEN
        osp$set_status_abnormal (dmc$device_manager_ident, dme$open_dflt_failure,
              'unable to open dflt - DMMSMAN', status);
        RETURN; {----->
      IFEND;

      retry := 0;
      REPEAT
        osp$test_set_main_sig_lock (entry_p^.update_lock, able_to_set_lock);
        IF NOT able_to_set_lock THEN
          retry := retry + 1;
          syp$cycle;
        IFEND;
      UNTIL able_to_set_lock or (retry > c$lock_retry_count);

      IF able_to_set_lock THEN
        get_dflt_space (dflt, avt_index, status);
        IF NOT status.normal THEN
          osp$fatal_system_error ('bad status updating dflt - DMMSMAN', ^status);
        IFEND;

        another_dflt_update_required := FALSE;

        osp$clear_mainframe_sig_lock (entry_p^.update_lock);
      IFEND;

      dmp$close_file (dflt, status);
      IF NOT status.normal THEN
        osp$fatal_system_error ('unable to close dflt', ^status);
      IFEND;
    IFEND;

{     At this point, we MUST get the avt lock in order to clear the 'update_in_progress' bit in the disk
{     table status.  If we do not clear this bit space manager will never run on this volume again.
{     This is possible if monitor in the other CPU has locked the avt in order to set the
{     update_required bits.

    dmp$lock_avt_entry (avt_index);

    entry_p^.disk_table_status := entry_p^.disk_table_status -
          $dmt$ms_volume_table_status [dmc$table_update_in_progress];

    IF another_dflt_update_required THEN
      entry_p^.disk_table_status := entry_p^.disk_table_status +
            $dmt$ms_volume_table_status [dmc$dflt_update_required];
    ELSE
      entry_p^.disk_table_status := entry_p^.disk_table_status -
            $dmt$ms_volume_table_status [dmc$dflt_update_required];
    IFEND;


    IF no_available_dflt_space THEN
      entry_p^.disk_table_status := entry_p^.disk_table_status +
            $dmt$ms_volume_table_status [dmc$no_available_dflt_entries];
    ELSE
      entry_p^.disk_table_status := entry_p^.disk_table_status -
            $dmt$ms_volume_table_status [dmc$no_available_dflt_entries];
    IFEND;

    dmp$unlock_avt_entry (avt_index);

  PROCEND dmp$volume_space_manager;
?? TITLE := '  dmp$run_volume_space_manager', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$run_volume_space_manager
    (    avt_index: dmt$active_volume_table_index;
     VAR status: ost$status);

    CONST
      c$partial_update = FALSE;

    osp$begin_system_activity;
    IF (dmv$active_volume_table.table_p <> NIL) AND (avt_index > 0) AND
          (avt_index <= UPPERBOUND (dmv$active_volume_table.table_p^)) AND
          (NOT dmv$active_volume_table.table_p^ [avt_index].entry_available) AND
          (dmc$mainframe_mounted IN dmv$active_volume_table.table_p^ [avt_index].mass_storage.status) THEN

      dmp$volume_space_manager (avt_index, c$partial_update, status);
    IFEND;
    osp$end_system_activity;

  PROCEND dmp$run_volume_space_manager;
?? TITLE := '  dmp$management_of_volume_space', EJECT ??

  PROCEDURE [XDCL, #GATE] dmp$management_of_volume_space;

    CONST
      c$partial_update = FALSE;

    VAR
      v$previous_volumes_low: [STATIC] integer := 0,
      v$previous_volumes_out: [STATIC] integer := 0;

    VAR
      avt_index: dmt$active_volume_table_index,
      entry_p: ^dmt$ms_active_vol_table_entry,
      i: integer,
      status: ost$status,
      str: string (80),
      volumes_low: integer,
      volumes_out: integer;

    osp$begin_system_activity;

    dmv$manage_volume_space_count := dmv$manage_volume_space_count + 1;
    volumes_low := 0;
    volumes_out := 0;

    FOR avt_index := LOWERBOUND (dmv$active_volume_table.table_p^) TO
          UPPERBOUND (dmv$active_volume_table.table_p^) DO

      entry_p := ^dmv$active_volume_table.table_p^ [avt_index].mass_storage;
      IF NOT dmv$active_volume_table.table_p^ [avt_index].entry_available AND
            (dmc$mainframe_mounted IN entry_p^.status) THEN

        dmp$volume_space_manager (avt_index, c$partial_update, status);

        IF NOT status.normal THEN
          CASE status.condition OF
          = dme$unable_to_lock_dat, dme$unable_to_lock_dflt, dme$unable_to_lock_avt_entry =
            status.normal := TRUE;
          ELSE
            STRINGREP (str, i, 'Bad status (', status.condition, ') from dmp$volume_space_manager, AVT= ',
                  avt_index, ' VSN= ', entry_p^.recorded_vsn);
            osp$fatal_system_error (str (1, i), ^status);
          CASEND;
        IFEND;

        IF entry_p^.space_gone THEN
          volumes_out := volumes_out + 1;
        ELSEIF entry_p^.space_low THEN
          volumes_low := volumes_low + 1;
        IFEND;
      IFEND;
    FOREND;

    IF (volumes_low <> v$previous_volumes_low) OR (volumes_out <> v$previous_volumes_out) THEN
      v$previous_volumes_low := volumes_low;
      v$previous_volumes_out := volumes_out;
      IF dmv$space_messages_to_console THEN
        display_informative_message (volumes_out, volumes_low);
      IFEND;
    IFEND;

    dmv$volume_table_space_delay := dmv$manage_volume_space_delay;

    osp$end_system_activity;

  PROCEND dmp$management_of_volume_space;

MODEND dmm$space_manager;
