#!/usr/local/bin/kermit +
#
# i k s d p y  --  IKSD display
#
# Active display of IKSD database.
# Main screen lists all sessions, with option to switch to
# a per-session detail screen.
#
# Illustrates:
# . File record i/o
# . WAIT FILE and \v(kbchar)
# . Arithmetic
# . Hexadecimal decoding
# . Single-keystroke program control
# . Compact substring notation
# . Rudimentary screen formatting
#
# Requires: C-Kermit 7.0 Beta.11 or later.
#
# PRERELEASE:
#   Last update: Tue Dec  7 19:19:03 1999
#   Documentation: iksdb.txt
#
# Author: F. da Cruz, Columbia University, November 1999.

local \%c \%n \%p block dbfile rate dir user info

; Configuration (change as needed)...

.rate = 4                               ; Screen refresh rate (sec)

if def \%1 .dbfile := \%1               ; Database file
if not def dbfile {
    if eq "\v(platform)" "32-bit_Windows" {
	if not equal "\$(winbootdir)" "" .dbfile := \$(winbootdir)/iksd.db
	else if not equal "\$(windir)" "" .dbfile := \$(windir)/iksd.db
	else if not equal "\$(SystemRoot)" "" .dbfile := \$(SystemRoot)/iksd.db
	else .dbfile = c:/iksd.db
    } else {
        .dbfile = /var/log/iksd.db
    }
}

; Operating variables and settings...

set case off                            ; Case matters not
set quiet on                            ; No noisy messages
set sleep cancel on                     ; Allow interrupt WAIT from keyboard

; Macros

define SHOWHELP {                       ; Main help screen
    cls
    echo {IKSD Display...}
    echo
    echo {Press:}
    echo {  The Space Bar to switch between summary and detail screens.}
    echo {  A digit to view a particular session, 0 through 9, or:}
    echo {    X to enter a session number with two or more digits.}
    echo {    M to return to the Main screen.}
    echo {    N to see the Next session (cycle through sessions).}
    echo {    H to see this Help message.}
    echo {    Q to Quit.}
    set ask-timer 0
    echo
    getc \%9 {Press any key to continue: }
    set ask-timer \m(rate)
}

define SHOWRECORD {                     ; Interpret a record
    .pid := \fltrim(\s(block[17:16]),0)
    .flags := \fhex2n(\s(block[1:4]))
    if ( (\m(flags)&1) ) { .\%v = (IN_USE) } else { .\%v = (FREE) }
    if ( (\m(flags)&2) ) { .\%v := \%v(REAL_USER) }
    if ( (\m(flags)&4) ) { .\%v := \%v(LOGGED_IN) }
    cls                                 ; Clear screen and print detail
    echo [\m(dbfile)] Session \%1: \v(time)...
    echo
    echo { Flags: \s(block[1:4]) \%v}
    echo { AuthT: \s(block[5:4])}
    echo { AuthM: \s(block[9:4])}
    echo { State: \s(block[13:4])}
    echo { Lhost: \fhex2ip(\s(block[41:8]))} ; 32+8+1
    echo { Rhost: \fhex2ip(\s(block[57:8]))} ; 48+8+1
    echo { PID:   \m(pid) (\fhex2n(\m(pid)))}
    echo { Start: \s(block[66:17])}
    echo { Last:  \s(block[84:17])}
    echo { User:  \s(block[1025:\fhex2n(\s(block[101:4]))])}
    echo { Dir:   \s(block[2049:\fhex2n(\s(block[105:4]))])}
    echo { Info:  \s(block[3073:\fhex2n(\s(block[109:4]))])}
    echo
}

define SHOWDETAIL {                     ; Show details for a session
    local \%k \%v \%x
    if not def \%1 return
    if ( < \fcode({\%1}) 32 ) return
    if eq {\%1} { } .\%1 = 0
    if eq {\%1} {N} .\%1 = 0
    .\%x = \%1
    while true {                        ; Loop in detail screen
        if def \%x switch \%x {         ; What did they type?
	  :[0-9]                        ; A digit
              .\%1 := \%x
	      break                     ; So that's the session number.
	  :H, showhelp,                 ; An 'H' so show help text.
              if not num \%1 return
              break
	  :Q, echo Q, exit 0                     ; 'Q' for quit.
          :N, .\%n ::= (\fsize(\m(dbfile))/4096) ; Check for new records
              .\%1 ::= \fmod(\%1+1,\%n)          ; Go to next session
              break
	  :X, xecho \13                          ; Long session number
	      screen cleol                       ; Clear this line
	      set ask-timer 0
	      ask \%1 { Enter session number: }  ; Prompt and wait for text
	      set ask-timer \m(rate)             ; up to CR with no timeout.
	      if not def \%1 .\%1 = 0            ; If CR only, use 0.
              if not numeric \%1 {               ; Check for n-ness.
                  echo Number required
                  return
              }
	      break
          :M, return                             ; Return to main screen.
	  :default
              echo Not a choice: "\%1"
              pause 1
              return
        }
        if ( > \%1 (\%n-1) || < \%1 0 ) { echo Out of range, pause 1, return }
        .\%k ::= \%1*4096               ; Seek position for desired record
        fclose \%c                      ; Close/Open to force data refresh
        fopen /read /binary \%c \m(dbfile)
        if fail end 1 CLOSE OPEN FAILURE
	fseek \%c \%k                   ; Seek to record
	if fail end 1 Seek failed.
	.\%p := \f_pos(\%c)
	fread /size:4096 \%c block      ; Read it
	if fail end 1 Read failed.
	if ( ! = \f_pos(\%c) \%p+4096 ) exit 1 FATAL - Database corrupt
        showrecord \%1                  ; Show it
	xecho {[0-9] (session digit), H[elp], or Q[uit]: }
	wait 60 file modification \m(dbfile)
	if success continue
	undef \%x
	if ( def \v(kbchar) ) { .\%x := \v(kbchar) } else { getc \%x }
        if not def \%x continue
        if eq { } {\%x} .\%x = M
    }
}

; Main display...

undef \%c                               ; Database not yet open
set ask-timer \m(rate)                  ; Refresh rate control

while true {                            ; Main loop
    cls
    if ( not def \%c ) {	        ; If database not open
	if exist \m(dbfile) {           ; If it exists
	    fopen /read /binary \%c \m(dbfile) ; Open it
	    if fail {
		echo \m(dbfile) - Open failed: \f_errmsg()
		sleep \m(rate)
		continue
	    }
	} else {                        ; It doesn't exist
	    echo [\m(dbfile)] - Does not exist: \v(time)...
            sleep \m(rate)
	    continue
	}
    }
    echo [\m(dbfile)] \v(time)...       ; Top line - filename & time
    echo
    echo {    Flgs Host........... Start... Last.... (PID)(user)(dir)(what)}
    .\%n = 0
    frewind \%c                         ; Rewind database
    if fail { undef \%c, continue }     ; On failure go gack and reopen it
    while ( ! \f_eof(\%c) ) {           ; Loop to read each record
        fread /size:4096 \%c block
        if fail break
        if ( != \fmod(\f_pos(\%c),4096) 0 ) exit 1 FATAL - Database corrupt
        incr \%p 4096
        .user := \ftrim(\s(block[1025:1024]))
        .dir  := \ftrim(\s(block[2049:1024]))
        .info := \ftrim(\s(block[3073:1024]))
        .\%a := \flpad(\%n,2). \s(block[1:4]) -
\frpad(\fhex2ip(\s(block[57:8])),15)\s(block[74:9])\s(block[92:9]) -
(\fhex2n(\s(block[17:16])))(\m(user))(\m(dir))(\m(info))
	echo \fleft(\%a,\v(cols))
        incr \%n
    }
    echo
    xecho {[0-9] (session digit), H[elp], or Q[uit]: }
    wait 60 file modification \m(dbfile)
    if success continue
    undef \%x
    if ( def \v(kbchar) ) { .\%x := \v(kbchar) } else { getc \%x }
    if ( defined \%x ) showdetail {\%x}
    if ( == \%n 1 ) { fclose \%c, undef \%c } ; Force refresh if only one
}
