?? RIGHT := 110 ??
?? NEWTITLE := 'NOS/VE Logging: Local Log Manager' ??
MODULE lgm$local_log_manager;

{ PURPOSE:
{   This module contains the code used to manage the local logs.
{
{ DESIGN:
{   The local logs are segments that are created during job begin.  Information about each local log is kept
{   in the log's corresponding log control descriptor (LCD).
{
{   Access to a log is interlocked via a job signature lock (contained in the log's LCD) and all local log
{   accesses eventually find their way to procedures in this module.  There are file entries that appear in
{   the $LOCAL catalog for each job that represent the local logs, but these files entries have a FAP
{   associated with them that calls the interfaces in this module.

?? NEWTITLE := 'Global Declarations Referenced by This Module.', EJECT ??
?? PUSH (LISTEXT := ON) ??
*copyc lgc$default_preallocation_size
*copyc lge$end_of_log
*copyc lge$log_full
*copyc lge$log_cycles_do_not_match
*copyc lge$log_not_available
*copyc lge$not_local_log
*copyc osc$space_unavailable_condition
*copyc osc$volume_unavailable_cond
*copyc oss$job_pageable
*copyc oss$job_paged_literal
*copyc ost$heap
*copyc pmt$ascii_logset
*copyc pmt$global_logset
*copyc pmt$job_log_entry
*copyc pmt$local_binary_logs
*copyc pmt$log_msg_origin
*copyc pmt$log_msg_text
*copyc pmt$logs
?? POP ??
*copyc i#build_adaptable_seq_pointer
*copyc i#move
*copyc lgp$add_log_entry
*copyc lgp$add_entry_to_system_log
*copyc lgp$get_log_entry
*copyc lgp$get_log_read_information
*copyc lgp$get_previous_log_entry_size
*copyc mmp$change_segment_inheritance
*copyc mmp$create_segment
*copyc osp$clear_job_signature_lock
*copyc osp$disestablish_cond_handler
*copyc osp$establish_condition_handler
*copyc osp$initialize_sig_lock
*copyc osp$set_job_signature_lock
*copyc osp$set_status_abnormal
*copyc osp$test_signature_lock
*copyc pmp$continue_to_cause
*copyc pmp$get_time
*copyc lgv$control_codes_to_quest_mark
*copyc lgv$log_names
*copyc lgv$origin_codes
?? OLDTITLE ??
?? NEWTITLE := 'Global Declarations Declared by This Module.', EJECT ??
?? FMT (FORMAT := OFF) ??
{ This is the local log control descriptor array.  Entries for global logs are not used.

  VAR
    lgv$local_log_ctl: [XDCL, #GATE, oss$job_pageable] array [pmt$logs] of ^lgt$log_control_descriptor := [
          ^lgv$job_account_log_lcd, ^lgv$job_statistic_log_lcd, NIL, NIL, NIL, NIL, NIL, NIL,
          ^lgv$job_log_lcd],

    lgv$job_account_log_lcd: [STATIC, oss$job_pageable] lgt$log_control_descriptor :=
          [*, pmc$job_account_log,   0, NIL, NIL, lgc$maximum_log_size,
                lgc$default_preallocation_size, FALSE, 0, FALSE],

    lgv$job_statistic_log_lcd: [STATIC, oss$job_pageable] lgt$log_control_descriptor :=
          [*, pmc$job_statistic_log, 0, NIL, NIL, lgc$maximum_log_size,
                lgc$default_preallocation_size, FALSE, 0, FALSE],

    lgv$job_log_lcd: [STATIC, oss$job_pageable] lgt$log_control_descriptor :=
          [*, pmc$job_log,           0, NIL, NIL, lgc$maximum_log_size,
                lgc$default_preallocation_size, FALSE, 0, FALSE];
?? FMT (FORMAT := ON) ??
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] lgp$add_entry_local_binary_log', EJECT ??
*copy lgh$add_entry_local_binary_log

  PROCEDURE [XDCL, #GATE] lgp$add_entry_local_binary_log
    (    local_binary_log: pmt$local_binary_logs;
         entry_p: ^lgt$log_entry;
     VAR status: ost$status);

    VAR
      lock_status: ost$signature_lock_status,
      log_control_descriptor_p: ^lgt$log_control_descriptor;

?? NEWTITLE := 'condition_handler', EJECT ??
{ PURPOSE:
{   This is a condition handler that is used to insure that the lock on a local log is released if
{   an error occurs.

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

      CASE condition.selector OF
      = pmc$block_exit_processing =
        clear_job_sig_lock (log_control_descriptor_p^.lock);
      = pmc$user_defined_condition =
        IF ((condition.user_condition_name = osc$volume_unavailable_cond) OR
            (condition.user_condition_name = osc$space_unavailable_condition)) THEN
          EXIT lgp$add_entry_local_binary_log;
        ELSE
          pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
        IFEND;
      ELSE
        pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
      CASEND;

    PROCEND condition_handler;
?? OLDTITLE, EJECT ??
    status.normal := TRUE;

{ Get a pointer to the appropriate log control descriptor.

    log_control_descriptor_p := lgv$local_log_ctl [local_binary_log];
    #SPOIL (log_control_descriptor_p);

{ Interlock the log.

    osp$establish_condition_handler (^condition_handler, TRUE);
    set_job_sig_lock (log_control_descriptor_p^.lock, lock_status);
    IF lock_status <> osc$sls_not_locked THEN
      RETURN;
    IFEND;
{ Add the entry to the log.  If the log is full, ignore the error (the message is lost).

    lgp$add_log_entry (entry_p, log_control_descriptor_p, status);
    IF (NOT status.normal) AND (status.condition = lge$log_full) THEN
      status.normal := TRUE;
    IFEND;

{ Clear the log interlock.

    clear_job_sig_lock (log_control_descriptor_p^.lock);
    osp$disestablish_cond_handler;

  PROCEND lgp$add_entry_local_binary_log;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] lgp$add_entry_to_ascii_log', EJECT ??
*copy lgh$add_entry_to_ascii_log

  PROCEDURE [XDCL, #GATE] lgp$add_entry_to_ascii_log
    (    ascii_logset: pmt$ascii_logset;
         origin: pmt$log_msg_origin;
         text: pmt$log_msg_text;
     VAR status: ost$status);

    VAR
      job_log_entry_p: ^pmt$job_log_entry,
      lock_status: ost$signature_lock_status,
      log_control_descriptor_p: ^lgt$log_control_descriptor,
      log_time: ost$time,
      text_size: lgt$log_entry_size;

?? NEWTITLE := 'condition_handler', EJECT ??
{ PURPOSE:
{   This is a condition handler that is used to insure that the lock on a local log is released if
{   an error occurs.

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

      CASE condition.selector OF
      = pmc$block_exit_processing =
        IF pmc$job_log IN ascii_logset THEN
          clear_job_sig_lock (log_control_descriptor_p^.lock);
        IFEND;
      = pmc$user_defined_condition =
        IF ((condition.user_condition_name = osc$volume_unavailable_cond) OR
            (condition.user_condition_name = osc$space_unavailable_condition)) THEN
          EXIT lgp$add_entry_to_ascii_log;
        ELSE
          pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
        IFEND;
      ELSE
        pmp$continue_to_cause (pmc$execute_standard_procedure, handler_status);
      CASEND;

    PROCEND condition_handler;
?? OLDTITLE, EJECT ??

    status.normal := TRUE;

    IF pmc$job_log IN ascii_logset THEN

{ Get a pointer to the log control descriptor for the job log.

      log_control_descriptor_p := lgv$local_log_ctl [pmc$job_log];
      #SPOIL (log_control_descriptor_p);

{ Check if the job log is available.

      IF log_control_descriptor_p^.log_data = NIL THEN
        osp$set_status_abnormal ('LG', lge$log_not_available, lgv$log_names [pmc$job_log], status);
        RETURN;
      IFEND;

{ Interlock the job log.

      osp$establish_condition_handler (^condition_handler, TRUE);
      set_job_sig_lock (log_control_descriptor_p^.lock, lock_status);
      IF lock_status <> osc$sls_not_locked THEN
        RETURN;
      IFEND;
    IFEND;

{ If the message goes to the system log, record it there and get the time stamp used.  Otherwise, get the
{ current time to be used for the time stamp.

    IF pmc$system_log IN ascii_logset THEN
      lgp$add_entry_to_system_log (origin, text, log_time, status);
    ELSE
      pmp$get_time (osc$millisecond_time, log_time, status);
    IFEND;
    IF NOT status.normal THEN
      IF pmc$job_log IN ascii_logset THEN
        clear_job_sig_lock (log_control_descriptor_p^.lock);
      IFEND;
      RETURN;
    IFEND;

{ If the message goes to the job log: format it, record it in the log and clear the log interlock.
{ If the log is full, ignore the error (the message is lost).

    IF pmc$job_log IN ascii_logset THEN
      text_size := lgc$maximum_log_entry_size - #SIZE (pmt$job_log_entry: [0]);
      IF STRLENGTH (text) < text_size THEN
        text_size := STRLENGTH (text);
      IFEND;
      PUSH job_log_entry_p: [text_size];

      job_log_entry_p^.time := log_time.millisecond;
      job_log_entry_p^.delimiter_1 (1) := '.';
      job_log_entry_p^.origin := lgv$origin_codes [origin];
      job_log_entry_p^.delimiter_2 (1) := '.';
      #TRANSLATE (lgv$control_codes_to_quest_mark, text, job_log_entry_p^.text);

      lgp$add_log_entry (#SEQ (job_log_entry_p^), log_control_descriptor_p, status);
      IF (NOT status.normal) AND (status.condition = lge$log_full) THEN
        status.normal := TRUE;
      IFEND;

      clear_job_sig_lock (log_control_descriptor_p^.lock);
      osp$disestablish_cond_handler;
    IFEND;

  PROCEND lgp$add_entry_to_ascii_log;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] lgp$get_entry_from_local_log', EJECT ??
*copy lgh$get_entry_from_local_log

  PROCEDURE [XDCL, #GATE] lgp$get_entry_from_local_log
    (    local_log: pmt$logs;
         log_cycle: lgt$log_cycle;
     VAR log_data: ^SEQ ( * );
     VAR log_entry_size: lgt$log_entry_size;
     VAR log_entry: lgt$log_entry;
     VAR status: ost$status);

    VAR
      log_control_descriptor_p: ^lgt$log_control_descriptor;

    status.normal := TRUE;

{ Verify that the specified log is a local log.

    IF local_log IN (-$pmt$global_logset []) THEN
      osp$set_status_abnormal ('LG', lge$not_local_log, lgv$log_names [local_log], status);
      RETURN;
    IFEND;

{ Get a pointer to the appropriate log control descriptor.

    log_control_descriptor_p := lgv$local_log_ctl [local_log];

{ Return a copy of the log entry header and as much of the log entry as will fit.

    lgp$get_log_entry (log_cycle, log_control_descriptor_p, log_data, log_entry_size, log_entry, status);

  PROCEND lgp$get_entry_from_local_log;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] lgp$get_local_log_read_info', EJECT ??
*copy lgh$get_local_log_read_info

  PROCEDURE [XDCL, #GATE] lgp$get_local_log_read_info
    (    local_log: pmt$logs;
         entry_count_from_end_of_log: ost$segment_length;
     VAR log_cycle: lgt$log_cycle;
     VAR log_data: ^SEQ ( * );
     VAR ending_offset: amt$file_byte_address;
     VAR status: ost$status);

    VAR
      log_control_descriptor_p: ^lgt$log_control_descriptor;

    status.normal := TRUE;

{ Verify that the specified log is a local log.

    IF local_log IN (-$pmt$global_logset []) THEN
      osp$set_status_abnormal ('LG', lge$not_local_log, lgv$log_names [local_log], status);
      RETURN;
    IFEND;

{ Get a pointer to the appropriate log control descriptor.

    log_control_descriptor_p := lgv$local_log_ctl [local_log];

{ Get the appropriate values from the log control descriptor.

    lgp$get_log_read_information (log_control_descriptor_p, entry_count_from_end_of_log, log_cycle, log_data,
          ending_offset, status);

  PROCEND lgp$get_local_log_read_info;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] lgp$get_local_previous_size', EJECT ??

  PROCEDURE [XDCL, #GATE] lgp$get_local_previous_size
    (    local_log: pmt$logs;
         log_cycle: lgt$log_cycle;
     VAR log_data: ^SEQ ( * );
     VAR previous_size: lgt$log_entry_size;
     VAR status: ost$status);

    VAR
      log_control_descriptor_p: ^lgt$log_control_descriptor;

    status.normal := TRUE;

{ Get a pointer to the appropriate log control descriptor.

    log_control_descriptor_p := lgv$local_log_ctl [local_log];

{ Get the size of the previous log entry.

    lgp$get_previous_log_entry_size (log_cycle, log_control_descriptor_p, log_data, previous_size, status);

  PROCEND lgp$get_local_previous_size;
?? OLDTITLE ??
?? NEWTITLE := '[XDCL, #GATE] lgp$setup_access_to_local_logs', EJECT ??
*copy lgh$setup_access_to_local_logs

  PROCEDURE [XDCL, #GATE] lgp$setup_access_to_local_logs
    (VAR status: ost$status);

    VAR
      initial_segment_attributes: [STATIC, READ, oss$job_paged_literal] array [1 .. 5] of
            mmt$attribute_descriptor := [
            {1} [mmc$kw_segment_number, osc$segnum_job_dayfile],
            {2} [mmc$kw_ring_numbers, 2, 3],
            {3} [mmc$kw_max_segment_length, lgc$maximum_log_size],
            {4} [mmc$kw_preset_value, pmc$initialize_to_zero],
            {5} [mmc$kw_hardware_attributes, [mmc$ha_read, mmc$ha_write]]],
      log: pmt$logs,
      segment_attributes: ^array [1 .. 5] of mmt$attribute_descriptor,
      segment_pointer: mmt$segment_pointer;

    status.normal := TRUE;

{ Initialize the segment attributes.

    PUSH segment_attributes;
    segment_attributes^ := initial_segment_attributes;

  /init_log_control_descriptor/
    FOR log := LOWERBOUND (lgv$local_log_ctl) TO UPPERBOUND (lgv$local_log_ctl) DO

{ Skip over the entries for global logs (they are not used).

      IF log IN (-$pmt$global_logset []) THEN
        CYCLE /init_log_control_descriptor/
      IFEND;

{ Initialize the signature lock used to control access to the log.

      osp$initialize_sig_lock (lgv$local_log_ctl [log]^.lock);

{ Make sure the job log is assigned to the correct segment number.  The other local logs do not have specific
{ segment numbers.

      IF log = pmc$job_log THEN
        segment_attributes^ [1] := initial_segment_attributes [1];
      ELSE
        segment_attributes^ [1].keyword := mmc$kw_null_keyword;
      IFEND;

{ Create the segment that will contain the log.

      mmp$create_segment (segment_attributes, mmc$sequence_pointer, 1, segment_pointer, status);
      IF NOT status.normal THEN
        RETURN;
      IFEND;
      mmp$change_segment_inheritance (segment_pointer.cell_pointer, mmc$si_share_segment, status);

{ Initialize the log data pointer.

      i#build_adaptable_seq_pointer (#RING (segment_pointer.seq_pointer),
          #SEGMENT (segment_pointer.seq_pointer), #OFFSET (segment_pointer.seq_pointer),
          #SIZE (lgt$log_entry_header), #SIZE (lgt$log_entry_header), lgv$local_log_ctl [log]^.log_data);
      RESET lgv$local_log_ctl [log]^.log_data;

{ Initialize the trailing log entry header.

      NEXT lgv$local_log_ctl [log]^.trailing_log_entry_header_p IN lgv$local_log_ctl [log]^.log_data;
      IF lgv$local_log_ctl [log]^.trailing_log_entry_header_p = NIL THEN
        osp$set_status_abnormal ('LG', lge$log_full, lgv$log_names [log], status);
        RETURN;
      IFEND;
      lgv$local_log_ctl [log]^.trailing_log_entry_header_p^.previous_size := 0;
      lgv$local_log_ctl [log]^.trailing_log_entry_header_p^.current_size := 0;

    FOREND /init_log_control_descriptor/;

  PROCEND lgp$setup_access_to_local_logs;
?? OLDTITLE ??
?? NEWTITLE := 'clear_job_sig_lock', EJECT ??

  PROCEDURE clear_job_sig_lock
    (VAR lock: ost$signature_lock);

    VAR
      lock_status: ost$signature_lock_status;

    osp$test_signature_lock (lock, lock_status);
    IF lock_status = osc$sls_locked_by_current_task THEN
      osp$clear_job_signature_lock (lock);
    IFEND;

  PROCEND clear_job_sig_lock;

?? OLDTITLE ??
?? NEWTITLE := 'set_job_sig_lock', EJECT ??

  PROCEDURE set_job_sig_lock
    (VAR lock: ost$signature_lock;
     VAR lock_status: ost$signature_lock_status);

    osp$test_signature_lock (lock, lock_status);
    IF lock_status = osc$sls_not_locked THEN
      osp$set_job_signature_lock (lock);
    IFEND;

  PROCEND set_job_sig_lock;
MODEND lgm$local_log_manager;
