



		    display_net_acct_table.pl1      08/04/87  1449.7rew 08/04/87  1221.5      101664



/****^  ***********************************************************
        *                                                         *
        * Copyright, (C) Honeywell Bull Inc., 1987                *
        *                                                         *
        *********************************************************** */

/****^  HISTORY COMMENTS:
  1) change(87-04-07,Brunelle), approve(87-07-31,MCR7694),
     audit(87-07-31,GDixon), install(87-08-04,MR12.1-1055):
     New program.
  2) change(87-04-21,Brunelle), approve(87-07-31,MCR7694),
     audit(87-07-31,GDixon), install(87-08-04,MR12.1-1055):
     Changed to select active_connection_info based on both the process_id and
     session_id.
  3) change(87-05-07,Brunelle), approve(87-07-31,MCR7694),
     audit(87-07-31,GDixon), install(87-08-04,MR12.1-1055):
     Add $test entrypoint to allow display of a NAT in a test mode in another
     directory.
  4) change(87-06-11,Brunelle), approve(87-07-31,MCR7694),
     audit(87-07-31,GDixon), install(87-08-04,MR12.1-1055):
     Fix problem of not displaying proper user for a network record.
  5) change(87-06-16,Brunelle), approve(87-07-31,MCR7694),
     audit(87-07-31,GDixon), install(87-08-04,MR12.1-1055):
     Changed the display values for delete & unassigned records in the brief
     display.
  6) change(87-07-31,Brunelle), approve(87-07-31,MCR7694),
     audit(87-07-31,GDixon), install(87-08-04,MR12.1-1055):
     Check version from network accounting.
     Correct errors discovered during auditing.
                                                   END HISTORY COMMENTS */

/* *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  * */
/*							       */
/* TO BE SUPPLIED:						       */
/*							       */
/* This program should be changed to cycle entirely through the	       */
/* network_account_array calling GET_CONNECTION_INFO for each entry and      */
/* storing data for later display.  This would allow us to know the proper   */
/* sizes for each of the display fields and would also allow us to sort      */
/* output into any order the user wishes.			       */
/*							       */
/* *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  *  * */

/* format: style4 */

display_net_acct_table: dnat: proc;

/* Utility to display the contents of the Network Account Table */

/* External procedures */

dcl  com_err_ entry () options (variable);
dcl  cu_$arg_count entry (fixed bin, fixed bin (35));
dcl  cu_$arg_ptr entry (fixed bin, ptr, fixed bin (21), fixed bin (35));
dcl  get_system_free_area_ entry () returns (ptr);
dcl  hpriv_connection_list_$get_next_owner entry (bit (18), bit (36), ptr, fixed bin (35));
dcl  hpriv_connection_list_$get_next_user entry (bit (36), bit (18), ptr, fixed bin (35));
dcl  ioa_ entry () options (variable);
dcl  network_accounting_gate_$dump_table entry (ptr, ptr, char (*), fixed bin (35));
dcl  network_accounting_gate_$get_path entry (char (*));
dcl  network_accounting_gate_$test entry (char (*));

/* External */

dcl  error_table_$badopt fixed bin (35) ext static;
dcl  error_table_$noentry fixed bin (35) ext static;

/* Conditions & Builtin */

dcl  (addr, index, null) builtin;
dcl  (cleanup, linkage_error) condition;

/* Internal Static */

dcl  ME char (23) int static options (constant) init ("display_net_acct_table");

/* Automatic */

dcl  access_error_string char (64);
dcl  arg_count fixed bin;
dcl  arg_len fixed bin (21);
dcl  arg_ptr ptr;
dcl  brief_sw bit (1);
dcl  connection_name char (32);
dcl  connection_offset bit (18);
dcl  error fixed bin (35);
dcl  error_message char (100);
dcl  group_id char (32);
dcl  i fixed bin;
dcl  seeking_user bit (1);
dcl  system_area_ptr ptr;
dcl  table_path char (168);

dcl  1 aci aligned like active_connection_info;

/* Based */

dcl  arg char (arg_len) based (arg_ptr);
dcl  system_area area based (system_area_ptr);
dcl  test_dir char (arg_len) based (arg_ptr);

%page;
COMMON:	brief_sw = "1"b;				/* default to brief mode */

/* see if they want long mode */
	call cu_$arg_count (arg_count, error);
	if error ^= 0 then do;
	     call com_err_ (error, ME);
	     return;
	end;
	do i = 1 to arg_count;
	     call cu_$arg_ptr (i, arg_ptr, arg_len, 0);
	     if index (arg, "-") ^= 1 then do;
		call com_err_ (0, ME, "Usage:  ^a {-control_arg}", ME);
		return;
	     end;
	     else if arg = "-brief" | arg = "-bf" then brief_sw = "1"b;
	     else if arg = "-long" | arg = "-lg" then brief_sw = "0"b;
	     else do;
		call com_err_ (error_table_$badopt, ME, "^a", arg);
		return;
	     end;
	end;

	network_account_array_ptr = null;
	system_area_ptr = get_system_free_area_ ();
	aci.version = ACT_INFO_VERSION_1;

	on cleanup call CLEAN_UP;
	on linkage_error begin;
	     access_error_string = "network_accounting_gate_";
	     go to NO_ACCESS_ERROR;
	end;

/* get location of the NAT being processed */
	call network_accounting_gate_$get_path (table_path);

/* get all entries in the NAT */
	call network_accounting_gate_$dump_table (system_area_ptr, network_account_array_ptr, error_message, error);
	if error ^= 0 then do;
	     call com_err_ (error, ME, "Dumping table ^a", table_path);
	     go to RETURN;
	end;

	if network_account_array_ptr = null then go to TABLE_EMPTY;

	else do;
	     if network_account_array.version ^= NET_ACCT_ARRAY_VERSION_1 then do;
		call com_err_ (0, ME, 
		     "Illegal version (^a) for net acct array.  Should be ^a",
		     network_account_array.version, NET_ACCT_ARRAY_VERSION_1);
		free network_account_array in (system_area);
		network_account_array_ptr = null;
	     end;
	end;
	

/* special case empty table */
	if network_account_array.count = 0 then do;
TABLE_EMPTY:   call ioa_ ("Table ^a contains no entries.", table_path);
	     go to RETURN;
	end;

	call ioa_ ("Dumping Network Account Table^/Pathname:  ^a", table_path);

	if brief_sw then do;
	     call ioa_ ("
                 Network              Connect
    Channel Name   Type   Bytes   Pkts   Secs User");
	end;

/* in case we don't have access on hpriv_connection_list_ */
	on linkage_error begin;
	     access_error_string = "hpriv_connection_list_";
	     go to NO_ACCESS_ERROR;
	end;

	do i = 1 to network_account_array.count;
	     network_account_data_ptr = addr (network_account_array.record (i));

/* get information from the connection list about this user */
	     call GET_CONNECTION_INFO;

	     call DISPLAY_THE_DATA_RECORD;
skip_this_entry:
	end;
RETURN:
	call CLEAN_UP;

	return;

NO_ACCESS_ERROR:
	call com_err_ (0, ME, "Sorry, you don't have access on ^a.", access_error_string);
	go to RETURN;
%page;

/* entrypoint to allow display of NAT in test mode.  This may be in a directory
   other than the standard system directory */

test: entry;

/* see if user gave directory to use */
	call cu_$arg_count (arg_count, error);
	if error ^= 0 then do;
	     call com_err_ (error, ME || "$test");
	     return;
	end;
	if arg_count = 0 then do;			/* must give directory */
	     call com_err_ (0, ME || "$test", "Must give name of test directory to use.");
	     return;
	end;

	network_account_array_ptr = null;

	on linkage_error begin;
	     access_error_string = "network_accounting_gate_";
	     go to NO_ACCESS_ERROR;
	end;

	call cu_$arg_ptr (1, arg_ptr, arg_len, error);
	call network_accounting_gate_$test (test_dir);
	go to RETURN;

CLEAN_UP: proc;

	if network_account_array_ptr ^= null then
	     free network_account_array in (system_area);

     end CLEAN_UP;
%page;

DISPLAY_THE_DATA_RECORD: proc;

/* subroutine to display the NAT record */

	if brief_sw then do;
	     call ioa_ ("^[D^; ^]^[U^; ^]^[P^; ^] ^13a  ^[UNK  ^s^;^5a^] ^6d ^6d ^6d ^[^.3b-^d^;^2s^a^]",
		network_account_data.delete_sw,
		network_account_data.unassigned_sw,
		network_account_data.purged,
		connection_name,
		network_account_data.network_id < 0,
		NETWORK_TYPE_VALUES (network_account_data.network_id),
		network_account_data.byte_count,
		network_account_data.packet_count,
		network_account_data.connect_time,
		group_id = "Unknown.Unk",
		network_account_data.process_id,
		network_account_data.session_handle,
		group_id);
	end;

	else do;

	     call ioa_ ("^/User process ID: ^w", network_account_data.process_id);
	     call ioa_ ("Connection handle: ^d", network_account_data.session_handle);
	     call ioa_ ("Connection name: ^a", connection_name);
	     call ioa_ ("Network type: ^[UNKNOWN ^s^;^a ^](^d)",
		network_account_data.network_id < 0,
		NETWORK_TYPE_VALUES (network_account_data.network_id), network_account_data.network_id);
	     call ioa_ ("User group ID: ^a", group_id);
	     call ioa_ ("Bytes since last zeroed: ^d", network_account_data.byte_count);
	     call ioa_ ("Packets since last zeroed: ^d", network_account_data.packet_count);
	     call ioa_ ("Connect seconds since last zeroed: ^d", network_account_data.connect_time);
	     call ioa_ ("Delete switch: ^[on^;off^]", network_account_data.delete_sw);
	     call ioa_ ("Unassigned switch: ^[on^;off^]", network_account_data.unassigned_sw);
	     call ioa_ ("Purged switch: ^[on^;off^]", network_account_data.purged);
	end;

     end DISPLAY_THE_DATA_RECORD;
%page;
GET_CONNECTION_INFO: proc;

/* get connection list info for user of NAT record */

	if network_account_data.unassigned_sw then	/*  if unassigned connection then */
	     seeking_user = "0"b;			/* look in owner list only */
	else seeking_user = "1"b;			/* else look in the user list first */
	connection_offset = "0"b;			/* start with first connection for this process */

retry_connection_seek:
	if seeking_user then
	     call hpriv_connection_list_$get_next_user ((network_account_data.process_id), connection_offset,
		addr (aci), error);
	else call hpriv_connection_list_$get_next_owner (connection_offset, (network_account_data.process_id),
		addr (aci), error);

	if error = 0 then do;

/* we found an entry for this processid.  if the session handle matches then
   we are all done.  else try to find the next entry for this processid */

	     if aci.connection_handle ^= network_account_data.session_handle then do;
		connection_offset = aci.offset;	/* set to try for next entry for this user */
		go to retry_connection_seek;		/* and try again */
	     end;
	end;
	else do;

/* no entry for this processid.  if we are seeking for the user, then shift
   to seek if there are any entries where this processid is the owner of the
   session.  else, punt out with an unknown name and groupid */

	     if error = error_table_$noentry then do;
fake_no_entry:	if seeking_user then do;
		     seeking_user = "0"b;		/* set to seek for owner of this session */
		     connection_offset = "0"b;	/* and start at the beginning of the owners chain */
		     go to retry_connection_seek;	/* and try again */
		end;
		connection_name = "UNKNOWN";
		group_id = "Unknown.Unk";
		return;
	     end;
	     else do;
		call com_err_ (error, ME, "Getting connection list entry.");
		go to RETURN;
	     end;
	end;
	connection_name = aci.connection_name;
	if network_account_data.unassigned_sw then
	     group_id = aci.owner_group_id;
	else group_id = aci.user_group_id;

     end GET_CONNECTION_INFO;
%page; %include active_connection_info;
%page; %include net_event_message;
%page; %include network_account_array;

     end display_net_acct_table;




		    network_accounting_.pl1         11/25/87  0830.2rew 11/25/87  0827.8      349569



/****^  ***********************************************************
        *                                                         *
        * Copyright, (C) Honeywell Bull Inc., 1987                *
        *                                                         *
        *********************************************************** */

/**** TBS
      We need to do something about robustness of this code WRT to the database.
      If something happens to the database while the system is running, all of the
      users will be crawling out of the inner ring.  This will spew error messages
      and AS dumps like mad.

      This should be handled by an any_other handler which will handle the problem. */

/****^  HISTORY COMMENTS:
  1) change(85-11-25,Herbst), approve(86-05-14,MCR7423),
     audit(87-08-03,GDixon), install(87-08-04,MR12.1-1055):
     Written 85-09-10, Herbst. 85-10-10,  Changed to use data records instead
     of putting all info in keys. Changed to set validation level before
     accessing the database via vfile_.
  2) change(86-01-07,Herbst), approve(86-05-14,MCR7423),
     audit(87-08-03,GDixon), install(87-08-04,MR12.1-1055):
     Multiple changes during the evolution of the code.
  3) change(86-10-10,Brunelle), approve(86-10-10,MCR7423),
     audit(87-08-03,GDixon), install(87-08-04,MR12.1-1055):
     Multiple changes during the evolution of the code.
  4) change(87-08-03,Brunelle), approve(87-08-03,MCR7694),
     audit(87-08-03,GDixon), install(87-08-04,MR12.1-1055):
     Correct logic errors found in audit.
  5) change(87-11-24,Brunelle), approve(87-11-24,MECR0001),
     audit(87-11-24,Beattie), install(87-11-25,MR12.2-1003):
     Correct errors causing crash on initialization including
     a) setting network_account_array_ptr to null and debug_sw OFF in INIT1
     b) not complaining if delete_$path gets error_table_$noentry error when
        deleting network_accounting_table.
                                                   END HISTORY COMMENTS */

/* format: style4 */

network_accounting_: proc ();

	return;					/* no entry here */

/* This module allows network programs to charge processes for bytes and
   packets sent over channels. */

/* Parameters */

dcl  P_area_ptr ptr parameter;			/* user area ptr to allocate in (I) */
dcl  P_array_ptr ptr parameter;			/* ptr to allocated array of all available DSA records (O) */
dcl  P_error_message char (*) parameter;		/* text error message (if needed) (O) */
dcl  P_path char (*) parameter;			/* path to set for test location of database (I) */
dcl  P_code fixed bin (35) parameter;			/* return error code (O) */
dcl  P_network_channel_use_ptr ptr;			/* ptr to data to use to update usage for a channel (I) */
dcl  P_process_id bit (36) aligned;			/* specific process id to scan NAT for (I) */

/* External Procedures & Variables */

dcl  cu_$arg_list_ptr entry (ptr);
dcl  cu_$arg_ptr entry (fixed bin, ptr, fixed bin (21), fixed bin (35));
dcl  cu_$level_get entry (fixed bin);
dcl  cu_$level_set entry (fixed bin);
dcl  delete_$path entry (char (*), char (*), bit (36) aligned, char (*), fixed bin (35));
dcl  error_table_$bad_processid fixed bin (35) ext static;
dcl  error_table_$end_of_info fixed bin (35) ext static;
dcl  error_table_$invalid_lock_reset fixed bin (35) ext static;
dcl  error_table_$lock_wait_time_exceeded fixed bin (35) ext static;
dcl  error_table_$locked_by_this_process fixed bin (35) ext static;
dcl  error_table_$no_record fixed bin (35) ext static;
dcl  error_table_$noentry fixed bin (35) ext static;
dcl  error_table_$not_initialized fixed bin (35) ext static;
dcl  error_table_$process_unknown fixed bin (35) ext static;
dcl  get_ring_ entry () returns (fixed bin (3));
dcl  hcs_$replace_acl entry (char (*), char (*), ptr, fixed bin, bit (1), fixed bin (35));
dcl  hcs_$validate_processid entry (bit (36) aligned, fixed bin (35));
dcl  ioa_$general_rs entry (ptr, fixed bin, fixed bin, char (*), fixed bin (21), bit (1) aligned, bit (1) aligned);
dcl  iox_$attach_name entry (char (*), ptr, char (*), ptr, fixed bin (35));
dcl  iox_$close entry (ptr, fixed bin (35));
dcl  iox_$control entry (ptr, char (*), ptr, fixed bin (35));
dcl  iox_$delete_record entry (ptr, fixed bin (35));
dcl  iox_$detach_iocb entry (ptr, fixed bin (35));
dcl  iox_$open entry (ptr, fixed bin, bit (1) aligned, fixed bin (35));
dcl  iox_$position entry (ptr, fixed bin, fixed bin (21), fixed bin (35));
dcl  iox_$seek_key entry (ptr, char (256) var, fixed bin (21), fixed bin (35));
dcl  iox_$write_record entry (ptr, ptr, fixed bin (21), fixed bin (35));
dcl  pathname_ entry (char (*), char (*)) returns (char (168));
dcl  sys_info$system_control_dir char (168) varying ext;
dcl  system_info_$timeup entry (fixed bin (71));
dcl  unique_chars_ entry (bit (*)) returns (char (15));

dcl  (cleanup) condition;
dcl  (addr, clock, divide, length, null, rtrim, size, substr, unspec) builtin;

/* Internal Static */

dcl  ABORT_NO bit (1) int static options (constant) init ("0"b);
dcl  ABORT_YES bit (1) int static options (constant) init ("1"b);
dcl  BOF fixed bin int static options (constant) init (-1);
dcl  CREATE_RECORD_NO bit (1) int static options (constant) init ("0"b);
dcl  CREATE_RECORD_YES bit (1) int static options (constant) init ("1"b);
dcl  FORWARD fixed bin int static options (constant) init (0);
dcl  INIT_RECORD_PID bit (36) int static options (constant) init ("003000777777"b3);
dcl  INIT_RECORD_SESSION_HANDLE fixed bin (35) int static options (constant) init (0);
dcl  INIT_RECORD_NETWORK_ID fixed bin (35) int static options (constant) init (-1);
dcl  ME char (19) int static options (constant) init ("network_accounting_");
dcl  MICROSECONDS_PER_HALF_SECOND fixed bin (35) int static options (constant) init (500000);
dcl  MICROSECONDS_PER_SECOND fixed bin (35) int static options (constant) init (1000000);
dcl  NAT_iocb_ptr ptr int static init (null);
dcl  NET_ARRAY_EXTRA_RECORD_COUNT fixed bin (35) int static options (constant) init (20);
dcl  OFF bit (1) int static options (constant) init ("0"b);
dcl  ON bit (1) int static options (constant) init ("1"b);
dcl  RW_ACCESS bit (3) int static options (constant) init ("101"b);
dcl  SEEK_HEAD_EQUAL_SEARCH fixed bin int static options (constant) init (0);
dcl  TABLE_NAME char (32) int static options (constant) init ("network_accounting_table");
dcl  database_directory char (168) int static init ("");
dcl  i_am_initializer bit (1) int static init ("0"b);	/* ON if this is Initializer process
						   or the process which last cleared the table */
dcl  system_boot_time fixed bin (71) int static init (0);

/* Automatic */

dcl  abort_label label;
dcl  caller_area_ptr ptr;				/* Ptr to user area */
dcl  current_ring fixed bin;
dcl  delta_time fixed bin (35);
dcl  debug_sw bit (1);
dcl  entrypoint_name char (32);			/* entrypoint called through */
dcl  error_code fixed bin (35);			/* general internal error code */
dcl  error_code_arg_sw bit (1);			/* ON if passed error code to use */
dcl  error_message_arg_sw bit (1);			/* ON if user passed error message string to use */
dcl  error_string char (32);
dcl  get_process_total_entry bit (1);
dcl  process_id_to_match bit (36) aligned;
dcl  purge_lock_error fixed bin (35);
dcl  record_count fixed bin;
dcl  record_locked_sw bit (1);			/* ON while have specific record locked for user */
dcl  reset_table_sw bit (1);				/* ON if supposed to reset entries in table after dumping them */
dcl  saved_ring fixed bin init (-1);			/* ring number where called from */

/* Based */

dcl  1 auto_NAR aligned like network_account_record;
dcl  1 auto_key aligned like network_account_record.key;
dcl  caller_area area based (caller_area_ptr);
dcl  1 database_acl (1) aligned like segment_acl_entry;
dcl  1 info aligned like indx_info;			/* room for vfs_info.incl structures */
dcl  key_char_overlay char (size (auto_key) * CHARS_PER_WORD) based (addr (auto_key));
dcl  1 record_status_info aligned like rs_info;
dcl  seek_head_info_room (20) fixed bin (35);
dcl  seek_head_info_ptr ptr;
dcl  1 seek_head_info based (seek_head_info_ptr),
       2 relation_type fixed bin,
       2 n fixed bin,
       2 search_key char (256 refer (seek_head_info.n));

dcl  debug_switch bit (1) unaligned defined (NAR.switches.mbz);
%page;
/* This entry charges the specified process ID/session ID/network type
   combination for packets and bytes transmitted on the connection.

   If is not gated and is only called from within the inner ring.

   It may be called by the login servers and the individual users of the
   connections */

charge_channel_use: entry (P_network_channel_use_ptr, P_code);

	entrypoint_name = "charge_channel_use";

	error_code_arg_sw = ON;
	error_message_arg_sw = OFF;			/* no return message string available */
	call INIT1 ();
	on cleanup call CLEAN_UP ();
	call INIT2 ();

	network_channel_use_ptr = P_network_channel_use_ptr; /* copy pointer to data to use for update */

/* set up the key for the record desired */
	unspec (auto_key) = "0"b;
	auto_key.session_handle = NCU.session_handle;
	auto_key.network_id = NCU.network_id;

	if debug_sw then do;
	     call REPORT_ERROR (0, ABORT_NO,
		"SH=^w, OPID=^.3b, UPID=^.3b, SS=^d, PC=^5d, BC=^5d",
		NCU.session_handle, NCU.owner_pid,
		NCU.user_pid, NCU.session_state,
		NCU.packet_count, NCU.byte_count);
	end;

/* NOTE: The update of the vaious combination of records is being done in
   this manner to make it more obvious as to what is being done */
	go to update_the_records (NCU.session_state);
%page;
update_the_records (1):

/* CREATE_SESSION - create a new record using the owner PID since the user
   PID should be blank */

/* make sure they gave us a valid PID to work with */
	if NCU.owner_pid = "0"b then do;
	     if debug_sw then do;
		call REPORT_ERROR (0, ABORT_NO,
		     "Called with invalid owner PID");
	     end;
	     P_code = error_table_$bad_processid;
	     go to CLEANUP_AND_RETURN;
	end;

/* locate desired record, creating it if necessary using the data in auto_key,
   and lock it */
	auto_key.process_id = NCU.owner_pid;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "creating owner record");

/* add in the data for this session */
	NAR.byte_count = NAR.byte_count + NCU.byte_count;
	NAR.packet_count = NAR.packet_count + NCU.packet_count;
	NAR.active = ON;				/* show activity */

	call RECORD_UNLOCK ();
	go to CLEANUP_AND_RETURN;
%page;
update_the_records (2):

/* ASSIGN_CONNECTION - update the data for the Owner PID and turn on the
   unassigned switch. Then create a new record for the User PID. */

/* make sure they gave us valid PIDs to work with */
	if NCU.owner_pid = "0"b | NCU.user_pid = "0"b then do;
	     if debug_sw then do;
		call REPORT_ERROR (0, ABORT_NO,
		     "Called with invalid owner or user PIDs");
	     end;
	     P_code = error_table_$bad_processid;
	     go to CLEANUP_AND_RETURN;
	end;

/* locate owner record and lock it */
	auto_key.process_id = NCU.owner_pid;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "read owner record for assign");

/* update the record */
	NAR.byte_count = NAR.byte_count + NCU.byte_count;
	NAR.packet_count = NAR.packet_count + NCU.packet_count;

/* update the connect time for the record while assigned to the owner */
	NAR.connected_time = NAR.connected_time + (clock () - NAR.update_time);

/* show the connection belongs to someone else */
	NAR.unassigned_switch = ON;
	NAR.active = ON;				/* show activity */
	call RECORD_UNLOCK ();			/* record free now */

/* Now create a new record for the User PID which will now be taking over the
   session.  The network type and session ID will remain the same. */
	auto_key.process_id = NCU.user_pid;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "creating user record");

/* NOTE: the RECORD_SEEK_AND_LOCK call generates a zero usage data record with
   update_time set to the current clock value */

	NAR.active = ON;				/* show activity */

	call RECORD_UNLOCK ();
	go to CLEANUP_AND_RETURN;
%page;
update_the_records (3):

/* UPDATE_CONNECTION - update data in record of whichever process is currently
   using the connection.  This is determined by the owner and user PIDs. */

/* if we have a user PID defined, we will use it.  Otherwise, we will use the
   owner PID.  If both are zeros, we will compain */
	if NCU.user_pid ^= "0"b then
	     auto_key.process_id = NCU.user_pid;
	else if NCU.owner_pid ^= "0"b then
	     auto_key.process_id = NCU.owner_pid;
	else do;
	     if debug_sw then do;
		call REPORT_ERROR (0, ABORT_NO,
		     "Called with invalid owner and user PIDs");
	     end;
	     P_code = error_table_$bad_processid;
	     go to CLEANUP_AND_RETURN;
	end;

/* locate desired record, creating it if necessary using the data in auto_key,
   and lock it */
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "read specified record");

/* update the record */
	NAR.byte_count = NAR.byte_count + NCU.byte_count;
	NAR.packet_count = NAR.packet_count + NCU.packet_count;
	NAR.active = ON;				/* show activity */

	call RECORD_UNLOCK ();
	go to CLEANUP_AND_RETURN;
%page;
update_the_records (4):

/* UNASSIGN_CONNECTION - update the data for the User PID and turn on the
   unassigned and delete switches. Then locate the record for the Owner PID,
   turn the unassigned switch off and set update time to the current clock
   value so we can accumulate connect time again. */

/* make sure they gave us valid PIDs to work with */
	if NCU.owner_pid = "0"b | NCU.user_pid = "0"b then do;
	     if debug_sw then do;
		call REPORT_ERROR (0, ABORT_NO,
		     "Called with invalid owner or user PIDs");
	     end;
	     P_code = error_table_$bad_processid;
	     go to CLEANUP_AND_RETURN;
	end;

/* locate user record and lock it */
	auto_key.process_id = NCU.user_pid;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "read user record for unassign");

/* update the record */
	NAR.byte_count = NAR.byte_count + NCU.byte_count;
	NAR.packet_count = NAR.packet_count + NCU.packet_count;

/* update the connect time for the record while assigned to the owner */
	NAR.connected_time = NAR.connected_time + (clock () - NAR.update_time);

/* show record may be deleted when data read and that the connection belongs
   to someone else now. */
	NAR.unassigned_switch = ON;
	NAR.delete_switch = ON;
	NAR.active = ON;				/* show activity */

	call RECORD_UNLOCK ();

/* now locate the owner record for this session */
	auto_key.process_id = NCU.owner_pid;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "locating owner record");

/* show thie connection belongs to the owner */
	NAR.unassigned_switch = OFF;

/* and set time so we can start accumulating connect time again */
	NAR.update_time = clock ();
	NAR.active = ON;				/* show activity */

	call RECORD_UNLOCK ();
	go to CLEANUP_AND_RETURN;
%page;
update_the_records (5):

/* DESTROY_SESSION - update the record for the Owner PID since the user PID
   should be blank.  In addition, set the delete switch on. */

/* make sure they gave us a valid PID to work with */
	if NCU.owner_pid = "0"b then do;
	     if debug_sw then do;
		call REPORT_ERROR (0, ABORT_NO,
		     "Called with invalid owner PID");
	     end;
	     P_code = error_table_$bad_processid;
	     go to CLEANUP_AND_RETURN;
	end;

/* locate desired record and lock it */
	auto_key.process_id = NCU.owner_pid;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "read owner record for destroy");

/* update the record */
	NAR.byte_count = NAR.byte_count + NCU.byte_count;
	NAR.packet_count = NAR.packet_count + NCU.packet_count;

/* show record may be deleted when data read and that the connection belongs
   to someone else now. */
	NAR.delete_switch = ON;
	NAR.unassigned_switch = ON;

/* update the connect time for the record while assigned to the owner */
	NAR.connected_time = NAR.connected_time + (clock () - NAR.update_time);
	NAR.active = ON;				/* show activity */

	call RECORD_UNLOCK ();
	go to CLEANUP_AND_RETURN;

CLEANUP_AND_RETURN:
	call CLEAN_UP ();
JUST_RETURN:
	return;
%page;
/* This entry reinitializes the network accounting table (NAT).
   It does this by
   1. deleting the current table,
   2. opening it with vfile to create it,
   3. setting access to all users on the system,
   4. adding a dummy record to cause the file to expand from an SSF to MSF.

   This entry is gated through network_accounting_gate_.

   It is meant to be called only by the Initializer process when login server
   is initialized. */

clear_table: entry (P_error_message, P_code);

	entrypoint_name = "clear_table";

	error_code_arg_sw = ON;
	error_message_arg_sw = ON;			/* can pass back return error string */

/* remember if we are the Initializer */
	i_am_initializer = ON;

	call INIT1 ();
	on cleanup call CLEAN_UP ();
	call INIT2 ();

	if debug_sw then do;
	     call REPORT_ERROR (0, ABORT_NO, "Clearing NAT");
	end;

/* complain if database is open */
	if NAT_iocb_ptr ^= null then do;
	     call REPORT_ERROR (0, ABORT_NO, "Database is currently open.");
	     NAT_iocb_ptr = null;
	end;

/* now delete the current beastie to start fresh */
	call delete_$path (database_directory, TABLE_NAME, "101100"b,
	     ME || "$clear_table", error_code);
	if error_code ^= 0 then
	     if error_code ^= error_table_$noentry then
		call REPORT_ERROR (error_code, ABORT_YES, "Deleting old NAT.");

/* now create the new table */
	call NAT_OPEN;

/* now set access on table for read/write for everyone
   NOTE: table is now an SSF */
	unspec (database_acl) = "0"b;
	database_acl (1).access_name = "*.*.*";
	database_acl (1).mode = RW_ACCESS;
	call hcs_$replace_acl (database_directory, TABLE_NAME,
	     addr (database_acl), 1, "1"b /* no SysDaemon */, error_code);

/* write dummy record to force virgin database from SSF to MSF structured file */
	auto_key.process_id = INIT_RECORD_PID;
	auto_key.session_handle = INIT_RECORD_SESSION_HANDLE;
	auto_key.network_id = INIT_RECORD_NETWORK_ID;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_YES, error_code);
	if error_code = 0 then do;
	     NAR.unassigned_switch = ON;
	     NAR.update_time = clock ();
	     call RECORD_UNLOCK ();
	end;
	else call REPORT_ERROR (error_code, ABORT_YES, "seek for initial record");

	go to CLEANUP_AND_RETURN;
%page;
debug: entry (new_debug_mode);
dcl  new_debug_mode bit (1);

	entrypoint_name = "debug";
	error_code_arg_sw = OFF;
	error_message_arg_sw = OFF;
	call INIT1 ();
	on cleanup call CLEAN_UP ();
	call INIT2 ();
	debug_switch = new_debug_mode;

	return;
%page;
/* This entry returns the full pathname of the current
   network_accounting_table (NAT).

   It is gated through network_accounting_gate_.

   It may be called by anyone desiring to know the where the NAT is */

get_path: entry returns (char (168));

	if database_directory = ""			/* uninitialized */ then
	     return (pathname_ ((sys_info$system_control_dir), TABLE_NAME));

	return (pathname_ (database_directory, TABLE_NAME));
%page;
/* This entry returns a copy of all of the data in the NAT.  The data is
   allocated in a user provided area.

   This entry is gated through the network_accounting_gate_.

   It is meant to be called by anyone wishing to see the current network
   accounting data and has access to the gate. */

dump_table: entry (P_area_ptr, P_array_ptr, P_error_message, P_code);

	entrypoint_name = "dump_table";
	get_process_total_entry = OFF;
	reset_table_sw = OFF;
	go to READ_TABLE;



/* This entry returns all records in the NAT for a specific processid.
   It allocates space in a user provided area and returns the data for the
   specific user.  If the delete bit is on for the record, it will delete it.

   This entry is gated through network_accounting_gate_.

   It is meant to be called by the Initializer when destroying a process to
   get final network accounting data for the user. */

get_process_total: entry (P_process_id, P_area_ptr, P_array_ptr, P_error_message, P_code);

	entrypoint_name = "get_process_total";
	get_process_total_entry = ON;
	reset_table_sw = ON;			/* but will only reset for given user */
	process_id_to_match = P_process_id;
	go to READ_TABLE;


/* This entry returns a copy of all of the data in the NAT.  The data is
   allocated in a user provided area.  In addition, it zeros out all of the
   current accumulated packet and byte totals and reset the current time to
   the current clock value.

   This entry is gated through the network_accounting_gate_.

   It is meant to be called by the Initializer during the accounting update
   cycle. */

read_and_reset_table: entry (P_area_ptr, P_array_ptr, P_error_message, P_code);

	entrypoint_name = "read_and_reset_table";
	get_process_total_entry = OFF;
	reset_table_sw = ON;
	go to READ_TABLE;
%page;
READ_TABLE:
	error_code_arg_sw = ON;
	error_message_arg_sw = ON;			/* can pass back return error string */
	caller_area_ptr = P_area_ptr;
	P_array_ptr = null;

	call INIT1 ();
	abort_label = RETURN_WHAT_WE_HAVE;
	on cleanup call CLEAN_UP ();
	call INIT2 ();

	if debug_sw then do;
	     call REPORT_ERROR (0, ABORT_NO,
		"^[: PID=^.3b^;^s^]",
		get_process_total_entry, process_id_to_match);
	end;

	on cleanup call CLEAN_UP ();

/* determine current size of the database so we can allocate a large enough area to contain it */
	unspec (indx_info) = "0"b;
	indx_info.info_version = vfs_version_1;
	call iox_$control (NAT_iocb_ptr, "file_status", addr (indx_info), error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "get current record count");
	record_count = indx_info.records;

/* If database is empty, get out now. */
	if record_count = 1 then go to RETURN_WHAT_WE_HAVE;

/* leave space for extra records in case people are adding to database while we are extracting */
	network_account_array_bound = record_count + NET_ARRAY_EXTRA_RECORD_COUNT;
	allocate network_account_array in (caller_area) set (network_account_array_ptr);
	network_account_array.version = NET_ACCT_ARRAY_VERSION_1;

	network_account_array.count = 0;		/* no output records yet */

/* position to the proper first record based on the entrypoint called thru */
	if get_process_total_entry then do;

/* locate 1st record with this process_id as part of key */
	     seek_head_info_ptr = addr (seek_head_info_room);
	     seek_head_info.relation_type = SEEK_HEAD_EQUAL_SEARCH; /* equal */
	     auto_key.process_id = process_id_to_match;	/* copy process ID to search for */
	     seek_head_info.n = length (unspec (auto_key.process_id)) / BITS_PER_CHAR;
	     unspec (seek_head_info.search_key) = unspec (auto_key.process_id);
	     call iox_$control (NAT_iocb_ptr, "seek_head", seek_head_info_ptr, error_code);
	     if error_code ^= 0 then do;
		if error_code ^= error_table_$no_record then
		     call REPORT_ERROR (error_code, ABORT_YES, "finding 1st record for process_id");
	     end;
	end;
	else do;
	     call iox_$position (NAT_iocb_ptr, BOF, 0, error_code);
	     if error_code ^= 0 then
		call REPORT_ERROR (error_code, ABORT_YES, "position to beginning of file");
	end;
%page;
/* now loop through the NAT transfering data to the area for the user */
	do while ("1"b);
	     call RECORD_LOCK (error_code);		/* get a record */
	     if error_code ^= 0 then do;
		if error_code = error_table_$lock_wait_time_exceeded then
		     go to position_to_next_record;
		error_string = "lock current record";
		go to RETURN_WHAT_WE_HAVE_WITH_ERROR;
	     end;

/* see if we've filled up the output array */
	     if network_account_array.count + 1 > network_account_array.max_count then do;
		P_code = 0;
		go to RETURN_WHAT_WE_HAVE;
	     end;

/* skip over the special record used to clear the table on bootload */
	     if NAR.process_id = INIT_RECORD_PID then
		if NAR.session_handle = INIT_RECORD_SESSION_HANDLE then
		     if NAR.network_id = INIT_RECORD_NETWORK_ID then
			go to skip_this_record;

/* if this is the get_process_total entrypoint and we have gone beyond the
   last of the records for the process then stop now. */
	     if get_process_total_entry then
		if NAR.process_id > process_id_to_match then
		     go to RETURN_WHAT_WE_HAVE;

/* bump to location of output record, get ptr to same and clear it out */
	     network_account_array.count = network_account_array.count + 1;
	     network_account_data_ptr = addr (network_account_array.record (network_account_array.count));
	     unspec (network_account_data) = "0"b;

/* see if we should purge the record from the database.  Decision will be
   based on fact there was no activity during last accounting update period,
   the record has zero packet/byte counts and the process id is invalid

   The record will only be purged IFF the caller is the Initializer
   process and it was called through the read_and_reset_table entrypoint */

	     if NAR.purged = OFF then do;
		if NAR.active = OFF & NAR.packet_count = 0 & NAR.byte_count = 0 then do;
		     call hcs_$validate_processid (NAR.process_id, purge_lock_error);
		     if purge_lock_error = error_table_$process_unknown then do;
			NAD.purged = ON;		/* show record should be purged */
			NAR.purged = ON;
			if i_am_initializer & reset_table_sw then do;
			     NAR.delete_switch = ON;	/* show record should be deleted NOW */
			     NAD.delete_sw = ON;
			end;
		     end;
		end;
	     end;

/* copy data from NAT to output record */
	     call READ_TABLE_COPY_NAT_DATA;

/* if the caller entered through the read_and_reset_table or get_process_total
   entries and is the initializer and the delete_switch is on
   then delete the record */
	     if i_am_initializer then
		if reset_table_sw then
		     if NAR.delete_switch = ON then
			call RECORD_DELETE ();
skip_this_record:

/* unlock the record and position to the next record if needed.  This is only
   done if the record wasn't deleted.  If it was deleted, already positioned
   to next record */
	     if record_locked_sw then do;
		call RECORD_UNLOCK ();
position_to_next_record:
		call iox_$position (NAT_iocb_ptr, FORWARD, 1, error_code);
		if error_code ^= 0 & error_code ^= error_table_$end_of_info then do;
		     error_string = "position to next record";
		     go to RETURN_WHAT_WE_HAVE_WITH_ERROR;
		end;
	     end;
	end;
RETURN_WHAT_WE_HAVE_WITH_ERROR:
	if error_code ^= error_table_$end_of_info & error_code ^= error_table_$no_record then
	     call REPORT_ERROR (error_code, ABORT_NO, error_string);

RETURN_WHAT_WE_HAVE:
	if debug_sw then do;
	     if network_account_array_ptr = null then
		call REPORT_ERROR (0, ABORT_NO, "Returning 0 entries");
	     else call REPORT_ERROR (0, ABORT_NO, "Returning ^d entr^[y^;ies^]",
		     network_account_array.count, network_account_array.count = 1);
	end;
	P_array_ptr = network_account_array_ptr;	/* pass location of array back to caller */
	network_account_array_ptr = null;		/* any further errors do not affect returned info */

	go to CLEANUP_AND_RETURN;
%page;
/* This entry changes the pathname of the directory in which the
   network_accounting_table (NAT) is located.  By default it is in
   sys_info_$system_control_dir.

   This entry is gated through network_accounting_gate_.

   This entry is entended only for use during testing of the answering service. */

test: entry (P_path);

	database_directory = P_path;

	if NAT_iocb_ptr ^= null then
	     call NAT_CLOSE ();

	return;
%page;
CLEAN_UP: proc ();

/* if we have a record locked, then unlock it */
	if record_locked_sw then do;
	     record_status_info.lock_sw = OFF;
	     record_status_info.unlock_sw = ON;
	     call iox_$control (NAT_iocb_ptr, "record_status", addr (record_status_info), error_code);
	     record_locked_sw = OFF;
	end;

/* if we have an internal network_account_array allocated and this is an abort
   then free the array */
	if network_account_array_ptr ^= null then
	     free network_account_array in (caller_area);

/* return to the proper ring */
	if saved_ring ^= -1 then
	     call cu_$level_set (saved_ring);

     end CLEAN_UP;
%page;
INIT1: proc ();

/* get and save current ring and ring we will be going to
   initialize any return argument values
   and all internal switches & variables */

	call cu_$level_get (saved_ring);		/* save level we were called from */
	current_ring = get_ring_ ();

	if error_code_arg_sw then
	     P_code = 0;				/* clear return code */
	if error_message_arg_sw then			/* if entry has return error message */
	     P_error_message = "";			/* then start with it nice and clean */
	record_locked_sw = OFF;			/* no record locked yet */
	network_account_array_ptr = null;		/* so cleanup stays happy */
	debug_sw = OFF;				/* start with it OFF */

	if database_directory = "" then		/* uninitialized */
	     database_directory = sys_info$system_control_dir;

/* set up data for later lock/unlock record calls */
	unspec (record_status_info) = "0"b;
	record_status_info.version = rs_info_version_2;
	record_status_info.record_ptr = null;

	abort_label = CLEANUP_AND_RETURN;

     end INIT1;

INIT2: proc ();

/* now access the database */
	call cu_$level_set (current_ring);		/* drop into the inner ring */

/* the clear_table entrypoint will be doing it's own handling of the database
   everyone else must get the database opened and set up */
	if entrypoint_name = "clear_table" then return;

	if NAT_iocb_ptr = null then
	     call NAT_OPEN;

/* locate Initializer's boot record */
	auto_key.process_id = INIT_RECORD_PID;
	auto_key.session_handle = INIT_RECORD_SESSION_HANDLE;
	auto_key.network_id = INIT_RECORD_NETWORK_ID;
	call RECORD_SEEK_AND_LOCK (CREATE_RECORD_NO, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "read initial record for debug flags");

/* see if we have valid database */
	if system_boot_time = 0 then
	     call system_info_$timeup (system_boot_time);

	if system_boot_time > NAR.update_time then do;
	     call NAT_CLOSE ();			/* this one's no good */
	     call REPORT_ERROR (error_table_$not_initialized, ABORT_YES, "Database not initialized");
	end;

/* setup the debug switch by looking for the initializer's dummy record */
	debug_sw = debug_switch;

	call RECORD_UNLOCK ();

     end INIT2;
%page;
NAT_CLOSE: proc ();

	if NAT_iocb_ptr ^= null then do;

	     call iox_$close (NAT_iocb_ptr, error_code);
	     if error_code ^= 0 then
		call REPORT_ERROR (error_code, ABORT_NO, "close database");

	     call iox_$detach_iocb (NAT_iocb_ptr, error_code);
	     if error_code ^= 0 then
		call REPORT_ERROR (error_code, ABORT_NO, "detach database");

	     NAT_iocb_ptr = null;
	end;

     end NAT_CLOSE;




NAT_OPEN: proc ();

dcl  stream_name char (32);
dcl  attach_description char (168);

	stream_name = "network_acct." || unique_chars_ (""b);
	attach_description = "vfile_ " || rtrim (pathname_ (database_directory, TABLE_NAME));
	attach_description = rtrim (attach_description) || " -stationary -share 10";

	call iox_$attach_name (stream_name, NAT_iocb_ptr,
	     attach_description, null, error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "attaching ^a>^a",
		database_directory, TABLE_NAME);
	call iox_$open (NAT_iocb_ptr, Keyed_sequential_update, "0"b, error_code);
	if error_code ^= 0 then do;

/* if we couldn't open, make sure we get rid of it */
	     call iox_$detach_iocb (NAT_iocb_ptr, (0));
	     NAT_iocb_ptr = null;
	     call REPORT_ERROR (error_code, ABORT_YES, "opening ^a>^a for update",
		database_directory, TABLE_NAME);
	end;

     end NAT_OPEN;
%page;
READ_TABLE_COPY_NAT_DATA: proc ();

/* copies data from the network_account_record (NAR) to the
   network_account_data (NAD) record.

   If called through the "read_and_reset_table", it will reset the data values
   in the NAR;  otherwise, it sill leave the NAR alone */

	NAD.key = NAR.key;
	NAD.delete_sw = NAR.delete_switch;
	NAD.purged = NAR.purged;
	NAD.unassigned_sw = NAR.unassigned_switch;
	NAD.byte_count = NAR.byte_count;
	NAD.packet_count = NAR.packet_count;

/* compute connect seconds (rounding up if needed) to return to the caller */
	delta_time = divide (NAR.connected_time + MICROSECONDS_PER_HALF_SECOND,
	     MICROSECONDS_PER_SECOND, 35, 0);
	if NAR.unassigned_switch = OFF then do;
	     delta_time = delta_time + divide ((clock () - NAR.update_time) + MICROSECONDS_PER_HALF_SECOND,
		MICROSECONDS_PER_SECOND, 35, 0);
	end;
	NAD.connect_time = delta_time;

/* reset incremental data values for next update interval and
   reset activity indicator so we can see if record was active */
	if reset_table_sw = ON then do;
	     NAR.byte_count,
		NAR.packet_count,
		NAR.connected_time = 0;
	     if NAR.unassigned_switch = OFF then
		NAR.update_time = clock ();
	     NAR.active = OFF;
	end;

     end READ_TABLE_COPY_NAT_DATA;
%page;
RECORD_DELETE: proc ();

/* subroutine to delete the record currently being accessed.
   This will implicitly unlock the record (nothing there) and
   position to next record in the file */

	if debug_sw then do;
	     call REPORT_ERROR (0, ABORT_NO,
		"Deleting record, PID=^.3b, SID=^w",
		NAR.process_id, NAR.session_handle);
	end;

	call iox_$delete_record (NAT_iocb_ptr, error_code);
	if error_code ^= 0 then do;
	     if error_code = error_table_$locked_by_this_process then ; /* IGNORE, record is gone anyway */
	     else do;
		call REPORT_ERROR (error_code, ABORT_NO,
		     "Unable to delete closed record for PID ^w, handle ^w",
		     NAR.process_id, NAR.session_handle);
	     end;
	end;
	record_locked_sw = OFF;

     end RECORD_DELETE;
%page;
RECORD_LOCK: proc (P_code);

/* lock the current record pointed to by rs_info in the database */

dcl  (P_code, error_code) fixed bin (35);

	P_code = 0;

	record_locked_sw = ON;
	record_status_info.lock_sw = ON;
	record_status_info.unlock_sw = OFF;
	call iox_$control (NAT_iocb_ptr, "record_status", addr (record_status_info), error_code);
	if error_code ^= 0 then do;

/* ignore any of the normal ignorable locking errors */
	     if error_code = error_table_$invalid_lock_reset
		| error_code = error_table_$lock_wait_time_exceeded
		| error_code = error_table_$locked_by_this_process then ;
	     else do;
		P_code = error_code;
		return;
	     end;
	end;

/* return ptr to the NAT data record */
	network_account_record_ptr = record_status_info.record_ptr;

	return;

     end RECORD_LOCK;
%page;
RECORD_SEEK_AND_LOCK: proc (create_record_flag, error_code);

/* this procedure will seek for record defined by values in auto_key

   If it doesn't find it and the create_record flag is ON, it will create a
   record using values in auto_key and current clock time.

   It will also lock the record prior to return */

dcl  create_record_flag bit (1);
dcl  error_code fixed bin (35);

/* attempt to locate record desired */
	call iox_$seek_key (NAT_iocb_ptr, (key_char_overlay), (0), error_code);
	if error_code ^= 0 then do;			/* some sort of problem */
	     if error_code = error_table_$no_record	/* record not there */
		& create_record_flag then do;		/* and we should create */
		if debug_sw then do;
		     call REPORT_ERROR (0, ABORT_NO,
			"seek_key: Creating record. PID=^.3b, SH=^w, NID=^2d",
			auto_key.process_id,
			auto_key.session_handle, auto_key.network_id);
		end;

		unspec (auto_NAR) = "0"b;
		auto_NAR.version = NET_ACCT_RECORD_VERSION_1;
		auto_NAR.key = auto_key;
		auto_NAR.update_time = clock ();
		call iox_$write_record (NAT_iocb_ptr, addr (auto_NAR), size (auto_NAR) * CHARS_PER_WORD, error_code);
		if error_code ^= 0 then
		     call REPORT_ERROR (error_code, ABORT_YES, "add a record");
	     end;
	     else call REPORT_ERROR (error_code, ABORT_YES, "seek specified record");
	end;

	call RECORD_LOCK (error_code);

     end RECORD_SEEK_AND_LOCK;
%page;
RECORD_UNLOCK: proc ();

/* unlock the current record pointed to by rs_info in the database */

dcl  error_code fixed bin (35);

	if ^record_locked_sw then return;		/* not locked so all done */

	record_status_info.lock_sw = OFF;
	record_status_info.unlock_sw = ON;
	call iox_$control (NAT_iocb_ptr, "record_status", addr (record_status_info), error_code);
	if error_code ^= 0 then
	     call REPORT_ERROR (error_code, ABORT_YES, "unlock record");

	record_locked_sw = OFF;

     end RECORD_UNLOCK;
%page;
REPORT_ERROR: proc options (variable);

/* sets return error code and message, if available.  Puts message into the
   system log also.  Does non-local goto to get out of network accounting
   if told to do so. */

/* arg	     meaning
   1	     error code
   2	     non-local goto flag
   3	     ioa_ control string
   4...	     optional arguments */

/* following gate is for debugging only and is not released with system */
/**** dcl  ring2_nat_debug_$syserr_error_code entry options (variable); */

dcl  error_code_to_return fixed bin (35) based (error_code_ptr);
dcl  abort_flag bit (1) based (abort_flag_ptr);

dcl  (arg_list_ptr, error_code_ptr, abort_flag_ptr) ptr;
dcl  buffer char (500);
dcl  int_code fixed bin (35);
dcl  len fixed bin (21);

/* get individual arg ptrs for 1st 2 args */
	call cu_$arg_list_ptr (arg_list_ptr);
	call cu_$arg_ptr (1, error_code_ptr, (0), int_code);
	call cu_$arg_ptr (2, abort_flag_ptr, (0), int_code);

	if error_code_arg_sw then
	     P_code = error_code_to_return;		/* set return node */

/* generate error message */
	call ioa_$general_rs (arg_list_ptr, 3, 4, buffer, len,
	     "1"b /* PAD STRING */, "0"b /* NO NL */);

	if error_message_arg_sw then			/* return to user if we can */
	     P_error_message = substr (buffer, 1, len);

/* stuff the error in the system log */
	if debug_sw then do;

/**** Following commented out for release.  Gate not shipped with release */
/****	     call ring2_nat_debug_$syserr_error_code (SYSERR_LOG_OR_PRINT, */
/****		error_code_to_return, "^a$^a: ^a", ME, entrypoint_name, */
/**** 		substr (buffer, 1, len)); */
	end;

	if abort_flag then
	     go to abort_label;

     end REPORT_ERROR;
%page; %include acl_structures;
%page; %include iox_modes;
%page; %include network_account_array;
dcl  1 NAD aligned based (network_account_data_ptr) like network_account_data;
%page; %include network_account_record;
dcl  1 NAR aligned based (network_account_record_ptr) like network_account_record;
%page; %include network_channel_use;
dcl  1 NCU aligned based (network_channel_use_ptr) like network_channel_use;
%page; %include rs_info;
%page; %include system_constants;
%page; %include vfs_info;

     end network_accounting_;






		    bull_copyright_notice.txt       08/30/05  1008.4r   08/30/05  1007.3    00020025

                                          -----------------------------------------------------------


Historical Background

This edition of the Multics software materials and documentation is provided and donated
to Massachusetts Institute of Technology by Group Bull including Bull HN Information Systems Inc. 
as a contribution to computer science knowledge.  
This donation is made also to give evidence of the common contributions of Massachusetts Institute of Technology,
Bell Laboratories, General Electric, Honeywell Information Systems Inc., Honeywell Bull Inc., Groupe Bull
and Bull HN Information Systems Inc. to the development of this operating system. 
Multics development was initiated by Massachusetts Institute of Technology Project MAC (1963-1970),
renamed the MIT Laboratory for Computer Science and Artificial Intelligence in the mid 1970s, under the leadership
of Professor Fernando Jose Corbato.Users consider that Multics provided the best software architecture for 
managing computer hardware properly and for executing programs. Many subsequent operating systems
incorporated Multics principles.
Multics was distributed in 1975 to 2000 by Group Bull in Europe , and in the U.S. by Bull HN Information Systems Inc., 
as successor in interest by change in name only to Honeywell Bull Inc. and Honeywell Information Systems Inc. .

                                          -----------------------------------------------------------

Permission to use, copy, modify, and distribute these programs and their documentation for any purpose and without
fee is hereby granted,provided that the below copyright notice and historical background appear in all copies
and that both the copyright notice and historical background and this permission notice appear in supporting
documentation, and that the names of MIT, HIS, Bull or Bull HN not be used in advertising or publicity pertaining
to distribution of the programs without specific prior written permission.
    Copyright 1972 by Massachusetts Institute of Technology and Honeywell Information Systems Inc.
    Copyright 2006 by Bull HN Information Systems Inc.
    Copyright 2006 by Bull SAS
    All Rights Reserved
