PiDP-8/I SoftwareCheck-in [e05a907c3f]
Not logged in

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:More print() fixes, this time for os8-cp.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both |  os8-run-python3
Files: files | file ages | folders
SHA3-256:  e05a907c3fad129790b0f4dd4d9b99dab411dbcaf9dcf37766ecbd0bca569eb1
User & Date: tangent 2019-04-16 11:49:25
Context
2019-05-11
15:33
Merged in trunk changes check-in: 19490adbd4 user: tangent tags: os8-run-python3
2019-04-16
11:49
More print() fixes, this time for os8-cp. check-in: e05a907c3f user: tangent tags: os8-run-python3
11:38
Fixed a case where pexpect is giving us a byte string, which annoys Python's regex matcher. Explicitly converting it to an ASCII text string. It's basically a null operation, but it makes Python 3 happy. check-in: 560dc8061a user: tangent tags: os8-run-python3
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to bin/os8-cp.in.

108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
...
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231

232
233
234
235
236
237
238
239
240
241
242
243
244
...
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
...
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
308
309
310
311
312
313
314
315

316
317
318
319
320
321
322
...
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
...
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567

568
569
570
571
572
573

574

575

576

577
578
579
580
581
582
583
584
585
586

587

588
589
590
591
592
593
594
595
596
597
...
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
...
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672

673
674

675
676
677
678
679
680
681
682
683
684
685
686
687
688
689

690

691
692
693

694

695
696
697
698
699
700
701
702
703
704
705
706
707

708

709
710
711
712
713
714
715
716
717
718
719
720
721
722
723

724
725
726
727
728
729
730
731
732

733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
_expandable_re = re.compile ("^\$([^/\s]+)/(\S*)$")


#### abort_prog ########################################################
# Print err_string and exit with -1 return status.

def abort_prog (err_str):
  print "Abort: " + err_str
  sys.exit(-1)


#### parse_attach ######################################################
# Parser for OS/8 attach spec.

def parse_attach (action_plan, match, imagename):
  if match.group(2) == None or match.group(2) == "":
    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)
    else:
      action_plan["sys"] = image_spec
  else:
    action_plan["mount"].append(image_spec)


#### path_expand #######################################################
................................................................................
# A dollar sign appearing in a POSIX pathname is expanded with substitutions
# from dirs.py.

def parse_action_file(fname):
  try:
    manifest = open(fname, "r")
  except IOError:
    print fname + " not found. Skipping."
    return None

  ioline_re = re.compile("(\S+)\s+(\S+)\s+(\S+)")

  action_plan = {}
  action_plan["sys"] = None
  action_plan["mount"] = []
  action_plan["copy"] = []

  for line in manifest:
    ioline = line.strip()
    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
      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:
        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

      elif option[0] not in _valid_pip_options:
        print "Unrecognize option in line: " + ioline
      elif source == None:
        print "Null value of source. Ignoring line: " + ioline
      elif destination == None:
        print "Null value of destination. Ignoring line: " + ioline
      else:
        append_copy(action_plan, option, source, destination)

  return action_plan


#### is_directory ######################################################
................................................................................
# device name, lacking a file name part after it.  Otherwise, we use the
# local OS's "is a directory" path check.

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 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."
    return False
  return os.path.isdir(path)


#### has_os8_wildcards #################################################
# Returns True if the passed file name has OS/8 style wildcards.

................................................................................
  file_list = []
  lines = before.split("\r")
  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)
    fname = m.group(1) + "." + m.group(2)
    file_list.append(fname)
  return file_list


#### append_copy #######################################################
# Append a copy control array to the action_plan
................................................................................

  if ":" in destination:
    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

      sys.exit(-1)
    destination = destination.upper()
    
    if copy_type == "":
      abort_prog ("append_copy: No OS/8 file spec found with source: " + source + ", destination: " + destination)
  else: destination = path_expand(destination)

................................................................................
    arg = sys.argv[idx]

    # First the simple bit set options
    if arg == "-d":
      DEBUG = True
    elif arg == "-h":
      if VERBOSE:
        print VERBOSE_USAGE
      else:
        print USAGE
      sys.exit(0)
    elif arg == "-q":
      QUIET = True
    elif arg == "-v":
      VERBOSE = 1

    # 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]
      mode_opt = new_opt

    # Not a simple bit set option.
    
    elif arg == "--action-file":
      if idx + 1 == numargs:             # Need filename, but no args left.
        abort_prog ("No action file name.")
................................................................................
          abort_prog ("No image file name.")
        idx +=1
        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
        
        # 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."
          more_files = glob.glob(arg)
          if more_files == []:

            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

            elif filespec_seen == 1:

              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
            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

          elif filespec_seen == 1:
            destination = arg
            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

    idx +=1        # Bottom of the while loop. Increment.
................................................................................
  elif filespec_seen == 1:
    abort_prog ("Only 1 file spec found. Nothing to do.")
  # 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 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)

    # If source is OS/8 and it has wild cards, but destination is a file, not a device,
................................................................................
#### main ##############################################################

def main ():

  action_plan = parse_args()
  if action_plan == None:
    abort_prog ("No action plan was parsed.")
  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 + '!'
    exit (1)
  # 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
  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
  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
    else:

      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      
    s.send_cmd ("att " + simh_dev + " " + imagename)

  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

        s.os8_send_cmd ('\\.', "ZERO " + os8name)
    else:
      os8name = os8dev + att_spec[1] + ":"

      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]
    source = do_copy[1]
    destination = do_copy[2]
    copy_type = do_copy[3]
    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 + "."

    # 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.
    # "within" -- Use COPY.
    
    if copy_type == "into":
      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
        # Use OS/8 Direct to enumerate our input files.

        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 
          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 
        s.os8_pip_from(source, destination, pip_option)
    elif copy_type == "within":
      if VERBOSE or DEBUG:
        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
    s.send_cmd ("det " + simh_dev)
  if VERBOSE or DEBUG:
    print "Detaching " + simh_boot_dev
  s.send_cmd ("det " + simh_boot_dev)

  # And shut down the simulator.
  if VERBOSE or DEBUG:
    print "Quitting simh."
  s.send_cmd ('quit')

if __name__ == "__main__": main()







|













|







 







|











|





|








|




|
>

|

|

|







 







|




|







 







|







 







|
>







 







|

|










|
|







 







|






|


>
|





>
|
>

>
|
>










>
|
>


|







 







|







 







|





|













|









>
|

>
|



|


|







>
|
>



>
|
>













>
|
>













|

>
|








>
|
|



|
|



|









|


|




|



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
...
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
...
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
...
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
...
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
...
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
...
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
...
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
_expandable_re = re.compile ("^\$([^/\s]+)/(\S*)$")


#### abort_prog ########################################################
# Print err_string and exit with -1 return status.

def abort_prog (err_str):
  print("Abort: " + err_str)
  sys.exit(-1)


#### parse_attach ######################################################
# Parser for OS/8 attach spec.

def parse_attach (action_plan, match, imagename):
  if match.group(2) == None or match.group(2) == "":
    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)
    else:
      action_plan["sys"] = image_spec
  else:
    action_plan["mount"].append(image_spec)


#### path_expand #######################################################
................................................................................
# A dollar sign appearing in a POSIX pathname is expanded with substitutions
# from dirs.py.

def parse_action_file(fname):
  try:
    manifest = open(fname, "r")
  except IOError:
    print(fname + " not found. Skipping.")
    return None

  ioline_re = re.compile("(\S+)\s+(\S+)\s+(\S+)")

  action_plan = {}
  action_plan["sys"] = None
  action_plan["mount"] = []
  action_plan["copy"] = []

  for line in manifest:
    ioline = line.strip()
    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)
      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:
        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)
      elif option[0] not in _valid_pip_options:
        print("Unrecognize option in line: " + ioline)
      elif source == None:
        print("Null value of source. Ignoring line: " + ioline)
      elif destination == None:
        print("Null value of destination. Ignoring line: " + ioline)
      else:
        append_copy(action_plan, option, source, destination)

  return action_plan


#### is_directory ######################################################
................................................................................
# device name, lacking a file name part after it.  Otherwise, we use the
# local OS's "is a directory" path check.

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 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.")
    return False
  return os.path.isdir(path)


#### has_os8_wildcards #################################################
# Returns True if the passed file name has OS/8 style wildcards.

................................................................................
  file_list = []
  lines = before.split("\r")
  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))
    fname = m.group(1) + "." + m.group(2)
    file_list.append(fname)
  return file_list


#### append_copy #######################################################
# Append a copy control array to the action_plan
................................................................................

  if ":" in destination:
    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)
      sys.exit(-1)
    destination = destination.upper()
    
    if copy_type == "":
      abort_prog ("append_copy: No OS/8 file spec found with source: " + source + ", destination: " + destination)
  else: destination = path_expand(destination)

................................................................................
    arg = sys.argv[idx]

    # First the simple bit set options
    if arg == "-d":
      DEBUG = True
    elif arg == "-h":
      if VERBOSE:
        print(VERBOSE_USAGE)
      else:
        print(USAGE)
      sys.exit(0)
    elif arg == "-q":
      QUIET = True
    elif arg == "-v":
      VERBOSE = 1

    # 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])
      mode_opt = new_opt

    # Not a simple bit set option.
    
    elif arg == "--action-file":
      if idx + 1 == numargs:             # Need filename, but no args left.
        abort_prog ("No action file name.")
................................................................................
          abort_prog ("No image file name.")
        idx +=1
        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)
        
        # 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.")
          more_files = glob.glob(arg)
          if more_files == []:
            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)
            elif filespec_seen == 1:
              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
            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)
          elif filespec_seen == 1:
            destination = arg
            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

    idx +=1        # Bottom of the while loop. Increment.
................................................................................
  elif filespec_seen == 1:
    abort_prog ("Only 1 file spec found. Nothing to do.")
  # 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 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)

    # If source is OS/8 and it has wild cards, but destination is a file, not a device,
................................................................................
#### main ##############################################################

def main ():

  action_plan = parse_args()
  if action_plan == None:
    abort_prog ("No action plan was parsed.")
  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 + '!')
    exit (1)
  # 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
  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)
  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)
    else:
      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)
    s.send_cmd ("att " + simh_dev + " " + imagename)

  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)
        s.os8_send_cmd ('\\.', "ZERO " + os8name)
    else:
      os8name = os8dev + att_spec[1] + ":"
      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]
    source = do_copy[1]
    destination = do_copy[2]
    copy_type = do_copy[3]
    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 + ".")
    # 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.
    # "within" -- Use COPY.
    
    if copy_type == "into":
      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)
        # Use OS/8 Direct to enumerate our input files.
        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)
          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)
        s.os8_pip_from(source, destination, pip_option)
    elif copy_type == "within":
      if VERBOSE or DEBUG:
        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)
    s.send_cmd ("det " + simh_dev)
  if VERBOSE or DEBUG:
    print("Detaching " + simh_boot_dev)
  s.send_cmd ("det " + simh_boot_dev)

  # And shut down the simulator.
  if VERBOSE or DEBUG:
    print("Quitting simh.")
  s.send_cmd ('quit')

if __name__ == "__main__": main()