Frecon code: scan.pal
From PROSE Programming Language - Wiki
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Scan Routines for Frecon
% Author: Mark R. Bannister
% Date: 19/Dec/2017
%
% Provides routines for scanning subdirectories underneath
% a path and collecting data for the File Reconnaissance Tool
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
~Module
EQUS {[frecon]}
._init
attr/load P0, [psString], P1, [psPointer], P2, [psBoolean]
func/def [scan_opendir], &[.scan_opendir], P1, P0, [path]
func/def [scan_openlast], &[.scan_openlast], P1
func/def [scan_closedir], &[.scan_closedir], NULL, P1, [handle]
func/def [scan_start], &[.scan_start], NULL, P2, [cksum]
func/def [scan], &[.scan_prompt]
error/def [BadPath], [Bad pathname]
local/rtn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Open top-level path provided from which scans are to begin and return
% a file object
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.scan_opendir
var/addr P0, [path]
attr/load P1, [psString]
var/global P2, P1, [scan_dir]
attr/direct P2, P1, P0, P1
.open_handle
%
% Set-up global variable manually
%
obj/def P2
class/add P2, [psFileHook], [psStatUnix]
attr/copy P0, P0, P1
attr/add P2, [psFilePath], P0
obj/commitref P2, ![-], [dir_handle], P2
func/rtn P2
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Open top-level path previously supplied to scan_opendir
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.scan_openlast
var/addr P0, [scan_dir]
attr/load P1, [psString]
local/jmp &[.open_handle]
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Close handle returned by scan_opendir
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.scan_closedir
var/addr P0, [handle]
attr/addr P0, P0, [psPointer]
obj/del P0 % delete global variable
func/rtn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Ask question and get Y/N response, where P0 contains the question
% and SCMP will be equal (1) upon return if Y was answered
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.get_yn
reg/load P14, ![.prose.sys.io.stdin],
P15, ![.prose.sys.io.stdout]
attr/load P12, [psStreamIn], P13, [psStreamOut]
.get_choice
%
% Display user prompt
%
attr/mvadd P15, P13, P0, [? [y/n\]: ]
%
% Grab one line of input
%
.get_stdin
attr/copy P0, P14, P12
%
% Force to uppercase
%
reg/jmpeq &[.get_stdin], P0, NULL % Ignore Ctrl+D
reg/load A, (P0, #0)
opa/shr #0x18
opa/and #0x5f
%
% Test for Y/y
%
reg/jmpeq &[.yes], A, #0x59
%
% Test for N/n
%
reg/jmpeq &[.no], A, #0x4e
%
% Sound bell on error
%
attr/add P15, P13, [\a]
local/jmp &[.get_choice]
.yes
reg/load SCMP, #1
local/rtn
.no
reg/load SCMP, #0
local/rtn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Prompt use before scan begins to determine if checksums are required
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.scan_prompt
reg/load P0, [Calculate checksums during the scan]
local/jsr &[.get_yn]
func/call NULL, [scan_start], SCMP
func/rtn
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Scan a filesystem - this will overwrite the contents of the existing
% database with a fresh scan from underneath the chosen scan path
%
% Takes a cksum argument that will be TRUE if checksums are also to
% be calculated on all regular files encountered
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
.scan_start
%
% Truncate the database and statistics to lose all existing records
%
func/call NULL, [db_truncate]
func/call NULL, [stat_reset]
%
% Save initial database record for top-level directory
%
func/bcall P0, [scan_openlast]
attr/addr P0, P0
func/bcall NULL, [db_write_record], P0, #0, #0
func/bcall NULL, [stat_store], P0
reg/load P1, (P0)
%
% Walk every file and directory under the top-level and create
% database records for each one
%
var/addr P5, [cksum]
attr/index P5, P5, [psBoolean] % checksums or not?
attr/load P6, [psInteger]
attr/def P7, P6, #0 % last record number
reg/load P8, ![.prose.sys.io.stdout], P9, ![.prose.sys.io.stderr]
class/load P10, [psStatUnix]
attr/load P11, [statMode], P12, [psStreamOut], P13, [psFilePath]
reg/load P14, ![.prose.error.sys.AccessDenied]
attr/add P8, P12, [\nScanning ...]
error/jmp &[.access_denied], P14
.loop
reg/load P2, (P1)
reg/jmpeq &[.loop_end], P2, NULL
class/add P2, P10
%
% Increment record number and write a new record to the database file
%
op/incr P7
attr/def P3, P6, P7
func/bcall NULL, [db_write_record], P2, P3, P5
func/bcall NULL, [stat_store], P2
%
% If there are child nodes, walk into subdirectory
%
reg/load P2, (P2)
reg/jmpeq &[.loop], P2, NULL % no child nodes, continue
stack/push P1
reg/move P1, P2
attr/add P8, P12, [.]
local/jmp &[.loop]
.access_denied
error/clr
error/jmp &[.access_denied], P14
reg/jmpne &[.access_p2], P2, NULL
.access_p1
attr/copy P2, P1, P13
local/jmp &[.access_error]
.access_p2
attr/copy P2, P2, P13
.access_error
reg/copy PUSH, [\nError: access denied: ], P2, [\n]
attr/add P9, P12, PULL
local/jmp &[.loop]
.loop_end
reg/jmpeq &[.scan_stop], PEEK, LOCK
stack/pull P1
local/jmp &[.loop]
.scan_stop
reg/clr P1
func/call NULL, [scan_closedir], P0
func/rtn