?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE : Tasking : Ring 3 task termination' ??
MODULE pmm$task_termination_ring_3;

{  PURPOSE:
{    This module contains procedures which direct that portion of task termination which occurs
{    in task services rings.  This consists primarily of dismantling the task environment.
{  DESIGN:
{    The dismantling of a task's environment is divided into two distinct phases.  The first
{    occurs in the terminating (child) task.  It consists of discarding all non_essential portions
{    of the task's address space and issuing a monitor request to relinquish the CPU.  The second
{    phase occurs in the task (the parent task) which originally initiated the terminating task.
{    It consists of discarding the remaining task environment.
{    Design of several procedures in this module reflects the fact that, if possible, a task
{    must terminate in spite of errors occurring in the task termination process.

?? NEWTITLE := '  Global declarations', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc oss$job_paged_literal
*copyc oss$task_private
*copyc oss$task_shared
*copyc avc$system_epilog
*copyc clc$compiling_for_test_harness
*copyc osc$dual_state_interactive
*copyc pmc$min_scc_program_execution
*copyc sfc$unlimited
*copyc tmc$signal_identifiers
*copyc tmc$wait_times
*copyc osd$code_base_pointer
*copyc osd$virtual_address
*copyc cle$epilog_file_missing
*copyc jme$job_monitor_conditions
*copyc jme$unable_to_alloc_all_space
*copyc pme$execution_exceptions
*copyc pme$system_exceptions
*copyc dmt$error_condition_codes
*copyc jmt$timesharing_signal
*copyc ost$caller_identifier
*copyc ost$global_task_id
*copyc pmt$loader_seq_descriptor
*copyc pmt$program_description
*copyc pmt$signal
*copyc pmt$task_control_block
*copyc pmt$task_execution_phase
*copyc pmt$task_state
*copyc pmt$task_termination_action
?? POP ??
*copyc pmf$job_mode
*copyc amp$get_file_attributes
*copyc avp$end_account
*copyc avp$get_capability
*copyc avp$get_file_value
*copyc avp$security_option_active
*copyc bap$loaded_ring_cleanup
*copyc bap$monitor_loaded_ring_cleanup
*copyc bap$monitor_task_term_cleanup
*copyc bap$task_termination_cleanup
*copyc clp$convert_integer_to_rjstring
*copyc clp$convert_integer_to_string
*copyc clp$create_environment_variable
*copyc clp$delete_all_from_cmnd_list
*copyc clp$delete_variable
*copyc clp$delete_variables
*copyc clp$erase_child_task
*copyc clp$establish_sys_command_lib
*copyc clp$evaluate_file_reference
*copyc clp$find_task_block
*copyc clp$pop_terminated_blocks
*copyc clp$put_job_command_response
*copyc clp$record_application_units
*copyc clp$reset_work_area_positions
*copyc clp$set_job_command_search_mode
*copyc clp$update_applic_resources
*copyc cmp$task_termination_cleanup
*copyc fmp$detach_all_tape_files
*copyc fmp$unlock_path_table_at_tskend
*copyc ifp$stop_interactive
*copyc iip$clear_job_locks
*copyc iip$xt_stop_xterm
*copyc jmp$disable_user_breaks
*copyc jmp$emit_communication_stat
*copyc jmp$enable_terminal_io
*copyc jmp$get_job_class_epilog
*copyc jmp$is_xterm_job
*copyc jmp$job_end
*copyc jmp$release_generic_queue_files
*copyc jmp$release_input_files
*copyc jmp$release_output_files
*copyc jmp$set_interactive_cond_state
*copyc jmp$set_job_term_disposition
*copyc jmp$set_job_termination_status
*copyc jmp$system_error
*copyc lop$close_apd_processing_files
*copyc lop$delete_linkage_tree
*copyc lop$delete_loader_library_list
*copyc lop$get_loader_seq_descriptor
*copyc lop$terminate_loader
*copyc mlp$task_termination_cleanup
*copyc mmp$verify_access
*copyc nap$incoming_message_cleanup
*copyc nap$process_task_termination
*copyc ofp$task_end
*copyc osp$append_status_file
*copyc osp$append_status_integer
*copyc osp$executing_in_job_monitor
*copyc osp$generate_log_message
*copyc osp$generate_message
*copyc osp$get_status_severity
*copyc osp$set_status_abnormal
*copyc osp$set_status_condition
*copyc osp$set_status_from_condition
*copyc osp$system_error
*copyc osp$verify_system_privilege
*copyc pfp$attach
*copyc pfp$clear_system_authority
*copyc pfp$convert_fs_to_pft$path
*copyc pfp$detach_reserved_cycles
*copyc pfp$task_termination_cleanup
*copyc pmp$add_final_interblock_ref
*copyc pmp$call_end_handler
*copyc pmp$collect_raw_task_statistics
*copyc pmp$continue_to_cause
*copyc pmp$cycle
*copyc pmp$debug_abort_file_specified
*copyc pmp$delete_non_inherited_segs
*copyc pmp$disconnect_task_from_queues
*copyc pmp$disestablish_cond_handler
*copyc pmp$enable_ts_io_in_job
*copyc pmp$establish_condition_handler
*copyc pmp$execute_epilog
*copyc pmp$exit
*copyc pmp$find_executing_task_tcb
*copyc pmp$find_executing_task_xcb
*copyc pmp$find_prog_options_and_libs
*copyc pmp$find_stack_segment
*copyc pmp$find_task_tcb
*copyc pmp$find_task_xcb
*copyc pmp$flag_all_child_tasks
*copyc pmp$generate_unique_name
*copyc pmp$get_global_task_id
*copyc pmp$get_job_names
*copyc pmp$get_loaded_rings
*copyc pmp$get_task_cp_time
*copyc pmp$get_task_id
*copyc pmp$get_termination_status
*copyc pmp$init_default_prog_options
*copyc pmp$load_debug_procedures
*copyc pmp$loaded_ring_cleanup
*copyc pmp$log
*copyc pmp$log_ascii
*copyc pmp$long_term_wait
*copyc pmp$monitor_loaded_ring_cleanup
*copyc pmp$outward_call
*copyc pmp$push_task_debug_mode
*copyc pmp$release_task_environment
*copyc pmp$set_task_state
*copyc pmp$signal_all_child_tasks
*copyc pmp$task_debug_mode_on
*copyc pmp$task_debug_ring
*copyc pmp$task_end
*copyc pmp$task_state
*copyc pmp$verify_current_child
*copyc pmp$wait
*copyc qfp$set_job_attributes
*copyc sfp$change_file_space_limit
*copyc sfp$clear_job_routing_ctl_lock
*copyc sfp$emit_audit_statistic
*copyc sfp$emit_statistic
*copyc sfp$internal_emit_statistic
*copyc syp$pop_inhibit_job_recovery
*copyc syp$push_inhibit_job_recovery
*copyc tmp$disable_preemptive_commo
*copyc tmp$fetch_job_statistics
*copyc tmp$find_ring_crossing_frame
*copyc clv$applications_active
*copyc clv$processing_phase
*copyc clv$standard_files
*copyc cmv$task_reserved_element_count
*copyc jmv$executing_within_system_job
*copyc jmv$jcb
*copyc jmv$job_attributes
*copyc jmv$job_termination_status
*copyc jmv$terminal_io_disabled
*copyc lov$apd_load
*copyc pmv$debug_logging_enabled
*copyc pmv$end_handler_list
*copyc jml$user_id

{Debug
*copyc bap$verify_task_file_table
*copyc osp$recoverable_system_error

  VAR
    bav$verify_tft_before_cleanup: [XREF] boolean;
{Debug End
{Debug
  VAR
    cmv$enable_task_cleanup_count: [XREF] boolean;
{Debug End

  VAR
    pmv$epilog_file: [XDCL, #GATE, oss$task_shared] string (fsc$max_path_size),
    pmv$task_execution_phase: [XDCL, #GATE, oss$task_private]
          pmt$task_execution_phase := LOWERVALUE (pmt$task_execution_phase),
    pmv$popper_handler_established: [XDCL, #GATE, oss$task_private] boolean := FALSE,
    termination_revocable: [STATIC] boolean := TRUE,
    termination_revocations: [STATIC] 0 .. 1000 := 0;

?? OLDTITLE ??
?? NEWTITLE := '[XDCL] pmp$exit_unix_task', EJECT ??
*copy pmh$exit_unix_task

  PROCEDURE [XDCL] pmp$exit_unix_task
    (    parent_task_gtid: ost$global_task_id;
         task_termination_action: pmt$task_termination_action);

?? NEWTITLE := 'truncate_stack', EJECT ??

{ PURPOSE:
{   The purpose of this request is to truncate the stack at the ring 3 ring
{ crossing frame.

    PROCEDURE truncate_stack;

      VAR
        local_status: ost$status,
        starting_frame_p: ^ost$stack_frame_save_area,
        xing_frame_p: ^ost$stack_frame_save_area;

      local_status.normal := TRUE;

{ Truncate the stack at the ring 3 ring crossing frame.

      starting_frame_p := NIL;
      tmp$find_ring_crossing_frame (starting_frame_p, xing_frame_p, local_status);
      IF NOT local_status.normal THEN
        pmp$exit (local_status);
      IFEND;

      starting_frame_p := xing_frame_p^.minimum_save_area.a2_previous_save_area;
      WHILE ((starting_frame_p <> NIL) AND (#RING (starting_frame_p) <= osc$tsrv_ring) AND
            local_status.normal) DO
        tmp$find_ring_crossing_frame (starting_frame_p, xing_frame_p, local_status);
        starting_frame_p := xing_frame_p^.minimum_save_area.a2_previous_save_area;
      WHILEND;

      IF local_status.normal THEN
        xing_frame_p^.minimum_save_area.a2_previous_save_area := NIL;

      ELSE
        pmp$exit (local_status);
      IFEND;

    PROCEND truncate_stack;
?? OLDTITLE ??

{ Record record the UNIX kernel termination information in the TCB.

{ Truncate the stack at the ring 3 ring crossing frame.

{ Send the task into termination.


  PROCEND pmp$exit_unix_task;
?? OLDTITLE ??
?? NEWTITLE := '  [XDCL, #GATE] pmp$record_program_termination', EJECT ??

  PROCEDURE [XDCL, #GATE] pmp$record_program_termination
    (    status: ost$status;
         program_termination_mode: pmt$program_termination_mode);

{  PURPOSE:
{    This procedure is called whenever a program terminates, i.e., calls PMP$EXIT or PMP$ABORT.
{    It records (in a secure location) the task completion status to be returned to the
{    executing task's parent in the event that the task actually terminates.  It also enables
{    abort_file processing when appropriate.
{  NOTE:
{    This procedure may be called more than once for a single task.  Only the first abnormal
{    status will be recorded.  Abort_file processing will be enabled only once.

    VAR
      ignore_status: ost$status;

?? NEWTITLE := 'rpt_condition_handler', EJECT ??

{ PURPOSE:
{   Condition handler for record_program_termination.

    PROCEDURE rpt_condition_handler
      (    condition: pmt$condition;
           descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR status: ost$status);

      VAR
        message: ost$status;

      IF (condition.selector <> ifc$interactive_condition) AND (condition.selector <> pmc$pit_condition) THEN
        osp$set_status_from_condition ('PM', condition, save_area, message, ignore_status);
        osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], message, ignore_status);
        pmp$disestablish_cond_handler (conditions, ignore_status);
        pmp$cleanup_loaded_rings; { does not return }
      IFEND;
    PROCEND rpt_condition_handler;
?? OLDTITLE, EJECT ??

    VAR
      caller: ost$caller_identifier,
      conditions: [STATIC, READ, oss$job_paged_literal] pmt$condition :=
            [pmc$condition_combination, $pmt$condition_combination
            [pmc$system_conditions, mmc$segment_access_condition, ifc$interactive_condition,
            pmc$pit_condition]],
      established_handler: pmt$established_handler,
      program_description: ^pmt$program_description,
      program_attributes: ^pmt$program_attributes,
      tcb_p: ^pmt$task_control_block,
      abort_file_specified: boolean,
      local_status: ost$status;

    osp$verify_system_privilege;

    #CALLER_ID (caller);
    IF (caller.ring <= osc$tsrv_ring) THEN
      termination_revocable := FALSE;
    IFEND;

    IF jmv$executing_within_system_job THEN
      IF osp$executing_in_job_monitor () THEN
        jmp$system_error ('system job exit', ^status);
      IFEND;
    IFEND;

    pmp$establish_condition_handler (conditions, ^rpt_condition_handler, ^established_handler, local_status);
    IF NOT local_status.normal THEN
      pmp$cleanup_loaded_rings; { does not return }
    ELSE
      IF program_termination_mode > pmp$task_state () THEN
        pmp$set_task_state (program_termination_mode);
      IFEND;
      IF NOT status.normal THEN
        pmp$flag_all_child_tasks (pmc$sf_terminate_task, local_status);
        pmp$find_executing_task_tcb (tcb_p);
        WHILE tcb_p^.first_child <> NIL DO
          pmp$long_term_wait (tmc$infinite_wait, tmc$infinite_wait);
        WHILEND;
        IF tcb_p^.nosve.termination_status^.normal THEN
          tcb_p^.nosve.termination_status^ := status;
        ELSE
          osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], status, ignore_status);
        IFEND;
      IFEND;

      pmp$debug_abort_file_specified (abort_file_specified);

      IF ((program_termination_mode = pmc$program_aborting) AND (NOT pmp$task_debug_mode_on ()) AND
            abort_file_specified) THEN
        pmp$load_debug_procedures (ignore_status);

{status is ignored because a debugger may not be loadable at this point.

      IFEND;
    IFEND;
  PROCEND pmp$record_program_termination;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] pmp$revoke_program_termination', EJECT ??
*copy pmh$revoke_program_termination

  PROCEDURE [XDCL, #GATE] pmp$revoke_program_termination
    (VAR status: ost$status);

{  PURPOSE:
{    This procedure is called to revoke a previously recorded program termination.  This
{    interface is used when a process executing subsequent to a program terminating
{    (e.g., debug facility or block_exit handler) desires to restart the program.

    VAR
      tcb_p: ^pmt$task_control_block;

    status.normal := TRUE;
    IF termination_revocable THEN
      IF (termination_revocations < UPPERVALUE (termination_revocations)) THEN
        pmp$set_task_state (pmc$task_active);
        pmp$find_executing_task_tcb (tcb_p);
        tcb_p^.nosve.termination_status^.normal := TRUE;
        termination_revocations := termination_revocations + 1;
      ELSE
        osp$set_status_abnormal ('PM', pme$maximum_term_revocations, '', status);
      IFEND;
    ELSE
      osp$set_status_abnormal ('PM', pme$termination_not_revocable, '', status);
    IFEND;

  PROCEND pmp$revoke_program_termination;
?? TITLE := '  [XDCL, #GATE] pmp$cleanup_loaded_rings', EJECT ??

  PROCEDURE [XDCL, #GATE] pmp$cleanup_loaded_rings;


{  PURPOSE:
{    This procedure is responsible for calling ring_dependent task cleanup procedures in each
{    ring in which the user program executed.  Rings are processed in order of increasing
{    privilege.
{  NOTE:
{    This procedure is designed to be called several times within a single task.  (This is
{    necessary since activating cleanup procedures in a less privileged ring requires an
{    outward call, which destroys preceding stack frames.)  It utilizes state memory to
{    determine which ring, if any, to process on a particular call.  For each ring, at most one
{    attempt will be made to call ring_dependent cleanup procedures in that ring.

?? NEWTITLE := 'clr_condition_handler', EJECT ??

{ PURPOSE:
{   Ignore interactive and pit conditions, put any others in the job log and
{   call terminate_task.

    PROCEDURE clr_condition_handler
      (    condition: pmt$condition;
           descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR status: ost$status);

      VAR
        executing_ring: ost$ring,
        message: ost$status,
        ignore_status: ost$status;

      IF (condition.selector <> ifc$interactive_condition) AND (condition.selector <> pmc$pit_condition) THEN
        osp$set_status_from_condition ('PM', condition, save_area, message, ignore_status);
        osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], message, ignore_status);
        pmp$disestablish_cond_handler (conditions, ignore_status);
        program_ring := #RING (^executing_ring);
        terminate_task; { does not return }
      IFEND;
    PROCEND clr_condition_handler;
?? OLDTITLE, EJECT ??

    CONST
      debug_bit = 56;

    TYPE
      pmt$user_mask = set of 0 .. 63;

    VAR
      converter: record
        case 0 .. 3 of
        = 0 =
          register: integer,
        = 1 =
          user_mask: pmt$user_mask,
        = 2 =
          pointer_to_procedure: ^procedure,
        = 3 =
          code_base_pointer: ^ost$external_code_base_pointer,
        casend,
      recend;

    VAR
      pmv$end_handler_to_call: [XDCL, #GATE, STATIC, oss$task_private] pmt$end_handler := NIL;

{Debug
    VAR
      task_file_table_ok: boolean;
{End Debug

    VAR
      program_ring: [STATIC, oss$task_private] pmt$loadable_ring := UPPERVALUE (pmt$loadable_ring),
      executing_ring: ost$ring,
      loaded_rings: pmt$loadable_rings,
      loader_seq_descriptor_p: ^pmt$loader_seq_descriptor,
      system_core_debugger_inactive: boolean,
      cleanup_cbp: ^ost$external_code_base_pointer,
      stack_segment: ^pmt$stack_segment,
      child_tasks_flagged: boolean,
      conditions: pmt$condition,
      established_handler: pmt$established_handler,
      tcb_p: ^pmt$task_control_block,
      handler: ^pmt$end_handler_desc,
      local_status: ost$status;

?? EJECT ??

    osp$verify_system_privilege;

    child_tasks_flagged := FALSE;
    termination_revocable := FALSE;
    pmp$set_task_execution_phase (pmc$task_loaded_ring_cleanup);

    system_core_debugger_inactive := (#READ_REGISTER (osc$pr_debug_list_pointer) DIV 100000000000(16) <> 1);

    IF program_ring < pmp$task_debug_ring () THEN
      IF pmp$task_debug_mode_on () THEN
        pmp$push_task_debug_mode (pmc$debug_mode_off, local_status);
      IFEND;

      IF system_core_debugger_inactive THEN
        converter.register := #READ_REGISTER (osc$pr_user_mask_reg);
        converter.user_mask := converter.user_mask - $pmt$user_mask [debug_bit];
        #WRITE_REGISTER (osc$pr_user_mask_reg, converter.register);
      IFEND;
    IFEND;

{ If this is an ADA parent we cannot procede further with children still executing.

    pmp$find_executing_task_tcb (tcb_p);
    IF (tcb_p^.nosve.ada_task_table <> NIL) AND (tcb_p^.nosve.ada_task_table^.table [0] = tcb_p^.task_id) THEN
      WHILE tcb_p^.first_child <> NIL DO
        IF (NOT tcb_p^.nosve.termination_status^.normal) AND (NOT child_tasks_flagged) THEN
          pmp$flag_all_child_tasks (pmc$sf_terminate_task, local_status);
          IF NOT local_status.normal THEN
            osp$system_error ('pmp$flag_all_child_tasks', ^local_status);
          IFEND;
          child_tasks_flagged := TRUE;
        IFEND;
        pmp$long_term_wait (tmc$infinite_wait, tmc$infinite_wait);
      WHILEND;
    IFEND;

    conditions.selector := pmc$condition_combination;
    conditions.combination := $pmt$condition_combination [pmc$system_conditions, mmc$segment_access_condition,
          ifc$interactive_condition, pmc$pit_condition];
    pmp$establish_condition_handler (conditions, ^clr_condition_handler, ^established_handler, local_status);

    IF local_status.normal THEN
      pmp$get_loaded_rings (loaded_rings);
      WHILE program_ring > #RING (^executing_ring) DO
        IF pmv$end_handler_list <> NIL THEN
          handler := pmv$end_handler_list^ [program_ring];
          WHILE handler <> NIL DO
            IF NOT ((handler^.disestablished) OR (handler^.called)) THEN
              handler^.called := TRUE;
              pmv$end_handler_to_call := handler^.end_handler;
              pmp$find_stack_segment (program_ring, stack_segment);
              RESET stack_segment;
              converter.pointer_to_procedure := ^pmp$call_end_handler;
              cleanup_cbp := converter.code_base_pointer;
              pmp$outward_call (cleanup_cbp, program_ring, NIL, NIL, stack_segment);
            IFEND;
            handler := handler^.link;
          WHILEND;
          IF (lov$apd_flags.apd_load AND (program_ring = tcb_p^.target_ring)) THEN

{ Determine if an end handler was called for the instrumented APD task.  If so, add the
{ final interblock reference.

            handler := pmv$end_handler_list^ [program_ring];
            WHILE ((handler <> NIL) AND (NOT handler^.called)) DO
              handler := handler^.link;
            WHILEND;
            IF handler <> NIL THEN
              lop$get_loader_seq_descriptor (loader_seq_descriptor_p);
              pmp$add_final_interblock_ref (loader_seq_descriptor_p);
            IFEND;
          IFEND;
        IFEND;

        program_ring := program_ring - 1;

        IF ((program_ring + 1) IN loaded_rings) THEN
          converter.pointer_to_procedure := ^pmp$loaded_ring_cleanup;
          cleanup_cbp := converter.code_base_pointer;

          pmp$find_stack_segment (program_ring + 1, stack_segment);
          RESET stack_segment;

          pmp$outward_call (cleanup_cbp, program_ring + 1, NIL, NIL, stack_segment);
        IFEND;
      WHILEND;
    IFEND;

    program_ring := #RING (^executing_ring);
    lop$terminate_loader;

    WHILE program_ring > osc$os_ring_1 DO
      IF pmv$end_handler_list <> NIL THEN
        handler := pmv$end_handler_list^ [program_ring];
        WHILE handler <> NIL DO
          IF NOT ((handler^.disestablished) OR (handler^.called)) THEN
            handler^.called := TRUE;
            pmv$end_handler_to_call := handler^.end_handler;
            pmp$call_end_handler;
          IFEND;
          handler := handler^.link;
        WHILEND;
        IF (lov$apd_flags.apd_load AND (program_ring = tcb_p^.target_ring)) THEN

{ Determine if an end handler was called for the instrumented APD task.  If so, add the
{ final interblock reference.

          handler := pmv$end_handler_list^ [program_ring];
          WHILE ((handler <> NIL) AND (NOT handler^.called)) DO
            handler := handler^.link;
          WHILEND;
          IF handler <> NIL THEN
            lop$get_loader_seq_descriptor (loader_seq_descriptor_p);
            pmp$add_final_interblock_ref (loader_seq_descriptor_p);
          IFEND;
        IFEND;
      IFEND;
      program_ring := program_ring - 1;
    WHILEND;

{ Close the files used for an instrumented APD task.

    IF lov$apd_flags.apd_load THEN
      lop$close_apd_processing_files;
    IFEND;

{ If the job is terminating, then delete all entries from the command list
{ since no user or site code can get control beyond this point.

    IF osp$executing_in_job_monitor () THEN
      clp$delete_all_from_cmnd_list ({ignore} local_status);
      clp$establish_sys_command_lib (NIL, local_status);
    IFEND;

    pfp$task_termination_cleanup;

{Debug
    IF bav$verify_tft_before_cleanup THEN
      bap$verify_task_file_table (task_file_table_ok);
      IF NOT task_file_table_ok THEN
        osp$recoverable_system_error ('Task File Table no ok', NIL);
      IFEND;
    IFEND;
{End Debug
    bap$loaded_ring_cleanup;

    IF pmp$task_debug_mode_on () THEN
      pmp$push_task_debug_mode (pmc$debug_mode_off, local_status);
    IFEND;

    IF system_core_debugger_inactive THEN
      converter.register := #READ_REGISTER (osc$pr_user_mask_reg);
      converter.user_mask := converter.user_mask - $pmt$user_mask [debug_bit];
      #WRITE_REGISTER (osc$pr_user_mask_reg, converter.register);
    IFEND;

    pmp$disestablish_cond_handler (conditions, local_status);

    terminate_task; { does not return }


  PROCEND pmp$cleanup_loaded_rings;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] pmp$execute_job_epilogs', EJECT ??
*copy pmh$execute_job_epilogs

  PROCEDURE [XDCL, #GATE] pmp$execute_job_epilogs;

{ TYPE
{   status = status
{ TYPEND

?? PUSH (LISTEXT := ON) ??

    VAR
      type_specification: [STATIC, READ, cls$declaration_section] record
        header: clt$type_specification_header,
      recend := [[1, 0, clc$status_type]];

?? POP ??

    VAR
      job_block: [STATIC, oss$task_private] ^clt$block := NIL,
      child_tasks_flagged: boolean,
      cleanup_cbp: ^ost$external_code_base_pointer,
      conditions: pmt$condition,
      converter: record
        case 1 .. 2 of
        = 1 =
          pointer_to_procedure: ^procedure,
        = 2 =
          code_base_pointer: ^ost$external_code_base_pointer,
        casend,
      recend,
      established_handler: pmt$established_handler,
      executing_ring: ost$ring,
      file_attributes: array [1 .. 1] of amt$get_item,
      ignore_status: ost$status,
      interrupt_capability: boolean,
      job_maximum_limit: sft$counter,
      job_mode: jmt$job_mode,
      job_termination_status: ost$status,
      job_termination_status_value: clt$data_value,
      job_warning_limit: sft$counter,
      loaded_rings: pmt$loadable_rings,
      local_status: ost$status,
      next_termination_phase: [STATIC, READ, oss$job_paged_literal] array
            [clc$job_begin_phase .. clc$job_end_phase] of clt$processing_phase :=
            [clc$job_end_phase, clc$system_epilog_phase, clc$system_epilog_phase, clc$class_epilog_phase,
            clc$account_epilog_phase, clc$project_epilog_phase,

{  The next termination phase for the user prolog is the user epilog; thus if
{  LOGOUT occurs in the user prolog, the user epilog is executed.  This is not
{  true for the other prologs, if LOGOUT occurs during one of them, the
{  corresponding epilog is skipped.

      clc$user_epilog_phase, clc$user_epilog_phase, clc$member_epilog_phase, clc$project_epilog_phase,
            clc$account_epilog_phase, clc$class_epilog_phase, clc$system_epilog_phase, clc$job_end_phase,
            clc$job_end_phase],
      operation_information: ^sft$audit_information,
      operation_status: ^ost$status,
      program_options_and_libraries: ^pmt$prog_options_and_libraries,
      program_ring: [STATIC, oss$task_private] pmt$loadable_ring := UPPERVALUE (pmt$loadable_ring),
      severity: ost$status_severity,
      stack_segment: ^pmt$stack_segment,
      statistic_data: jmt$comm_acct_statistic_data,
      tcb_p: ^pmt$task_control_block,
      unique_name: ost$unique_name;

?? NEWTITLE := 'eje_condition_handler', EJECT ??

    PROCEDURE eje_condition_handler
      (    condition: pmt$condition;
           descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR status: ost$status);

      VAR
        ignore_status: ost$status,
        message: ost$status;

      IF (condition.selector <> ifc$interactive_condition) AND (condition.selector <> pmc$pit_condition) THEN
        osp$set_status_from_condition ('PM', condition, save_area, message, ignore_status);
        osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], message, ignore_status);
        pmp$disestablish_cond_handler (conditions, ignore_status);
        pmp$exit (ignore_status);
      IFEND;

    PROCEND eje_condition_handler;
?? OLDTITLE ??
?? NEWTITLE := 'clean_up_blocks', EJECT ??

    PROCEDURE clean_up_blocks
      (VAR job_block: ^clt$block;
       VAR status: ost$status);

      VAR
        status_value: clt$data_value;

      clp$find_task_block (job_block, status);
      IF NOT status.normal THEN
        osp$system_error ('Unable to get pointer to SCL Task Block', ^status);
      IFEND;

      clp$pop_terminated_blocks (job_block, status);

      clp$delete_variable ('OSV$STATUS', ignore_status);
      status_value.kind := clc$status;
      status_value.status_value := ^status;
      clp$create_environment_variable ('OSV$STATUS', clc$job_scope, clc$read_write,
            clc$immediate_evaluation, #SEQ (type_specification), ^status_value, ignore_status);

      clp$reset_work_area_positions (ignore_status);

    PROCEND clean_up_blocks;
?? OLDTITLE ??
?? NEWTITLE := 'execute_epilog', EJECT ??

    PROCEDURE execute_epilog;

      VAR
        cycle_selector: pft$cycle_selector,
        epilog_path: ^pft$path,
        evaluated_file_reference: fst$evaluated_file_reference,
        file_reference_parsing_options: clt$file_ref_parsing_options,
        local_file_name: amt$local_file_name,
        password: pft$password,
        share_selections: pft$share_selections,
        tcb_p: ^pmt$task_control_block,
        usage_selections: pft$usage_selections;


      file_reference_parsing_options := $clt$file_ref_parsing_options [clc$file_ref_evaluation_stage];
      clp$evaluate_file_reference (pmv$epilog_file, file_reference_parsing_options, FALSE,
            evaluated_file_reference, local_status);
      IF local_status.normal THEN
        pmp$generate_unique_name (unique_name, ignore_status);
        local_file_name := unique_name.value;
        PUSH epilog_path: [1 .. evaluated_file_reference.number_of_path_elements];
        pfp$convert_fs_to_pft$path (evaluated_file_reference, epilog_path^);
        cycle_selector.cycle_option := pfc$highest_cycle;
        password := ' ';
        usage_selections := $pft$usage_selections [pfc$read, pfc$execute];
        share_selections := $pft$share_selections [pfc$read, pfc$execute];
        pfp$attach (local_file_name, epilog_path^, cycle_selector, password, usage_selections,
              share_selections, pfc$no_wait, local_status);
        IF local_status.normal THEN
          pmv$epilog_file (1, 7) := '$LOCAL.';
          pmv$epilog_file (8, * ) := local_file_name (1, 31);
        ELSEIF (local_status.condition = pfe$cycle_busy) OR
              (local_status.condition = pfe$usage_not_permitted) THEN

{ Let SCL have a try at it.

          local_status.normal := TRUE;
        IFEND;
      IFEND;

      IF local_status.normal THEN

{ Changing the command search mode from exclusive mode must be done from ring 3.

        IF clv$processing_phase > clc$user_epilog_phase THEN
          clp$set_job_command_search_mode (clc$global_command_search, local_status);
          IF NOT local_status.normal THEN
            osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], local_status, ignore_status);
          IFEND;
        IFEND;

        converter.pointer_to_procedure := ^pmp$execute_epilog;
        cleanup_cbp := converter.code_base_pointer;
        pmp$find_executing_task_tcb (tcb_p);
        pmp$find_stack_segment (tcb_p^.target_ring, stack_segment);
        RESET stack_segment;
        pmp$outward_call (cleanup_cbp, tcb_p^.target_ring, NIL, NIL, stack_segment);
      ELSEIF local_status.condition = pfe$unknown_permanent_file THEN
        IF clv$processing_phase = clc$user_epilog_phase THEN
          osp$set_status_condition (cle$epilog_file_missing, local_status);
          osp$append_status_file (osc$status_parameter_delimiter, pmv$epilog_file, local_status);
          osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], local_status, ignore_status);
        IFEND;
        local_status.normal := TRUE;
      IFEND;

    PROCEND execute_epilog;
?? OLDTITLE, EJECT ??

    osp$verify_system_privilege;

    IF clv$processing_phase < clc$user_epilog_phase THEN

{  The following code is executed only once during job monitor task termination.

      pmp$set_task_execution_phase (pmc$task_loaded_ring_cleanup);
      clean_up_blocks (job_block, local_status);
      IF jmv$terminal_io_disabled THEN
        jmp$enable_terminal_io;
      IFEND;

      IF jmv$job_termination_status = NIL THEN
        pmp$get_termination_status (job_termination_status);
        jmp$set_job_termination_status (job_termination_status);
      ELSE
        job_termination_status := jmv$job_termination_status^;
      IFEND;
      IF NOT job_termination_status.normal AND
            (job_termination_status.condition = dme$unable_to_alloc_all_space) THEN
        osp$set_status_abnormal ('JM', jme$unable_to_alloc_all_space, '', job_termination_status);
      IFEND;
      job_termination_status_value.kind := clc$status;
      job_termination_status_value.status_value := ^job_termination_status;
      clp$create_environment_variable ('OSV$JOB_TERMINATION_STATUS', clc$job_scope, clc$read_only,
            clc$immediate_evaluation, #SEQ (type_specification), ^job_termination_status_value,
            ignore_status);
      IF NOT job_termination_status.normal THEN
        osp$get_status_severity (job_termination_status.condition, severity, local_status);
        IF severity >= osc$error_status THEN
          osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], job_termination_status, ignore_status);
          osp$generate_message (job_termination_status, ignore_status);
        IFEND;
      IFEND;
    IFEND;

    child_tasks_flagged := FALSE;
    conditions.selector := pmc$condition_combination;
    conditions.combination := $pmt$condition_combination [pmc$system_conditions, mmc$segment_access_condition,
          ifc$interactive_condition, pmc$pit_condition];
    pmp$establish_condition_handler (conditions, ^eje_condition_handler, ^established_handler, local_status);
    IF NOT local_status.normal THEN
      osp$system_error ('pmp$establish_condition_handler', ^local_status);
    IFEND;

    pmp$find_executing_task_tcb (tcb_p);
    WHILE tcb_p^.first_child <> NIL DO
      IF NOT child_tasks_flagged THEN
        pmp$flag_all_child_tasks (pmc$sf_terminate_task, local_status);
        IF NOT local_status.normal THEN
          osp$system_error ('pmp$flag_all_child_tasks', ^local_status);
        IFEND;
        child_tasks_flagged := TRUE;
      IFEND;
      pmp$long_term_wait (tmc$infinite_wait, tmc$infinite_wait);
    WHILEND;

    clv$processing_phase := next_termination_phase [clv$processing_phase];

    WHILE clv$processing_phase <> clc$job_end_phase DO
      CASE clv$processing_phase OF

      = clc$user_epilog_phase =

{ Execute user epilog

        avp$get_capability (avc$interrupt_epilogs, avc$user, interrupt_capability, local_status);
        IF NOT local_status.normal THEN
          IF (local_status.condition = ave$unknown_field) OR
                (local_status.condition = ave$field_was_deleted) THEN
            interrupt_capability := TRUE;
          ELSE
            osp$generate_log_message ($pmt$ascii_logset [pmc$system_log], local_status, ignore_status);
          IFEND;
          local_status.normal := TRUE;
        IFEND;

        IF NOT interrupt_capability THEN
          jmp$disable_user_breaks;
        IFEND;

        avp$get_file_value (avc$user_epilog, avc$user, pmv$epilog_file, local_status);
        IF local_status.normal THEN
          IF ((pmv$epilog_file (1, 5) <> '$NULL') AND (pmv$epilog_file (1, 12) <> '$LOCAL.$NULL')) THEN
            execute_epilog;
            IF NOT local_status.normal THEN
              osp$get_status_severity (local_status.condition, severity, ignore_status);
              IF severity >= osc$error_status THEN
                clp$put_job_command_response (' Following error in USER epilog:', ignore_status);
                osp$generate_message (local_status, ignore_status);
                local_status.normal := TRUE;
              IFEND;
            IFEND;
          IFEND;
        ELSE
          local_status.normal := TRUE;
        IFEND;

      = clc$project_epilog_phase =

{ Execute project epilog

        avp$get_file_value (avc$project_epilog, avc$project, pmv$epilog_file, local_status);
        IF local_status.normal THEN
          IF ((pmv$epilog_file (1, 5) <> '$NULL') AND (pmv$epilog_file (1, 12) <> '$LOCAL.$NULL')) THEN
            execute_epilog;
            IF NOT local_status.normal THEN
              osp$get_status_severity (local_status.condition, severity, ignore_status);
              IF severity >= osc$error_status THEN
                clp$put_job_command_response (' Following error in PROJECT epilog:', ignore_status);
                osp$generate_message (local_status, ignore_status);
                local_status.normal := TRUE;
              IFEND;
            IFEND;
          IFEND;
        ELSE
          local_status.normal := TRUE;
        IFEND;

      = clc$account_epilog_phase =

{ Execute account epilog

        avp$get_file_value (avc$account_epilog, avc$account, pmv$epilog_file, local_status);
        IF local_status.normal THEN
          IF ((pmv$epilog_file (1, 5) <> '$NULL') AND (pmv$epilog_file (1, 12) <> '$LOCAL.$NULL')) THEN
            execute_epilog;
            IF NOT local_status.normal THEN
              osp$get_status_severity (local_status.condition, severity, ignore_status);
              IF severity >= osc$error_status THEN
                clp$put_job_command_response (' Following error in ACCOUNT epilog:', ignore_status);
                osp$generate_message (local_status, ignore_status);
                local_status.normal := TRUE;
              IFEND;
            IFEND;
          IFEND;
        ELSE
          local_status.normal := TRUE;
        IFEND;

{ Execute job_class epilog

      = clc$class_epilog_phase =

        IF interrupt_capability THEN
          jmp$disable_user_breaks;
        IFEND;
        pmp$get_loaded_rings (loaded_rings);

        WHILE program_ring > #RING (^executing_ring) DO
          program_ring := program_ring - 1;
          IF ((program_ring + 1) IN loaded_rings) THEN
            converter.pointer_to_procedure := ^pmp$monitor_loaded_ring_cleanup;
            cleanup_cbp := converter.code_base_pointer;
            pmp$find_stack_segment (program_ring + 1, stack_segment);
            RESET stack_segment;
            pmp$outward_call (cleanup_cbp, program_ring + 1, NIL, NIL, stack_segment);
          IFEND;
        WHILEND;

{ Move the file space limits up to unlimited so the site epilogs always run.

        job_warning_limit := sfc$unlimited;
        job_maximum_limit := sfc$unlimited;
        sfp$change_file_space_limit (sfc$perm_file_space_limit, ^job_warning_limit, ^job_maximum_limit,
              {accumulator = } NIL, {job_warning_checking = } NIL);
        sfp$change_file_space_limit (sfc$temp_file_space_limit, ^job_warning_limit, ^job_maximum_limit,
              {accumulator = } NIL, {job_warning_checking = } NIL);

        bap$monitor_loaded_ring_cleanup;
        bap$monitor_task_term_cleanup;
        lop$delete_loader_library_list;
        lop$delete_linkage_tree;
        pmp$find_prog_options_and_libs (program_options_and_libraries);
        pmp$init_default_prog_options (program_options_and_libraries^.default_options, local_status);
        program_options_and_libraries^.job_library_list := NIL;
        program_options_and_libraries^.debug_library_list := NIL;
        program_options_and_libraries^.default_options^.debug_input :=
              clv$standard_files [clc$sf_command_file].path_handle_name;
        program_options_and_libraries^.default_options^.debug_output :=
              clv$standard_files [clc$sf_standard_output_file].path_handle_name;
        program_options_and_libraries^.default_options^.abort_file :=
              clv$standard_files [clc$sf_null_file].path_handle_name;
        fmp$detach_all_tape_files;
        clp$delete_variables (job_block^.variables);
        jmp$get_job_class_epilog (pmv$epilog_file, local_status);
        IF local_status.normal AND (pmv$epilog_file <> '') THEN
          execute_epilog;
          IF NOT local_status.normal THEN
            osp$get_status_severity (local_status.condition, severity, ignore_status);
            IF severity >= osc$error_status THEN
              clp$put_job_command_response (' Following error in JOB_CLASS epilog:', ignore_status);
              osp$generate_message (local_status, ignore_status);
              local_status.normal := TRUE;
            IFEND;
          IFEND;
        ELSE
          local_status.normal := TRUE;
        IFEND;

{ Execute system epilog

      = clc$system_epilog_phase =
        pmv$epilog_file := avc$system_epilog;

        ?IF NOT clc$compiling_for_test_harness THEN
          job_mode := pmf$job_mode ();
          IF job_mode = jmc$batch THEN
            statistic_data.statistic_id := jmc$ca_standard_output_file;
            jmp$emit_communication_stat (statistic_data);
          ELSEIF job_mode = jmc$interactive_connected THEN
            statistic_data.statistic_id := jmc$ca_interactive_interval;
            jmp$emit_communication_stat (statistic_data);
          IFEND;

          IF avp$security_option_active (avc$vso_security_audit) THEN
            PUSH operation_information;
            operation_information^.audited_operation := sfc$ao_job_end;

{ This statistic has no counters or descriptive data.

            PUSH operation_status;
            operation_status^.normal := TRUE;
            sfp$emit_audit_statistic (operation_information^, operation_status^);
          IFEND;
          avp$end_account (local_status);
          IF NOT local_status.normal THEN
            osp$generate_log_message ($pmt$ascii_logset [pmc$system_log], local_status, ignore_status);
            local_status.normal := TRUE;
          IFEND;

          execute_epilog;
          IF NOT local_status.normal THEN
            osp$get_status_severity (local_status.condition, severity, ignore_status);
            IF severity >= osc$error_status THEN
              clp$put_job_command_response (' Following error in SYSTEM epilog:', ignore_status);
              osp$generate_message (local_status, ignore_status);
              local_status.normal := TRUE;
            IFEND;
          IFEND;
        ?IFEND;

      = clc$job_end_phase =
        RETURN;
      ELSE
        ;
      CASEND;

      clv$processing_phase := next_termination_phase [clv$processing_phase];

    WHILEND;

  PROCEND pmp$execute_job_epilogs;
?? TITLE := '  terminate_task' ??
?? EJECT ??

  PROCEDURE terminate_task;

{  PURPOSE:
{    This procedure initiates the dismantling of a task's environment.  Activation of this procedure
{    guarantees that the task will terminate (if possible).  No further user program codes will
{    be executed.
{  NOTE:
{    This procedure is implemented as several phases.  Although the normal case is for this
{    procedure to be called only once per task, it is designed to handle multiple calls.
{    (Multiple calls could occur due to conditions arising outside the scope of the procedure's
{    condition handler.)  State memory is used to insure a normal progression of phases in
{    the event of multiple calls.


    TYPE
      pmt$task_termination_phase = (null_phase, initial_phase, access_method_cleanup, permanent_files_cleanup,
            namve_cleanup, queue_file_management_cleanup, interactive_cleanup, incoming_message_cleanup,
            operator_facility_cleanup, memory_link_cleanup, local_queues_cleanup, preemptive_comm_cleanup,
            configuration_cleanup, emit_statistics, job_termination, release_segments, relinquish_cpu);

    VAR
      recovery_inhibited: [STATIC] boolean := FALSE,
      task_termination_phase: [STATIC] pmt$task_termination_phase := LOWERVALUE (pmt$task_termination_phase);

?? NEWTITLE := '    confine_environment' ??
?? NEWTITLE := '    asynchronous_condition_handler' ??
?? NEWTITLE := '    fatal_condition_handler' ??
?? EJECT ??

    PROCEDURE confine_environment;

{   PURPOSE:
{     The purpose of this procedure is to confine the task environment to terminate_task  by trucating
{     the stack.

      VAR
        terminate_task_sfsa: ^ost$stack_frame_save_area;

      terminate_task_sfsa := #PREVIOUS_SAVE_AREA ();
      terminate_task_sfsa^.minimum_save_area.a2_previous_save_area := NIL;

    PROCEND confine_environment;
?? OLDTITLE ??

    PROCEDURE asynchronous_condition_handler
      (    condition: pmt$condition;
           descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR status: ost$status);

      IF (condition.selector <> pmc$pit_condition) THEN
        IF (task_termination_phase = PRED (initial_phase)) THEN
          pmp$continue_to_cause (pmc$execute_standard_procedure, status);
          IF NOT status.normal THEN
            pmp$exit (status);
          IFEND;
        ELSE
          status.normal := TRUE;

{   Interactive and job resource conditions are ignored after all child tasks
{   have terminated.

        IFEND;
      ELSE

{   Pit conditions are always ignored during task termination.

        status.normal := TRUE;
      IFEND;

    PROCEND asynchronous_condition_handler;
?? OLDTITLE ??

    PROCEDURE fatal_condition_handler
      (    condition: pmt$condition;
           descriptor: ^pmt$condition_information;
           save_area: ^ost$stack_frame_save_area;
       VAR status: ost$status);

      VAR
        ignore_status: ost$status;

      osp$set_status_from_condition ('PM', condition, save_area, status, ignore_status);
      osp$system_error ('task cannot terminate', ^status);

    PROCEND fatal_condition_handler;
?? OLDTITLE, EJECT ??

    VAR
      tcb_p: ^pmt$task_control_block,
      parent_id: ost$global_task_id,
      asynchronous_conditions: [STATIC, READ, oss$job_paged_literal] pmt$condition :=
            [pmc$condition_combination, $pmt$condition_combination
            [jmc$job_resource_condition, ifc$interactive_condition, pmc$pit_condition]],
      fatal_conditions: [STATIC, READ, oss$job_paged_literal] pmt$condition :=
            [pmc$condition_combination, $pmt$condition_combination
            [pmc$system_conditions, mmc$segment_access_condition]],
      established_handler: ^pmt$established_handler,
      child_tasks_flagged: boolean,
      local_status: ost$status;

    confine_environment;
    PUSH established_handler;
    pmp$establish_condition_handler (asynchronous_conditions, ^asynchronous_condition_handler,
          established_handler, local_status);
    IF NOT local_status.normal THEN
      osp$system_error ('pmp$establish - task end', ^local_status);
    IFEND;
    PUSH established_handler;
    pmp$establish_condition_handler (fatal_conditions, ^fatal_condition_handler, established_handler,
          local_status);
    IF NOT local_status.normal THEN
      osp$system_error ('pmp$establish - task end', ^local_status);
    IFEND;
    child_tasks_flagged := FALSE;
    pmp$set_task_execution_phase (pmc$task_termination_cleanup);
    WHILE TRUE DO
      task_termination_phase := SUCC (task_termination_phase);
      CASE task_termination_phase OF
      = initial_phase =
        task_termination_phase := PRED (initial_phase); {inhibit phase advancement}
        pmp$set_task_state (pmc$task_terminating);
        pmp$find_executing_task_tcb (tcb_p);
        WHILE tcb_p^.first_child <> NIL DO

{  Loop until all child tasks have terminated.  The repetitive check of status
{  occurs for the following reason.  If a task has terminated with normal
{  status, and after reaching this point in termination gets flagged by its
{  parent to terminate, the termination status will be changed to abnormal
{  rather than exit being called within the task.  This means that the task
{  will at that time, flag its child tasks to tell them to terminate.  Note:
{  This process will only flag the children once.

          IF (NOT tcb_p^.nosve.termination_status^.normal) AND (NOT child_tasks_flagged) THEN
            pmp$flag_all_child_tasks (pmc$sf_terminate_task, local_status);
            IF NOT local_status.normal THEN
              osp$system_error ('pmp$flag_all_child_tasks', ^local_status);
            IFEND;
            child_tasks_flagged := TRUE;
          IFEND;
          pmp$long_term_wait (tmc$infinite_wait, tmc$infinite_wait);
        WHILEND;

{  Inhibit job recovery while the task terminates.  If recovery were to take
{  place while attempting to "cleanup" the results could get messy.  For
{  example, consider the case of BAM cleanup being called and then recovery
{  processing attempting to undo part of the cleanup.

        IF NOT recovery_inhibited THEN
          syp$push_inhibit_job_recovery;
          recovery_inhibited := TRUE;
        IFEND;
        pmp$disestablish_cond_handler (fatal_conditions, local_status);
        IF NOT local_status.normal THEN
          osp$system_error ('pmp$establish - task end', ^local_status);
        IFEND;
        task_termination_phase := initial_phase; {enable phase advancement}
?? EJECT ??

{   Call procedures to do necessary task_termination cleanup for other
{   functional areas.

      = access_method_cleanup =
        bap$task_termination_cleanup;
      = permanent_files_cleanup =
        pfp$detach_reserved_cycles (local_status); {ignore status}
      = namve_cleanup =
        nap$process_task_termination;
      = queue_file_management_cleanup =
        jmp$release_input_files;
        jmp$release_output_files;
        jmp$release_generic_queue_files;
      = interactive_cleanup =
        iip$clear_job_locks (local_status);
        IF jmv$job_attributes.originating_application_name = osc$dual_state_interactive THEN

{   For dual_state interactive origin jobs, job end processing MUST be done
{   before the memory link goes away - that is why this operation is done here
{   instead of job_end.

          IF osp$executing_in_job_monitor () AND (pmf$job_mode () = jmc$interactive_connected) THEN
            ifp$stop_interactive;
          IFEND;
        ELSEIF jmp$is_xterm_job () THEN
          IF osp$executing_in_job_monitor () AND (pmf$job_mode () = jmc$interactive_connected) THEN
            iip$xt_stop_xterm (local_status);
          IFEND;
        IFEND;
      = incoming_message_cleanup =
        nap$incoming_message_cleanup;
      = operator_facility_cleanup =
        ofp$task_end;
      = memory_link_cleanup =

{!  This phase exits only to cover task_services programming errors.

        mlp$task_termination_cleanup;
      = local_queues_cleanup =
        pmp$disconnect_task_from_queues;
      = preemptive_comm_cleanup =
        IF NOT osp$executing_in_job_monitor () THEN
          task_termination_phase := PRED (preemptive_comm_cleanup); { inhibit phase advancement }
          tmp$disable_preemptive_commo;
          task_termination_phase := preemptive_comm_cleanup; { enable phase advancement }
        IFEND;

      = configuration_cleanup =
        IF (cmv$task_reserved_element_count <> 0) OR (NOT cmv$enable_task_cleanup_count) THEN
          cmp$task_termination_cleanup;
        IFEND;

      = emit_statistics =
        IF clv$applications_active > 0 THEN
          clp$record_application_units;
        IFEND;
        sfp$clear_job_routing_ctl_lock;

      = job_termination =
        IF osp$executing_in_job_monitor () THEN
          task_termination_phase := PRED (job_termination); { inhibit phase advancement }
          PUSH established_handler;
          pmp$establish_condition_handler (fatal_conditions, ^fatal_condition_handler, established_handler,
                local_status);
          IF NOT local_status.normal THEN
            osp$system_error ('pmp$establish - task end', ^local_status);
          IFEND;
          IF recovery_inhibited THEN
            syp$pop_inhibit_job_recovery;
            recovery_inhibited := FALSE;
          IFEND;
          jmp$job_end; { should not return }
        IFEND;
?? EJECT ??

      = release_segments =
        task_termination_phase := PRED (release_segments); { inhibit phase advancement }
        PUSH established_handler;
        pmp$establish_condition_handler (fatal_conditions, ^fatal_condition_handler, established_handler,
              local_status);
        IF NOT local_status.normal THEN
          osp$system_error ('pmp$establish - task end', ^local_status);
        IFEND;
        pmp$delete_non_inherited_segs (local_status);
        IF NOT local_status.normal THEN
          osp$system_error ('pmp$delete_non_inherited_segs', ^local_status);
        IFEND;
        task_termination_phase := release_segments; { enable phase advancement }

      = relinquish_cpu =
        task_termination_phase := PRED (relinquish_cpu); { inhibit phase advancment }
        PUSH established_handler;
        pmp$establish_condition_handler (fatal_conditions, ^fatal_condition_handler, established_handler,
              local_status);
        IF NOT local_status.normal THEN
          osp$system_error ('pmp$establish - task end', ^local_status);
        IFEND;
        pmp$find_executing_task_tcb (tcb_p);
        pmp$get_global_task_id (tcb_p^.parent^.task_id, parent_id, local_status);
        IF recovery_inhibited THEN
          syp$pop_inhibit_job_recovery;
          recovery_inhibited := FALSE;
        IFEND;
        pmp$task_end (tcb_p^.task_id, parent_id); { does not return }
      CASEND;
    WHILEND;
  PROCEND terminate_task;
?? TITLE := '  [XDCL] pmp$child_termination_handler', EJECT ??

  PROCEDURE [XDCL] pmp$child_termination_handler
    (    originator: ost$global_task_id;
         signal: pmt$signal);

{  PURPOSE:
{    This procedure is the handler for the signal pmc$ss_child_terminated.  This signal has a
{    recognition ring of 4 and is sent to a terminating task's parent task when the terminating task
{    relinquishes the CPU.  This procedure discards the last elements of the terminating child
{    task's environment.

    VAR
      converter: record
        case 0 .. 1 of
        = 0 =
          signal_contents: pmt$signal_contents,
        = 1 =
          task_id: pmt$task_id,
        casend,
      recend,

      task_id: pmt$task_id,
      current_child: boolean,
      ignore_status: ost$status,
      task_control_block: ^pmt$task_control_block,
      record_status: ost$status,
      local_status: ost$status;

?? EJECT ??

    PROCEDURE record_task_status
      (VAR tcb: pmt$task_control_block;
       VAR record_status: ost$status);

      VAR
        invalid_segment_condition: [STATIC, READ, oss$job_paged_literal] pmt$condition :=
              [pmc$system_conditions, $pmt$system_conditions [pmc$invalid_segment_ring_0,
              pmc$access_violation], * ],
        seg_established_handler: pmt$established_handler,
        ignore_status: ost$status;


      PROCEDURE segment_condition_handler
        (    condition: pmt$condition;
             descriptor: ^pmt$condition_information;
             save_area: ^ost$stack_frame_save_area;
         VAR status: ost$status);

        osp$set_status_abnormal ('PM', pme$task_status_inaccessible, '', record_status);
        EXIT record_task_status;
      PROCEND segment_condition_handler;


      record_status.normal := TRUE;
      pmp$establish_condition_handler (invalid_segment_condition, ^segment_condition_handler,
            ^seg_established_handler, local_status);
      IF NOT local_status.normal THEN
        osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], local_status, ignore_status);
        osp$generate_message (local_status, ignore_status);
        osp$generate_log_message ($pmt$ascii_logset [pmc$job_log], tcb.nosve.termination_status^,
              ignore_status);
        osp$generate_message (tcb.nosve.termination_status^, ignore_status);
      ELSE
        IF mmp$verify_access (#LOC (tcb.nosve.parent_task_status_variable), mmc$va_write) THEN
          tcb.nosve.parent_task_status_variable^.status := tcb.nosve.termination_status^;
          tcb.nosve.parent_task_status_variable^.complete := TRUE;
        ELSE
          osp$set_status_abnormal ('PM', pme$task_status_inaccessible, '', record_status);
        IFEND;
      IFEND;
    PROCEND record_task_status;

?? EJECT ??

    converter.signal_contents := signal.contents;
    task_id := converter.task_id;

    pmp$verify_current_child (task_id, current_child);
    IF NOT current_child THEN
      osp$system_error ('unknown child terminated', NIL);
    ELSE
      clp$erase_child_task (task_id, local_status);
      IF NOT local_status.normal THEN
        osp$generate_log_message ($pmt$ascii_logset [pmc$job_log, pmc$system_log], local_status,
              ignore_status);
      IFEND;
      issue_task_end_statistics (task_id);
      pmp$find_task_tcb (task_id, task_control_block);
      record_task_status (task_control_block^, record_status);
      pmp$release_task_environment (task_id);

      IF NOT record_status.normal THEN
        pmp$exit (record_status);
      IFEND;
    IFEND;
  PROCEND pmp$child_termination_handler;

?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] pmp$condition_task_termination', EJECT ??
*copy pmh$condition_task_termination

  PROCEDURE [XDCL, #GATE] pmp$condition_task_termination;

    VAR
      detached_job_wait_time_change: jmt$job_attribute_change,
      timesharing_signal: jmt$timesharing_signal,
      ignore_status: ost$status;

    osp$verify_system_privilege;
    pfp$clear_system_authority;
    fmp$unlock_path_table_at_tskend;

    IF osp$executing_in_job_monitor () THEN

{ Record the job as terminated

      jmp$set_job_term_disposition;

{ If the job is interactive ...

      IF pmf$job_mode () <> jmc$batch THEN

{ Enable interactive conditions

        jmp$set_interactive_cond_state ({interactive_conditions_enabled} TRUE);

{ enable IO in all tasks and set the detached job wait time to zero.

        pmp$enable_ts_io_in_job;

        detached_job_wait_time_change.key := jmc$detached_job_wait_time;
        detached_job_wait_time_change.detached_job_wait_time := 0;
        qfp$set_job_attributes (detached_job_wait_time_change, ignore_status);

{ Wake up (ready) all tasks in the job.

        timesharing_signal.signal_id := jmc$timesharing_signal_id;
        timesharing_signal.signal_contents.signal_kind := jmc$timesharing_restart_tasks;
        timesharing_signal.signal_contents.restart_tasks := jmc$ts_restart_child_tasks;
        pmp$signal_all_child_tasks (timesharing_signal.signal, ignore_status);
      IFEND;
    IFEND;

  PROCEND pmp$condition_task_termination;

?? TITLE := 'TEMPORARY procedures for compatibility with HCS tasking', EJECT ??
?? NEWTITLE := 'issue_task_end_statistics', EJECT ??

  PROCEDURE issue_task_end_statistics
    (    task_id: pmt$task_id);

    VAR
      cp_time: pmt$task_cp_time,
      int: integer,
      ignore_status: ost$status,
      local_status: ost$status,
      raw_task_statistics: pmt$raw_task_statistics,
      task_statistics: array [1 .. 9] of sft$counter,
      task_exception_stats: array [1 .. 2] of sft$counter,
      task_exception_descp: string (100),
      task_exception_user_job_name: jmt$user_supplied_name,
      task_exception_sys_job_name: jmt$system_supplied_name,
      tcb_p: ^pmt$task_control_block,
      xcb_p: ^ost$execution_control_block;

    VAR
      gtid_converter: record
        case boolean of
        = FALSE =
          global_task_id: ost$global_task_id,
        = TRUE =
          integer_value: 0 .. 0ffffff(16),
        casend,
      recend;

    local_status.normal := TRUE;
    pmp$find_task_xcb (task_id, xcb_p);
    IF xcb_p = NIL THEN
      osp$system_error ('task XCB lost', NIL);
    ELSE
      tcb_p := xcb_p^.task_control_block;
      IF pmv$debug_logging_enabled THEN
        gtid_converter.global_task_id := xcb_p^.global_task_id;
        osp$set_status_abnormal ('PM', pme$task_end_information, { task_name } xcb_p^.save9, local_status);
        osp$append_status_integer (osc$status_parameter_delimiter, gtid_converter.integer_value, 16, TRUE,
              local_status);
        IF tcb_p^.nosve.termination_status^.normal THEN
          osp$append_status_integer (osc$status_parameter_delimiter, 0, 16, FALSE, local_status);
        ELSE
          osp$append_status_integer (osc$status_parameter_delimiter,
                tcb_p^.nosve.termination_status^.condition, 16, TRUE, local_status);
        IFEND;
        osp$generate_log_message ($pmt$ascii_logset [pmc$system_log], local_status, ignore_status);
      IFEND;
      raw_task_statistics.cp_time.task_time := xcb_p^.cp_time.time_spent_in_job_mode;
      raw_task_statistics.cp_time.monitor_time := xcb_p^.cp_time.time_spent_in_mtr_mode;
      raw_task_statistics.task_name := xcb_p^.save9 {task_name} ;
      raw_task_statistics.page_fault_count := xcb_p^.paging_statistics.page_fault_count;
      raw_task_statistics.working_set_max_used := xcb_p^.paging_statistics.working_set_max_used;
      raw_task_statistics.maxws_aio_slowdown := xcb_p^.maxws_aio_slowdown;
      issue_formatted_statistics (raw_task_statistics, ' Task complete  ', ignore_status);

      IF NOT jmv$executing_within_system_job THEN
        sfp$internal_emit_statistic (pml$task_name, xcb_p^.save9, NIL, xcb_p^.global_task_id, ignore_status);

        task_statistics [1] := raw_task_statistics.cp_time.task_time;
        task_statistics [2] := raw_task_statistics.cp_time.monitor_time;
        task_statistics [3] := xcb_p^.paging_statistics.page_fault_count;
        task_statistics [4] := xcb_p^.paging_statistics.page_in_count;
        task_statistics [5] := xcb_p^.paging_statistics.pages_reclaimed_from_queue;
        task_statistics [6] := xcb_p^.paging_statistics.new_pages_assigned;
        task_statistics [7] := xcb_p^.paging_statistics.working_set_max_used;
        task_statistics [8] := xcb_p^.maxws_aio_slowdown;
        task_statistics [9] := xcb_p^.paging_statistics.pages_from_server;

        sfp$internal_emit_statistic (pml$task_end, xcb_p^.save9, ^task_statistics, xcb_p^.global_task_id,
              ignore_status);

        IF xcb_p^.maxws_aio_slowdown > 0 THEN
          task_exception_user_job_name := ' ';
          task_exception_sys_job_name := ' ';
          pmp$get_job_names (task_exception_user_job_name, task_exception_sys_job_name, ignore_status);
          task_exception_stats [1] := xcb_p^.paging_statistics.working_set_max_used;
          task_exception_stats [2] := xcb_p^.maxws_aio_slowdown;
          task_exception_descp := ' ';
          STRINGREP (task_exception_descp, int, 'SN= ', task_exception_sys_job_name, ' JN= ',
                task_exception_user_job_name, ' TN= ', xcb_p^.save9);
          sfp$internal_emit_statistic (pml$task_end_exception, task_exception_descp, ^task_exception_stats,
                xcb_p^.global_task_id, ignore_status);
        IFEND;

      IFEND;
      IF clv$applications_active > 0 THEN
        clp$update_applic_resources (raw_task_statistics.cp_time, xcb_p^.paging_statistics);
      IFEND;
    IFEND;
  PROCEND issue_task_end_statistics;
?? TITLE := '    issue_formatted_statistics', EJECT ??

  PROCEDURE issue_formatted_statistics
    (    raw_task_statistics: pmt$raw_task_statistics;
         prefix: string (16);
     VAR status: ost$status);

    VAR
      message: string (105),
      strng: ost$string,
      i: integer;

    message (1, 16) := prefix;
    message (17, 31) := raw_task_statistics.task_name;
    pmp$log (message (1, 47), status);
    IF NOT status.normal THEN
      RETURN
    IFEND;
    message := '     job time =      0.000  monitor time =      0.000  page faults = **********';
    message (80, 26) := '  max working set = ******';
    i := (raw_task_statistics.cp_time.task_time + 500) DIV 1000;
    clp$convert_integer_to_rjstring (i DIV 1000, 10, FALSE, ' ', message (16, 7), status);
    clp$convert_integer_to_rjstring (i MOD 1000, 10, FALSE, '0', message (24, 3), status);
    i := (raw_task_statistics.cp_time.monitor_time + 500) DIV 1000;
    clp$convert_integer_to_rjstring (i DIV 1000, 10, FALSE, ' ', message (43, 7), status);
    clp$convert_integer_to_rjstring (i MOD 1000, 10, FALSE, '0', message (51, 3), status);
    clp$convert_integer_to_string (raw_task_statistics.page_fault_count, 10, FALSE, strng, status);
    IF status.normal THEN
      message (70, 10) := strng.value (1, strng.size);
    IFEND;
    clp$convert_integer_to_string (raw_task_statistics.working_set_max_used, 10, FALSE, strng, status);
    IF status.normal THEN
      message (100, 6) := strng.value (1, strng.size);
    IFEND;
    pmp$log (message, status);
    IF raw_task_statistics.maxws_aio_slowdown <> 0 THEN
      message := ' ';
      STRINGREP (message, i,
            '     Excess paging at Maximum Working Set limit (a job attribute) caused slowdown ',
            raw_task_statistics.maxws_aio_slowdown, ' times.');
      pmp$log (message, status);
    IFEND

  PROCEND issue_formatted_statistics;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL] pmp$emit_job_end_statistics', EJECT ??
*copy pmh$emit_job_end_statistics

  PROCEDURE [XDCL] pmp$emit_job_end_statistics
    (VAR status: ost$status);

    CONST
      line_size_maximum = 160;

    VAR
      line: string (line_size_maximum),
      line_size: 0 .. line_size_maximum,
      job_statistics: jmt$job_statistics,
      statistics: array [1 .. 8] of sft$counter,
      task_id: pmt$task_id;

?? NEWTITLE := '[INLINE] add_to_line', EJECT ??

    PROCEDURE [INLINE] add_to_line
      (    text: string ( * ));

      line (line_size + 1, STRLENGTH (text)) := text;
      line_size := line_size + STRLENGTH (text);
      WHILE (line_size > 0) AND (line (line_size) = ' ') DO
        line_size := line_size - 1;
      WHILEND;

    PROCEND add_to_line;
?? OLDTITLE ??
?? EJECT ??
    status.normal := TRUE;

{ Manufacture a LOGOUT command in the system log for the job.
    line_size := 0;
    add_to_line ('LOGOUT "LU=');
    add_to_line (jmv$jcb.user_id.user);
    add_to_line (', UJN=');
    add_to_line (jmv$jcb.jobname);
    add_to_line (', LF=');
    add_to_line (jmv$jcb.user_id.family);
    pmp$log_ascii (line (1, line_size), $pmt$ascii_logset [pmc$system_log],
          pmc$msg_origin_command, {ignore} status);

    pmp$get_task_id (task_id, { ignore } status);

{   This cycle request will cause dispatcher to update the XCB and IJL with the
{   latest available task and job statistic data.

    pmp$cycle ({ ignore } status);

    tmp$fetch_job_statistics (job_statistics, status);
    IF NOT status.normal THEN
      RETURN;
    IFEND;
    issue_task_end_statistics (task_id);

    statistics [1] := job_statistics.cp_time.time_spent_in_job_mode;
    statistics [2] := job_statistics.cp_time.time_spent_in_mtr_mode;
    statistics [3] := job_statistics.paging_statistics.page_fault_count;
    statistics [4] := job_statistics.paging_statistics.page_in_count;
    statistics [5] := job_statistics.paging_statistics.pages_reclaimed_from_queue;
    statistics [6] := job_statistics.paging_statistics.new_pages_assigned;
    statistics [7] := job_statistics.paging_statistics.working_set_max_used;
    statistics [8] := job_statistics.paging_statistics.pages_from_server;
    sfp$emit_statistic (jml$job_end_statistics, '', ^statistics, status);
  PROCEND pmp$emit_job_end_statistics;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] pmp$acquire_raw_task_statistics', EJECT ??

  PROCEDURE [XDCL, #GATE] pmp$acquire_raw_task_statistics
    (VAR active_task_count: 0 .. pmc$max_task_id;
     VAR active_task_statistics: array [1 .. * ] of pmt$raw_task_statistics);

    VAR
      task: 0 .. pmc$max_task_id,
      task_count: 0 .. pmc$max_task_id,
      task_statistics: ^array [1 .. * ] of pmt$raw_task_statistics;

    osp$verify_system_privilege;

    PUSH task_statistics: [1 .. UPPERBOUND (active_task_statistics)];
    pmp$collect_raw_task_statistics (task_count, task_statistics^);
    active_task_count := task_count;
    IF (task_count > UPPERBOUND (task_statistics^)) THEN
      task_count := UPPERBOUND (task_statistics^);
    IFEND;
    FOR task := 1 TO task_count DO
      active_task_statistics [task] := task_statistics^ [task];
    FOREND;


  PROCEND pmp$acquire_raw_task_statistics;
?? OLDTITLE ??
?? NEWTITLE := '[INLINE, XDCL, #GATE] pmp$set_task_execution_phase', EJECT ??
*copy pmh$set_task_execution_phase

{ NOTE:
{   The task execution phase can only progress - it NEVER reverts to a previous
{   phase For example - when loaded_ring_cleanup is called - we will stop the
{   cleanup with calls to pmp$exit - this in turn, may call
{   pmp$pop_all_stack_frames - so we may try to set us back into popping when
{   we are actually to the the point of loaded_ring_cleanup.

  PROCEDURE [INLINE, XDCL, #GATE] pmp$set_task_execution_phase
    (    execution_phase: pmt$task_execution_phase);

    osp$verify_system_privilege;
    IF execution_phase > pmv$task_execution_phase THEN
      pmv$task_execution_phase := execution_phase;
    IFEND;
  PROCEND pmp$set_task_execution_phase;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] pmp$set_popper_handler_activity', EJECT ??
*copy pmh$set_popper_handler_activity

  PROCEDURE [XDCL, #GATE] pmp$set_popper_handler_activity
    (    active: boolean);

    osp$verify_system_privilege;
    pmv$popper_handler_established := active;
  PROCEND pmp$set_popper_handler_activity;
?? OLDTITLE ??
MODEND pmm$task_termination_ring_3;
