Index: Makefile.in ================================================================== --- Makefile.in +++ Makefile.in @@ -249,10 +249,13 @@ @srcdir@/src/cc8/Makefile.in \ @srcdir@/src/SIMH/Makefile.in \ @srcdir@/src/SIMH/PDP8/Makefile.in INFILES = \ @srcdir@/bin/os8-cp.in \ + @srcdir@/bin/os8-run.in \ + @srcdir@/bin/teco-pi-demo.in \ + @srcdir@/bin/txt2os8.in \ @srcdir@/bin/pidp8i.in \ @srcdir@/boot/0.script.in \ @srcdir@/boot/2.script.in \ @srcdir@/boot/3.script.in \ @srcdir@/boot/4.script.in \ @@ -262,12 +265,15 @@ @srcdir@/boot/run-v3f.script.in \ @srcdir@/boot/tss8.script.in \ @srcdir@/etc/pidp8i.service.in \ @srcdir@/etc/sudoers.in \ @srcdir@/etc/usb-mount@.service.in \ + @srcdir@/lib/os8script.py.in \ + @srcdir@/lib/simh.py.in \ @srcdir@/src/pidp8i/gpio-common.c.in \ @srcdir@/tools/simh-update.in \ + @srcdir@/tools/test-os8-send-file.in \ $(OS8RUN_INFILES) OS8RUN_OUTFILES := $(subst @srcdir@/,,$(OS8RUN_INFILES)) OS8RUN_OUTFILES := $(subst .in,,$(OS8RUN_OUTFILES)) PRECIOUS_OUTFILES := $(subst @srcdir@/,,$(PRECIOUS_INFILES)) PRECIOUS_OUTFILES := $(subst .in,,$(PRECIOUS_OUTFILES)) Index: auto.def ================================================================== --- auto.def +++ auto.def @@ -445,36 +445,56 @@ catch {exec hostname} host set user $::env(USER) define BUILDUSER "$user@$host" define BUILDTS [clock format [clock seconds] -format "%Y.%m.%d at %T %Z"] -# The os8-run script requires Python and some non-core modules. -set status [catch {exec python -c exit} result] -if {$status != 0} { - user-error "Python 2 does not appear to be installed here. It is required." +# The os8-run script requires Python 2 or 3 and some non-core modules. +set status [catch {exec python3 -c exit} result] +if {$status == 0} { + set pyver 3 + set pycmd "python3" +} else { + set status [catch {exec python2 -c exit} result] + if {$status == 0} { + set pyver 2 + set pycmd "python2" + } else { + set status [catch {exec python -c exit} result] + if {$status == 0} { + set status [catch {exec python --version | grep -q 'Version 2'} result] + set pyver [expr $status == 0 ? 2 : 3] + set pycmd "python" + } + } +} +if {$pyver == ""} { + user-error "Python does not appear to be installed here. It is required." } -msg-result "Python 2 is installed here." -set status [catch {exec python -c "import pexpect" 2> /dev/null} result] +define PYCMD $pycmd +define PYVER $pyver +msg-result "Python $pyver is installed here as '$pycmd'." +set status [catch {exec $pycmd -c "import pexpect" 2> /dev/null} result] if {$status != 0} { set msg "The Python pexpect module is not installed here. Fix with\n\n" - append msg " sudo apt install python-pip\n" - append msg " sudo pip install pexpect\n" + append msg " sudo apt install $pycmd-pip\n" + append msg "\nTHEN:\n" + append msg " sudo pip$pyver install pexpect\n" append msg "\nOR:\n" append msg "\n sudo easy_install pexpect\n" append msg "\nOR:\n" - append msg "\n sudo apt install python-pexpect\n" + append msg "\n sudo apt install $pycmd-pexpect\n" user-error $msg } msg-result "Python module pexpect is installed here." -set status [catch {exec python -c "import pkg_resources" 2> /dev/null} result] +set status [catch {exec $pycmd -c "import pkg_resources" 2> /dev/null} result] if {$status != 0} { set msg "The Python pkg_resources module is not installed here. Fix with\n" - append msg "\n sudo pip install pkg_resources\n" + append msg "\n sudo pip$pyver install pkg_resources\n" append msg "\nOR:\n" append msg "\n sudo easy_install pkg_resources\n" append msg "\nOR:\n" - append msg "\n sudo apt install python-pkg-resources\n" + append msg "\n sudo apt install $pycmd-pkg-resources\n" user-error $msg } msg-result "Python module pkg_resources is installed here." # Check for Perl and that it can run the test corpus builder. Not fatal @@ -543,10 +563,13 @@ make-config-header src/config.h \ -auto {ENABLE_* HAVE_* PACKAGE_* SIZEOF_*} \ -bare {ILS_MODE PCB_*} make-template bin/pidp8i.in make-template bin/os8-cp.in +make-template bin/os8-run.in +make-template bin/teco-pi-demo.in +make-template bin/txt2os8.in make-template boot/common.script.in make-template boot/0.script.in make-template boot/2.script.in make-template boot/3.script.in make-template boot/4.script.in @@ -557,13 +580,15 @@ make-template boot/tss8.script.in make-template etc/pidp8i.service.in make-template etc/sudoers.in make-template etc/usb-mount@.service.in make-template examples/Makefile.in +make-template lib/os8script.py.in make-template lib/pidp8i/__init__.py.in make-template lib/pidp8i/dirs.py.in make-template lib/pidp8i/ips.py.in +make-template lib/simh.py.in make-template media/os8/init.tx.in make-template media/os8/3finit.tx.in make-template src/Makefile.in make-template src/cc8/Makefile.in make-template src/cc8/os8/Makefile.in @@ -570,7 +595,10 @@ make-template src/pidp8i/gpio-common.c.in make-template src/pidp8i/main.c.in make-template src/SIMH/Makefile.in make-template src/SIMH/PDP8/Makefile.in make-template tools/simh-update.in +make-template tools/test-os8-send-file.in make-template Makefile.in -exec chmod +x "$builddir/bin/pidp8i" "$builddir/tools/simh-update" "$builddir/bin/os8-cp" +foreach f [concat [glob "$builddir/bin/*"] [glob "$builddir/tools/*"]] { + file attributes $f -permissions +x +} Index: bin/os8-cp.in ================================================================== --- bin/os8-cp.in +++ bin/os8-cp.in @@ -1,14 +1,14 @@ -#!/usr/bin/env python +#!/usr/bin/env @PYCMD@ # -*- coding: utf-8 -*- ######################################################################## # Generalized facility to manipulate os8 device images from the POSIX # (host) side using OS/8 system programs under SIMH. # # See USAGE message below for details. # -# Copyright © 2018 by Bill Cattey and Warren Young +# Copyright © 2018-2019 by Bill Cattey and Warren Young # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, @@ -110,11 +110,11 @@ #### abort_prog ######################################################## # Print err_string and exit with -1 return status. def abort_prog (err_str): - print "Abort: " + err_str + print("Abort: " + err_str) sys.exit(-1) #### parse_attach ###################################################### # Parser for OS/8 attach spec. @@ -124,11 +124,11 @@ abort_prog ("Need unit number for: " + match.group(1) + ".") image_spec = [match.group(1), match.group(2), imagename] if match.group(3) == 's': if action_plan ["sys"] != None: - print ("Already specified system device. Ignoring sys mount of: " + imagename) + print("Already specified system device. Ignoring sys mount of: " + imagename) else: action_plan["sys"] = image_spec else: action_plan["mount"].append(image_spec) @@ -194,11 +194,11 @@ def parse_action_file(fname): try: manifest = open(fname, "r") except IOError: - print fname + " not found. Skipping." + print(fname + " not found. Skipping.") return None ioline_re = re.compile("(\S+)\s+(\S+)\s+(\S+)") action_plan = {} @@ -206,37 +206,38 @@ action_plan["mount"] = [] action_plan["copy"] = [] for line in manifest: ioline = line.strip() - if DEBUG: print "parse_action_file: ioline: " + ioline + if DEBUG: print("parse_action_file: ioline: " + ioline) if ioline == "": continue if ioline[0] == '#': continue # Allow comments m=re.match(ioline_re, ioline) if m== None: - print "Ignoring line: " + ioline + print("Ignoring line: " + ioline) continue option = m.group(1) source = m.group(2) destination = m.group(3) if option == "att": m = re.match(_dev_actfile_re, source) - if m== None: + if m == None: abort_prog ("Could not parse attach spec: " + source) parse_attach (action_plan, m, path_expand(destination)) else: if len(option) != 1: - print "Format options are only 1 letter in size. Ignoring line: " + ioline + print("Format options are only 1 letter in size. " + \ + "Ignoring line: " + ioline) elif option[0] not in _valid_pip_options: - print "Unrecognize option in line: " + ioline + print("Unrecognize option in line: " + ioline) elif source == None: - print "Null value of source. Ignoring line: " + ioline + print("Null value of source. Ignoring line: " + ioline) elif destination == None: - print "Null value of destination. Ignoring line: " + ioline + print("Null value of destination. Ignoring line: " + ioline) else: append_copy(action_plan, option, source, destination) return action_plan @@ -254,16 +255,16 @@ def is_directory(path): if DEBUG: "is_directory (" + path + ")" m = re.match(_os8_file_re, path) if m != None: - if DEBUG: print "OS/8 Match: DEV: " + m.group(1) + ", File: " + str(m.group(2)) + if DEBUG: print("OS/8 Match: DEV: " + m.group(1) + ", File: " + str(m.group(2))) if m.group(2) == None or m.group(2) == "": return True # Just a device so yes it's a directory. else: return False if has_os8_wildcards(path): - if DEBUG: print "Has wildcards." + if DEBUG: print("Has wildcards.") return False return os.path.isdir(path) #### has_os8_wildcards ################################################# @@ -286,11 +287,11 @@ for line in lines[1:]: # First line is our command. Skip it. line = line.strip() if line == "": continue m = re.match("(\S+)\s*\.(\S+)", line) if m == None: continue - # if DEBUG: print "file_list_from_expect: group 1: " + m.group(1) + ", group 2: " + m.group(2) + # if DEBUG: print("file_list_from_expect: group 1: " + m.group(1) + ", group 2: " + m.group(2)) fname = m.group(1) + "." + m.group(2) file_list.append(fname) return file_list @@ -310,11 +311,12 @@ if copy_type == "from": copy_type = "within" else: copy_type = "into" if "/" in destination: - print "append_copy, into: Illegal OS/8 file spec containing a slash:" + destination + print("append_copy, into: Illegal OS/8 file spec containing " + \ + "a slash:" + destination) sys.exit(-1) destination = destination.upper() if copy_type == "": abort_prog ("append_copy: No OS/8 file spec found with source: " + source + ", destination: " + destination) @@ -513,13 +515,13 @@ # First the simple bit set options if arg == "-d": DEBUG = True elif arg == "-h": if VERBOSE: - print VERBOSE_USAGE + print(VERBOSE_USAGE) else: - print USAGE + print(USAGE) sys.exit(0) elif arg == "-q": QUIET = True elif arg == "-v": VERBOSE = 1 @@ -526,12 +528,12 @@ # look for option args. elif arg in _arg_to_option: new_opt = _arg_to_option[arg] if mode_opt == new_opt: - print "Warning redundant reset of mode option to " + \ - _pip_option_info[new_opt] + print("Warning redundant reset of mode option to " + \ + _pip_option_info[new_opt]) mode_opt = new_opt # Not a simple bit set option. elif arg == "--action-file": @@ -553,29 +555,34 @@ parse_attach (action_plan, m, sys.argv[idx]) # Do file parser if we didn't get an OS/8 attach spec. else: - if DEBUG: print "File parsing of: " + arg + if DEBUG: print("File parsing of: " + arg) # Need to know if arg is Linux. If so, we need to do globbing. # If you want OS/8 globbing, specify a device to prevent globbing # from being run. m = re.match(_os8_file_re, arg) if m == None: # Yup, it's POSIX. Glob it. - if DEBUG: print arg + " is POSIX." + if DEBUG: print(arg + " is POSIX.") more_files = glob.glob(arg) if more_files == []: - if DEBUG: print "No more files in POSIX Glob. Our file is: " + arg + if DEBUG: + print("No more files in POSIX Glob. Our file is: " + arg) more_files.append(arg) # If file not found may be an OS/8 internal xfer. for new_file in more_files: if filespec_seen == 0: source = new_file first_mode = mode_opt - if DEBUG: print "Globber: Setting initial source: " + source + " and mode: " + first_mode + if DEBUG: + print("Globber: Setting initial source: " + source + \ + " and mode: " + first_mode) elif filespec_seen == 1: - if DEBUG: print "Globber: Setting initial destination: " + destination + if DEBUG: + print("Globber: Setting initial destination: " + \ + destination) destination = new_file else: if DEBUG: "Globber: Appending destination to list. New file is: " + new_file file_and_mode_list.append([mode_opt,destination]) destination = new_file @@ -582,14 +589,16 @@ filespec_seen += 1 else: if filespec_seen == 0: source = arg first_mode = mode_opt - if DEBUG: print "Setting initial source: " + source + " and mode: " + first_mode + if DEBUG: + print("Setting initial source: " + source + \ + " and mode: " + first_mode) elif filespec_seen == 1: destination = arg - if DEBUG: print "Setting initial destination: " + destination + if DEBUG: print("Setting initial destination: " + destination) else: file_and_mode_list.append([mode_opt, destination]) destination = arg if DEBUG: "Appending destination to list. New file is: " + new_file filespec_seen += 1 @@ -603,11 +612,11 @@ # Now it gets a little complicated... # If neither source nor destination is OS/8, pretend they both were OS/8 "DSK:" # If source is OS/8, and has OS/8 wild cards, the destination must be a directory. else: # If more than 2 files, the destination must be either an OS/8 device or a Linux directory. - if DEBUG: print "Destination: " + destination + if DEBUG: print("Destination: " + destination) if filespec_seen > 2 and is_directory(destination) == False: abort_prog ("Destination must be a Linux directory or OS/8 device for multiple source files.") m1 = re.match(_os8_file_re, source) m2 = re.match(_os8_file_re, destination) @@ -638,20 +647,20 @@ def main (): action_plan = parse_args() if action_plan == None: abort_prog ("No action plan was parsed.") - if DEBUG: print str(action_plan) + if DEBUG: print(str(action_plan)) # Create the SIMH child instance and tell it where to send log output try: s = simh (dirs.build, True) except (RuntimeError) as e: - print "Could not start simulator: " + e.message + '!' + print("Could not start simulator: " + e.message + '!') exit (1) - # s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) - s.set_logfile (open ("logfile.txt", 'w')) + # s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) + s.set_logfile (open ("logfile.txt", 'wb')) if VERBOSE: s.verbose = True # Perform sys attach att_spec = action_plan["sys"] if att_spec == None: att_spec = _default_att_spec @@ -658,42 +667,48 @@ simh_boot_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit. imagename = att_spec[2] if not os.path.exists (imagename): abort_prog ("Requested boot image file: " + imagename + " not found.") if VERBOSE or DEBUG: - print "Attaching " + simh_boot_dev + " to " + imagename + print("Attaching " + simh_boot_dev + " to " + imagename) s.send_cmd ("att " + simh_boot_dev + " " + imagename) images_to_zero = [] # Attach other mounts for att_spec in action_plan["mount"]: simh_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit. imagename = att_spec[2] if os.path.exists (imagename): - if VERBOSE or DEBUG: print "Modifying existing " + simh_dev + " image " + imagename + if VERBOSE or DEBUG: + print("Modifying existing " + simh_dev + " image " + imagename) else: - if VERBOSE or DEBUG: print "Will create a new image file named: " + imagename + if VERBOSE or DEBUG: + print("Will create a new image file named: " + imagename) # Save this att_spec so we can zero it later. images_to_zero.append (att_spec) if VERBOSE or DEBUG: - print "Attaching " + simh_dev + " to " + imagename + print("Attaching " + simh_dev + " to " + imagename) s.send_cmd ("att " + simh_dev + " " + imagename) - if VERBOSE or DEBUG: print "Booting " + simh_boot_dev + "..." + if VERBOSE or DEBUG: print("Booting " + simh_boot_dev + "...") s.send_cmd ("boot " + simh_boot_dev) for att_spec in images_to_zero: os8dev = _os8_from_simh_dev[att_spec[0]] if os8dev in _os8_partitions: for partition in _os8_partitions[os8dev]: os8name = os8dev + partition + att_spec[1] + ":" - if VERBOSE or DEBUG: print "Initializing directory of " + os8name + " in " + imagename + if VERBOSE or DEBUG: + print("Initializing directory of " + os8name + " in " + \ + imagename) s.os8_send_cmd ('\\.', "ZERO " + os8name) else: os8name = os8dev + att_spec[1] + ":" - if VERBOSE or DEBUG: print "Initializing directory of " + os8name + " in " + imagename + if VERBOSE or DEBUG: + print("Initializing directory of " + os8name + " in " + \ + imagename) s.os8_send_cmd ('\\.', "ZERO " + os8name) # Perform copy operations for do_copy in action_plan["copy"]: mode_opt = do_copy[0] @@ -703,11 +718,13 @@ if mode_opt in _option_to_pip: pip_option = _option_to_pip[mode_opt] else: abort_prog ("Unrecognized mode option: " + mode_opt) - if DEBUG: print "Source: " + source + ", Destination: " + destination + ", Mode: " + mode_opt + "." + if DEBUG: + print("Source: " + source + ", Destination: " + destination + \ + ", Mode: " + mode_opt + ".") # Is this "from" OS/8 to POSIX, "into" OS/8 from POSIX or "within" OS/8? # "into" -- Attach source to simh ptr # If we are operating "from" and source has wild cards, # Use DIRECT to create list of files. # "from" -- Attach destination to ptp. We've already done POSIX globing. @@ -717,48 +734,50 @@ s.os8_pip_to(source, destination, pip_option) elif copy_type == "from": if has_os8_wildcards(source): # Split off device from source: os8dev = source[0:source.index(":")+1] - if DEBUG: print "Wild card dev: " + os8dev + if DEBUG: print("Wild card dev: " + os8dev) # Use OS/8 Direct to enumerate our input files. - if DEBUG: print "Calling OS/8 DIRECT on wild card filespec: " + source + if DEBUG: + print("Calling OS/8 DIRECT on wild card filespec: " + source) s.os8_send_cmd ('\\.', "DIR " + source + "/F=1") # Now harvest direct output. One file per line. Ignore blank lines. # Maybe parse the FREE BLOCKS Output. # Done when we see a dot. s._child.expect("\d+\s+FREE BLOCKS") files = file_list_from_expect(s._child.before) for filename in files: if VERBOSE or DEBUG: - print "Wildcard call os8_pip_from: copy from: {" + os8dev + "}{" + filename + "}" + \ - " to: " + destination + ", mode: " + pip_option + print("Wildcard call os8_pip_from: copy from: " + \ + "{" + os8dev + "}{" + filename + "}" + \ + " to: " + destination + ", mode: " + pip_option) s.os8_pip_from(os8dev + filename, destination, pip_option) else: if VERBOSE or DEBUG: - print "Call os8_pip_from: copy from: " + source + " to " + destination + \ - ", mode: " + pip_option + print("Call os8_pip_from: copy from: " + source + " to " + \ + destination + ", mode: " + pip_option) s.os8_pip_from(source, destination, pip_option) elif copy_type == "within": if VERBOSE or DEBUG: - print "Call COPY of: " + source + " to " + destination + print("Call COPY of: " + source + " to " + destination) s.os8_send_cmd ('\\.', "COPY " + destination + "< " + source) else: abort_prog ("Unrecognized copy type: " + copy_type) # Should never happen. # Detach all mounts and then sys. s.back_to_cmd ('\\.') for att_spec in action_plan["mount"]: simh_dev = att_spec[0] + att_spec[1] # Compose simh dev from name and unit. if VERBOSE or DEBUG: - print "Detaching " + simh_dev + print("Detaching " + simh_dev) s.send_cmd ("det " + simh_dev) if VERBOSE or DEBUG: - print "Detaching " + simh_boot_dev + print("Detaching " + simh_boot_dev) s.send_cmd ("det " + simh_boot_dev) # And shut down the simulator. if VERBOSE or DEBUG: - print "Quitting simh." + print("Quitting simh.") s.send_cmd ('quit') if __name__ == "__main__": main() DELETED bin/os8-run Index: bin/os8-run ================================================================== --- bin/os8-run +++ /dev/null @@ -1,228 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# Script runner for OS/8 under SIMH. -# The library module os8script.py does the heavy lifting. -# -# See USAGE message below for details. -# -# Copyright © 2018 by Bill Cattey and Warren Young -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys - -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Remaining Python core modules -import subprocess -import string -import re -import shutil -import argparse -from itertools import chain - -# Our local modules -from pidp8i import * -from simh import * -from os8script import * - - -#### GLOBALS AND CONSTANTS ############################################# - -DEBUG = False -VERBOSE = False -VERY_VERBOSE = False -QUIET = False -SCRIPT_FILE = "" - -USAGE = "usage: " + os.path.basename (__file__) + \ - " [-h] [-d] [-v] [-vv] [--enable enable_option] ... \n\t[--disable disable_option] ... " + \ - "\n \tscript [script] ... " - -_en_dis_arg_re = re.compile("^(enable|disable)_(\S+)$") - - -def opt_set(en_dis, option, options_enabled, options_disabled): - if option == None or en_dis == None: return - # print en_dis + " " + option - if en_dis == "enable": - if option not in options_enabled: - options_enabled.append(option) - if option in options_disabled: - options_disabled.remove(option) - elif en_dis == "disable": - if option not in options_disabled: - options_disabled.append(option) - if option in options_enabled: - options_enabled.remove(option) - else: return - - -def add_bool (self, *args, **kwargs): - kwargs['action'] = 'store_true' - kwargs['default'] = False - self.add_argument (*args, **kwargs) - - -#### parse_args ######################################################## - -def parse_args (script_files, options_enabled, options_disabled): - global DEBUG - global VERBOSE - global VERY_VERBOSE - global USAGE - - enable_usage = "" - disable_usage = "" - - idx = 1 - numargs = len(sys.argv) - - if numargs < 2: - print USAGE - sys.exit(-1) - - # Add arguments corresponding to --*-os8-* configure script options - max_obn_len = 0 - for obn, vals in os8opts.opts.iteritems(): - max_obn_len = max(max_obn_len, len(obn)) - for obn, vals in os8opts.opts.iteritems(): - if vals[0]: - # Enable option - pad_str = (max_obn_len - len (obn)) * " " - new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" - disable_usage += new_usage - else: - # Disable option - pad_str = (max_obn_len - len (obn)) * " " - new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" - enable_usage += new_usage - - if enable_usage != "": - USAGE += "\n Known enable options: \n" - USAGE += enable_usage - - if disable_usage != "": - USAGE += "\n Known disable options: \n" - USAGE += disable_usage - - while idx < numargs: - arg = sys.argv[idx] - # print "idx: " + str(idx) + ", arg: " + arg - # print "Files: " + str(script_files) - # print "Options: " + str(options_enabled) - if arg == "-d" or arg == "--debug": - DEBUG = True - elif arg == "-h" or arg == "--help": - print USAGE - sys.exit(0) - elif arg == "-v" or arg == "--verbose": - VERBOSE = 1 - elif arg == "-vv" or arg == "--very-verbose": - VERY_VERBOSE = 1 - elif arg == "--enable": - idx +=1 - if idx == numargs: - print "expecting an option but got none." - else: - option = sys.argv[idx] - # Only add the option once - if option not in options_enabled: options_enabled.append(option) - elif arg == "--disable": - idx +=1 - if idx == numargs: - print "expecting an option but got none." - else: - option = sys.argv[idx] - # Only add the option once - if option not in options_disabled: options_disabled.append(option) - else: - script_files.append(arg) - idx += 1 - - - -#### main ############################################################## -# Program entry point. Parses the command line and drives the above. - -def main (): - script_files = [] - options_enabled = [] - options_disabled = [] - - parse_args (script_files, options_enabled, options_disabled) - if len(script_files) == 0: - print "Need a script file to run." - sys.exit(-1) - - if VERBOSE: - print "script_files: " + str(script_files) - if DEBUG: - print "options_enabled" + str(options_enabled) - print "options_disabled" + str(options_disabled) - - # Append SIMH and OS/8 output to a file by default. - # - # We append because we're run twice in each test directory via the - # os8-sys Makefile target called by test-os8-run, once for the "dist" - # media and once for the actual RK05 bootable media. So, the second - # run must append its logs to the first run's log file. - # - # Send the log info to the console instead of the progress messages if - # -v was given. See https://stackoverflow.com/questions/21239338 - s = simh (dirs.build, True) - if VERBOSE: s.verbose = True - s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'a') \ - if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'w', 0)) - - os8 = os8script (s, options_enabled, options_disabled, verbose=VERBOSE, debug=DEBUG) - - for script_file in script_files: - if VERBOSE: - print os.path.basename (__file__) + " -- Language Version: " + os8.lang_version - - print "Running script file: " + script_file - os8.run_script_file (script_file) - - # After all scripts are done, we remove any scratch files, - # detach any mounted devices, and shut down simh gracefully. - - for filename in os8.scratch_list: - if os8.verbose: print "Deleting scratch_copy: " + filename - os.remove(filename) - - s.send_cmd ("detach all") - - s.quit () - if VERBOSE: print "Done!" - - -if __name__ == "__main__": - main() ADDED bin/os8-run.in Index: bin/os8-run.in ================================================================== --- /dev/null +++ bin/os8-run.in @@ -0,0 +1,228 @@ +#!/usr/bin/env @PYCMD@ +# -*- coding: utf-8 -*- +######################################################################## +# Script runner for OS/8 under SIMH. +# The library module os8script.py does the heavy lifting. +# +# See USAGE message below for details. +# +# Copyright © 2018-2019 by Bill Cattey and Warren Young +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys + +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Remaining Python core modules +import subprocess +import string +import re +import shutil +import argparse +from itertools import chain + +# Our local modules +from pidp8i import * +from simh import * +from os8script import * + + +#### GLOBALS AND CONSTANTS ############################################# + +DEBUG = False +VERBOSE = False +VERY_VERBOSE = False +QUIET = False +SCRIPT_FILE = "" + +USAGE = "usage: " + os.path.basename (__file__) + \ + " [-h] [-d] [-v] [-vv] [--enable enable_option] ... \n\t[--disable disable_option] ... " + \ + "\n \tscript [script] ... " + +_en_dis_arg_re = re.compile("^(enable|disable)_(\S+)$") + + +def opt_set(en_dis, option, options_enabled, options_disabled): + if option == None or en_dis == None: return + # print(en_dis + " " + option) + if en_dis == "enable": + if option not in options_enabled: + options_enabled.append(option) + if option in options_disabled: + options_disabled.remove(option) + elif en_dis == "disable": + if option not in options_disabled: + options_disabled.append(option) + if option in options_enabled: + options_enabled.remove(option) + else: return + + +def add_bool (self, *args, **kwargs): + kwargs['action'] = 'store_true' + kwargs['default'] = False + self.add_argument (*args, **kwargs) + + +#### parse_args ######################################################## + +def parse_args (script_files, options_enabled, options_disabled): + global DEBUG + global VERBOSE + global VERY_VERBOSE + global USAGE + + enable_usage = "" + disable_usage = "" + + idx = 1 + numargs = len(sys.argv) + + if numargs < 2: + print(USAGE) + sys.exit(-1) + + # Add arguments corresponding to --*-os8-* configure script options + max_obn_len = 0 + for obn, vals in os8opts.opts.items(): + max_obn_len = max(max_obn_len, len(obn)) + for obn, vals in os8opts.opts.items(): + if vals[0]: + # Enable option + pad_str = (max_obn_len - len (obn)) * " " + new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" + disable_usage += new_usage + else: + # Disable option + pad_str = (max_obn_len - len (obn)) * " " + new_usage = " " + obn + ": " + pad_str + vals[1] + "\n" + enable_usage += new_usage + + if enable_usage != "": + USAGE += "\n Known enable options: \n" + USAGE += enable_usage + + if disable_usage != "": + USAGE += "\n Known disable options: \n" + USAGE += disable_usage + + while idx < numargs: + arg = sys.argv[idx] + # print("idx: " + str(idx) + ", arg: " + arg) + # print("Files: " + str(script_files)) + # print("Options: " + str(options_enabled)) + if arg == "-d" or arg == "--debug": + DEBUG = True + elif arg == "-h" or arg == "--help": + print(USAGE) + sys.exit(0) + elif arg == "-v" or arg == "--verbose": + VERBOSE = 1 + elif arg == "-vv" or arg == "--very-verbose": + VERY_VERBOSE = 1 + elif arg == "--enable": + idx +=1 + if idx == numargs: + print("expecting an option but got none.") + else: + option = sys.argv[idx] + # Only add the option once + if option not in options_enabled: options_enabled.append(option) + elif arg == "--disable": + idx +=1 + if idx == numargs: + print("expecting an option but got none.") + else: + option = sys.argv[idx] + # Only add the option once + if option not in options_disabled: options_disabled.append(option) + else: + script_files.append(arg) + idx += 1 + + + +#### main ############################################################## +# Program entry point. Parses the command line and drives the above. + +def main (): + script_files = [] + options_enabled = [] + options_disabled = [] + + parse_args (script_files, options_enabled, options_disabled) + if len(script_files) == 0: + print("Need a script file to run.") + sys.exit(-1) + + if VERBOSE: + print("script_files: " + str(script_files)) + if DEBUG: + print("options_enabled" + str(options_enabled)) + print("options_disabled" + str(options_disabled)) + + # Append SIMH and OS/8 output to a file by default. + # + # We append because we're run twice in each test directory via the + # os8-sys Makefile target called by test-os8-run, once for the "dist" + # media and once for the actual RK05 bootable media. So, the second + # run must append its logs to the first run's log file. + # + # Send the log info to the console instead of the progress messages if + # -v was given. See https://stackoverflow.com/questions/21239338 + s = simh (dirs.build, True) + if VERBOSE: s.verbose = True + s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'ab') \ + if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'wb', 0)) + + os8 = os8script (s, options_enabled, options_disabled, verbose=VERBOSE, debug=DEBUG) + + for script_file in script_files: + if VERBOSE: + print(os.path.basename (__file__) + " -- Language Version: " + os8.lang_version) + + print("Running script file: " + script_file) + os8.run_script_file (script_file) + + # After all scripts are done, we remove any scratch files, + # detach any mounted devices, and shut down simh gracefully. + + for filename in os8.scratch_list: + if os8.verbose: print("Deleting scratch_copy: " + filename) + os.remove(filename) + + s.send_cmd ("detach all") + + s.quit () + if VERBOSE: print("Done!") + + +if __name__ == "__main__": + main() DELETED bin/teco-pi-demo Index: bin/teco-pi-demo ================================================================== --- bin/teco-pi-demo +++ /dev/null @@ -1,185 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# teco-pi-demo - Starts the simulator with the OS/8, sends one of the -# famous TECO "calculate pi" program to it, and starts it running at -# a very slow rate of speed to act as a blinkenlights demo. -# -# Copyright © 2017-2019 by Warren Young. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Other core modules we need -from datetime import datetime -import time - -# Our local modules -from pidp8i import * -from simh import * - - -#### main ############################################################## - -def main (): - # Check for command line flags - benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b' - - # Create the SIMH child instance and tell it where to send log output - try: - s = simh (dirs.build) - except (RuntimeError) as e: - print "Could not start simulator: " + e.message + '!' - exit (1) - s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) - - # Find and boot the built OS/8 bin disk - rk = os.path.join (dirs.os8mo, 'v3d.rk05') - if not os.path.isfile (rk): - print "Could not find " + rk + "; OS/8 media not yet built?" - exit (1) - print "Booting " + rk + "..." - s.send_cmd ("att rk0 " + rk) - s.send_cmd ("boot rk0") - - # Start TECO8 in the simulator under OS/8 - s.os8_send_cmd ('\\.', "R TECO") - - # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html - # and it was created by Stanley Rabinowitz. - # - # The 248 preceding "UN" in the first line of the macro is the number - # of digits of pi to calculate. That value was reached by experiment - # as the largest value that runs without crashing TECO with a - # - # ?MEM STORAGE CAPACITY EXCEEDED - # - # error. You can see that by increasing the value below, commenting - # out the throttle setting below, and running the demo. On a Pi 3, it - # should take a bit over an hour to complete, if it doesn't error out. - # - # With the simulator throttled, generating 248 digits takes 17 years! - # - # That is based on generating 1 digit every ~16 seconds on a Pi 3 when - # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or - # 17ms per instruction, as below — you multiply the seconds by the - # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds - # per digit. Multiplying that by 248 gives ~17 years. - macro = [ - 'GZ0J\UNQN"E 248UN \' BUH BUV HK', - 'QN< J BUQ QN*10/3UI', - 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ', - 'QA-(QQ*QJ)-2\ 10@I// -1%I >', - 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >', - 'QV^T @^A/', - '/HKEX', - ] - - # First and last lines are handled specially, so slice them off. - first = macro.pop (0) - last = macro.pop () - - # Send the first line of the macro; implicitly awaits 1st TECO prompt - s.os8_send_cmd ('\\*', first) - - # Blindly send core lines of the macro; TECO gives no prompts for 'em. - for line in macro: - s.os8_send_line (line) - - # Send last line of macro sans CR, followed by two Esc characters to - # start it running. - s.os8_send_str (last) # not os8_send_line! - s.os8_send_ctrl ('[') - s.os8_send_ctrl ('[') - - if benchmark: - # Run demo long enough to get a good sense of the simulator's - # execution rate while unthrottled on this host hardware. If - # you don't run it long enough, the IPS value is untrustworthy. - try: - s.spin (10) - except pexpect.TIMEOUT: - # Explicitly shift back from OS/8 context to SIMH command context. - # We cannot rely on class simh to do this automatically because it - # expects to see a . prompt from the prior command, but we're - # still in TECO here, so we must be explicit. - s.os8_send_ctrl ('e') - - # Ask the simulator what IPS rate we ran that benchmark at. - s.send_cmd ('show clocks') - line = s.read_tail ('Execution Rate:') - curr_ips = int (line.strip().replace(',', '').split(' ')[0]) - pf = open ('lib/pidp8i/ips.py', 'a') - pf.write ('current = ' + str (curr_ips) + ' # ' + \ - str (datetime.today ()) + '\n') - pf.close () - s.send_cmd ('quit') - pdp_ratio = float (curr_ips) / ips.pdp8i - rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus - print "\nYour system is " + format (rpi_ratio, '.1f') + \ - " times faster than a Raspberry Pi Model B+" - print "or " + format (pdp_ratio, '.1f') + \ - " times faster than a PDP-8/I.\n" - else: - # Normal mode. Tell SIMH and throttle down to a rate suitable for a - # blinkenlights demo. 1/17 means SIMH runs one instruction then - # waits for 17ms, yielding ~59 IPS. - time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this - s.os8_send_ctrl ('e') # same justification as above - s.send_cmd ('set throttle 1/17') - - # You can't hit Ctrl-E while running this script in the foreground - # since pexpect takes over stdio. Therefore, if you want to be able - # to send commands to the simulator while the demo is running, - # uncomment the line below, which will let you send commands to the - # simulator via telnet. From another terminal or SSH session: - # - # $ telnet localhost 3141 - # - # or from a remote machine: - # - # $ telnet 192.168.1.2 3141 - # - # It's disabled by default because SIMH can't be made to listen only - # on localhost, so doing this may be a security risk. SIMH disables - # obviously-unsafe commands like ! on the remote console, but it is - # possible some mischief may be possible via this path anyway. It - # could be used to exfiltrate a sensitive file via ATTACH, for one - # thing. For another, it's a potential DoS vector. - #s.send_cmd ('set remote telnet=3141') - - # Let it run. Never exits. - s.send_cmd ('cont') - s.spin () - - -if __name__ == "__main__": - main() ADDED bin/teco-pi-demo.in Index: bin/teco-pi-demo.in ================================================================== --- /dev/null +++ bin/teco-pi-demo.in @@ -0,0 +1,185 @@ +#!/usr/bin/env @PYCMD@ +# -*- coding: utf-8 -*- +######################################################################## +# teco-pi-demo - Starts the simulator with the OS/8, sends one of the +# famous TECO "calculate pi" program to it, and starts it running at +# a very slow rate of speed to act as a blinkenlights demo. +# +# Copyright © 2017-2019 by Warren Young. +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Other core modules we need +from datetime import datetime +import time + +# Our local modules +from pidp8i import * +from simh import * + + +#### main ############################################################## + +def main (): + # Check for command line flags + benchmark = len (sys.argv) > 1 and sys.argv[1] == '-b' + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) + + # Find and boot the built OS/8 bin disk + rk = os.path.join (dirs.os8mo, 'v3d.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + # Start TECO8 in the simulator under OS/8 + s.os8_send_cmd ('\\.', "R TECO") + + # The macro comes from http://www.iwriteiam.nl/HaPi_TECO_macro.html + # and it was created by Stanley Rabinowitz. + # + # The 248 preceding "UN" in the first line of the macro is the number + # of digits of pi to calculate. That value was reached by experiment + # as the largest value that runs without crashing TECO with a + # + # ?MEM STORAGE CAPACITY EXCEEDED + # + # error. You can see that by increasing the value below, commenting + # out the throttle setting below, and running the demo. On a Pi 3, it + # should take a bit over an hour to complete, if it doesn't error out. + # + # With the simulator throttled, generating 248 digits takes 17 years! + # + # That is based on generating 1 digit every ~16 seconds on a Pi 3 when + # running unthrottled, roughly 8 MIPS. When throttled to 59 IPS — or + # 17ms per instruction, as below — you multiply the seconds by the + # factor 8 MIPS / 59 IPS = ~136000, giving about 2.2 million seconds + # per digit. Multiplying that by 248 gives ~17 years. + macro = [ + 'GZ0J\UNQN"E 248UN \' BUH BUV HK', + 'QN< J BUQ QN*10/3UI', + 'QI< \+2*10+(QQ*QI)UA B L K QI*2-1UJ QA/QJUQ', + 'QA-(QQ*QJ)-2\ 10@I// -1%I >', + 'QQ/10UT QH+QT+48UW QW-58"E 48UW %V \' QV"N QV^T \' QWUV QQ-(QT*10)UH >', + 'QV^T @^A/', + '/HKEX', + ] + + # First and last lines are handled specially, so slice them off. + first = macro.pop (0) + last = macro.pop () + + # Send the first line of the macro; implicitly awaits 1st TECO prompt + s.os8_send_cmd ('\\*', first) + + # Blindly send core lines of the macro; TECO gives no prompts for 'em. + for line in macro: + s.os8_send_line (line) + + # Send last line of macro sans CR, followed by two Esc characters to + # start it running. + s.os8_send_str (last) # not os8_send_line! + s.os8_send_ctrl ('[') + s.os8_send_ctrl ('[') + + if benchmark: + # Run demo long enough to get a good sense of the simulator's + # execution rate while unthrottled on this host hardware. If + # you don't run it long enough, the IPS value is untrustworthy. + try: + s.spin (10) + except pexpect.TIMEOUT: + # Explicitly shift back from OS/8 context to SIMH command context. + # We cannot rely on class simh to do this automatically because it + # expects to see a . prompt from the prior command, but we're + # still in TECO here, so we must be explicit. + s.os8_send_ctrl ('e') + + # Ask the simulator what IPS rate we ran that benchmark at. + s.send_cmd ('show clocks') + line = s.read_tail ('Execution Rate:') + curr_ips = int (line.strip().replace(',', '').split(' ')[0]) + pf = open ('lib/pidp8i/ips.py', 'a') + pf.write ('current = ' + str (curr_ips) + ' # ' + \ + str (datetime.today ()) + '\n') + pf.close () + s.send_cmd ('quit') + pdp_ratio = float (curr_ips) / ips.pdp8i + rpi_ratio = float (curr_ips) / ips.raspberry_pi_b_plus + print "\nYour system is " + format (rpi_ratio, '.1f') + \ + " times faster than a Raspberry Pi Model B+" + print "or " + format (pdp_ratio, '.1f') + \ + " times faster than a PDP-8/I.\n" + else: + # Normal mode. Tell SIMH and throttle down to a rate suitable for a + # blinkenlights demo. 1/17 means SIMH runs one instruction then + # waits for 17ms, yielding ~59 IPS. + time.sleep (0.02) # FIXME: simulator chokes on 'cont' without this + s.os8_send_ctrl ('e') # same justification as above + s.send_cmd ('set throttle 1/17') + + # You can't hit Ctrl-E while running this script in the foreground + # since pexpect takes over stdio. Therefore, if you want to be able + # to send commands to the simulator while the demo is running, + # uncomment the line below, which will let you send commands to the + # simulator via telnet. From another terminal or SSH session: + # + # $ telnet localhost 3141 + # + # or from a remote machine: + # + # $ telnet 192.168.1.2 3141 + # + # It's disabled by default because SIMH can't be made to listen only + # on localhost, so doing this may be a security risk. SIMH disables + # obviously-unsafe commands like ! on the remote console, but it is + # possible some mischief may be possible via this path anyway. It + # could be used to exfiltrate a sensitive file via ATTACH, for one + # thing. For another, it's a potential DoS vector. + #s.send_cmd ('set remote telnet=3141') + + # Let it run. Never exits. + s.send_cmd ('cont') + s.spin () + + +if __name__ == "__main__": + main() DELETED bin/txt2os8 Index: bin/txt2os8 ================================================================== --- bin/txt2os8 +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code. -# -# It is intended to be be called manually when we have a POSIX -# directory full of ASCII files we want to bulk-copy into SIMH. -# -# The argument is taken both as the name of the image to create -# and the list of files to copy in. -# -# For now, it takes all input and produces all output in the -# current working directory. -# -# IMPORTANT: Currently all input files are mindlessly passed through -# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files. -# It WILL mutilate non-ASCII files. -# -# This program is based on the old cc8-tu56-update program, last -# shipped by this project in release v20171222. -# -# Copyright © 2017 by Warren Young and Bill Cattey -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys -import argparse - -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Our local modules -from pidp8i import * -from simh import * - -# Other global Python modules -import glob -import subprocess - - -#### GLOBALS AND CONSTANTS ############################################# - -progmsg = True - - -#### main ############################################################## - -def main (): - global progmsg - - # Set up the arg parser and use it to parse the command line. - parser = argparse.ArgumentParser() - parser.add_argument("name", - help="Create an OS/8 image from a list of ASCII files.") - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument("--tu56", "-t", action="store_true") - group.add_argument("--rk05a", "-ra", action="store_true") - group.add_argument("--rk05b", "-rb", action="store_true") - - args = parser.parse_args() - - print "Filename: " + args.name - if args.tu56: - sdev = "dt0" - os8dev = "DTA0:" - imagename = args.name + ".tu56" - stat_str = "DECtape" - - if args.rk05a: - sdev = "rk1" - os8dev = "RKA1:" - imagename = args.name + ".rk05" - stat_str = "partition A of" - - if args.rk05b: - sdev = "rk1" - os8dev = "RKB1:" - imagename = args.name + ".rk05" - stat_str = "partition B of" - - listname = args.name + ".list" - - # Create the SIMH child instance and tell it where to send log output - try: - s = simh (dirs.build, True) - except (RuntimeError) as e: - print "Could not start simulator: " + e.message + '!' - exit (1) - s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) - - # Attach a clean version of the image to the simulator - if os.path.exists (imagename): - print "Overwriting old " + stat_str + " image " + imagename - - s.send_cmd ("att " + sdev + " " + imagename) - - # Find and boot the bootable OS/8 disk. Use the "patched" version - # because that is what "make run" uses; we use that command to - # inspect this script's work. - rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05') - if not os.path.isfile (rk): - print "Could not find " + rk + "; OS/8 media not yet built?" - exit (1) - print "Booting " + rk + "..." - s.send_cmd ("att rk0 " + rk) - s.send_cmd ("boot rk0") - - s.os8_send_cmd ('\\.', "ZERO " + os8dev) - - manifest = open (listname, "r") - - for line in manifest: - src = line.strip() - if src == "": continue - if src[0] == '#': continue # Allow commenting out files - - dest = src.upper () - s.os8_send_file (src, os8dev + dest) - - # Exit simulator nicely so that image detaches cleanly - s.back_to_cmd ('\\.') - s.send_cmd ("det " + sdev) - s.send_cmd ('quit') - - -if __name__ == "__main__": main() ADDED bin/txt2os8.in Index: bin/txt2os8.in ================================================================== --- /dev/null +++ bin/txt2os8.in @@ -0,0 +1,151 @@ +#!/usr/bin/env @PYCMD@ +# -*- coding: utf-8 -*- +######################################################################## +# Create a tu56 or rk05 image and fill it with ASCII files, i.e. source code. +# +# It is intended to be be called manually when we have a POSIX +# directory full of ASCII files we want to bulk-copy into SIMH. +# +# The argument is taken both as the name of the image to create +# and the list of files to copy in. +# +# For now, it takes all input and produces all output in the +# current working directory. +# +# IMPORTANT: Currently all input files are mindlessly passed through +# txt2ptp which transforms POSIX ASCII files to OS/8 ASCII files. +# It WILL mutilate non-ASCII files. +# +# This program is based on the old cc8-tu56-update program, last +# shipped by this project in release v20171222. +# +# Copyright © 2017-2019 by Warren Young and Bill Cattey +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE +# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +# Except as contained in this notice, the names of the authors above +# shall not be used in advertising or otherwise to promote the sale, +# use or other dealings in this Software without prior written +# authorization from those authors. +######################################################################## + +# Bring in just the basics so we can bring in our local modules +import os +import sys +import argparse + +sys.path.insert (0, os.path.dirname (__file__) + '/../lib') +sys.path.insert (0, os.getcwd () + '/lib') + +# Our local modules +from pidp8i import * +from simh import * + +# Other global Python modules +import glob +import subprocess + + +#### GLOBALS AND CONSTANTS ############################################# + +progmsg = True + + +#### main ############################################################## + +def main (): + global progmsg + + # Set up the arg parser and use it to parse the command line. + parser = argparse.ArgumentParser() + parser.add_argument("name", + help="Create an OS/8 image from a list of ASCII files.") + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument("--tu56", "-t", action="store_true") + group.add_argument("--rk05a", "-ra", action="store_true") + group.add_argument("--rk05b", "-rb", action="store_true") + + args = parser.parse_args() + + print "Filename: " + args.name + if args.tu56: + sdev = "dt0" + os8dev = "DTA0:" + imagename = args.name + ".tu56" + stat_str = "DECtape" + + if args.rk05a: + sdev = "rk1" + os8dev = "RKA1:" + imagename = args.name + ".rk05" + stat_str = "partition A of" + + if args.rk05b: + sdev = "rk1" + os8dev = "RKB1:" + imagename = args.name + ".rk05" + stat_str = "partition B of" + + listname = args.name + ".list" + + # Create the SIMH child instance and tell it where to send log output + try: + s = simh (dirs.build, True) + except (RuntimeError) as e: + print "Could not start simulator: " + e.message + '!' + exit (1) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) + + # Attach a clean version of the image to the simulator + if os.path.exists (imagename): + print "Overwriting old " + stat_str + " image " + imagename + + s.send_cmd ("att " + sdev + " " + imagename) + + # Find and boot the bootable OS/8 disk. Use the "patched" version + # because that is what "make run" uses; we use that command to + # inspect this script's work. + rk = os.path.join (dirs.os8mo, 'os8v3d-patched.rk05') + if not os.path.isfile (rk): + print "Could not find " + rk + "; OS/8 media not yet built?" + exit (1) + print "Booting " + rk + "..." + s.send_cmd ("att rk0 " + rk) + s.send_cmd ("boot rk0") + + s.os8_send_cmd ('\\.', "ZERO " + os8dev) + + manifest = open (listname, "r") + + for line in manifest: + src = line.strip() + if src == "": continue + if src[0] == '#': continue # Allow commenting out files + + dest = src.upper () + s.os8_send_file (src, os8dev + dest) + + # Exit simulator nicely so that image detaches cleanly + s.back_to_cmd ('\\.') + s.send_cmd ("det " + sdev) + s.send_cmd ('quit') + + +if __name__ == "__main__": main() Index: doc/class-simh.md ================================================================== --- doc/class-simh.md +++ doc/class-simh.md @@ -84,17 +84,17 @@ ## Logging The next step is to tell the `s` object where to send its logging output: - s.set_logfile (os.fdopen (sys.stdout.fileno (), 'w', 0)) + s.set_logfile (os.fdopen (sys.stdout.fileno (), 'wb', 0)) Contrast the corresponding line in `os8-run` which chooses whether to send logging output to the console or to a log file: - s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'a') \ - if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'w', 0)) + s.set_logfile (open (dirs.log + 'os8-run' + '.log', 'ab') \ + if not VERY_VERBOSE else os.fdopen (sys.stdout.fileno (), 'wb', 0)) Note that this more complicated scheme appends to the log file instead of overwriting it because there are cases where `os8-run` gets run more than once with different script inputs, so we want to preserve the prior script outputs, not keep only the latest. DELETED lib/os8script.py Index: lib/os8script.py ================================================================== --- lib/os8script.py +++ /dev/null @@ -1,1887 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -######################################################################## -# simh-os8-script.py Library for scripting OS/8 under SIMH -# Contains validators and callers for os8 and simh commands to make -# it easier to create scripts. -# -# Copyright © 2017 by Jonathan Trites, William Cattey, and Warren Young. -# -# Permission is hereby granted, free of charge, to any person obtaining -# a copy of this software and associated documentation files (the -# "Software"), to deal in the Software without restriction, including -# without limitation the rights to use, copy, modify, merge, publish, -# distribute, sublicense, and/or sell copies of the Software, and to -# permit persons to whom the Software is furnished to do so, subject to -# the following conditions: -# -# The above copyright notice and this permission notice shall be -# included in all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# IN NO EVENT SHALL THE AUTHORS LISTED ABOVE BE LIABLE FOR ANY CLAIM, -# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT -# OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE -# OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# -# Except as contained in this notice, the names of the authors above -# shall not be used in advertising or otherwise to promote the sale, -# use or other dealings in this Software without prior written -# authorization from those authors. -######################################################################## - -# Bring in just the basics so we can bring in our local modules -import os -import sys -import tempfile -sys.path.insert (0, os.path.dirname (__file__) + '/../lib') -sys.path.insert (0, os.getcwd () + '/lib') - -# Python core modules we use -import re -from string import Template -import shutil -import subprocess - -# Our local modules -from pidp8i import * -from simh import * - -# Script Language Version -# Update this version number as the language evolves. -# Version 1.0 is the first public version. -LANG_VERSION = "1.0" - -# Error Class Definitions ############################################## -# Enables us to use exceptions from within this module. - -class Error(Exception): - """Base Class for exceptions in this module.""" - pass - -class InputError(Error): - """Exception raised for errors in the input. - - Attributes: - expr -- input expression in which the error occurred - msg -- explanation of the error - """ - - def __init__(self, msg): - self.msg = msg - - def __str__(self): - return self.msg - - -# Private globals ###################################################### -# Visible within this file, but not to the outside. - -# Identify a begin enabled/not_disabled command. group(1) contains either the enabled or -# disabled flag. Put the rest of the line in group(2) -_begin_en_dis_comm_re = re.compile ("^begin\s+(enabled|default|version)\s+(.+)$") - -# Identify an end enabled/not_disabled command. group(1) contains either the enabled or -# disabled flag. Put the rest of the line in group(2) -_end_en_dis_comm_re = re.compile ("^end\s+(enabled|default|version)\s+(.+)$") - -# Identify an end comm and put the rest of the line in group(1) -_end_comm_re = re.compile ("^end\s+(.+)?$") - -# Identify an end option command and put the rest of the line in group(1) -_end_option_comm_re = re.compile ("^end\s+option\s+(.+)$") - -# A valid version spec -_version_parse_re = re.compile ("^((\d+\.)*)?(\d+)?$") - -# Name of the DECtape image file we create -_new_sys_tape_prefix = "system" - -# Parser regexps used in patcher -_com_os8_parse_str = "^\.([a-zA-Z]+)\s*(.*)$" -_com_os8_parse = re.compile(_com_os8_parse_str) -_com_split_str = "^([a-zA-Z]+)\s*(.*)$" -_com_split_parse = re.compile(_com_split_str) -_odt_parse_str = "^([0-7]+)\s*/\s*(\S+)\s+([0-7;]+)" -_odt_parse = re.compile(_odt_parse_str) - -# Put command keyword in group(1) and the rest is in group(3) -_comm_re_str = "^(\S+)(\s+(.+))?$" -_comm_re = re.compile(_comm_re_str) - -# Identify an end comm and put the rest of the line in group(1) -_end_comm_re = re.compile ("^end\s+(.+)?$") - -# Identify an end option command and put the rest of the line in group(1) -_end_option_comm_re = re.compile ("^end\s+option\s+(.+)$") - -# Identify a begin command and put the rest of the line in group(1) -_begin_option_comm_re = re.compile ("^begin\s+option\s+(.+)$") - -# Parse an argument string into a sys device with -# device name in group(1), unit number in group(2) -# We put all bootable devices into this string so that when -# we add more devices, for example rl for RL01, we change one -# string not many. -_simh_boot_dev_str = "(rk|td|dt|rx)(\d*)" -_simh_boot_re = re.compile("^" + _simh_boot_dev_str + "$") - -# Parse an argument string for mount into SIMH device -# device name in group(1), unit number in group(2) -# And the rest in group (3) -_mount_regex_str = "^" + _simh_boot_dev_str + "\s+(.+)$" -_mount_re = re.compile(_mount_regex_str) - -# Map of SIMH device names to OS/8 device name prefixes. -_os8_from_simh_dev = {"rk" : "RK", "td" : "DTA", "dt" : "DTA", "rx" : "RX"} - -_os8_partitions = {"RK": ["A", "B"]} - -# OS/8 file name matching regex -_os8_file_re = re.compile("(\S+):(\S+)?") - -# Regular expression for syntax checking inside FOTP -# Destination is in group(1), Source is in group(3) -_fotp_re = re.compile ("^((\S+:)?\S+)<((\S+:)?\S+)$") - -# Regular expression for detecting the 2 arg and 3 arg forms -# of the "pal8" script command. - -# OS/8 name regex template: -# Optional device spec, i.e. DTA0: -# File spec with a specific extension or no extension. - -_os8_fspec = Template ("((\S+:)?([A-Z0-9]{1,6}|[A-Z0-9]{1,6}\.$ext))") -_os8_BN_fspec = _os8_fspec.substitute(ext="BN") -_os8_PA_fspec = _os8_fspec.substitute(ext="PA") -_os8_LS_fspec = _os8_fspec.substitute(ext="LS") - -# For the two arg form: -# The full destination spec is in group(1), The full source spec is in group(4). -# The device components, if any, are in group(2) for destination, and -# group(5) for source. -# The file components are in group(3) for destination, and group (6) for source. -# The destination file must either end in ".BN" or have no extension. -# The source must file either end in ".PA" or have no extension. -_two_arg_pal_re = re.compile ("^" + _os8_BN_fspec + "\s*<\s*" + _os8_PA_fspec + "$") - -# For the 3 arg form: -# The full destination spec is in group(1), The full source spec is in group(7). -# The full listing spec is in group(4) -# The device components, if any, are in group(2) for destination, group(5) -# for listing, and group(8) for source. -# The file components are in group(3) for destination, and group(9) for source, -# and group(6) for listing. -# The destination file must either end in ".BN" or have no extension. -# The source must file either end in ".PA" or have no extension. -# The listing must either end in "LS" or have no extension. - -_three_arg_pal_re = re.compile ("^" + _os8_BN_fspec + "\s*,\s*" + _os8_LS_fspec + "\s*<\s*" + _os8_PA_fspec + "$") - -# Regular expression for syntax checking inside ABSLDR -# One or more OS/8 binary files and optional args beginning with a slash. - -_absldr_re = re.compile ("^" + _os8_BN_fspec + "(," + _os8_BN_fspec + ")*(/\S)*$") - -# Regular expressions for syntax checking for cpto and cpfrom. -# May be where destination and default option /A is implied. -# Or