summaryrefslogtreecommitdiff
path: root/dmake/msdos/exec.asm
blob: db745aece6b2b87080c9f8b51ca90570b8b6b969 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
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
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
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
540
541
542
543
544
545
546
547
548
549
550
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
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
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
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
; 
; DESCRIPTION
;      This code is a model independent version of DOS exec that will swap
;      the calling process out to secondary storage prior to running the
;      child.  The prototype for calling the exec function is below.
;
;      exec( int swap, char far *program, char far *cmdtail,
;	     int environment_seg, char far *tmpfilename );
;
;
;      To assemble this file issue the command:
;
;         tasm /mx /t /dmmodel exec.asm
;
;      where 'model' is one of {small, compact, medium, large}, you may
;      also use MASM 5.1 to assemble this file, in this case simply replace
;      'tasm' with 'masm' in the above command line.
;
; AUTHOR
;      Dennis Vadura, dvadura@watdragon.uwaterloo.ca
;      CS DEPT, University of Waterloo, Waterloo, Ont., Canada
;
; COPYRIGHT
;      Copyright (c) 1990 by Dennis Vadura.  All rights reserved.
; 
;      This program is free software; you can redistribute it and/or
;      modify it under the terms of the GNU General Public License
;      (version 1), as published by the Free Software Foundation, and
;      found in the file 'LICENSE' included with this distribution.
; 
;      This program is distributed in the hope that it will be useful,
;      but WITHOUT ANY WARRANTY; without even the implied warrant of
;      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;      GNU General Public License for more details.
; 
;      You should have received a copy of the GNU General Public License
;      along with this program;  if not, write to the Free Software
;      Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;
ifdef have286
 .286    ; define have286 with -D for 80286 processor or better
    mpusha Macro
    	pusha
    Endm

    mpopa Macro
    	popa
    Endm

else	; 8088/8086 compatible
    mpusha Macro
    	push ax
	push cx
	push dx
	push bx
	push sp
	push bp
	push si
	push di
    Endm

    mpopa Macro
    	pop di
	pop si
	pop bp
	add sp,2
	pop bx
	pop dx
	pop cx
	pop ax
    Endm
endif

ifdef msmall
	        .model	small
argbase		equ	4
endif
ifdef mcompact
		.model  compact
argbase		equ	4
endif
ifdef mmedium
		.model	medium
argbase		equ	6
endif
ifdef mlarge
		.model	large
argbase		equ	6
endif
a_swap		equ	<bp+argbase+0>
a_prog		equ	<bp+argbase+2>
a_tail		equ	<bp+argbase+6>
a_env 		equ	<bp+argbase+10>
a_tmp		equ	<bp+argbase+12>

a_handle	equ	<bp+argbase>


; Define all useful equ's
swap_xms	equ	0		; we swapped it out to xms
swap_ems	equ	2		; we swapped it out to ems
swap_file	equ	4		; we swapped it out to a file
seg_no_alloc	equ	0		; this is part of a segment
seg_alloc	equ	1		; this is a full segment header
seg_data	equ	2		; this is data for part of a segment


; Define any global/external variables that we will be accessing from here.
	        .data
		extrn	_errno:word		; Set to dos ret code from exec
		public  _Interrupted		; Set to 1 if interrupted 0
_Interrupted	dw	0			; otherwise

	        .code
		assume	cs:@code, ds:@code, ss:@code, es:@code

		even
execstack	dw	64  dup (?)	; put the temporary exec stack right
exec_sp		label	word		; at the start.

old_ss		dw	?		; save stack seg across exec
old_sp		dw	?		; save stack ptr across exec
progsize	dw	?		; original size of the program
rootsize	dw	?		; size of base root kept during swap
resend		dw	?		; paragraph where resident code ends
envseg		dw	?		; paragraph of environment segment
psp		dw	?		; our own psp
swap		dw	?		; swapping selection flag
eretcode	dw	?		; return code from exec
interrupted	dw	?		; interrupted flag for exec
arenahead	dw	?		; start of memory block list
alstr		dw	?		; allocation strategy save spot
in_exec		dw	0		; flag, 1 ==> in exec

cmdpath		db	65  dup(?)	; file to exec
cmdtail		db	129 dup(?)	; its command tail
fcb		db	37  dup(0)	; dummy fcb
tmpseg		db	7   dup(?)	; block header buffer

tmpname		db	65  dup(0)	; name of temporary file resource

		even
tmphandle	dw	?		; handle for temporary file
real_21h	dd	0		; will be DOS's 21h vector if doing -C

std_fil_handle	dw	?		; file handle for -C file
std_fil_number	db	?		; system file number for -C file
our_stdout	db	?		; sys file number our stdout handle

error_rhdr	db	"exec: Failure reading header block", 0DH, 0AH, '$'
error_rseg	db	"exec: Failure reading segment data", 0DH, 0AH, '$'
error_resize	db	"exec: Failure on resize", 0DH, 0AH, '$'
error_free	db	"exec: Failure to free a block", 0DH, 0AH, '$'
error_string	db	"exec: Program swap failure", 0DH, 0AH, '$'
error_alloc	db	"exec: Memory blocks don't match", 0DH, 0AH, '$'

		even
write_header label word
   whdr_xms_ptr		dw	word ptr whdr_xms
   whdr_ems_ptr		dw	word ptr whdr_ems
   whdr_file_ptr	dw	word ptr whdr_file

write_seg label word
   wseg_xms_ptr		dw	word ptr wseg_xms
   wseg_ems_ptr		dw	word ptr wseg_ems
   wseg_file_ptr	dw	word ptr wseg_file

read_header label word
   rhdr_xms_ptr		dw	word ptr rhdr_xms
   rhdr_ems_ptr		dw	word ptr rhdr_ems
   rhdr_file_ptr	dw	word ptr rhdr_file

read_seg label word
   rseg_xms_ptr		dw	word ptr rseg_xms
   rseg_ems_ptr		dw	word ptr rseg_ems
   rseg_file_ptr	dw	word ptr rseg_file

free_resource label word
   free_xms_ptr		dw	word ptr free_xms_resource
   free_ems_ptr		dw	word ptr free_ems_resource
   free_file_ptr	dw	word ptr free_file_resource

reset_resource label word
   reset_xms_ptr	dw	word ptr reset_xms_resource
   reset_ems_ptr	dw	word ptr reset_ems_resource
   reset_file_ptr	dw	word ptr reset_file_resource

old_ctl_brk label dword
   old_ctl_brk_off	dw	?
   old_ctl_brk_seg 	dw	?

old_crit_err label dword
   old_crit_err_off	dw	?
   old_crit_err_seg 	dw	?

exec_block label word
  ex_envseg	dw	?			; env seg, use parent's if 0
  ex_cmdtail	dd	?			; command tail for exec
  ex_fcb1	dd	far ptr fcb		; fcb's aren't used by dmake
  ex_fcb2	dd	far ptr fcb
  ex_ss		dw	?			; saved ss for exec
  ex_sp		dw	?			; saved sp for exec
  ex_error	dw	0			; error code for dos exec


; Special 21h (DOS call) handler to tee stdout/stderr writes to the -C file.
; Ignore 21h calls that aren't writes to 1 or 2; i.e., pass them to DOS handler.
; If write call was from this process, it's pretty simple to duplicate it
; to the -C file.  If it's from another process, we try to write to its
; inherited handle.  Worst case is where the handle wasn't inherited: someone
; closed it.  In that instance we have to switch to dmake's PSP to do the
; duplicate write.

; Subprocesses do not get their stdout/stderr teed to the -C file if
; their stdout/stderr no longer points to the file/device that dmake's
; stdout points to.  This is tested by looking at the process's job
; file table, which is a table that maps process handles to DOS system file
; table numbers.  (The far pointer to the JFT is at the PSP offset 34h.)
; The JFT is also queried to see if the -C file was inherited.

; O_BINARY, O_TEXT problems are ignored here.  These are fudged by the
; C library before it calls DOS; since we're working below that level
; we don't have to worry about it.

simulate_21h Macro
    pushf			;; direct call to DOS
    call cs:[real_21h]
    Endm

	assume cs:@code, ds:nothing, es:nothing, ss:nothing
our_21h_handler proc far
    pushf
    cmp ah,40h		; is this a write?
    jne call_dos	; --no
    cmp bx,1		; write on handle 1 (stdout?)
    je duplicate_it
    cmp bx,2		; stderr?
    je duplicate_it

call_dos:
    popf
    jmp [real_21h]	; far jump to real handler, which will do the sys call
    			; and return to the original caller

duplicate_it:
    mpusha
    push ds
    push es
    mov bp,sp

    mov di,std_fil_handle	; handle of the -C file

  If @CodeSize eq 0
  	; Small/compact models allow for quick test of us versus subprocess.
	; False negative (it's us with a different CS) will be picked
	; up by code just below.  (Might happen due to call from C library.)
	; False positives would be bad, but can't happen.
    mov ax,[bp+24]	; caller's CS
    cmp ax,@code	; same as us?
    je call_from_dmake
  Endif

    mov ah,51h		; get PSP ("undocumented version" works in DOS 2.0+)
    simulate_21h	; PSP segment returned in BX
    cmp bx,psp		; our PSP?
    je call_from_dmake	; --yes, no PSP changing needed

    mov es,bx		; set ES to current (caller's) PSP
    lds bx,es:[34h]	; set DS:BX pointing to caller's job file table

    mov si,[bp+12]	; file handle caller passed in (known to be 1 or 2)
    mov al,[bx+si]	; system file number corresponding to caller's handle
    cmp al,our_stdout	; same as our stdout?
    jne do_real_write	; no--subprocess must have redirected it

    mov al,[bx+di]	; see if caller has dup of -C file still open
    cmp al,std_fil_number
    je use_dup		; yes--we can write using caller's PSP

    	; Calling process (or some intermediate process) has closed
	; the -C descriptor.  We'll use dmake's (our) -C descriptor, but
	; to do so we'll have to change the PSP.  Disable BREAK handling
	; so that ^break doesn't kill the wrong process.

    mov ax,3300h	; get BREAK flag
    simulate_21h
    mov si,dx		; save BREAK state in SI
    sub dx,dx		; now turn break flag off
    mov ax,3301h
    simulate_21h	; don't want ^Break recoginized while PSP changed
    mov bx,psp		; set dmake's PSP
    mov ah,50h
    simulate_21h

    mov bx,di			; handle of -C file
    ; CX still has caller's count
    mov ds,[bp+2]		; restore caller's DS
    mov dx,[bp+14]		; DS:DX again points to caller's buffer
    mov ah,40h
    simulate_21h		; write the copy

    mov bx,es		; caller's PSP
    mov ah,50h		; set PSP
    simulate_21h	; restore caller's PSP
    mov dx,si		; break state before we changed it
    mov ax,3301h
    simulate_21h	; restore break state

    jmp short do_real_write

use_dup:
    mov ds,[bp+2]		; restore caller's DS
    mov dx,[bp+14]		; DS:DX again points to caller's buffer

call_from_dmake:
    mov bx,di			; handle of -C file
    mov ah,40h			; write
    ; CX still has caller's count
    simulate_21h		; write to the file

do_real_write:
    pop es
    pop ds
    mpopa
    popf
    jmp [real_21h]	; far jump to real handler, which will do the sys call
    			; and return to the original caller
our_21h_handler endp

	assume	cs:@code, ds:@code, ss:@code, es:@code

;-----------------------------------------------------------------------------
; First define the critical-error and control-brk handlers. 
; The critical error handler simply pops the machine state and returns an
; access denied result code.
crit_err_handler proc far
		add	sp, 6		; ip/cs/flags ...
		pop	ax
		pop	bx
		pop	cx
		pop	dx
		pop	si
		pop	di
		pop	bp
		pop	ds
		pop	es
		push	bp		; fix up the return flags
		mov	bp, sp
		xchg	ax, [bp+6]	; get the flag byte.
		or	ax, 1		; set the carry bit
		xchg	ax, [bp+6]	; put it back.
		pop	bp
		mov	ax, 5		; access denied
		iret
crit_err_handler endp


;-----------------------------------------------------------------------------
; Here we set the interrupted flag, and terminate the currently running
; process.
ctl_brk_handler proc far
		clc				; make sure carry is clear
		inc	cs:interrupted		; set the flag

; Make certain it isn't us that is going to get terminated.
; There is a small window where the in_exec flag is set but the child is
; not running yet, I assume that DOS doesn't test for ctl_brk at that time
; as it is bussily creating a new process.
		cmp	cs:in_exec,0
		je	just_return		; note this implies CF == 0
		stc				; set CF to abort child
just_return:	iret
ctl_brk_handler endp


;-----------------------------------------------------------------------------
; Something really nasty happened, so abort the exec call and exit.
; This kills the calling process altogether, and is a very nasty way of
; termination since files may still be open etc.
abort_exec_rhdr label near
		mov	dx, offset error_rhdr
		jmp	print_it
abort_exec_rseg label near
		mov	dx, offset error_rseg
		jmp	print_it
abort_exec_resize label near
		mov	dx, offset error_resize
		jmp	print_it
abort_exec_free label near
		mov	dx, offset error_free
		jmp	print_it
abort_exec_alloc label near
		mov	dx, offset error_alloc
		jmp	print_it
abort_exec proc near
		mov	dx, offset error_string
print_it:	push	dx
		mov	bx, [swap]
		call	[free_resource+bx]
		mov	ax, cs
		mov	ds, ax
		pop	dx
		mov	ah, 9
		int	21H
kill_program:	mov	ax, 04cffH			; nuke it!
		int	21H
abort_exec endp


;-----------------------------------------------------------------------------
; lodsw/stosw loop to copy data.  Called only for word copy operations.
; 	ds:si  - point at source
;	es:di  - point at destination
;	cx     - count of bytes to copy.
copy_data proc near
		shr	cx, 1		; convert to word count
		jnc	copy_words
		movsb
copy_words:	rep	movsw		; copy the words.
		ret
copy_data endp



;=============================================================================
; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO READ XMS RECORDS.
;=============================================================================
rhdr_xms proc near
		ret
rhdr_xms endp

rseg_xms proc near
		ret
rseg_xms endp

reset_xms_resource proc near
		ret
reset_xms_resource endp

free_xms_resource proc near
		ret
free_xms_resource endp
;=============================================================================



;=============================================================================
; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO READ EMS RECORDS.
;=============================================================================
rhdr_ems proc near
		ret
rhdr_ems endp

rseg_ems proc near
		ret
rseg_ems endp

reset_ems_resource proc near
		ret
reset_ems_resource endp

free_ems_resource proc near
		ret
free_ems_resource endp
;=============================================================================



;=============================================================================
; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO READ FILE RECORDS.
;=============================================================================
; This routine reads a segment header from a file.
; The header is a seven byte record formatted as follows:
;	segment address		- of data
;	offset address		- of data
; 	length in paragraphs	- of data
;	mode			- 1 => segment header (allocate seg on read)
;				  0 => subsegment, don't allocate on read.
; The information is placed into the tmpseg data area in the code segment.
; The routine aborts if an error is detected.
rhdr_file proc near
		mov	dx, offset tmpseg	; read the header record out
		mov	cx, 7
		mov	bx, [tmphandle]
		mov	ah, 03fH
		int	21H
		jnc	rhdr_done		; make sure it worked
		jmp	abort_exec_rhdr

rhdr_done:	cmp	ax, 7
		je	exit_rhdr_file
		or	ax, ax
		je	signal_eof
		jmp	abort_exec_rhdr

signal_eof:	stc
exit_rhdr_file:	ret
rhdr_file endp


;-----------------------------------------------------------------------------
; Read a segment from the temporary file whose handle is in cs:tmphandle.
; The routine aborts if an error is detected.
rseg_file proc near
		push	ds
		mov	ds, word ptr cs:tmpseg; Now read the whole segment
		mov	dx, word ptr cs:tmpseg+2
		mov	cx, word ptr cs:tmpseg+4
		mov	bx, cs:tmphandle
		mov	ah, 03fH
		int	21H
		pop	ds
		jnc	rseg_done
		jmp	abort_exec_rseg

rseg_done:	cmp	ax, [word ptr tmpseg+4]
		je	exit_rseg_file
		jmp	abort_exec_rseg		; If we didn't get read full
exit_rseg_file:	ret				; segment then abort
rseg_file endp


;-----------------------------------------------------------------------------
; Seek to the beginning of the file.
reset_file_resource proc near
		mov	bx, [tmphandle]
		xor	cx, cx
		mov	dx, cx
		mov	ax, 04200H		; seek to begining of file
		int	21H
		ret
reset_file_resource endp


;-----------------------------------------------------------------------------
; unlink the temporary file allocated for swapping.
; We close the file first, and then delete it.   We ignore errors here since
; we can't do anything about them anyway.
free_file_resource proc near
		mov	bx, [tmphandle]		; get the file handle
		mov	ah, 03eH		; close the file
		int	21H
		mov	dx, offset tmpname	; Now delete the temp file
		mov	ah, 041H
		int	21H
		ret
free_file_resource endp
;=============================================================================



;=============================================================================
; CODE TO SWAP THE IMAGE IN FROM SECONDARY STORAGE
;=============================================================================
swap_in proc near
		mov	bx, [alstr]		; get previous alloc strategy
		mov	ax, 5801H		; and set it back
		int	21H
		mov	bx, [swap]		; get type of resource
		call	[reset_resource+bx]	; reset the resource
		mov	es, [psp]		; resize the program back
		mov	bx, [progsize]		; to original size
		mov	ah, 04AH
		int	21H
		jnc	read_seg_loop
		jmp	abort_exec

read_seg_loop:	mov	bx, [swap]		; get type of resource
		call	[read_header+bx]	; get seg header
		jc	exit_swap_in		; all done
		mov	al, [tmpseg+6]
		cmp	al, seg_no_alloc	; see if dummy segment header
		je	read_seg_loop
		cmp	al, seg_alloc		; do we need to do an alloc?
		jne	read_data		; nope

; Allocate back the memory for a segment that is not the [psp], note that this
; must come back to the same segment we had previously since other segments
; may have pointers stored in their variables that point to this segment using
; segment:offset long pointers.
		mov	bx, [word ptr tmpseg+4]	; get count of paragraphs
		mov	ah, 048H		; dos_alloc
		int	21H
		jc	alloc_error		; oops!
		cmp	ax, [word ptr tmpseg]	; did we get the same segment?
		je	read_seg_loop		; yup!
alloc_error:	jmp	abort_exec_alloc

read_data:	mov	bx, [swap]
		call	[read_seg+bx]		; this must succeed, if fail
		jmp	read_seg_loop		; we never come back here

exit_swap_in:	mov	bx, [swap]		; all done, so free resource
		call	[free_resource+bx]
		ret
swap_in endp


;=============================================================================
; CODE TO SWAP THE IMAGE OUT TO SECONDARY STORAGE
;=============================================================================
; This routine is called to swap the non-resident portion of the program
; out to the resource specified by the value of [cs:swap].  If the swap out
; fails, then appropriate routines are called to free the resources allocated
; up to that point.
;
; The steps used to swap the program out are as follows:
;	- calculate new size of program to remain resident and size to swap
;	  out.
;	- write out non-resident portion of current segment
;	- walk DOS allocation chain and write out all other segments owned by
;	  the current program that are contiguous with the _psp segment
;	- copy the environment down to low memory
;	- resize the current _psp segment to savesize
;	- free all segments belonging to program except current _psp segment
swap_out proc near
		mov	ax, 05800H	; get memory alocation strategy
		int	021H
		mov	[alstr], ax	; and save it for future restoration.
		mov	di, [psp]	; compute length of program to current
		mov	bx, cs		; value of cs, and find program size
		sub	bx, di		; by looking at length stored in
		mov	ax, di		; arena header found in front of psp
		dec	ax
		mov	es, ax
		mov	si, es:3	; si is size of program in paragraphs
		mov	[progsize], si	; progsize now contains the size.

; Now compute length of program segment to save.
; Length is:   cs - psp + (offset overlay_code_here+15 >> 4)
		mov	ax, offset overlay_code_here+15
		shr	ax, 1
		shr	ax, 1
		shr	ax, 1
		shr	ax, 1
		add	bx, ax			; bx is size of program to keep
		sub	si, bx			; si is # of paragraphs to save.
		add	di, bx			; di is paragraph to start at
		mov	rootsize, bx
		mov	resend, di		; cs:resend is saved start para
		mov	al, seg_no_alloc	; set no allocation for segment
		call	write_segment
		jc	abort_swap_out

; We have now saved the portion of the program segment that will not remain
; resident during the exec.  We should now walk the DOS allocation chain and
; write out all other segments owned by the current process.
save_segments:	mov	ax, [psp]
		dec	ax
		mov	es, ax
		mov	bx, offset write_segment_data
		call	walk_arena_chain
		jc	abort_swap_out

; Now we must walk the chain of allocated memory blocks again and free
; all those that are owned by the current process, except the one that is
; the current process' psp.
free_segments:	mov	ax, [psp]
		dec	ax
		mov	es,ax
		mov	bx, offset free_dos_segment
		call	walk_arena_chain
		jnc	resize_program
		jmp	abort_exec_free		; can't fix it up now.

; We now resize the program to the size specified by cs:rootsize.  This will
; free most of the memory taken up by the current program segment.
resize_program: mov	es, [psp]		; es is segment to resize.
		mov	bx, [rootsize]		; bx is size of segment.
		mov	ah, 04aH		; resize memory block
		int	21H
		jnc	swap_out_ok
		jmp	abort_exec_resize	; disaster
swap_out_ok:	ret

; The swap out failed for some reason, so free any allocated resources
; and set the carry bit.
abort_swap_out:	mov	bx, [swap]
		call	[free_resource+bx]
		xor	ax, ax
		mov	[swap], ax		; clear the swap flag
		stc
		ret
swap_out endp


;=============================================================================
; CODE TO SET-UP FOR AND EXEC THE CHILD PROCESS
;=============================================================================
; Actually execute the program.  If cs:swap is set, this code will invoke the
; swap-out/swap-in code as required.
do_exec proc near
		cmp	[swap], 0		; does the user want to swap?
		je	no_swap_out		; nope
		call	init_swap		; figger out where to swap to
		jc	no_swap_out		; if carry set then don't swap
		call	swap_out

no_swap_out:	cmp	[interrupted], 0	; were we interrupted?
		jne	leave_exec		; yep, so clean up, don't exec

; free passed in environment block if it is non zero.
; This way the parent program does not need to free it.
		mov	ax, [envseg]
		or	ax, ax
		je	setup_block
		push	ax
		mov	es, ax
		mov	ah, 49H
		int	21H
		pop	ax

; set up the parameter block for the DOS exec call.
;    offset  contents
;        00  segment address of environment to be passed,
; 	     0 => use parents env.
;        02  pointer to command tail for new process.
;        06  pointer to fcb1
;        0a  pointer to fcb2
setup_block:	mov	ax, [envseg]
		mov	[ex_envseg], ax
		mov	cx, cs
		mov	[word ptr ex_cmdtail], offset cmdtail
		mov	[word ptr ex_cmdtail+2], cx

; set up registers for exec call
;	ds:dx	- pointer to pathname of program to execute
;	es:bx	- pointer to above parameter block
		mov	dx, offset cmdpath
		mov	es, cx
		mov	bx, offset exec_block

; Under DOS 2.x exec is notorious for clobbering registers and guarantees
; to preserve only cs:ip.
		push	ds
		mov	[ex_sp], sp
		mov	[ex_ss], ss
		mov	[ex_error], 0		; clear exec error code
		inc	[in_exec]		; set internal flag
		mov	ax, 04b00H
		int	21H

; returned from exec, so restore possibly clobbered registers.
		mov	ss, cs:ex_ss
		mov	sp, cs:ex_sp
		pop	ds

; check to make certain the exec call worked.
		jnc	it_worked

; exec call failed.  Save return code from msdos.
		mov	[ex_error], ax
		jmp	leave_exec

it_worked:	mov	ah, 04dH	; get the return code
		int	21H
		cmp	ah,1		; check if terminated by ^C
		jnz	nosigint
		inc	interrupted	; yes so set flag
nosigint:	xor	ah, ah		; 8-bit return code, so clear ah
		mov	[eretcode], ax

leave_exec:	cmp	[swap], 0	; check swap, if non-zero swap back in
		je	no_swap_in
		call	swap_in

; Clear the in_exec after the swap back in.  This way we are guaranteed to
; get parent in and the resources freed should a ^C be hit when we are reading
; the image in.
no_swap_in:	mov	[in_exec], 0
		ret
do_exec endp				



;==============================================================================
; Everything past this point is overwriten with the environment and new
; program after the currently executing program is swapped out.
;==============================================================================
overlay_code_here label word

;-----------------------------------------------------------------------------
; Figure out where we can swap to and initialize the resource we are going to
; use.  We try XMS, EMS, and a tempfile (if specified), in that order.  We set
; [cs:swap] to the correct value based on which of the resources exists.
; If none can be used, then [cs:swap] is set to 0, and no swap takes place.
; The exec code will still attempt to execute the child in this instance, but
; may fail due to lack of resources.   Each swap_out_* routine must provide
; its own clean-up handler should it not be able to write all program
; segments to the swap resource.
init_swap proc near
		mov	[swap], 0
;call	init_xms
;jnc	init_done
;call	init_ems
;jnc	init_done
		call	init_file
init_done:	ret
init_swap endp


;-----------------------------------------------------------------------------
; This routine is used to walk the DOS allocated memory block chain
; starting at address supplied in the es register.  For each block it
; calls the routine specified by the bx register with the segment length
; in si, and its address in di.  It does not apply the routine to the
; segment if the segment is the same as the current program's [cs:psp] value.
memheader struc
   magic	db	?	; either 'Z' for end or 'M' for allocated
   owner	dw	?	; psp of owner block
   len		dw	?	; length in paragraphs of segment
memheader ends

walk_arena_chain proc near
		mov	si, word ptr es:3		; get length
		mov	di, es
		inc	di
		mov	ax, word ptr es:1

; Stop the search if the block is NOT owned by us.  Ignore our own psp block
; and our environment segment block.
		cmp	ax, cs:psp			; is it owned by us?
		jne	walk_done			; NOPE!  -- all done
		cmp	di, cs:envseg			; skip our environment
		je	next_block
		cmp	di, cs:psp			; skip our psp
		je	next_block

; Now save state and call the routine pointed at by [bx].
		push	di
		push	si
		push	bx
		call	bx
		pop	bx
		pop	si
		pop	di
		jc	exit_walk			; if error then stop
		mov	al, byte ptr es:0		; check if at end 
		cmp	al, 'Z'
		je	walk_done

next_block:	add	di, si				; go on to next segment
		mov	es, di
		jmp	walk_arena_chain
walk_done:	clc
exit_walk:	ret
walk_arena_chain endp


;-----------------------------------------------------------------------------
; This routine takes a dos segment found in the di register and free's it.
free_dos_segment proc near
		mov	es, di		; free dos memory block
		mov	ah, 49H
		int	21H
		ret
free_dos_segment endp


;-----------------------------------------------------------------------------
; Called to invoke write_segment with proper values in the al register.  Only
; ever called from walk_arena_chain, and so al should be set to seg_alloc.
write_segment_data label near
		mov	al, seg_alloc	; and fall through into write_segment
;-----------------------------------------------------------------------------
; This routine writes a segment as a block of data segments if the number of
; paragraphs to write exceeds 0x0fff (rarely the case).
; It stuffs the info into tmpseg, and then calls wheader and wseg to get the
; data out.
;
;	di:dx	segment:offset of segment;  offset is ALWAYS zero.
;	si	number of paragraphs to write.
;	al	mode of header to write
write_segment proc near
		push	di
		push	si
		xor	dx,dx
		mov	bx, [swap]
		call	[write_header+bx]
		pop	si
		pop	di
		jc	exit_wseg

do_io_loop:	cmp	si, 0		; are we done yet?
		je	exit_wseg	; yup so leave.
		mov	cx, si		; # of paragraphs to move
		cmp	cx, 0fffH	; see if we have lots to move?
		jle	do_io
		mov	cx, 0fffH	; reset to max I/O size

do_io:		push	cx		; save # of paragraphs we are writing
		shl	cx, 1		; shift cx by four to the left
		shl	cx, 1
		shl	cx, 1
		shl	cx, 1
		push    di		; save the start, and count left
		push    si
		mov	si, cx
		xor	dx,dx
		mov	al, seg_data
		mov	bx, [swap]
		push	bx
		call	[write_header+bx]
		pop	bx
		call	[write_seg+bx]
		pop	si
		pop	di
		pop	dx		; original paragraph count in dx
		jc	exit_wseg	; it failed so exit.
		add	di, dx		; adjust the pointers, and continue.
		sub	si, dx
		jmp     do_io_loop
exit_wseg:	ret
write_segment endp


;=============================================================================
; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO WRITE XMS RECORDS.
;=============================================================================
init_xms proc near
		ret
init_xms endp

whdr_xms proc near
		ret
whdr_xms endp

wseg_xms proc near
		ret
wseg_xms endp
;=============================================================================


;=============================================================================
; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO WRITE EMS RECORDS.
;=============================================================================
init_ems proc near
		ret
init_ems endp

whdr_ems proc near
		ret
whdr_ems endp

wseg_ems proc near
		ret
wseg_ems endp
;=============================================================================


;=============================================================================
; THE FOLLOWING SECTION DEALS WITH ALL ROUTINES REQUIRED TO WRITE FILES.
;=============================================================================
;-----------------------------------------------------------------------------
; Attempt to create a temporary file.  If the tempfile name is NIL then return
; with the cary flag set.
init_file proc near
		mov	al, [tmpname]
		or	al, al
		je	err_init_file
		mov	dx, offset tmpname
		xor 	cx, cx
		mov	ah, 03cH
		int	21H
		jc	err_init_file		; if carry set then failure
		mov	[tmphandle], ax		; init swapping
		mov	[swap], swap_file
		jmp	exit_init_file
err_init_file:	stc
exit_init_file: ret
init_file endp


;-----------------------------------------------------------------------------
; This routine writes a segment header to a file.
; The header is a seven byte record formatted as follows:
;	segment address		- of data
;	offset address		- of data
; 	length in paragraphs	- of data
;	mode			- 1 => segment header (allocate seg on read)
;				  0 => subsegment, don't allocate on read.
; Routine takes three arguments:
;	di:dx	segment:offset of segment
;	si	number of paragraphs to write.
;	al	mode of header to write
whdr_file proc near
		mov	[word ptr tmpseg], di	; save the segment/offset
		mov	[word ptr tmpseg+2], dx
		mov	[word ptr tmpseg+4], si	; save the segment length
		mov	[tmpseg+6], al
		mov	dx, offset tmpseg	; write the header record out
		mov	cx, 7
		mov	bx, [tmphandle]
		mov	ah, 040H
		int	21H
		jc	exit_whdr_file		; make sure it worked
		cmp	ax, 7
		je	exit_whdr_file		; oh oh, disk is full!
err_whdr_file:	stc
exit_whdr_file:	ret
whdr_file endp


;-----------------------------------------------------------------------------
; Write a segment to the temporary file whose handle is in cs:tmphandle
; Parameters for the write are assumed to be stored in the tmpseg data area.
; function returns carry set if failed, carry clear otherwise.
wseg_file proc near
		push	ds
		mov	ds, word ptr cs:tmpseg ; Now write the whole segment
		mov	dx, word ptr cs:tmpseg+2
		mov	cx, word ptr cs:tmpseg+4
		mov	bx, cs:tmphandle
		mov	ah, 040H
		int	21H
		pop	ds
		jc	exit_wseg_file		; make sure it worked
		cmp	ax, [word ptr tmpseg+4]
		je	exit_wseg_file
err_wseg_file:	stc				; it failed (usually disk full)
exit_wseg_file:	ret
wseg_file endp
;=============================================================================


;=============================================================================
; _exec: THIS IS THE MAIN ENTRY ROUTINE TO THIS MODULE
;=============================================================================
; This is the main entry routine into the swap code and corresponds to the
; following C function call:
;
; exec( int swap, char far *program, char far *cmdtail, int environment_seg,
;	char far *tmpfilename );
;
; Exec performs the following:
;	1. set up the local code segment copies of arguments to the exec call.
;	2. switch to a local stack frame so that we don't clobber the user
;	   stack.
;	3. save old interrupt vectors for ctrl-brk.
;	4. install our own handler for the ctrl-brk interrupt, our handler
;	   terminates the current running process, and returns with non-zero
;	   status code.
;	5. get our psp
;	6. setup arguments for exec call
;	7. exec the program, save result code on return.
;       8. restore previous ctrl-brk and crit-error handler.
;       9. restore previous process stack, and segment registers.
;      10. return from exec with child result code in AX
;	   and global _Interrupted flag set to true if child execution was
;	   interrupted.

; NOTE:  When first called the segments here assume the standard segment
;        settings.
		assume cs:@code, ds:DGROUP,es:DGROUP,ss:DGROUP

		public	_exec
_exec proc
	    	push	bp		; set up the stack frame
		mov	bp, sp
		push	si		; save registers we shouldn't step on.
		push	di
		push	ds

; set up for copying of parameters passed in with long pointers.
		push	cs		; going to use lodsb/stosb, set up es
		pop	es		; as destination.
		assume  es:@code	; let the assembler know :-)
		cld			; make sure direction is right

; Copy all parameters into the bottom of the code segment.  After doing so we
; will immediately switch stacks, so that the user stack is preserved intact.
		mov	ax, ss:[a_swap]		; save swap
		mov	es:swap, ax
		mov	ax, ss:[a_env]		; save env seg to use
		mov	es:envseg, ax

		mov 	di, offset cs:cmdpath	; copy the command
		lds 	si, ss:[a_prog]		; 65 bytes worth
		mov	cx, 65
		call	copy_data

		mov	di, offset cs:cmdtail	; copy the command tail
		lds	si, ss:[a_tail]		; 129 bytes worth
		mov	cx, 129
		call	copy_data

		mov	di, offset cs:tmpname	; copy the temp file name
		lds	si, ss:[a_tmp]		; 65 bytes worth.
		mov	cx, 65
		call	copy_data

; Now we save the current ss:sp stack pointer and swap stack to our temporary
; stack located in the current code segment.  At the same time we reset the
; segment pointers to point into the code segment only.
swap_stacks:	mov	ax, ss
		mov	es:old_ss, ax
		mov	es:old_sp, sp
		mov	ax, cs
		mov	ds, ax
		mov	ss, ax			; set ss first, ints are then
		mov	sp, offset cs:exec_sp	; disabled for this instr too
		assume  ds:@code, ss:@code	; let the assembler know :-)

; Now we save the old control break and critical error handler addresses.
; We replace them by our own routines found in the resident portion of the
; swapping exec code.
set_handlers:	mov	[interrupted], 0	; clear interrupted flag
		mov	[eretcode], 0		; clear the return code
		mov	ax, 03523H		; get int 23 handler address
		int	21H
		mov	cs:old_ctl_brk_off, bx
		mov	cs:old_ctl_brk_seg, es
		mov	dx, offset ctl_brk_handler
		mov	ax, 02523H		; set int 23 handler address
		int	21H

		mov	ax, 03524H		; get int 24 handler address
		int	21H
		mov	cs:old_crit_err_off, bx
		mov	cs:old_crit_err_seg, es
		mov	dx, offset crit_err_handler
		mov	ax, 02524H		; set int 24 handler address
		int	21H

; Go and execute the child, we've set up all of its parameters.  The do_exec
; routine will attempt to perform a swap of the code if requested to do so by
; a non-zero value in the variable cs:swap.
		mov	ah, 051H		; get the psp
		int	21H
		mov	cs:psp, bx
		call	do_exec

; We're back from the exec, so fix things up the way they were.
; Restore the old control-break and critical-error handlers.
		lds	dx, cs:old_ctl_brk
		mov	ax, 02523H
		int	21H
		lds	dx, cs:old_crit_err
		mov	ax, 02524H
		int	21H

; Restore previous program stack segment registers, and data segment.
		mov	ax, cs:old_ss
		mov	ss, ax			; mov into ss first, that way
		mov	sp, cs:old_sp		; no interrupts in this instr.
		pop	ds

; Tell the assembler we have swaped segments again.
		assume	ds:DGROUP,es:DGROUP,ss:DGROUP

; Set the global Interrupted flag so that parent can tell it was interrupted.
		mov	ax, seg DGROUP:_Interrupted
		mov	es, ax
		mov	ax, cs:interrupted
		mov	es:_Interrupted, ax

; Set the global errno value to reflect the success/failure of the DOS
; exec call.
		mov	ax, seg DGROUP:_errno
		mov	es, ax
		mov	ax, cs:ex_error
		mov	es:_errno, ax

; Fetch the child's return code, pop rest of stuff off of the stack
; and return to the caller.
		mov	ax, cs:eretcode
		pop	di
		pop	si
		pop	bp
		ret
_exec endp

; void do_hook_std_writes(int handle);
;	This saves the 21h interrupt vector and changes it to point
;	into this code.  Argument is the file handle of the -C file.

    public _do_hook_std_writes
_do_hook_std_writes proc
    		push	bp
		mov	bp,sp
		push	di

		mov	di, ss:[a_handle]	; handle of -C file
		mov	std_fil_handle, di

		mov	ah, 51h			; request our PSP
		int	21h
		mov	[psp], bx		; save it

		mov	es, bx
		les	bx, es:[34h]		; pointer to job file table
		mov	al, es:[bx+1]		; system file # of our stdout
		mov	[our_stdout], al
		mov	al, es:[bx+di]		; system file number of -C file
		mov	std_fil_number, al

		mov	ax,3521h		; request vector 21h
		int	21h			; it's returned in ES:BX
		mov	word ptr [real_21h], bx
		mov	word ptr [real_21h+2], es

		push	ds
		mov	ax,cs
		mov	ds,ax
		lea	dx,our_21h_handler	; DS:DX is the new vector
		mov	ax,2521h		; set vector 21h
		int	21h

		pop	ds
		pop	di
		pop	bp
		ret
_do_hook_std_writes endp

; void do_unhook_std_writes(void);
;	This restores the 21h interrupt vector.
;	The saved vector is zero if it wasn't changed (no -C option).

    public _do_unhook_std_writes
_do_unhook_std_writes proc
		push	ds

    		lds	dx, [real_21h]	; put saved vector into DS:DX
		mov	ax, ds
		or	ax, dx
		jz	unhook_return	; zero means we didn't hook 21h

		mov	ax,2521h	; set vector 21h
		simulate_21h

unhook_return:	pop ds
		ret
_do_unhook_std_writes endp
end