home *** CD-ROM | disk | FTP | other *** search
/ Columbia Kermit / kermit.zip / dtss / kermit.pl1 < prev    next >
Text File  |  2020-01-01  |  120KB  |  3,505 lines

  1. %title 'DCTS Kermit -- "Elementary" File Transfer Protocol';
  2. %index;
  3. %tables off;
  4. %external;
  5.  
  6.  
  7. /*
  8.  
  9. (C) Copyright 1986  Trustees of Dartmouth College.
  10.  
  11.  
  12. Philip D.L. Koch
  13.  
  14.  
  15. Use of this program without fee is permitted, provided that such use is not
  16. made for commercial advantage, and that Dartmouth College is credited.
  17. The Kermit protocol was designed at Columbia University and is copyrighted
  18. by them.
  19.  
  20. Release Date: May 29, 1986.
  21.  
  22. Please indicate this release date in all correspondence concerning
  23. this version of DCTS Kermit.
  24.  
  25. */
  26.  
  27.  
  28. Kermit: procedure() options(main);
  29. %subtitle 'A Note About VPL1';
  30.  
  31. /* This program is designed to run in virtual mode; ie, using VPL1.  However,
  32. since there are so few differences between virtual and non-virtual PL1, it
  33. won't be too hard to switch back if, for example, you don't have a VPL1 system
  34. installed.  The changes required to switch to non-virtual PL/I are:
  35.  
  36. 1. Remove all "spawn" statements.  This means removing the timeout capability
  37.    and the ability to do outbound connections, since both are done with tasks.
  38.  
  39. 2. Replace any "global" options in "on" statements with a "call" of the
  40.    on unit, possibly moving the "on" statement itself to the main procedure.
  41.  
  42. 3. Write your own "wait" routine, to take the place of the one built into
  43.    VPL1.  If you're lazy, you can simply remove the waits without major
  44.    problem.
  45.  
  46. 4. Use of the "block" and "unblock" functions must be removed.
  47.  
  48. The non-virtual PL1 compiler finds all of the places where the above must
  49. be done, since they involve illegal syntax or undeclared names.
  50.  
  51. Even better, though, would be to install VPL1.
  52.  
  53. */
  54. %subtitle 'What This Program Does';
  55.  
  56. /* Kermit is an 'underground', volunteer protocol originally developed
  57. at Columbia by Frank da Cruz, Bill Catchings et al.  It is used to transfer
  58. files between different computers (typically a personal computer and a
  59. timesharing computer).  Implementations of Kermit are available for nearly
  60. every machine and OS you can think of.  Refer to Columbia's documentaion
  61. for a definition of the protocol and the state machine which implements the
  62. protocol; we use their model fairly literally.  In addition to the 'basic',
  63. or rquired, functionality, this implementation includes (+) or does not
  64. include (-) the following additional features refernced in Appendix II of
  65. the Columbia Kermit Protocal Manual:
  66.  
  67.   + File groups can be sent and received, using pattern or list
  68.     notation.  File types may be mixed.  Errors interrupt transfer
  69.     of a single file rather than the whole group.  A log of the
  70.     transactions can optionally be kept.
  71.  
  72.   + Filenames can be converted to and from 'normal' form, and overwriting
  73.     an existing file of the same name can optionally be prevented.
  74.  
  75.   - Full 8-bit transission is allowed, but binary DTSS files (9-bits) cannot
  76.     be transferred.
  77.  
  78.   + 8th bit prefixing is allowed in both directions.
  79.  
  80.   + Repeat-count prefixing is allowed in both directions.
  81.  
  82.   + Terminal Emulation is provided for (mostly for IBM).
  83.  
  84.   + Communications Options.  Duplex, parity, handshake, and line terminators
  85.     can all be specified.
  86.  
  87.   - Only single and double character checksums are implemented.
  88.  
  89.   + Basic Server mode is implemented.
  90.  
  91.   + Many Advanced Server commands are implemented.
  92.  
  93.   + Commands can be sent to the other end, if it is a server.
  94.  
  95.   - Host commands are not implemented.
  96.  
  97.   + File transfers can be interrupted.
  98.  
  99.   + Local File Management Commands are allowed (as they are in server
  100.     mode).
  101.  
  102.   - File Attribute Information is not implemented.
  103.  
  104.   + Debugging facilities are provided.
  105. */
  106. %subtitle 'Globals';
  107.  
  108. %include 'params';
  109. %subtitle 'Main Program';
  110.  
  111. /*
  112.  
  113. Control reaches this point when Kermit is initially run.  We initialize,
  114. then call the interactive command processor.
  115.  
  116. */
  117.  
  118. dcl init     entry; /* initialization of parameters etc */
  119. dcl command  entry(file,file); /* command processor */
  120.  
  121.  
  122. call init; /* initialize Kermit */
  123. call command($screen,$screen); /* process commands from the terminal */
  124.  
  125. end kermit;
  126. %subtitle 'Initialization';
  127.  
  128. /* Init --
  129.  
  130. Called once, at program startup, by the main procedure.
  131.  
  132. */
  133.  
  134. init: procedure;
  135.  
  136. %list off;%include 'params';%list on;
  137.  
  138. dcl default  entry; /* set up default protocol parameters */
  139. dcl ticktock entry; /* ticktock timer task */
  140.  
  141. call default; /* protocol defaults, kept in 'params' */
  142.  
  143. local    = ''b; /* not local until we connect out */
  144. serving  = ''b; /* not serving until SERVER command */
  145. logging  = ''b; /* not logging */
  146. debuging = ''b; /* and not debugging */
  147. break    = ''b; /* no breaks received yet */
  148. $errmsg  = ''; /* no error message yet */
  149. $timeout = 0; /* stop the timeout clock */
  150. $bufp    = addr($qb); /* $buf overlays $qb */
  151. $comffrn = 1; /* frn of comfile to other Kermit */
  152.  
  153. open file($comf)  frn(1) unformatted; /* comfile to other Kermit */
  154. open file($screen)frn(1) stream; /* use same comfile for screen output */
  155. open file($cat)   frn(2) unformatted; /* default catalog is current catalog */
  156.  
  157. spawn ticktock; /* fire up the ticktock timeout mechanism */
  158.  
  159. put file($screen) line('Type HELP for a command list.');
  160.  
  161. end init;
  162. %subtitle 'Command Processor';
  163.  
  164. /* Command --
  165.  
  166. This is the user interface and command processor.
  167.  
  168. */
  169.  
  170. command: procedure(input,output);
  171.  
  172. %list off;%include 'params';%list on;
  173.  
  174. dcl input    file; /* INPUT: input file (source of commands) */
  175. dcl output   file; /* INPUT: output file (place to write prompts, errors, etc) *
  176. /
  177.  
  178. dcl cmd      char var; /* the command line we read from 'input' */
  179. dcl c3       char var; /* temp for keywords */
  180. dcl b        bit(1) aligned; /* temp */
  181.  
  182. dcl open     entry(file,dstr,fixed,char var); /* to open files */
  183. dcl protocol entry(fixed,char var,bit(1)aligned); /* protocol machine */
  184. dcl filelist entry(dstr,char var,bit(1)aligned); /* to make lists of files match
  185. ing template */
  186. dcl unsave   entry(file); /* to unsave a file */
  187. dcl logf     entry(char var); /* to write to logfile, if logging */
  188. dcl split    entry(char var,char var); /* to split up filespec1 and filespec2 */
  189. %page;
  190.  
  191. /* read next command and branch on it */
  192.  
  193. NEXTCMD: do while(more(input)); /* loop over all the input */
  194. if input=$screen /* must we prompt? */
  195. then put file(input) edit('DCTS Kermit>')(a); /* yes, do so */
  196. get file(input) line(cmd); /* read the command */
  197. call trim(cmd); /* trim off spaces etc */
  198. c3 = substr(cmd,1,min(3,length(cmd))); /* pad or truncate to 3 chars */
  199. call upper(c3); /* map to uppercase */
  200. if      c3='BYE' then call bye;
  201. else if c3='CON' then call connect;
  202. else if c3='CWD' then call cwd;
  203. else if c3='DEL' then call delete;
  204. else if c3='DIR' then call directory;
  205. else if c3='EXI' then call exit;
  206. else if c3='FIN' then call finish;
  207. else if c3='GEN' then call generic;
  208. else if c3='GET' then call get;
  209. else if c3='HEL' then call help;
  210. else if c3='LOG' then call log;
  211. else if c3='QUI' then call quit;
  212. else if c3='REC' then call receive;
  213. else if c3='REM' then call remote;
  214. else if c3='SEN' then call send;
  215. else if c3='SER' then call server;
  216. else if c3='SET' then call set;
  217. else if c3='SPA' then call space;
  218. else if c3='TAK' then call take;
  219. else if c3='TRA' then call transmit;
  220. else if c3='TYP' then call type;
  221. else call error('unrecognized command');
  222. end;
  223.  
  224. return; /* return to caller when input exhausted */
  225. %subtitle 'Command Processor -- Bye';
  226.  
  227. /* Bye command --
  228.  
  229.       BYE
  230.  
  231. Valid only for local systems.  Sends a GL (logout) command to the remote
  232. system, and stops this program.
  233.  
  234. */
  235.  
  236. bye: procedure();
  237.  
  238. call spell('BYE'); /* check spelling */
  239. call ckeol; /* disallow arguments */
  240. if ~local then call error('BYE valid only after CONNECT');
  241. call protocol(3,'GL',b); /* send the command to remote server to shut down */
  242. stop; /* ignore status and stop */
  243.  
  244. end bye;
  245. %subtitle 'Command Processor -- Connect';
  246.  
  247. /* Connect command --
  248.  
  249.         CONNECT  [address]
  250.  
  251. Establishes a connection to another computer on the network, putting
  252. this program in local mode.  We use NETFACE connection files to gain
  253. outbound access to the network.
  254.  
  255. */
  256.  
  257. connect: procedure();
  258.  
  259. dcl terminal entry; /* task to emulate terminal */
  260. dcl termwait fixed ext init(0); /* Q to block on during terminal emulation */
  261.  
  262. call spell('CONNECT'); /* check spelling, trim spaces */
  263. if serving then call error('cannot CONNECT after SERVER');
  264.  
  265. /* if address not supplied, we are to reestablish a prior connection */
  266.  
  267. if cmd='' then do; /* any arguments? */
  268. if ~local then call error('must supply connection address');
  269. end;
  270.  
  271. /* if address supplied, create new connection to it */
  272.  
  273. else do; /* if cmd~='' */
  274. close file($comf); /* first, close old connection file */
  275. on undf($comf) call error('connection failed (NETFACE not running)'); /* if mode
  276. -3 open fails */
  277. if entreg(6)<0 /* if run as an X-system */
  278. then open file($comf) title('x$net:stream') stream update mode(3); /* then use X
  279. NET */
  280. else open file($comf) title('$net:stream')  stream update mode(3); /* else use o
  281. fficial */
  282. put file($comf) line('connect "'||cmd||'"'); /* initial line to NETFACE is conne
  283. ct command */
  284. get file($comf) line(cmd); /* reply from NETFACE is status of connection */
  285. if length(cmd)<3 | substr(cmd,1,3)~='000' then do; /* good status? (ie, connecti
  286. on established) */
  287. local = ''b; /* not connected */
  288. close file($comf); /* make sure residual NETFACE comfile gone */
  289. open file($comf) frn(frn($screen)); /* No: back to default connection */
  290. call error('could not connect ('||cmd||')'); /* report failures */
  291. end;
  292. $comffrn = frn($comf); /* save frn of comfile to Kermit for "timeout" */
  293. end;
  294.  
  295. /* connection (re)established, so spawn terminal emulator */
  296.  
  297. local = '1'b; /* set the flag for local mode operation */
  298. spawn terminal; /* spawn terminal emulator task */
  299. call block(termwait); /* then block until terminal emulation is suspended */
  300.  
  301. end connect;
  302. %subtitle 'Command Processor -- Cwd';
  303.  
  304. /* Cwd command --
  305.  
  306.        CWD  [directory]
  307.  
  308. Changes the current catalog, or directory.  If not specified, the directory
  309. defaults to '*MYCAT';
  310.  
  311. */
  312.  
  313. cwd: procedure();
  314.  
  315. dcl err char var; /* error message from 'open' */
  316.  
  317. call spell('CWD'); /* check spelling and trim spaces */
  318. if cmd='' then cmd = '*MYCAT'; /* default catalog is user# */
  319. close file($cat); /* close the current catalog */
  320. call open($cat,(cmd),0,err); /* open new cat w CSRAO */
  321. if err~='' then do; /* did open fail? */
  322. open file($cat) unformatted frn(2); /* yes, back to user# */
  323. call error('can''t open catalog ('||err||')');
  324. end;
  325.  
  326. end cwd;
  327. %subtitle 'Command Processor -- Delete';
  328.  
  329. /* Delete command --
  330.  
  331.        DELETE filespec
  332.  
  333. Deletes the file(s) specified.
  334.  
  335. */
  336.  
  337. delete: procedure();
  338.  
  339. dcl (list,next,err) char var; /* filenames etc */
  340. dcl tempf file constant internal; /* temporary for files to delete */
  341. dcl i; /* temp */
  342.  
  343. call spell('DELETE'); /* check spelling, delete spaces */
  344. if cmd='' then call error('must name file(s) to DELETE');
  345. call filelist((cmd),list,''b); /* get list of files to delete */
  346. if list='' then call error('no files match template');
  347.  
  348. do while(list~=''); /* loop over each file */
  349. i = index(list,'|'); /* get end of next name */
  350. next = substr(list,1,i-1); /* extract ext filename from list */
  351. list = substr(list,i+1); /* truncate list */
  352. call open(tempf,(next),3,err); /* try to open */
  353. if err~='' then do; /* if open failed */
  354. call  logf(next||' not deleted ('||err||')');
  355. if ~local then call print(next||' not deleted ('||err||')');
  356. end;
  357. else do; /* we got RWA on it, so delete it */
  358. call unsave(tempf); /* do it */
  359. call  logf(next||' deleted');
  360. if ~local then call print(next||' deleted');
  361. end;
  362. end;
  363.  
  364. end delete;
  365. %subtitle 'Command Processor -- Directory';
  366.  
  367. /* Directory command --
  368.  
  369.        DIRECTORY  [filespec]
  370.  
  371. Displays a directory listing of the specified file(s), or all files in the
  372. current catalog if the filespec is null.
  373.  
  374. */
  375.  
  376. directory: procedure();
  377.  
  378. dcl list char var; /* file names */
  379. dcl line char var; /* line being built */
  380. dcl n; /* count of files per line */
  381. dcl i; /* temp */
  382.  
  383. call spell('DIRECTORY'); /* check spelling and trim spaces */
  384. if cmd='' then cmd = '*'; /* ask for all filenames if filespec is null */
  385. call filelist((cmd),list,'1'b); /* get list of formatted filenames//lengths */
  386. cmd = 'SPACE'; /* we'll use the SPACE command to print header */
  387. call print(''); /* blank line */
  388. call space; /* print catalog name, length, and remaining */
  389. call print(''); /* blank line */
  390. line = ''; /* initialize line */
  391. n = 0; /* and #files on line */
  392. do while(list~=''); /* loop over each filename */
  393.    i = index(list,'|'); /* delimit next filename */
  394.    line = line || '   ' || substr(list,1,i-1); /* add to line */
  395.    n = n + 1; /* bump count */
  396.    if mod(n,3)=0 then do; /* 3 on this line? */
  397.       call print(line); /* yes, output */
  398.       line = ''; /* and start again */
  399.    end;
  400.    list = substr(list,i+1); /* drop name from list */
  401. end;
  402. call print(line); /* end last line */
  403.  
  404. end directory;
  405. %subtitle 'Command Processor -- Exit';
  406.  
  407. /* Exit command --
  408.  
  409.        EXIT
  410.  
  411. stops the program, just like QUIT.
  412.  
  413. */
  414.  
  415. exit: procedure();
  416.  
  417. call spell('EXIT'); /* check the spelling */
  418. call ckeol; /* disallow arguments */
  419. stop; /* then stop */
  420.  
  421. end exit;
  422. %subtitle 'Command Processor -- Finish';
  423.  
  424. /* Finish command --
  425.  
  426.          FINISH
  427.  
  428. Valid only on local systems.  Like BYE, it shuts down the remote server,
  429. but asks it not to close the connection, and we don't stop either.  After
  430. a FINISH, the user may re-connect to the same or another remote system.
  431.  
  432. */
  433.  
  434. finish: procedure();
  435.  
  436. call spell('FINISH'); /* check spelling */
  437. call ckeol; /* disallow arguments */
  438. if ~local then call error('FINISH valid only after CONNECT');
  439. call protocol(3,'GF',b); /* send Generic Finish command to remote server */
  440. if ~b then call errorp; /* handle protocol error */
  441. local = ''b; /* this undoes the CONNECT */
  442. logging = ''b; /* and turns off logging */
  443. close file($log); /* so close the logfile (if open) */
  444.  
  445. end finish;
  446. %subtitle 'Command Processor -- Generic';
  447.  
  448. /* Generic command --
  449.  
  450.       GENERIC command [parameter]
  451.  
  452. Valid only if this is a server, this command is 'created' by the protocol
  453. machine for G packets, which contain Generic commands.  Refer to the REMOTE
  454. command, which is the "other side" which sends these commands.  The command
  455. is exactly one character long, and the parameter (if any) is preceeded by a
  456. coded length.  We don't allow users to type these.
  457.  
  458. */
  459.  
  460. generic: procedure();
  461.  
  462. dcl stop entry; /* task to stop this program after a short pause */
  463. dcl c1   char(1); /* the command */
  464. dcl i    fixed; /* temp */
  465.  
  466. call spell('GENERIC'); /* check spelling, trim spaces */
  467. if ~serving | cmd='' then call error('illegal command');
  468.  
  469. c1 = substr(cmd,1,1); /* extract command */
  470. if length(cmd)<3 /* if too short for any parameters... */
  471. then cmd = ''; /* then set parameter null */
  472. else do; /* handle coded parameter length */
  473.   i = byte(cmd,2) - 32; /* get coded length */
  474.   i = min(i,length(cmd)-2); /* clamp down if packet too short for parameter */
  475.   cmd = ' ' || substr(cmd,3,i); /* extract command parameter */
  476. end;
  477.  
  478. if      c1='C' then call cwd; /* CWD */
  479. else if c1='E' then call delete; /* DELETE */
  480. else if c1='D' then call directory; /* DIRECTORY */
  481. else if c1='U' then call space; /* SPACE */
  482. else if c1='H' then call help; /* HELP */
  483. else if c1='T' then call type; /* TYPE */
  484. else if c1='L' then spawn stop; /* BYE */
  485. else if c1='F' then spawn stop; /* FINISH */
  486.  
  487. else call error('DCTS does not implement this REMOTE command type');
  488.  
  489. end generic;
  490. %subtitle 'Command Processor -- Get';
  491.  
  492. /* Get command --
  493.  
  494.       GET filespec [newname]
  495.  
  496. Receive the file(s) from a remote server. The optional "newname" argument
  497. overrides the name the first file received will be saved as.
  498.  
  499. */
  500.  
  501. get: procedure();
  502.  
  503. call spell('GET'); /* check spelling, trim leading spaces */
  504. if ~local then call error('must CONNECT before GET');
  505. if cmd='' then call error('must name file(s) to GET');
  506. call protocol(4,'R'||cmd,b); /* send server the 'R' command */
  507. if ~b then call errorp; /* handle protocol error */
  508.  
  509. end get;
  510. %subtitle 'Command Processor -- Help';
  511.  
  512. /* Help command --
  513.  
  514.        HELP  [topic]
  515.  
  516. This is a weak implementation.  Feel free to use the EXPLAIN/HELP module
  517. if you wish.
  518.  
  519. */
  520.  
  521. help: procedure();
  522.  
  523. dcl (c,d) char var; /* for calling 'extract' */
  524. dcl i;
  525.  
  526. call spell('HELP'); /* check spelling, trim spaces */
  527. if cmd='' /* if no topic... */
  528. then c = ''; /* then set it null */
  529. else call extract(c,d,i); /* else map to uppercase */
  530. c = substr(c,1,min(length(c),3)); /* truncate topic to 3 chars */
  531.  
  532. if c='SET' then do; /* the SET parameters */
  533. call print('SET CHECKSUM [1,2]sets the block checksum type (2 is "safer")');
  534. call print('SET DEBUG [ON,OFF]prints info helpful when debugging Kermit');
  535. call print('SET MAPNAME       maps filenames sent to and received from other sys
  536. tem');
  537. call print('                  to uppercase, discarding non-alphanumerics except
  538. "." and "*"');
  539. call print('SET SAMENAME      sends files w same name they are saved with (the d
  540. efault)');
  541. call print('SET REPLACE       will overwrite an existing file with one received'
  542. );
  543.  
  544. return; /* don't print the boilerplate about "files" args etc */
  545. end;
  546.  
  547. if~serving then do; /* the non-server commands */
  548. call print('The DCTS Kermit commands are:');
  549. call print('BYE               shuts down remote server and stops Kermit');
  550. call print('CONNECT [addr]    to connect to another computer on network');
  551. call print('CWD [catalog]     to change current catalog');
  552. call print('DELETE files      to unsave, or delete, files');
  553. call print('DIRECTORY [files] to list files and their lengths');
  554. call print('EXIT              stops Kermit (use BYE if server also running)');
  555. call print('FINISH            shuts down remote server but not Kermit');
  556. call print('GET files [newname] requests other computer to send us the file(s)')
  557. ;
  558. call print('LOG [file]        logs a record of each file xfered (or not)');
  559. call print('QUIT              same as EXIT');
  560. call print('RECEIVE [newname] waits for file being sent by other computer');
  561. call print('                  (use GET if remote end is a server, RECEIVE if not
  562. )');
  563. call print('REMOTE command    sends a command to a remote server');
  564. call print('SEND files [newname] sends the file(s) to other computer');
  565. call print('SERVER            puts this program in remote server mode');
  566. call print('SET parameter     sets nonstandard parameters (HELP SET for a list)'
  567. );
  568. call print('SPACE             prints size and max of current catalog');
  569. call print('TAKE file         inputs Kermit commands from file');
  570. call print('TRANSMIT files    sends file(s) "naked" to other computer');
  571. call print('TYPE files        lists file(s) on other computer''s screen');
  572. end;
  573.  
  574. else if serving then do; /* relevent commands for remote service */
  575. call print('The commands you may send to a DCTS Kermit Server are:');
  576. call print('SEND files [newname]     to send files to the DCTS server');
  577. call print('GET  files [newname]     to receive files from the DCTS server');
  578. call print('BYE                      terminates both Kermits, closes connection'
  579. );
  580. call print('FINISH                   to stop the DCTS server only (cf BYE)');
  581. call print('REMOTE CWD catalog       to change DCTS''s current catalog');
  582. call print('REMOTE DELETE files      to delete file(s) on DCTS');
  583. call print('REMOTE DIRECTORY [files] for DCTS catalog contents');
  584. call print('REMOTE SPACE             for size and max of DCTS catalog');
  585. call print('REMOTE KERMIT command    to send DCTS Kermit any command');
  586. call print('                         (ie, REMOTE KERMIT SET REPLACE)');
  587. call print('REMOTE TYPE files        to have DCTS files listed on your screen');
  588. end;
  589.  
  590. call print('Optional arguments are bracketed, as in "[addr]".');
  591. call print('The "files" arguments may contain wildcards (*) like "*.L".');
  592. call print('The "newname" argument, if present, is the name the file will be sav
  593. ed as.');
  594. call print('Please refer to "Kermit User''s Guide" for details.');
  595.  
  596. end help;
  597. %subtitle 'Command Processor -- Log';
  598.  
  599. /* Log command --
  600.  
  601.        LOG  [filename]
  602.  
  603. Log transactions (and debugging information, if enabled) to the named file.
  604. If the filename is null, logging is turned off if already on.
  605.  
  606. */
  607.  
  608. log: procedure();
  609.  
  610. dcl err char var; /* error message */
  611.  
  612. call spell('LOG'); /* check spelling, trim spaces, leave filename in 'cmd' */
  613.  
  614. if cmd='' then do; /* turning Logging off */
  615. close file($log); /* so close file */
  616. logging = ''b; /* set flag off */
  617. end;
  618. else do; /* wants to Log */
  619. call open($log,(cmd),4,err); /* first, try to open existing file */
  620. if err~='' /* if not found... */
  621. then call open($log,(cmd),2,err); /* then try to create */
  622. if err~='' then call error('can''t open LOG file ('||err||')');
  623. reset file($log) to(lof($log)+1); /* append to end of file */
  624. logging = '1'b; /* turn flag on */
  625. end;
  626.  
  627. end log;
  628. %subtitle 'Command Processor -- Quit';
  629.  
  630. /* Quit command --
  631.  
  632.       QUIT
  633.  
  634. stops the program, just like EXIT.
  635.  
  636. */
  637.  
  638. quit: procedure();
  639.  
  640. call spell('QUIT'); /* check the spelling */
  641. call ckeol; /* disallow arguments */
  642. stop; /* then stop */
  643.  
  644. end quit;
  645. %subtitle 'Command Processor -- Receive';
  646.  
  647. /* Receive command --
  648.  
  649.       RECEIVE [filespec]
  650.  
  651. Passively wait for a file; valid on either the local or remote end, as long
  652. as neither is in server mode.  The optional filename overrides the name the
  653. first file received will be saved as.
  654.  
  655. */
  656.  
  657. receive: procedure();
  658.  
  659. call spell('RECEIVE'); /* check spelling */
  660. if serving then call error('RECEIVE illegal while serving');
  661. if ~local then call wait(5); /* wait for user to escape back to local kermit */
  662. call protocol(2,cmd,b); /* receive the files (using optional filespec) */
  663. if ~b then do;/* handle protocol error */
  664. call print('Receive failed; if other end is a server, you should use GET, not RE
  665. CEIVE.');
  666. call errorp; /* handle protocol error message, if any */
  667. end;
  668.  
  669. end receive;
  670. %subtitle 'Command Processor -- Remote';
  671.  
  672. /* Remote command --
  673.  
  674.      REMOTE command [parameter]
  675.  
  676. Send a command to the remote server.  Valid only at the local end.  Most
  677. of these turn into 'G' packets, which take a single letter command in the
  678. data field, possibly followed by the parameter.  A 'G' packet parameter is
  679. preceeded by a char encoding it's length; refer to the protocol manual.
  680.  
  681. */
  682.  
  683. remote: procedure();
  684.  
  685. dcl rcmd    char var;/* remote command */
  686. dcl rpar    char var; /* parameter field */
  687. dcl i; /* temp */
  688.  
  689. call spell('REMOTE'); /* check spelling, trim spaces */
  690. if ~local then call error('REMOTE must follow CONNECT');
  691. call extract(rcmd,rpar,i); /* extract command, parameter, value */
  692. rcmd = substr(rcmd,1,min(length(rcmd),3)); /* truncate to 3 chars */
  693. rpar = substr(rpar,1,min(length(rpar),50)); /* truncate parameter too */
  694.  
  695. if      rcmd='CWD' then rcmd = 'GC'; /* Change Working Directory */
  696. else if rcmd='DEL' then rcmd = 'GE'; /* Delete */
  697. else if rcmd='DIR' then rcmd = 'GD'; /* Directory */
  698. else if rcmd='SPA' then rcmd = 'GU'; /* Space */
  699. else if rcmd='HEL' then rcmd = 'GH'; /* Help */
  700. else if rcmd='HOS' then rcmd = 'C';  /* Host command */
  701. else if rcmd='KER' then rcmd = 'K';  /* Kermit command */
  702. else if rcmd='TYP' then rcmd = 'GT'; /* Type */
  703.  
  704. else call error('unsupported REMOTE command (try REMOTE KERMIT <cmd>)');
  705.  
  706. if substr(rcmd,1,1)='G' & rpar~='' /* if a Generic command with a parameter */
  707. then rcmd = rcmd || chr(length(rpar)+32) || rpar; /* then add in length */
  708. else rcmd = rcmd || rpar; /* else just add parameter to command wo length */
  709.  
  710. call protocol(4,rcmd,b); /* send the command to remote server */
  711. if ~b then call errorp; /* handle protocol error */
  712.  
  713. end remote;
  714. %subtitle 'Command Processor -- Send';
  715.  
  716. /* Send command --
  717.  
  718.      SEND filespec1 [filespec2]
  719.  
  720. Sends one or more files to other end, which may or may not be a server.
  721. If filespec2 is present, it determines the name with which the only file
  722. shipped will be saved.
  723.  
  724. */
  725.  
  726. send: Procedure();
  727.  
  728. dcl (spec1,spec2) char var; /* the two filespecs */
  729. dcl i fixed; /* temp */
  730.  
  731. call spell('SEND'); /* check spelling */
  732. if serving then call error('SEND illegal while serving');
  733. spec1 = cmd; /* initialize filespec1 */
  734. if spec1='' then call error('must name file(s) to SEND');
  735. call split(spec1,spec2); /* separate filespec1 and filespec2 */
  736. call filelist((spec1),spec1,''b); /* get list of files matching filespec1 */
  737. if spec1='' then call error('no files match template');
  738. if spec2~='' then do; /* if filespec2 supplied... */
  739.    if index(spec2,'?')~=0 /* make sure we can add to filelist */
  740.    then call error('destination name cannot contain "?"');
  741.    spec1 = substr(spec1,1,length(spec1)-1); /* truncate trailing '|' */
  742.    if index(spec1,'|')~=0 /* filespec2 valid only when sending single file */
  743.    then call error('cannot use destination name with wildcards');
  744.    spec1 = spec1 || '?' || spec2 || '|'; /* add destination name */
  745. end;
  746. if ~local then call wait(5); /* wait for user to escape back to local Kermit */
  747. call protocol(1,spec1,b); /* send the file(s) */
  748. if ~b then call errorp; /* handle protocol error */
  749.  
  750. end send;
  751. %subtitle 'Command Processor -- Server';
  752.  
  753. /* Server command --
  754.  
  755.       SERVER
  756.  
  757. invokes server mode.  We call the protocol machine, which never returns.
  758.  
  759. */
  760.  
  761. server: procedure();
  762.  
  763. call spell('SERVER'); /* check spelling */
  764. call ckeol; /* disallow arguments */
  765. if local | serving /* appropriate? */
  766. then call error('SERVE illegal after SERVE or CONNECT');
  767. call print('Kermit Server running on DCTS host.  Please type your escape');
  768. call print('sequence to return to your local machine.  Shut down the server');
  769. call print('by typing the Kermit BYE command on your local machine.');
  770. call flush(output); /* flush above message to screen */
  771. call wait(5); /* OK: wait 5 sec for user to return to local system */
  772. call protocol(0,'',b); /* then enter server mode */
  773. signal error; /* die if we ever return from it */
  774.  
  775. end server;
  776. %subtitle 'Command Processor -- Set';
  777.  
  778. /* Set command --
  779.  
  780.       SET parameter [option] [value]
  781.  
  782. Establish or modify various parameters for file transfer or terminal connection.
  783. This is a catch-all set of implementation oddities.
  784.  
  785. */
  786.  
  787. set: procedure();
  788.  
  789. dcl param   char var; /* set parameter */
  790. dcl option  char var; /* set options/value */
  791. dcl i; /* temp */
  792.  
  793. call spell('SET'); /* check spelling etc */
  794. call extract(param,option,i); /* extract the parameter and value from command li
  795. ne */
  796. call upper(option); /* map option to uppercase */
  797.  
  798. if      param='CHECKSUM'then do; /* SET CHECKSUM */
  799.   if (i=1) | (i=2) /* if type 1 or 2 (only ones supported) */
  800.   then params.iwant = i; /* then set my preference */
  801.   else call error('CHECKSUM must be set to 1 or 2');
  802. end;
  803.  
  804. else if param='DEBUG'   then do; /* SET DEBUG */
  805.   if option='OFF' /* SET DEBUG OFF */
  806.   then debuging = '0'b; /* set global flag */
  807.   else debuging = '1'b; /* SET DEBUG ON */
  808. end;
  809.  
  810. else if param='MAPNAME'  then do; /* SET MAPNAME */
  811.   params.fncnv = '1'b; /* turn on filename normalization (default is off) */
  812. end;
  813.  
  814. else if param='SAMENAME' then do; /* SET SAMENAME */
  815.   params.fncnv = ''b; /* then turn off filename normalization */
  816. end;
  817.  
  818. else if param='REPLACE' then do; /* SET REPLACE */
  819.   params.overwrit = '1'b; /* overwrite files of same name */
  820. end;
  821.  
  822. else call error('illegal SET parameter');
  823.  
  824. end set;
  825. %subtitle 'Command Processor -- Space';
  826.  
  827. /* Space command --
  828.  
  829.       SPACE
  830.  
  831. Displays space remaining in current catalog.
  832.  
  833. */
  834.  
  835. space: procedure;
  836.  
  837. dcl 1 cathdr, /* catalog header */
  838.     2 max    fixed, /* max */
  839.     2 aloc   fixed, /* aloc */
  840.     2 xx(2)  fixed, /* (unused) */
  841.     2 accw   fixed, /* access word */
  842.     2 ents   fixed, /* #entries */
  843.     2 capac  fixed, /* capacity */
  844.     2 pop    fixed, /* population */
  845.     2 pad    fixed(20); /* padding for use with provide treename */
  846. dcl cnam     char var; /* temp for catalog name */
  847. dcl c        char(80) based; /* overlays 'cathdr' */
  848. dcl (m1,m2,m3) fixed static; /* MME parameters */
  849. dcl regs(0:11) fixed; /* MME parameters */
  850.  
  851. call spell('SPACE'); /* check spelling */
  852. call ckeol; /* disallow arguments */
  853. m1 = waddr(cathdr); /* point to catalog entry buffer */
  854. m2 = wlen(cathdr); /* length of buffer */
  855. unspec(regs(*)) = ''b; /* clear register block to 0s */
  856. regs(0) = frn($cat); /* X0: frn */
  857. regs(3) = waddr(m1); /* X3: ptr to ptr to buffer */
  858. regs(5) = 3; /* X5: request type (provide treename, char string) */
  859. regs(7) = waddr(m2); /* X7: ptr to length to read */
  860. call mme(500246b3,regs(*)); /* Request Status: provide treename */
  861. if regs(10)>1000000b3 then signal error; /* die if this fails */
  862.  
  863. cnam = substr(addr(cathdr)->c,1,(wlen(cathdr)+regs(11))*4); /* get name */
  864.  
  865. regs(1) = waddr(m3); /* X1: ptr to entry# */
  866. m3 = 0; /* we want entry#0 */
  867. call mme(500214b3,regs(*)); /* Read Catalog, entry #0 (catalog header) */
  868. if regs(10)>1000000b3 then signal error; /* die if this fails */
  869.  
  870. call print(cnam||'  max:'||max(cathdr.max)||'  aloc:'||max(cathdr.aloc)||'  capa
  871. city:'||max(cathdr.capac));
  872.  
  873. /* handle cat maxes: convert -1 to 'infinite' */
  874.  
  875. max: procedure(n) returns(char var);
  876.  
  877. dcl n fixed; /* INPUT: the number */
  878.  
  879. if n<0 /* infinite? */
  880. then return('infinite'); /* yes, say so */
  881. else return(n); /* else just convert number directly */
  882.  
  883. end max;
  884.  
  885. end space;
  886. %subtitle 'Command Processor -- Take';
  887.  
  888. /* Take command --
  889.  
  890.       TAKE filespec
  891.  
  892. reads commands from the specified file.
  893.  
  894. */
  895.  
  896. take: procedure();
  897.  
  898. dcl takef file int; /* the file we read commands from */
  899. dcl err   char var; /* error message from open */
  900.  
  901. call spell('TAKE'); /* check spelling */
  902. if per(takef)~=0 /* recursive? */
  903. then call error('TAKE command may not be in TAKE file');
  904. call open(takef,(cmd),1,err); /* open the file */
  905. if err~='' /* did open fail? */
  906. then call error('can''t open TAKE file: '||err);
  907. call command(takef,output); /* OK: recurse to handle the commands */
  908. close file(takef); /* done with the file */
  909.  
  910. end take;
  911. %subtitle 'Command Processor -- Transmit';
  912.  
  913. /* Transmit command --
  914.  
  915.        TRANSMIT filespec
  916.  
  917. Sends the file, naked, to the remote connection.  Valid only if we're in
  918. local mode, this can be used to ship a file to a system that doesn't have
  919. a Kermit.  Put remote system in "accept file" mode in an editor, escape
  920. back to this program, TRANSMIT, re-connect and quit the editor.  Good luck!
  921.  
  922. */
  923.  
  924. transmit: procedure();
  925.  
  926. dcl (list,next) char var; /* filenames */
  927. dcl i        fixed; /* temp */
  928. dcl buf(512) fixed; /* buffer for file-to-output copies */
  929. dcl tempf    file int; /* temp for files being listed */
  930.  
  931. call spell('TRANSMIT'); /* ckeck spelling, trim spaces */
  932. if ~local then call error('must CONNECT to remote system before TRANSMIT');
  933. if cmd='' then call error('must name file to TRANSMIT');
  934. call filelist((cmd),list,''b); /* construct list of files to be sent */
  935. if list='' then call error('no files matched template');
  936.  
  937. do while(list~=''); /* loop over each file to be transmitted */
  938. i = index(list,'|'); /* find delimiter */
  939. next = substr(list,1,i-1); /* extract next filename */
  940. list = substr(list,i+1); /* truncate list */
  941. call open(tempf,(next),1,next); /* open file */
  942. if next~='' then call error('can''t open file ('||next||')');
  943. i = 1; /* prime matread loop */
  944. do while(i>0); /* loop while more in file */
  945.    i = matread(tempf,buf,wlen(buf)); /* read from file */
  946.    if i>0 then i = matwrite($comf,buf,i); /* and write to connection */
  947. end;
  948. close file(tempf); /* done with file */
  949. end;
  950.  
  951. end transmit;
  952. %subtitle 'Command Processor -- Type';
  953.  
  954. /* Type command --
  955.  
  956.        TYPE filespec
  957.  
  958. Send the indicated file(s), but to the local screen.  Ie, "type" them
  959. on the local screen.  This is often called when we are a server, via the
  960. GT generic command.
  961.  
  962. */
  963.  
  964. type: procedure();
  965.  
  966. dcl (list,next) char var; /* filenames */
  967. dcl i        fixed; /* temp */
  968. dcl buf(512) fixed; /* buffer for file-to-output copies */
  969. dcl tempf    file int; /* temp for files being listed */
  970.  
  971. call spell('TYPE'); /* ckeck spelling, trim spaces */
  972. if cmd='' then call error('must name file to TYPE');
  973. call filelist((cmd),list,''b); /* construct list of files to be sent */
  974. if list='' then call error('no files matched template');
  975.  
  976. do while(list~=''); /* loop over each file to be typed */
  977. i = index(list,'|'); /* find delimiter */
  978. next = substr(list,1,i-1); /* extract next filename */
  979. list = substr(list,i+1); /* truncate list */
  980. call open(tempf,(next),1,next); /* open file */
  981. if next~='' then call error('can''t open file ('||next||')');
  982. i = 1; /* prime matread loop */
  983. do while(i>0); /* loop while more in file */
  984.    i = matread(tempf,buf,wlen(buf)); /* read from file */
  985.    if i>0 then i = matwrite(output,buf,i); /* and write to default output */
  986. end;
  987. close file(tempf); /* done with file */
  988. end;
  989.  
  990. end type;
  991. %subtitle 'Command Processor -- Subroutines';
  992.  
  993.  
  994. /* extract first word from command line */
  995.  
  996. extract: procedure(first,rest,nval);
  997.  
  998. dcl first char var; /* OUTPUT: first word in 'cmd', mapped to uppercase */
  999. dcl rest  char var; /* OUTPUT: rest of 'cmd', not mapped but trimmed */
  1000. dcl nval  fixed; /* OUTPUT: if rest is numeric, numeric value, else -1 */
  1001. dcl (a,b) char var; /* temps */
  1002. dcl i; /* temp */
  1003.  
  1004. i = index(cmd,' '); /* space delimits first word from rest */
  1005. if i=0 then i = length(cmd) + 1; /* if no space, its all the first word */
  1006. a = substr(cmd,1,i-1); /* extract first word */
  1007. b = substr(cmd,i); /* and rest */
  1008. call upper(a); /* map first word to uppercase */
  1009. call trim(b); /* trim spaces from rest */
  1010.  
  1011. if b~='' & verify(b,'0123456789')=0 /* if b is entirely numeric */
  1012. then nval = fixedbin(b); /* then return its numeric value */
  1013. else nval = -1; /* else flag as non-numeric */
  1014.  
  1015. first = a; /* assign value of first word (mapped and trimmed) */
  1016. rest  = b; /* and rest (trimmed but not mapped) */
  1017.  
  1018. end extract;
  1019.  
  1020.  
  1021. /* trim leading and trailing spaces */
  1022.  
  1023. trim: procedure(arg);
  1024.  
  1025. dcl arg char var; /* UPDATE: argument to trim */
  1026. dcl i; /* temp */
  1027.  
  1028. i = verify(arg,' '); /* find first non-space */
  1029. if i=0 /* only spaces in string? */
  1030. then arg = ''; /* yes, set null */
  1031. else do; /* at least one non-space */
  1032.      arg = substr(arg,i); /* trim leading spaces */
  1033.      do i = length(arg) /* search arg */
  1034.             to 2 by -1 /* right to left */
  1035.             while(substr(arg,i,1)=''); /* looking for first non-blank */
  1036.      end;
  1037.      arg = substr(arg,1,i); /* then trim trailing spaces */
  1038. end;
  1039.  
  1040. end trim;
  1041.  
  1042.  
  1043. /* check spelling of first word in 'cmd' and trim leading spaces */
  1044.  
  1045. spell: procedure(word);
  1046.  
  1047. dcl word char var; /* INPUT: the word to spell, in uppercase */
  1048. dcl txt  char var; /* the first word stripped from the command line */
  1049. dcl i; /* temp */
  1050.  
  1051. i = index(cmd,' '); /* find delimiter to first word in command line */
  1052. if i=0 then i = length(cmd)+1; /* if no space, entire line is first word */
  1053. txt = substr(cmd,1,i-1); /* extract first word (note: word null if first char is
  1054.  space!) */
  1055. cmd = substr(cmd,i); /* then strip off command line */
  1056. call upper(txt); /* map first word to uppercase */
  1057. call trim(cmd); /* trim spaces from remaining portion of command line */
  1058.  
  1059. if txt~='' & index(word,txt)~=1 /* if typed word not subset of full spelling */
  1060. then call error(txt||' is not a command'); /* then error */
  1061.  
  1062. end spell;
  1063.  
  1064.  
  1065. /* map to uppercase */
  1066.  
  1067. upper: procedure(arg);
  1068.  
  1069. dcl arg char var; /* UPDATE: argument to be mapped */
  1070.  
  1071. arg = translate(arg,'QWERTYUIOPASDFGHJKLZXCVBNM','qwertyuiopasdfghjklzxcvbnm');
  1072.  
  1073. end upper;
  1074.  
  1075.  
  1076. /* give error message if unprocessed arguments in command */
  1077.  
  1078. ckeol: procedure();
  1079.  
  1080. if cmd~='' then call error('arguments not allowed: '||cmd);
  1081.  
  1082. end ckeol;
  1083.  
  1084.  
  1085. /* print line to output file */
  1086.  
  1087. print: procedure(line);
  1088.  
  1089. dcl line char var; /* INPUT: line to print */
  1090.  
  1091. put file(output) line(line); /* do it */
  1092.  
  1093. end print;
  1094.  
  1095.  
  1096. /* print error message and abort this command */
  1097.  
  1098. error: procedure(msg);
  1099.  
  1100. dcl msg char var; /* the text of the error message */
  1101.  
  1102. call print('Error, '||msg); /* print the message */
  1103. goto NEXTCMD; /* then abort this command, try next */
  1104.  
  1105. end error;
  1106.  
  1107.  
  1108. /* protocol error (called when 'protocol' returns w bad status) */
  1109.  
  1110. errorp: procedure();
  1111.  
  1112. if $errmsg='' /* if no clue as to why protocol error occured... */
  1113. then call error('communication with remote Kermit failed');
  1114. else call error(($errmsg)); /* use specific message if there is one */
  1115.  
  1116. end errorp;
  1117.  
  1118. end command;
  1119. %subtitle 'Command Processor -- Split';
  1120.  
  1121. /* Split --
  1122.  
  1123. Split up a string into two component words, separated by the first space.
  1124. The words may be quoted, and if only one word is present, the 2nd is set null.
  1125. This is used by the GET and SEND commands.  It is assumed that leading spaces
  1126. have already been trimmed.
  1127.  
  1128. */
  1129.  
  1130. split: procedure(s1,s2);
  1131.  
  1132. dcl s1    char var; /* UPDATE: string to split, first word on exit */
  1133. dcl s2    char var; /* OUTPUT: second word */
  1134. dcl (i,j) fixed; /* temps */
  1135.  
  1136. j = index(s1,' '); /* by default, delimiter is space */
  1137. if j=0 then j = length(s1) + 1; /* first word is entire string if no space */
  1138.  
  1139. if index(s1,'"')=1 then do; /* 1st word quoted? */
  1140.    i = index(s1,'"',2); /* look for delimiter */
  1141.    if i>0 then do; /* if delimiter found */
  1142.       s1 = substr(s1,2); /* strip off leading '"' */
  1143.       j = i - 1; /* trailing '"' splits string */
  1144.    end;
  1145. end;
  1146.  
  1147. s2 = substr(s1,min(j+1,length(s1)+1)); /* extract 2nd word */
  1148. s1 = substr(s1,1,j-1); /* extract 1st word */
  1149.  
  1150. i = verify(s2,' ');/* look for leading spaces in word 2 */
  1151. if i=0 /* all spaces? */
  1152. then s2 = ''; /* yup */
  1153. else do; /* at least one non-space */
  1154.      s2 = substr(s2,i); /* trim leading spaces */
  1155.      do while(substr(s2,length(s2),1)=' '); /* loop while last char is a space *
  1156. /
  1157.         s2 = substr(s2,1,length(s2)-1); /* trim it */
  1158.      end;
  1159. end;
  1160.  
  1161. if index(s2,'"')=1 then do; /* 2nd word quoted too? */
  1162.    i = index(s2,'"',2); /* find delimiting quote */
  1163.    if i>0 then do; /* if found */
  1164.       s2 = substr(s2,2,i-2); /* extract quoted 2nd word */
  1165.    end;
  1166. end;
  1167.  
  1168. end split;
  1169. %subtitle 'Command Processor -- Stop Task';
  1170.  
  1171. /* Stop --
  1172.  
  1173. This is a task spawned by the BYE and FINISH generic commands, which are
  1174. valid only if we're in server mode.  We wait 5 seconds and stop the program.
  1175. The wait allows the protocol machine a little time to ACK the 'G' packet,
  1176. which is a courtesy to the other end.  Clearly this won't always work, and
  1177. it might have been better to figure out another way to do this, but at least
  1178. this scheme avoids a pathologic connection between command processing and
  1179. the protocol machine.
  1180.  
  1181. */
  1182.  
  1183. stop: procedure();  /* Spawned by Generic BYE and FINISH */
  1184.  
  1185. call wait(5); /* wait awhile */
  1186. stop; /* and stop */
  1187.  
  1188. end stop;
  1189. %subtitle 'Files -- Drive';
  1190.  
  1191. /* Drive --
  1192.  
  1193. Issue a drive MME.  Used to set modes etc on $screen and $comf.
  1194.  
  1195. */
  1196.  
  1197. drive: procedure(file,mode);
  1198.  
  1199. dcl file     file; /* INPUT: the file */
  1200. dcl mode     fixed; /* INPUT: the mode (type/function) */
  1201. dcl regs(0:11) fixed; /* mme parameters */
  1202.  
  1203. unspec(regs) = ''b; /* clear the regs */
  1204. regs(0) = frn(file); /* X0: frn */
  1205. regs(8) = mode; /* RA: mode */
  1206. call mme(500232b3,regs); /* issue Drive MME, ignore status */
  1207.  
  1208. end drive;
  1209. %subtitle 'Files -- Filelist';
  1210.  
  1211. /* Filelist --
  1212.  
  1213. This routine makes a list of files in a catalog, using the wildcard notation
  1214. '*'.  The list consists of a set of simple names, each terminated with an '|'.
  1215. The list may be null.  Two types of lists are made, formatted and not.
  1216.  
  1217.    1. Formatted lists include catalogs and lengths, and each entry is fixed leng
  1218. th:
  1219.          "FRED       123499|JOSEPH        756|"
  1220.  
  1221.    2. Unformatted lists do not include catalogs or lengths, and have spaces
  1222.       compressed out:
  1223.          "FRED|JOSEPH|"
  1224.  
  1225. The template is treated as the explicit name (perhaps a treename) of a single
  1226. file if it either contains an ':' or does not contain an '*', or contains '***'.
  1227.  
  1228. */
  1229.  
  1230. filelist: procedure(template,list,formatted);
  1231.  
  1232. %list off;%include 'params';%list on;
  1233.  
  1234. dcl template dstr; /* UPDATE: the template */
  1235. dcl list     char var; /* OUTPUT: the list of names */
  1236. dcl formatted bit(1) aligned; /* INPUT: true iff formatted list wanted */
  1237. dcl ss(0:10) char var; /* the search strings */
  1238. dcl nss      fixed; /* the number of search string in the template */
  1239. dcl (m1,m2,m3) fixed static; /* MME parameters */
  1240. dcl regs(0:11) fixed; /* MME parameters */
  1241. dcl 1 rcbuf(100), /* read catalog entry buffer */
  1242.     2 name   char(8), /* filename */
  1243.     2 pwd    char(8), /* password */
  1244.     2 accw   fixed, /* access word */
  1245.     2 dp     fixed, /* days uses// pref */
  1246.     2 dates  fixed, /* DLU//DLM */
  1247.     2 len    fixed; /* length */
  1248. dcl (i,j,k,e,n) fixed; /* temps */
  1249. dcl c        char var; /* temp */
  1250.  
  1251. template = translate(template,'QWERTYUIOPASDFGHJKLZXCVBNM','qwertyuiopasdfghjklz
  1252. xcvbnm');
  1253. list = ''; /* initialize list to null */
  1254.  
  1255. /* distinguish between templates and explicit fileames */
  1256.  
  1257. if ( index(template,':')~=0 /* if it appears to be a treeame... */
  1258.    | index(template,'***')~=0 /* or if its in DLIBRARY... */
  1259.    | index(template,'*')=0) /* or if there are no wildcards... */
  1260.   & ~formatted /* and formatted list not wanted... */
  1261. then do; /* then it is not a template */
  1262. if template~='' /* unless null (in which case we don't add the '|') */
  1263. then list = template || '|'; /* then return single filename in list */
  1264. return; /* and done */
  1265. end;
  1266.  
  1267. /* parse the template into a set of search strings (mapping * to '') */
  1268.  
  1269. ss(0) = 'xx'; /* assume 0th search string is NOT a wildcard */
  1270. nss = 0; /* no search strings so far */
  1271. do while(template~='' & nss<hbound(ss,1)); /* loop stripping off search strings
  1272. */
  1273. nss = nss + 1; /* next string */
  1274. if substr(template,1,1)='*' then do; /* a wildcard? */
  1275. ss(nss) = ''; /* yes, set flag */
  1276. template = substr(template,2); /* strip off the * */
  1277. end;
  1278. else do; /* not a wildcard */
  1279. i = index(template,'*'); /* search for next * */
  1280. if i=0 then i = length(template) + 1; /* use rest of string if no * */
  1281. ss(nss) = substr(template,1,i-1); /* get string */
  1282. template = substr(template,i); /* strip it off */
  1283. end;
  1284. end;
  1285.  
  1286. /* set up parameters for Read Catalog MME */
  1287.  
  1288. m1 = 1; /* first entry to read is #1 */
  1289. m2 = waddr(rcbuf); /* ptr to catalog entry buffer */
  1290. m3 = wlen(rcbuf); /* length of entry buffer in words */
  1291. regs(0) = frn($cat); /* X0: frn of catalog */
  1292. regs(1) = waddr(m1); /* X1: ptr to m1 */
  1293. regs(3) = waddr(m2); /* X3: ptr to m2 */
  1294. regs(6) = 0; /* X6: trap (unused?) */
  1295. regs(7) = waddr(m3); /* X7: ptr to m3 */
  1296. regs(10)= 0; /* initialize to enter loop first time */
  1297.  
  1298. /* loop over each file in the catalog */
  1299.  
  1300. do while(regs(10)=0); /* loop until we get bad status from Read Catalog */
  1301.    call mme(500214b3,regs(*)); /* read next 100 entries */
  1302.    regs(1) = 0; /* use implicit ptr into catalog hereafter */
  1303.    do j = 1 to (regs(11)+m3)/wlen(rcbuf(1)); /* for each entry read */
  1304.       if rcbuf(j).accw>0 | formatted then do; /* if not a catalog, or formatted
  1305. list */
  1306.          c = rcbuf(j).name; /* get name */
  1307.          e = 0; /* no mismatch yet */
  1308.          do k = 1 to nss while(e=0); /* for each search string */
  1309.             if ss(k)~='' then do; /* if not a wildcard */
  1310.                n = index(c,ss(k)); /* does string occur in filename? */
  1311.                if n=0 /* if not... */
  1312.                then e = 1; /* then no match */
  1313.                else if n~=1 & ss(k-1)~='' /* if matched in middle but no wildcar
  1314. d... */
  1315.                then e = 1; /* then no match */
  1316.                else c = substr(c,n+length(ss(k))); /* strip off match */
  1317.             end;
  1318.          end;
  1319.          if  e=0 /* if no mismatches */
  1320.           & (c='' | ss(nss)='') /* and either all matched or trailing wildcard *
  1321. /
  1322.          then do; /* then add to list */
  1323.             if ~formatted /* if tight packed filenames wanted... */
  1324.             then list = list || trim(rcbuf(j).name) || '|';
  1325.             else list = list || rcbuf(j).name || fmt(rcbuf(j).len) || '|';
  1326.          end;
  1327.       end;
  1328.    end;
  1329. end;
  1330.  
  1331. return; /* done: all catalog entries read */
  1332.  
  1333.  
  1334. /* trim trailing spaces off a filename */
  1335.  
  1336. trim: proc(name) returns(char var);
  1337.  
  1338. dcl name char(8); /* INPUT: space filled filename */
  1339. dcl i;
  1340.  
  1341. if name='' then return(''); /* handle name of 8 spaces */
  1342. do i = 1 to 8 while(substr(name,1,i)~=name); end;
  1343. return (substr(name,1,i)); /* return name without space fill */
  1344.  
  1345. end trim;
  1346.  
  1347. /* format number in fixed length character field */
  1348.  
  1349. fmt: procedure(n) returns(char(12));
  1350.  
  1351. dcl n fixed; /* INPUT: number to format */
  1352. dcl p pic'zzzzzzzzzzz9'; /* temp */
  1353.  
  1354. if n<0 then return('    infinite');/* handle negative lengths */
  1355. p = n; /* if positive, format */
  1356. return(string(p)); /* and return */
  1357.  
  1358. end fmt;
  1359.  
  1360. end filelist;
  1361. %subtitle 'Files -- Logf';
  1362.  
  1363. /* Logf --
  1364.  
  1365. Write lines to the logfile, if enabled, and to the screen, if local.
  1366.  
  1367. */
  1368.  
  1369. logf: procedure(line);
  1370.  
  1371. %list off;%include 'params';%list on;
  1372.  
  1373. dcl line     char var; /* INPUT: the line to log */
  1374. dcl (date,time) builtin;
  1375.  
  1376. if logging /* if logging enabled... */
  1377. then put file($log) line(date(),' ',time(),' ',line); /* prefix with date/time s
  1378. tamp */
  1379.  
  1380. if local then do; /* ie, if we have a screen... */
  1381.    put file($screen) line(line); /* tell user whats going on */
  1382.    call flush($screen); /* immediately */
  1383. end;
  1384.  
  1385. end logf;
  1386. %subtitle 'Files -- Normaliz';
  1387.  
  1388. /* Normaliz --
  1389.  
  1390. Normalize a filename, by stripping it of any chars except uppercase letters,
  1391. digits, and the period and * (for wildcards), then truncating to at most 8 chars
  1392. .
  1393.  
  1394. */
  1395.  
  1396. normaliz: procedure(name);
  1397.  
  1398. %list off;%include 'params';%list on;
  1399.  
  1400. dcl name dstr; /* UPDATE: the filename */
  1401. dcl i    fixed; /* temp */
  1402.  
  1403. name = translate(name,'QWERTYUIOPASDFGHJKLZXCVBNM','qwertyuiopasdfghjklzxcvbnm')
  1404. ;
  1405. i = 1; /* enter loop at least once */
  1406. do while(i~=0); /* loop removing non-approved characters */
  1407. i = verify(name,'QWERTYUIOPASDFGHJKLZXCVBNM1234567890.*'); /* any offenders? */
  1408. if i~=0 /* yes! remove it!! */
  1409. then name = substr(name,1,i-1) || substr(name,i+1);
  1410. end;
  1411. if length(name)>8 then name = substr(name,1,8); /* truncate if necessary */
  1412. if length(name)=0 then name = 'NONAME'; /* try to be helpful */
  1413.  
  1414. end normaliz;
  1415. %subtitle 'Files -- Open';
  1416.  
  1417. /* Open --
  1418.  
  1419. This is the open file subroutine; it creates and saves output files if
  1420. necessary, scratches them if already saved (and overwriting is enabled),
  1421. and maps bad statuses into strings.
  1422.  
  1423. */
  1424.  
  1425. open: procedure(file,name,type,err);
  1426.  
  1427. %list off;%include 'params';%list on;
  1428.  
  1429. dcl file     file; /* INPUT: the file to open */
  1430. dcl name     dstr; /* INPUT: the filename */
  1431. dcl type     fixed; /* INPUT: 0:catalog  1:input  2:output  3:delete  4:log */
  1432. dcl err      char var; /* OUTPUT: status: null if good, else error message */
  1433.  
  1434. dcl tw       fixed static; /* mme parameter: tallyword */
  1435. dcl acc(2)   fixed static; /* mme parameter: access control words */
  1436. dcl snam     char(80) static; /* mme parameter: name to catalog with */
  1437. dcl regs(0:11)fixed; /* mme parameter: register block */
  1438. dcl i;  /* temp */
  1439.  
  1440. on undf(file) begin;/* catch open errors */
  1441. i = shr(stw1(file),18)&777b3; /* get major status */
  1442. goto ERROR; /* handle */
  1443. end;
  1444.  
  1445. close file(file); /* make sure the file is closed */
  1446. err = ''; /* initialize error message to 'good' */
  1447.  
  1448. /* open of catalog: must try for OSRA */
  1449.  
  1450. if type=0 then do; /* if open of catalog (probably CWD command) */
  1451. open file(file) title(name) unformatted access(455000b3); /* note cannot use $ca
  1452. t!! */
  1453. if (per(file)&400000b3)=0 then do; /* if not a catalog... */
  1454.    close file(file); /* then close it */
  1455.    err = 'not a catalog'; /* say what is wrong */
  1456. end;
  1457. return; /* return if opened successfully */
  1458. end;
  1459.  
  1460. /* input file: always an error if can't open */
  1461.  
  1462. if type=1 then do; /* handle input files, the easy case */
  1463. open file(file) catfrn(frn($cat)) title(name) stream input;
  1464. return; /* got it, done */
  1465. end;
  1466.  
  1467. /* file to be deleted: always an error if can't open */
  1468.  
  1469. if type=3 then do; /* DELETE command opens files mode 3 */
  1470. open file(file) catfrn(frn($cat)) title(name) unformatted update;
  1471. return; /* return if opened */
  1472. end;
  1473.  
  1474. /* output file (types 2 and 4): try to open, to see if its there */
  1475.  
  1476. begin; /* we'll handle bad statuses ourselves */
  1477.   on undf(file) goto NOT_THERE; /* we hope open will fail */
  1478.   open file(file) catfrn(frn($cat)) title(name) stream update;
  1479.   if type=4 then return; /* if opening a logfile, don't worry about overwriting
  1480. */
  1481.   if params.overwrit then do; /* we got it: ok? */
  1482.      scratch file(file); /* yes, scratch it */
  1483.      return; /* and done */
  1484.   end;
  1485.   err = 'it already exists but REPLACE option not SET'; /* explain what went wro
  1486. ng */
  1487.   close file(file); /* close the file */
  1488.   return; /* done: don't overwrite a file without permission */
  1489. end;
  1490.  
  1491. NOT_THERE: i = shr(stw1(file),18)&777b3; /* lets take a look at the status */
  1492. if i~=3 then goto ERROR; /* make sure open failed because 'file not found' */
  1493.  
  1494. /* create and catalog an output file */
  1495.  
  1496. open file(file) scratch pref(6) stream; /* first, make a scratch file */
  1497. snam = name; /* move parameter into static storage for MME */
  1498. tw = shl(waddr(snam),18) + shl(length(name),6) + 40b3; /* make tallyword to name
  1499.  */
  1500. acc(1) = 0; /* no trap mask necessary */
  1501. acc(2) = 007400007400b3; /* save w RWAL,RWAL */
  1502. regs(0) = frn($cat); /* X0: frn of initial cat */
  1503. regs(1) = waddr(tw); /* X1: ptr to tallyword */
  1504. regs(2) = frn(file); /* X2: frn of file to be cataloged */
  1505. regs(3) = 0; /* X3: mfd frn */
  1506. regs(4) = waddr(acc); /* X4: ptr to access info */
  1507. regs(5) = 0; /* X5: don't tell us filename */
  1508. regs(6) = 0; /* X6: (unused) */
  1509. regs(7) = 0; /* X7: no load/dump info */
  1510. call mme(500241b3,regs(*)); /* issue Tally Catalog */
  1511.  
  1512. i = shr(regs(10),18)&777b3; /* get major status from MME */
  1513.  
  1514. if i=0 then return; /* done if file cataloged successfully */
  1515.  
  1516. /* handle errors: we assume the major status is in 'i' */
  1517.  
  1518. ERROR: close file(file); /* make sure file closed */
  1519. if i<=12b3 then do case(i); /* handle low statuses */
  1520. err = 'access error'; /* 1: partial status */
  1521. err = 'in use'; /* 2: lockout */
  1522. err = 'not found'; /* 3: not found */
  1523. err = 'incorrect password'; /* 4: protection violation */
  1524. err = 'access error'; /* 5: fail */
  1525. err = 'illegal filename'; /* 6: bad treename */
  1526. err = 'access error'; /* 7: fetch */
  1527. err = 'migrated'; /* 10: migrated */
  1528. signal error; /* 11: (not a status) */
  1529. err = 'illegal filename'; /* 12: format error */
  1530. end;
  1531. else if i=40b3  then err = 'quotas error';
  1532. else if i=60b3  then err = 'out of storage';
  1533. else if i=100b3 then err = 'access error';
  1534. else signal error; /* other statuses are our fault */
  1535.  
  1536. return; /* return bad status */
  1537.  
  1538. end open;
  1539. %subtitle 'Files -- Reset';
  1540.  
  1541. /* Reset --
  1542.  
  1543. Issue Reset Status MME on a file.  We are passed the frn, rather than the
  1544. file declaration, since the fcb is probably busy and thus can't be used.
  1545.  
  1546. */
  1547.  
  1548. reset: procedure(frn);
  1549.  
  1550. dcl frn        fixed; /* INPUT: frn of file to reset */
  1551. dcl regs(0:11) fixed; /* mme parameters */
  1552.  
  1553. unspec(regs(*)) = ''b; /* clear the registers */
  1554.  
  1555. regs(0) = frn; /* X0: frn */
  1556. call mme(500235b3,regs(*)); /* issue Reset Status, ignore status */
  1557.  
  1558. end reset;
  1559. %subtitle 'Files -- Unsave';
  1560.  
  1561. /* Unsave --
  1562.  
  1563. Uncatalog and close a file.  Called when reading a file is interrupted.
  1564.  
  1565. */
  1566.  
  1567. unsave: procedure(file);
  1568.  
  1569. dcl file     file; /* INPUT: the file to unsave */
  1570. dcl regs(0:11) fixed; /* register block for MMEs */
  1571.  
  1572. unspec(regs(*)) = ''b; /* clear out the registers */
  1573.  
  1574. regs(0) = frn(file); /* X0: frn */
  1575. call mme(500204b3,regs(*)); /* Uncatalog MME, ignore status */
  1576.  
  1577. close file(file); /* close the scratch file, destroying it */
  1578.  
  1579. end unsave;
  1580. %subtitle 'Protocol Machine';
  1581.  
  1582. /* Protocol --
  1583.  
  1584. This is the finite state automaton that implements the Kermit file
  1585. server protocol.  When called, we expect the global parameters to
  1586. be set up, and file "$comf" to be the comfile to the remote Kermit.
  1587.  
  1588. We return '1'b iff the transaction was successful; if not, $errmsg MAY
  1589. contain an explanatory message.
  1590.  
  1591. */
  1592.  
  1593. protocol: procedure(istate,text,ok);
  1594.  
  1595. %list off;%include 'params';%list on;
  1596.  
  1597. dcl istate   fixed; /* INPUT: initial state  */
  1598. dcl text     char var; /* UPDATE: filename list (send), command (send command) *
  1599. /
  1600. dcl ok       bit(1) aligned; /* OUTPUT: true iff transaction successful */
  1601.  
  1602. dcl typ      char(1) static; /* type of most recently read packet */
  1603. dcl seq      fixed static; /* sequence# of most recently read packet */
  1604. dcl data     dstr static; /* data field of most recently read packet */
  1605. dcl nil      dstr static init(''); /* canonic null data field (saves space) */
  1606. dcl err      char var; /* for use with "open" */
  1607.  
  1608. dcl command  entry(file,file); /* to parse commands */
  1609. dcl getp     entry(char(1),fixed,dstr); /* to read next packet */
  1610. dcl putp     entry(char(1),fixed,dstr); /* to send next packet */
  1611. dcl normaliz entry(dstr); /* apply 'normalizing' filename convetions */
  1612. dcl open     entry(file,dstr,fixed,char var); /* to open a file */
  1613. dcl unsave   entry(file); /* uncatalog and close */
  1614. dcl filelist entry(dstr,char var,bit(1)aligned); /* process wildcard file lists
  1615. */
  1616. dcl default  entry; /* to set up protocol defaults */
  1617. dcl logf     entry(char var); /* to write lines to logfile, if logging */
  1618. dcl drive    entry(file,fixed); /* to issue Drive MMEs */
  1619. dcl split    entry(char var,char var); /* to split up 'filespec1' and 'filespec2
  1620. ' */
  1621. %page;
  1622.  
  1623.  
  1624. on condition(abort) begin; /* catch aborted transactions */
  1625. close file($dataf); /* done with this file */
  1626. call odometer(0); /* disable the odometer if necessary */
  1627. ok = '0'b; /* set bad status */
  1628. goto EXIT; /* the exit from the transaction */
  1629. end;
  1630.  
  1631. ok = '1'b; /* assume transaction completes successfully */
  1632. $errmsg = ''; /* clear out the error message text */
  1633. params.chkt = 1; /* checksums revert to the default */
  1634. call flush($screen); /* flush pending screen output */
  1635.  
  1636. /* Drive network comfile, for two reasons: (1) to clear out pending input,
  1637.    and (2) to put real terminal-like systems (ie, things coming in on RS232
  1638.    lines) into Short-Timeout mode, so we can read parity bits.  We
  1639.    can't drive STO on just anything, since VAX/VMS can't handle drives.  So if
  1640.    we are in local mode (ie, if the remote end is almost certainly a host,
  1641.    NOT a terminal), then we simply flush pending input.  It is important to
  1642.    flush out the NAKs that have been accumulating while user thinks in local mod
  1643. e.
  1644.    In theory, Short Timeout can cause us to receive incomplete packets (ie,
  1645.    before the CR), but in practice it seems to be OK.  LBL modes are unattractiv
  1646. e
  1647.    since they discard parity coming in.  */
  1648.  
  1649.  
  1650. if local /* if we're confident remote end is a host server... */
  1651. then call drive($comf,400000b3); /* then just flush pending input */
  1652. else call drive($comf,000011b3); /* else use Short-Timeout drive (also flushes)
  1653. */
  1654.  
  1655. do while(oninterrupt()~=''b);end; /* discard stacked specials */
  1656.  
  1657. do case(istate+1); /* branch on initial state */
  1658. call server; /* 0: server mode */
  1659. call send(text); /* 1: send file(s) */
  1660. call receive(text); /* 2: receive file(s) */
  1661. call sendcmd(text); /* 3: send command to remote server (short response expected
  1662. ) */
  1663. call sendlong(text); /* 4: send command to remote server (long response expected
  1664. ) */
  1665. end;
  1666.  
  1667. EXIT: if ~local then call drive($comf,000000b3); /* set LBL back for command mod
  1668. e */
  1669. return; /* return status in 'ok' */
  1670. %subtitle 'Protocol Machine -- Receive';
  1671.  
  1672. /* Receive --
  1673.  
  1674. This is the basic driver for non-server receive file operations. */
  1675.  
  1676. receive: procedure(name);
  1677.  
  1678. dcl name char var; /* UPDATE: if not null, overrides F packet filename */
  1679.  
  1680. $n = 0; /* set seq# 0 */
  1681. $r = 0; /* no retries yet */
  1682.  
  1683. do while(1); /* loop until we get an S */
  1684. call getp(typ,seq,data); /* read the next packet */
  1685.  
  1686. if typ='S' & seq=0 then do; /* Good, he sent a Send_initiate */
  1687. call rparms(data,'0'b); /* process the S-parameters */
  1688. call gparms(data); /* set up our parameters */
  1689. call putp('Y',$n,data); /* ACK him with our parameters */
  1690. call next; /* bump $n */
  1691. call readhdr(name); /* OK, read file(s) */
  1692. return; /* and done */
  1693. end;
  1694.  
  1695. else if typ='T' then do; /* timeouts are retried */
  1696. call putp('N',$n,nil); /* NAK first */
  1697. call retry; /* then bump retry count */
  1698. end;
  1699.  
  1700. else signal cond(abort); /* abort on other packet type */
  1701. end;
  1702.  
  1703. end receive;
  1704. %subtitle 'Protocol Machine -- Readhdr';
  1705.  
  1706. /* Readhdr --
  1707.  
  1708. This loops looking for a file header or EOT message, and reads in the file.
  1709. It returns only after the B (end of transaction) or an error.  We are passed
  1710. an optional name to save the file with, which (if present) overrides that
  1711. enclosed in the F packet.
  1712.  
  1713. */
  1714.  
  1715. readhdr: procedure(name);
  1716.  
  1717. dcl name char var; /* UPDATE: optional name to save file with */
  1718.  
  1719. do while(1); /* loop over each file in this transaction */
  1720.  
  1721. call choose(); /* choose which checksum type to expect */
  1722. break = ''b; /* clear don't-send-file flag */
  1723. call getp(typ,seq,$buf); /* read the next packet */
  1724.  
  1725. if typ='F' & seq=$n then do; /* File header */
  1726. call unquote; /* unquote '$buf' */
  1727. if params.fncnv /* should we convert filenames? */
  1728. then call normaliz($buf); /* yes, do so */
  1729. if name~='' /* but wait, do we have an override on filename? */
  1730. then $buf = name; /* yes, use this name instead of one coming from remote system
  1731.  */
  1732. else name = $buf; /* else remember name to save it with */
  1733. call open($dataf,$buf,2,err); /* try to open/create the file */
  1734. call putp('Y',$n,nil); /* ACK the F packet whether open worked or not */
  1735. call next; /* bump $n, zero $r */
  1736. if err~='' then do; /* if open fails... */
  1737.    call logf(name||' received but not saved ('||err||')');
  1738.    break = '1'b; /* set not-xfered flag */
  1739.    open file($dataf) scratch; /* continue transaction, in case more files coming
  1740.  */
  1741.    call readdata; /* NOTE: we do not abort just because file can't be opened! */
  1742. end;
  1743. else do; /* open succeeeded */
  1744.    call logf(name||' receive initiate'); /* log start of xfer */
  1745.    call odometer(2); /* start the odometer */
  1746.    call readdata; /* now read in the D packets */
  1747.    call odometer(1); /* stop the odometer */
  1748.    if ~break /* if file received properly */
  1749.    then call logf(name||' received successfully'); /* log end of xfer */
  1750.    else call logf(name||' not received (transmit error)');
  1751. end;
  1752. name = ''; /* clear override name in case multiple files arrive */
  1753. end;
  1754.  
  1755. else if typ='X' & seq=$n then do; /* Text to be typed on user screen? */
  1756. if ~local then call error('X illegal to remote Kermit');
  1757. open file($dataf) frn(frn($screen)); /* refer to screen from I/O file */
  1758. call putp('Y',$n,nil); /* ACK the X packet */
  1759. call next; /* bump $n, zero $r */
  1760. call readdata; /* now read in the D packets */
  1761. end;
  1762.  
  1763. else if typ='B' & seq=$n then do; /* EOT ? (ie, no more files to be sent) */
  1764. call putp('Y',$n,nil); /* ACK the B packet */
  1765. return; /* and done reading this group of files */
  1766. end;
  1767.  
  1768. else if typ='S' & ((seq+1)&63)=$n then do; /* retransmission of the S ? */
  1769. call retry; /* bump $r */
  1770. call gparms(data); /* get our S-parameters */
  1771. call putp('Y',seq,data); /* re-ACK his S, in case our ACK was lost */
  1772. end;
  1773.  
  1774. else if typ='Z' & ((seq+1)&63)=$n then do; /* retransmission of the Z ? */
  1775. call retry; /* bump $r */
  1776. call putp('Y',seq,nil); /* re-ACK the Z, in case first ACk was lost */
  1777. end;
  1778.  
  1779. else if typ='T' then do; /* timeout? */
  1780. call retry; /* bump $r */
  1781. call putp('N',$n,nil); /* NAK the unreceived packet */
  1782. end; /* and keep trying */
  1783.  
  1784. else signal cond(abort); /* abort transaction for other packets */
  1785.  
  1786. end;
  1787.  
  1788. end readhdr;
  1789. %subtitle 'Protocol Machine -- Readdata';
  1790.  
  1791. /* Readdata --
  1792.  
  1793. Receive data up to the end of the file.  We loop over D packets until the
  1794. Z which ends the file.
  1795.  
  1796. */
  1797.  
  1798. readdata: procedure();
  1799.  
  1800. do while(1); /* loop until Z or error */
  1801.  
  1802. call getp(typ,seq,$buf); /* read the next packet */
  1803.  
  1804. if typ='D' then do; /* D packet? */
  1805. if seq~=$n then do; /* wrong seq# ? */
  1806. call retry; /* yes, bump $r */
  1807. if ((seq+1)&63)~=$N /* if not retransmission of last... */
  1808. then signal cond(abort); /* then simply abort */
  1809. call putp('Y',seq,nil); /* re-ACK it, perhaps the first ACK was lost */
  1810. end;
  1811. else do; /* D with correct seq# */
  1812. call putb; /* unquote '$buf' and write to file '$dataf' */
  1813. call odometer(3); /* count these bytes xferred */
  1814. if break /* iterruption wanted? */
  1815. then call putp('Y',$n,'X'); /* yes, ACK but ask for a 'Z' next */
  1816. else call putp('Y',$n,nil); /* just ACK if no interruption wanted */
  1817. call next; /* bump $n, zero $r */
  1818. end;
  1819. end;
  1820.  
  1821. else if typ='Z' & seq=$n then do; /* Z (eof) packet? */
  1822. if $buf~='D' /* if not a 'discard' request, but a normal 'Z' */
  1823. then close file($dataf); /* then simply close file */
  1824. else do; /* if data field of Z packet not null, its a break request */
  1825. call unsave($dataf); /* so discard file and close */
  1826. break = '1'b; /* set flag that file not xfered */
  1827. end;
  1828. call putp('Y',$n,nil); /* ACK the Z */
  1829. call next; /* bump $n, zero $r */
  1830. return; /* done with reading this file */
  1831. end;
  1832.  
  1833. else if ((seq+1)&63)=$n & (typ='F' | typ='X') then do; /* retransmission? */
  1834. call retry; /* bump $r */
  1835. call putp('Y',seq,nil); /* re-ACK it, our first ACK probably lost */
  1836. end;
  1837.  
  1838. else if typ='T' then do; /* Timeout ? */
  1839. call retry; /* bump $r */
  1840. call putp('N',$n,nil); /* NAK the unreceived packet */
  1841. end;
  1842.  
  1843. else signal cond(abort); /* abort on bad packets */
  1844.  
  1845. end;
  1846.  
  1847. end readdata;
  1848. %subtitle 'Protocol Machine -- Server';
  1849.  
  1850. /* Server --
  1851.  
  1852. This is the main loop for server-mode operation.  We loop over messages,
  1853. until a BYE or EXIT command stops the program from on high.  Abort signals
  1854. are trapped here, lest they terminate server-mode.  Thus, we never leave
  1855. this loop except when the program stop.
  1856.  
  1857. */
  1858.  
  1859. server: procedure();
  1860.  
  1861. dcl exchngd  bit(1) init(''b); /* true when we've seen his I parameters */
  1862. dcl cmdf     file internal; /* temp file for command output */
  1863. dcl list     char var; /* file list */
  1864. dcl i        fixed; /* temp */
  1865.  
  1866. serving = '1'b; /* set "server mode" flag */
  1867.  
  1868. on condition(abort) begin; /* catch errors */
  1869. close file ($dataf); /* close I/O file */
  1870. goto LOOP; /* loop for next command */
  1871. end;
  1872.  
  1873. LOOP: do while(1); /* loop over commands */
  1874.  
  1875. $n = 0; /* start transaction with packet #0 */
  1876. $r = 0; /* and no retries */
  1877. i  = params.time; /* save normal timeout period */
  1878. params.time = 60; /* wait up to 60 seconds for server commands, per protocol */
  1879. params.chkt = 1; /* checksums revert to the default */
  1880. call getp(typ,seq,$buf); /* read next packet */
  1881. params.time = i; /* once we recieve command, timeout normally */
  1882.  
  1883. if seq~=0 then call error('expected packet 0'); /* only seq=0 is acceptable */
  1884.  
  1885. if typ='I' then do; /* I: exchange parameters wo file xfer */
  1886. call rparms($buf,'0'b); /* read his parameters */
  1887. call gparms(data); /* then get ours */
  1888. call putp('Y',$n,data); /* ACK with our parameters */
  1889. exchngd = '1'b; /* remember we've seen his parameters */
  1890. end;
  1891.  
  1892. else if typ='S' then do; /* S: start to receive a file */
  1893. call rparms($buf,'0'b); /* read his parameters */
  1894. call gparms(data); /* then give him ours */
  1895. call putp('Y',$n,data); /* ACK the S with our parameters */
  1896. call next; /* bump $n */
  1897. call readhdr(''); /* read in the file (no override name) */
  1898. end;
  1899.  
  1900. else if typ='R' then do; /* R: send him our file(s) */
  1901. call unquote; /* unquote filename in $buf */
  1902. if params.fncnv /* should we convert filenames? */
  1903. then call normaliz($buf); /* yes, do so */
  1904. call filelist($buf,list,''b); /* process wildcards and make a list of files */
  1905. if list='' then call error('no files specified'); /* empty list? */
  1906. call send(list); /* OK: send him the files */
  1907. end;
  1908.  
  1909. /* Handle Server Commands, either interactive syntax (K) or special
  1910.    coded sytax (G).  We unquote these and write to a scratch file,
  1911.    then call the command processor to read from the scratch file and
  1912.    output to the $dataf file.  Then, the output is examined, and
  1913.    either put in an ACK if possible, else sent as text with X and D
  1914.    packets.  */
  1915.  
  1916. else if typ='K' | typ='G' then do;
  1917. open file(cmdf) scratch; /* open a command file */
  1918. open file($dataf) scratch; /* open a scratch file for command output */
  1919. if typ='G' /* Generic commad? */
  1920. then put file(cmdf) edit('generic ')(a); /* then prefix command */
  1921. call unquote; /* unquote '$buf' */
  1922. put file(cmdf) line($buf); /* write to command file */
  1923. reset file(cmdf); /* then reset */
  1924. call command(cmdf,$dataf); /* process the commands */
  1925. close file(cmdf); /* done with command file */
  1926. reset file($dataf); /* reset the output */
  1927. call quote; /* get first packet's worth of output in '$buf' */
  1928. data = $buf; /* save, in case output is short */
  1929. call quote; /* see if there is another packet's worth */
  1930. if length($buf)~=0 then do; /* if response will not fit in the 'Y'... */
  1931. reset file($dataf); /* reset again */
  1932. if ~exchngd | params.iwant>1 /* if haven't seen his I parameters or nonstandard
  1933. chkt */
  1934. then call send('*TTY|'); /* then must send S, the X, the Ds */
  1935. else do; /* if we've seen his I, no need for the S */
  1936. call next; /* so bump $n */
  1937. call sendhdr('*TTY'); /* then send the X and Ds */
  1938. call sendbreak; /* then send the 'B' */
  1939. end;
  1940. end;
  1941. else do; /* it fits in a single ACK */
  1942. call putp('Y',$n,data); /* so reply to the command */
  1943. close file($dataf); /* done with command output file */
  1944. end;
  1945. end;
  1946.  
  1947. else if typ='C' then call error('DTSS does not implement HOST commands');
  1948.  
  1949. else if typ='T' then call putp('N',$n,nil); /* NAK timeouts */
  1950.  
  1951. else call error('unexpected packet'); /* random packets result in E's */
  1952.  
  1953. end;
  1954.  
  1955. end server;
  1956. %subtitle 'Protocol Machine -- Send';
  1957.  
  1958. /* Send --
  1959.  
  1960. This is the entry point to send file(s); it is called both from the server
  1961. and from interactive mode.  We are passed a list of files to send.  If the
  1962. name is '*TTY|', then we assume that we are to send file $dataf (already open)
  1963. to the other end's screen with the 'X' packet protocol.  Otherwise, the file
  1964. name list is assumed to be in the format compiled by "filelist", which consists
  1965. of one or more treenames terminated with '|' characters.  If a file is to be
  1966. sent with a different destination name (SEND FRED XFRED), then the two names
  1967. are delimited with an '?' character, ie 'FRED?XFRED|'.
  1968.  
  1969. */
  1970.  
  1971. send: procedure(names);
  1972.  
  1973. dcl names    char var; /* UPDATE: the filename list (described above) */
  1974. dcl dname    char var; /* destination filename */
  1975. dcl err      char var; /* error message from 'open' */
  1976. dcl i        fixed; /* temp */
  1977.  
  1978. /* first, send the S packet (exchanging parameters) */
  1979.  
  1980. $n, $r = 0; /* start with packet #0, no retries */
  1981. do while($n=0); /* loop until we get a good ACK */
  1982. call gparms(data); /* get our S-parameters */
  1983. call putp('S',$n,data); /* send the Send_initiate */
  1984. call getp(typ,seq,data); /* read the reply */
  1985. if typ='Y' & seq=0 /* the expected reply? */
  1986. then call next; /* yes, bump $n (exiting loop), zero $r */
  1987. else call retry; /* bad response, bump $r */
  1988. end;
  1989. call rparms(data,'1'b); /* handle other side's parameters */
  1990. call choose(); /* choose checksum type */
  1991.  
  1992. /* loop over each file */
  1993.  
  1994. do while(names~=''); /* loop over each file specified */
  1995. i = index(names,'|'); /* find next name */
  1996. $buf = substr(names,1,i-1); /* extract filename */
  1997. names = substr(names,i+1); /* truncate list */
  1998. i = index($buf,'?'); /* distinct destination name? */
  1999. if i=0 /* if not... */
  2000. then dname = $buf; /* then send with same name */
  2001. else do; /* destination name supplied */
  2002. dname = substr($buf,i+1); /* extract destination name */
  2003. $buf  = substr($buf,1,i-1); /* and local name */
  2004. end;
  2005. if params.fncnv then do; /* should we normalize the filenames we send? */
  2006.    data = dname; /* yes, move into 'dstr' so normaliz can operate on it */
  2007.    call normaliz(data); /* then normalize */
  2008.    dname = data; /* put back where it belongs */
  2009. end;
  2010. if $buf='*TTY' /* if special name for "send $dataf to screen" */
  2011. then err = ''; /* then we've already got $dataf open as frn 1 */
  2012. else call open($dataf,$buf,1,err); /* open the file to be sent */
  2013. if err='' /* error? */
  2014. then call sendhdr(dname); /* no, send file */
  2015. else do; /* open failed */
  2016. call logf($buf||' not sent ('||err||')'); /* log failed xfer */
  2017. if names='' /* if no more names... */
  2018. then call error('can''t open '||$buf||' ('||err||')');
  2019. else; /* if more to send, DO NOT tell other end or stop */
  2020. end;
  2021. end;
  2022.  
  2023. call sendbreak; /* send the B to delimit entire list of files */
  2024.  
  2025. end send;
  2026. %subtitle 'Protocol Machine -- Sendhdr';
  2027.  
  2028. /* Sendhdr --
  2029.  
  2030. Send the file or text header, then call senddata to pump out the data.
  2031. The file sent is $dataf, which should already be open and ready to go.
  2032. We are passed the name of the file, which is sent to the other end in the
  2033. F packet.  The special name '*TTY' stands for the destination's screen;
  2034. we send an X instead of an F in this case.  When we return, the end-of-file
  2035. Z packet has already been sent; our caller is responsible for the B.
  2036.  
  2037. */
  2038.  
  2039. sendhdr: procedure(destn);
  2040.  
  2041. dcl destn    char var; /* UPDATE: destination name (*TTY for screen) */
  2042.  
  2043. if destn~='*TTY' then call logf(destn||' send initiate'); /* log start of xfer *
  2044. /
  2045. break = ''b; /* clear xmit failure flag */
  2046. data  = destn; /* move destination name into 'dstr' for 'putp' */
  2047.  
  2048. do while(1); /* loop over retries */
  2049.  
  2050. if destn='*TTY' /* if the special name... */
  2051. then call putp('X',$n,nil); /* then send to the screen */
  2052. else call putp('F',$n,data); /* send F for file xfer */
  2053. call getp(typ,seq,$buf); /* read the response */
  2054.  
  2055. if (typ='Y' & seq=$n) /* if the expected ACK... */
  2056.  | (typ='N' & ((seq-1)&63)=$n) /* or if a NAK for next packet (ie, we missed ACK
  2057. ) */
  2058. then do; /* then proceed to send data */
  2059. call next; /* bump $n, zero $r */
  2060. call odometer(2); /* start the odometer running */
  2061. call senddata; /* then send the D packets, finish with Z */
  2062. call odometer(1); /* stop the odometer */
  2063. if destn~='*TTY' then do; /* don't log xmit of 'screen' files */
  2064.    if break /* if xmit failed for some reason */
  2065.    then call logf(destn||' not sent (transmit failure)');
  2066.    else call logf(destn||' sent successfully');
  2067. end;
  2068. return; /* and done */
  2069. end;
  2070.  
  2071. else if typ='N' | typ='T' then call retry; /* retry NAKs and Timeouts */
  2072.  
  2073. else signal cond(abort); /* abort on other packet types */
  2074.  
  2075. end;
  2076. end sendhdr;
  2077. %subtitle 'Protocol Machine -- Senddata';
  2078.  
  2079. /* Senddata --
  2080.  
  2081. Send the contents of a file or textual information.  We loop repeatedly
  2082. sending D packets until the end of the file is reached, at which point
  2083. we send a Z, close the file, and return.
  2084.  
  2085. */
  2086.  
  2087. senddata: procedure();
  2088.  
  2089. dcl ok       bit(1); /* flag for loop control */
  2090.  
  2091. do while(1); /* loop over each D packet successfully sent */
  2092.  
  2093. call quote; /* read '$dataf' and quote into '$buf' */
  2094. if length($buf)=0 | break then do; /* if end-of-file or interrupted... */
  2095. call sendeof; /* send the Z packet */
  2096. return; /* and done */
  2097. end;
  2098. ok = '1'b; /* prime to enter loop */
  2099.  
  2100. do while(ok); /* loop until packet successfully written */
  2101.  
  2102. call putp('D',$n,$buf); /* send a data packet */
  2103. call getp(typ,seq,data); /* then read the response */
  2104.  
  2105. if (typ='Y' & seq=$n) /* if the expected ACK... */
  2106.  | (typ='N' & ((seq-1)&63)=$n) /* or if a NAK for next packet... */
  2107. then do; /* then other side got our data */
  2108. call next; /* bump $n, zero $r */
  2109. call odometer(3); /* count these bytes xfered */
  2110. if data='X' | data='Z' then do; /* does he want us to interrupt xfer? */
  2111. break = '1'b; /* yes, set flag */
  2112. call sendeof; /* stop */
  2113. return; /* and done */
  2114. end;
  2115. ok = ''b; /* get next bufferload */
  2116. end;
  2117.  
  2118. else if typ='N' | typ='T' | typ='Y' then call retry; /* retry NAKs, ACKs, and Ti
  2119. meouts */
  2120.  
  2121. else signal cond(abort); /* else abort on other packets */
  2122.  
  2123. end;
  2124. end;
  2125. end senddata;
  2126. %subtitle 'Protocol Machine -- Sendeof';
  2127.  
  2128. /* Sendeof --
  2129.  
  2130. Send the Z packet and close the file.  Higher levels are responsible for
  2131. sending the next file (if any), or sending the B (if there are no more
  2132. files) to end this transaction.
  2133.  
  2134. */
  2135.  
  2136. sendeof: procedure();
  2137.  
  2138. close file($dataf); /* first, close the file */
  2139.  
  2140. do while(1); /* loop until packet successfully sent */
  2141. if break /* if interrupting... */
  2142. then data = 'D'; /* then tell other side to discard the file */
  2143. else data = ''; /* else tell him to keep it */
  2144. call putp('Z',$n,data); /* send the EOF */
  2145. call getp(typ,seq,data); /* read the response */
  2146.  
  2147. if (typ='Y' & seq=$n) /* if the expected ACK... */
  2148.  | (typ='N' & ((seq-1)&63)=$n) /* or if a NAK for next packet... */
  2149. then do; /* then he got our Z packet */
  2150. call next; /* bump $n, zero $r */
  2151. return; /* done */
  2152. end;
  2153.  
  2154. else if typ='N' | typ='T' then call retry; /* retry other NAKs and timeouts */
  2155.  
  2156. else signal cond(abort); /* else abort transaction on other packets */
  2157.  
  2158. end;
  2159.  
  2160. end sendeof;
  2161. %subtitle 'Protocol Machine -- Sendbreak';
  2162.  
  2163. /* Sendbreak --
  2164.  
  2165. Send the B packet, to delimit a list of files being sent.
  2166.  
  2167. */
  2168.  
  2169. sendbreak: procedure();
  2170.  
  2171. do while(1); /* loop until proper reply recieved */
  2172.  
  2173. call putp('B',$n,nil); /* send the B */
  2174. call getp(typ,seq,data); /* read the reply */
  2175.  
  2176. if      typ='Y' & seq=$n then return; /* done if ACK received */
  2177. else if typ='N' & seq=0  then return; /* also ok if we missed his ACK */
  2178. else if typ='N' & seq=$n then call retry; /* retry if he missed the B */
  2179. else if typ='T'          then call retry; /* retry timeouts etc */
  2180. else signal cond(abort); /* reject others */
  2181.  
  2182. end;
  2183.  
  2184. end sendbreak;
  2185. %subtitle 'Protocol Machine -- Sendlong';
  2186.  
  2187. /* Sendlong --
  2188.  
  2189. This is the main entry for sending commands to a remote server that
  2190. expect a long response.  We just ship off an I packet and then join
  2191. the short response entry 'sendcmd'.
  2192.  
  2193. */
  2194.  
  2195. sendlong: procedure(text);
  2196.  
  2197. dcl text char var; /* UPDATE: command type and text */
  2198.  
  2199. $n, $r = 0; /* initialize packet# and retry# */
  2200.  
  2201. do while(1); /* loop until packet received properly */
  2202. call gparms(data); /* set up our S-parameters */
  2203. call putp('I',$n,data); /* ship it */
  2204. call getp(typ,seq,data); /* read response to our I */
  2205.  
  2206. if typ='Y' & seq=$n then do; /* if we got a good response */
  2207. call rparms(data,'1'b); /* process his parameters */
  2208. call sendcmd(text); /* send the command */
  2209. return; /* and done */
  2210. end;
  2211.  
  2212. else if typ='E' then do; /* he'll send an E if he doesn't like our parameters */
  2213. call default; /* so set up default parameters */
  2214. call sendcmd(text); /* send the command */
  2215. return; /* and done */
  2216. end;
  2217.  
  2218. else if typ='N' | typ='T' then call retry; /* retry NAKs and timeouts */
  2219.  
  2220. else signal cond(abort); /* else abort transaction on other packets */
  2221. end;
  2222.  
  2223. end sendlong;
  2224. %subtitle 'Protocol Machine -- Sendcmd';
  2225.  
  2226. /* Sendcmd --
  2227.  
  2228. This is the entry used by the command interpreter to send commands
  2229. to the remote server Kermit at the other end.  In practice, this is
  2230. probably VM/CMS since they can't connect to us.  The command type
  2231. and text is sent in the parameter 'text'.  The type is taken from the
  2232. first char of 'text', and should be R for Receive, G for Generic, K for
  2233. remote, or C for Host.  The reply to our command (X,Y,S, or F) determines
  2234. whether to send the reply to the screen or to a file.
  2235.  
  2236. */
  2237.  
  2238. sendcmd: procedure(text);
  2239.  
  2240. dcl text  char var; /* UPDATE: command type and text */
  2241. dcl gctyp char(1); /* the command type */
  2242. dcl gctxt char var; /* the command text */
  2243. dcl nname char var; /* override name, if any */
  2244. dcl i     fixed; /* temp */
  2245.  
  2246. /* go through a few shenanigans in order to quote the silly command */
  2247.  
  2248. nname = ''; /* clear override name */
  2249. gctyp = substr(text,1,1); /* extract command (G,R,K, or C) */
  2250. gctxt = substr(text,2); /* and text */
  2251. if gctyp='R' then do; /* called for GET command? */
  2252.    call split(gctxt,nname); /* split up "files" and "newname" args */
  2253.    if params.fncnv then do; /* should we normalize the filename? */
  2254.       data = gctxt; /* yes, move into a 'dstr' for normalization */
  2255.       call normaliz(data); /* normalize the file */
  2256.       gctxt = data; /* then put back */
  2257.    end;
  2258. end;
  2259. open file($dataf) scratch linesize(0); /* must write command here, to quote it *
  2260. /
  2261. put edit(gctxt)(a) file($dataf); /* write command to file */
  2262. reset file($dataf); /* reset it */
  2263. call quote; /* read '$dataf' and quote into '$buf' */
  2264. close file($dataf); /* done with temp file */
  2265.  
  2266. $n, $r = 0; /* initialize seq# and retry# */
  2267.  
  2268. do while(1); /* loop retrying command */
  2269.  
  2270. call putp(gctyp,$n,$buf); /* send the G, R, K, or C packet */
  2271. call getp(typ,seq,data); /* then read the response */
  2272.  
  2273. if typ='Y' & seq=0 then do; /* ACK: short response in data field */
  2274. $buf = data; /* move response to buffer used by 'unquote' */
  2275. call unquote; /* unquote the response */
  2276. put file($screen) line($buf); /* print response */
  2277. return; /* thats all */
  2278. end;
  2279.  
  2280. else if typ='X' & seq<=1 then do; /* X: long response to be printed */
  2281. open file($dataf) frn(frn($screen)); /* output to screen */
  2282. call flush($screen); /* keep output in sync */
  2283. $n = seq; /* NOTE: some Kermits send X with seq=0, and some with seq=1 */
  2284. call putp('Y',$n,nil); /* ACK the X */
  2285. call next; /* bump $n, zero $r */
  2286. break = ''b; /* clear flag, so readdata won't abort xmit */
  2287. call readdata; /* read the data packets */
  2288. close file($dataf); /* done with command response */
  2289. call readhdr(''); /* look for B, F, or X */
  2290. return; /* then done */
  2291. end;
  2292.  
  2293. else if typ='S' & seq=0 then do; /* S: file about to come */
  2294. call rparms(data,'0'b); /* read his parameters */
  2295. call gparms(data); /* assemble our parameters */
  2296. call putp('Y',$n,data); /* send our parameters in the ACK */
  2297. call next; /* Bump $n, zero $r */
  2298. call readhdr(nname); /* look for B, F, or X */
  2299. return; /* and done */
  2300. end;
  2301.  
  2302. else if typ='N' | typ='T' then call retry; /* retry NAKs and Timeouts */
  2303.  
  2304. else signal cond(abort); /* else abort on wierd packets */
  2305.  
  2306. end;
  2307.  
  2308. end sendcmd;
  2309. %Subtitle 'Protocol Machine -- Quote';
  2310.  
  2311. /* Quote--
  2312.  
  2313. Read input from $dataf and quote into $qbuf.  This is the only routine
  2314. that quotes data.  It handles control, 8th bit, and repeat prefixing,
  2315. as well as breaking up data into appropriate sized blocks.
  2316.  
  2317. We keep an explicit buffer of input chars, for efficiency and to be able
  2318. to back up easily.  Before trying to understand either this routine or its
  2319. sister "unquote", it is helpful to take a look (in the global dcls) at how
  2320. $buf overlays $qbuf.  We do this for several reasons:
  2321.    1. It lets $but be longer than normal 'dstr' strings, which is helpful
  2322.       when unquoting, which can make the data get very much larger due to
  2323.       repeat sequences.
  2324.    2. It is more efficient to refer to $qbuf than to $buf, in some cases,
  2325.       because the PL/I compiler has trouble dealing with varying length
  2326.       strings.  $Qbuf is declared nonvarying.
  2327.  
  2328. Note that the use of "ovy.c4" requires subscriptrange to be turned off.
  2329.  
  2330. */
  2331.  
  2332. (nosubscriptrange, nostringrange):
  2333.  
  2334. quote: procedure();
  2335.  
  2336. dcl nc       fixed static; /* next char in input buffer */
  2337. dcl tc       fixed static; /* total #chars in input buffer */
  2338. dcl buf(2048)char(1) static; /* input buffer */
  2339. dcl uctl(0:255) fixed static; /* used to "uncontrol" chars */
  2340. dcl 1 ovy, /* used for mapping char(1)//fixed */
  2341.     2 c4(0)  char(1), /* character representation */
  2342.     2 c      fixed; /* fixed representation */
  2343. dcl (i,j,k)  fixed; /* temps */
  2344.  
  2345. /* if file has not been read, our static data is uninitialized */
  2346.  
  2347. if loc($dataf)=1 then do; /* is our buffer initialized? */
  2348. nc = 1; /* no, initialize next char pos */
  2349. tc = 0; /* and set buffer empty */
  2350. ovy.c = 0; /* clear next-char buffer */
  2351. do i = 0 to 127; /* loop initializing "uctl" */
  2352. uctl(i)     = 0; /* assume not necessary to prefix */
  2353. uctl(i+128) = 0; /* handle 8-th bit too */
  2354. if i=params.qctl /* if a prefix... */
  2355.  | i=params.qbin
  2356.  | i=params.rept
  2357. then do; /* these must be prefixed, but not modified */
  2358.   uctl(i)     = i; /* ie, send '#' as '##' */
  2359.   uctl(i+128) = i + 128; /* must escape '#' with 8-th bit set too! */
  2360. end;
  2361. if i<32 then do; /* true control char? */
  2362.   uctl(i)     = i + 64; /* map 0-37b3 ==> 100-137b3 */
  2363.   uctl(i+128) = i + 64 + 128; /* map 200-237b3 ==> 300-337b3 */
  2364. end;
  2365. if i=127 then do; /* ASCII DEL? */
  2366.   uctl(i)     = 77b3; /* map 177b3 ==> 77b3 */
  2367.   uctl(i+128) = 77b3 + 128; /* map 377b3 ==> 277b3 */
  2368. end;
  2369. end;
  2370. end;
  2371.  
  2372. i = 0; /* #chars packed into $qbuf */
  2373.  
  2374. /* loop getting a char from buffer, quoting, and packing into $qbuf */
  2375. /* we keep i=(length of quoted text), across this loop              */
  2376.  
  2377. do while(1); /* loop until $qbuf filled or $dataf exhausted */
  2378.  
  2379. /* test for buffer empty */
  2380.  
  2381. if nc>tc then do; /* buffer empty? */
  2382. j = matread($dataf,buf,wlen(buf)); /* read next bufferload */
  2383. if j=-1 then signal transmit($dataf); /* error? */
  2384. tc = j * 4; /* get #chars read */
  2385. if tc=0 then do; /* none? */
  2386. $qlen = i; /* yes, set length of $qbuf */
  2387. return; /* and exit */
  2388. end;
  2389. nc = 1; /* reset next char# to read */
  2390. if (lof($dataf)+1)=loc($dataf) then do; /* did we read last word in file? */
  2391. do j = tc to tc-2 by -1 while(buf(j)=chr(0)); /* skip trailing nulls */
  2392. tc = tc - 1; /* skip up to 3 nulls in last word */
  2393. end;
  2394. end;
  2395. end;
  2396.  
  2397. ovy.c4(4) = buf(nc); /* get next input char */
  2398. nc = nc + 1; /* bump next char# */
  2399.  
  2400. /* handle repeated occurances, if enabled */
  2401.  
  2402. if params.rept~=0 then do; /* repeats allowed? */
  2403. j = min(tc-nc+1,93); /* how many chars left in buffer? */
  2404. do k = 1 to j while(buf(nc+k-1)=ovy.c4(4)); /* count dups */
  2405. end;
  2406. if k>3 then do; /* enough for use of repeat prefix? */
  2407. substr($qbuf,i+1,1) = chr(params.rept); /* yes! pack prefix */
  2408. substr($qbuf,i+2,1) = chr(k+32); /* pack in count */
  2409. i = i + 2; /* adjust length of $qbuf */
  2410. nc = nc + k - 1; /* adjust cur char in buffer */
  2411. end;
  2412. end;
  2413.  
  2414. /* test for parity bits, and handle if set */
  2415.  
  2416. if (ovy.c&600b3)~=0 then do; /* parity bits set? */
  2417. if (ovy.c&400b3)~=0 then do; /* yes, test 9th bit */
  2418.    break = '1'b; /* set, so abort transmission */
  2419.    $qlen = 0 ; /* by faking EOF and setting not-sent flag */
  2420.    call logf('binary files (9th bits set) cannot be transferred');
  2421.    return; /* and done (data path only 8 bits wide) */
  2422. end;
  2423. if params.qbin~=0 then do; /* 8th bit prefix needed? */
  2424. ovy.c = ovy.c & 177b3; /* yes, strip off 8th bit */
  2425. substr($qbuf,i+1,1) = chr(params.qbin); /* then pack in the prefix */
  2426. i = i + 1; /* bump len of $qbuf */
  2427. end;
  2428. end;
  2429.  
  2430. /* does this char need to be escaped w the control prefix? */
  2431.  
  2432. if uctl(ovy.c)~=0 then do; /* control prefix required? */
  2433. ovy.c = uctl(ovy.c); /* yes, uncontrollify */
  2434. substr($qbuf,i+1,1) = '#'; /* pack in control prefix */
  2435. i = i + 1; /* bump len of $qbuf */
  2436. end;
  2437.  
  2438. /* pack char into $qbuf and test for possible overflow */
  2439.  
  2440. substr($qbuf,i+1,1) = ovy.c4(4); /* pack char into $qbuf */
  2441. i = i + 1; /* bump len of $qbuf */
  2442. if i>(params.maxl-5-6) then do; /* might next char sequence overflow packet len?
  2443.  */
  2444. $qlen = i; /* yes, set length of $qbuf */
  2445. return; /* and done */
  2446. end;
  2447. end;
  2448.  
  2449. end quote;
  2450. %subtitle 'Protocol Machine -- Unquote';
  2451.  
  2452. /* Unquote --
  2453.  
  2454. Unquote '$qbuf' in place.  Due to repeats, the unquoted text can be much
  2455. larger then the original, so the declaration of $qbuf is kludged up by
  2456. padding it with lots of extra space so it will never overflow.  Refer to "quote"
  2457. .
  2458.  
  2459. This is the only routine that unquotes data.  The approach is to process
  2460. text from left to right, packing data between prefixes down against the
  2461. bottom of $qbuf, and handling prefixes as they occur.  We use the INDEX bif
  2462. to search for the next prefix.  If expanding a repeat would overwrite
  2463. still-quoted text, we move the remaining quoted text to the end of $qbuf.
  2464. At all times, the variables 'a', 'b', and 'c' delimit the processed and
  2465. unprocessed text as follows:
  2466.  
  2467.  1                         a        b                 b+c    (end of $qbuf)
  2468. +---------------------------+------+------------------+---------+
  2469. | processed (unquoted) text |      | unprocessed text |         |
  2470. +---------------------------+------+------------------+---------+
  2471.  
  2472. */
  2473.  
  2474. (nostringrange):
  2475.  
  2476. unquote: procedure();
  2477.  
  2478. dcl a        fixed; /* length, or last char pos, of processed text */
  2479. dcl b        fixed; /* start of unprocessed text (first char pos) */
  2480. dcl c        fixed; /* length of unprocessed text remaining */
  2481. dcl (x,y,z)  fixed; /* pos of next #, &, and ~ prefix ((b+c+1) if none) */
  2482. dcl (i,j,k)  fixed; /* temps */
  2483.  
  2484. a = 0; /* no processed text yet */
  2485. b = 1; /* first unprocessed char */
  2486. c = $qlen; /* length of unprocessed text */
  2487.  
  2488. substr($qbuf,c+1,1) = chr(params.qctl); /* delimit text w control prefix */
  2489. x = index($qbuf,chr(params.qctl)); /* find leftmost '#' */
  2490. if params.qbin=0 /* if no 8th bit prefixing... */
  2491. then y = 777777b3; /* then say leftmost is very far right indeed */
  2492. else do; /* we are 8th bit prefixing */
  2493.      substr($qbuf,c+2,1) = chr(params.qbin); /* delimit text with prefix */
  2494.      y = index($qbuf,chr(params.qbin)); /* find leftmost */
  2495. end;
  2496. if params.rept=0 /* if no repeat prefixing... */
  2497. then z = 777777b3; /* then say leftmost '~' is very far right indeed */
  2498. else do; /* we are repeat prefixing */
  2499.      substr($qbuf,c+3,1) = chr(params.rept); /* delimit text w prefix */
  2500.      z = index($qbuf,chr(params.rept)); /* find leftmost */
  2501. end;
  2502.  
  2503. LOOP: do while(c>0); /* loop until all text processed */
  2504.  
  2505. i = min(x,y,z); /* get leftmost prefix */
  2506. j = min(i-b,c); /* get unprocessed chars before prefix */
  2507.  
  2508. if j<0 then do; /* leftmost prefix to left of unprocessed chars?? */
  2509.    if x<b then x = index($qbuf,chr(params.qctl),b); /* find next '#' */
  2510.    if y<b then y = index($qbuf,chr(params.qbin),b); /* find next '&' */
  2511.    if z<b then z = index($qbuf,chr(params.rept),b); /* find next '~' */
  2512.    goto LOOP; /* this can happen when a prefix is used literally... */
  2513. end; /* for instance, '#~' or '~#&#A' */
  2514.  
  2515. if j~=0 & b~=1 /* if any plain-text, and it needs to be moved... */
  2516. then substr($qbuf,a+1,j) = substr($qbuf,b,j); /* pack down plain-text */
  2517.  
  2518. a = a + j; /* adjust length of processed text */
  2519. b = b + (j + 1); /* and start of unprocessed (skipping the prefix) */
  2520. c = c - (j + 1); /* and length of remaining unprocessed text */
  2521.  
  2522. if c>0 then do; /* if the prefix was not a delimiter past end of input... */
  2523.  
  2524. if i=x then do; /* handle control char at b */
  2525. call control; /* move down to processed text */
  2526. x = index($qbuf,chr(params.qctl),b); /* find next '#' */
  2527. end;
  2528.  
  2529. else if i=y then do; /* handle 8th bit prefix */
  2530.      if b=x then do; /* but wait, is it a '&#' sequence? */
  2531.         b = b + 1; /* yes, skip the # too */
  2532.         c = c - 1; /* adjust length of unprocessed text */
  2533.         call control; /* handle control character at b */
  2534.         x = index($qbuf,chr(params.qctl),b); /* find next '#' */
  2535.      end;
  2536.      else call move1; /* else just move down char at b */
  2537.      byte($qbuf,a) = byte($qbuf,a) | 200b3; /* add the 8th bit */
  2538.      y = index($qbuf,chr(params.qbin),b); /* find next '&' */
  2539. end;
  2540.  
  2541. else do; /* handle repeat prefix */
  2542.      i = byte($qbuf,b) - 32 - 1; /* get repeat count (one too low) */
  2543.      b = b + 1; /* point to repeated character */
  2544.      c = c - 1; /* adjust length */
  2545.      j = 0; /* assume no 8th bit prefix involved */
  2546.      if b>=y & substr($qbuf,b,1)=chr(params.qbin) then do; /* 8-bit prefix? */
  2547.         j = 200b3; /* yes, remember to add this bit in */
  2548.         b = b + 1; /* then advance past the '&' */
  2549.         c = c - 1; /* adjust len */
  2550.      end;
  2551.      if b>=x & substr($qbuf,b,1)=chr(params.qctl) then do; /* repeated control c
  2552. har? */
  2553.         b = b + 1; /* yes, advance past the '#' */
  2554.         c = c - 1; /* adjust len */
  2555.         call control; /* handle control character at b */
  2556.      end;
  2557.      else call move1; /* else just move down character at b */
  2558.      if j~=0 then byte($qbuf,a) = byte($qbuf,a) | 200b3; /* or in 8th bit if nee
  2559. ded */
  2560.      z = index($qbuf,chr(params.rept),b); /* find next '~' */
  2561.      if (a+i)>=b & c>0 then do; /* must we move up unprocessed text? */
  2562.         k = length($qbuf) - 3 - c; /* will move text here (save room for xtra pr
  2563. efixes) */
  2564.         substr($qbuf,k,c+3) = substr($qbuf,b,c+3); /* move way up */
  2565.         x = x + (k-b); /* adjust positions of next prefixes */
  2566.         y = y + (k-b);
  2567.         z = z + (k-b);
  2568.         b = k; /* new start of unprocessed text */
  2569.      end;
  2570.      k = a + i; /* new end of processed text */
  2571.      do while(a<k); /* loop duplicating repeated character */
  2572.         a = a + 1; /* bump length of processed text */
  2573.         substr($qbuf,a,1) = substr($qbuf,a-1,1); /* copy another char */
  2574.      end;
  2575. end;
  2576. end;
  2577.  
  2578. end; /* of 'do while(c>0);' */
  2579.  
  2580. $qlen = a; /* set length of processed, unquoted, text */
  2581. return; /* and done */
  2582.  
  2583. /* move one char from unprocessed to processed block */
  2584.  
  2585. move1: procedure();
  2586.  
  2587. substr($qbuf,a+1,1) = substr($qbuf,b,1); /* move the char */
  2588. a = a + 1; /* adjust length of processed text */
  2589. b = b + 1; /* and start of unprocessed text */
  2590. c = c - 1; /* and length of unprocessed text */
  2591.  
  2592. end move1;
  2593.  
  2594. /* make char at 'b' a control char, and move down */
  2595.  
  2596. control: procedure();
  2597.  
  2598. k = byte($qbuf,b); /* get the char */
  2599. if ((k&177b3)>=100b3) & ((k&177b3)<=137b3)
  2600. then k = k - 100b3; /* map 100-137 ==> 000-037 */
  2601. else if (k&177b3)=77b3
  2602. then k = k + 100b3; /* map 077 ==> 177 */
  2603. byte($qbuf,a+1) = k; /* move down */
  2604. a = a + 1; /* adjust length of processed text */
  2605. b = b + 1; /* adjust start of unprocessed test */
  2606. c = c - 1; /* and length of unprocessed text */
  2607.  
  2608. end control;
  2609.  
  2610. end unquote;
  2611. %subtitle 'Protocol Machine -- Gparms';
  2612.  
  2613. /* Gparms --
  2614.  
  2615. Assemble our preferred protocol parameters into S-format.
  2616.  
  2617. */
  2618.  
  2619. gparms: procedure(d);
  2620.  
  2621. dcl d dstr; /* OUTPUT: the S-format parameters */
  2622.  
  2623. d = (9)' '; /* we send a 9-character string */
  2624. byte(d,1) = 94+32; /* allow 94-character packets */
  2625. byte(d,2) =  7+32; /* timeout after 7 seconds */
  2626. byte(d,3) =  0+32; /* no padding characters needed */
  2627. byte(d,5) = ascii('cr')+32; /* please put a CR at the end of every packet */
  2628. substr(d,6,1) = '#'; /* the control character quote */
  2629. substr(d,7,1) = 'Y'; /* we can quote 8th bit if other end needs to */
  2630.  
  2631. if params.iwant~=0 /* if we have a preferred checksum type... */
  2632. then byte(d,8) = params.iwant + ascii('0'); /* then state it */
  2633. else if params.uwant~=0 /* else if you have a preferred checksum... */
  2634. then byte(d,8) = params.uwant + ascii('0'); /* then that's OK with us */
  2635. else substr(d,8,1) = '1'; /* else, lets use one character checksums */
  2636.  
  2637. if params.rept~=0 /* if other end has already suggested a repeat character... */
  2638. then byte(d,9) = params.rept; /* then use it */
  2639. else byte(d,9) = ascii('~'); /* else suggest we repeat with '~' */
  2640.  
  2641. end gparms;
  2642. %subtitle 'Protocol Machine -- Rparms';
  2643.  
  2644. /* Rparms --
  2645.  
  2646. Process S-format parameters received from other end.  Note that, for most fields
  2647. ,
  2648. a space means use the default value; we assume 'default' has already been called
  2649. .
  2650.  
  2651. */
  2652.  
  2653. rparms: procedure(d,sent);
  2654.  
  2655. dcl d    dstr; /* INPUT: data field of packet */
  2656. dcl sent bit(1) aligned; /* INPUT: true if we've already sent our suggestions */
  2657. dcl d9   char(9); /* we only interpret the first 9 bytes */
  2658. dcl i; /* temp */
  2659.  
  2660. (nostrz): d9 = d; /* truncate or space fill, as necessary */
  2661.  
  2662. if substr(d9,1,1)~='' then params.maxl = byte(d9,1) - 32; /* max packet len */
  2663. if substr(d9,2,1)~='' then params.time = byte(d9,2) - 32; /* timeout */
  2664. if substr(d9,3,1)~='' then params.npad = byte(d9,3) - 32; /* #padding chars */
  2665. if substr(d9,4,1)~='' then do; /* handle padding char, if specified */
  2666.    i = byte(d9,4); /* get it so we can 'uncontrol' it */
  2667.    if (i&64)~=0 /* if was 0-37b3 */
  2668.    then params.padc = i - 64; /* then set up */
  2669.    else params.padc = i + 64; /* probably padding with DELs (177b3) */
  2670. end;
  2671. if substr(d9,5,1)~='' then params.eol  = byte(d9,5) - 32; /* packet delimiter */
  2672. if substr(d9,6,1)~='' then params.qctl = byte(d9,6); /* control char quote */
  2673. if substr(d9,7,1)~='' then do; /* handle 8th bit quoting, if requested */
  2674.    i = byte(d9,7); /* get requested quote character */
  2675.    if (i>=33 & i<=62) /* if in allowed range... */
  2676.     | (i>=96 & i<=126) /* (this handles values of 'N' and 'Y' properly) */
  2677.    then params.qbin = i; /* then use this char to quote 8th bit */
  2678.    else params.qbin = 0; /* else no quoting */
  2679. end;
  2680. if substr(d9,8,1)~='' then do; /* handle checksum type */
  2681.    i = byte(d9,8) - ascii('0'); /* map to 1-3 */
  2682.    if (i=1 | i=2) /* if an implemented checksum type... */
  2683.    then params.uwant = i; /* then remember your preference */
  2684.    else params.uwant = 0; /* treat unimplemented requests like no requests */
  2685.    if ~sent & params.iwant=0 /* if I haven't sent mine, and I have no preference
  2686.  */
  2687.    then params.iwant = params.uwant; /* then I'll do whatever you want */
  2688. end;
  2689. if substr(d9,9,1)~='' then do; /* handle repeating, if requested */
  2690.    i = byte(d9,9); /* get his suggestion */
  2691.    if ~sent /* if we haven't yet sent our parameters... */
  2692.     | (i=ascii('~')) /* or if we've sent our suggestion, and he agrees */
  2693.    then params.rept = i; /* then we'll be repeat prefixig */
  2694.    else params.rept = 0; /* else no repeating */
  2695. end;
  2696. end rparms;
  2697. %subtitle 'Protocol Machine -- Odometer';
  2698.  
  2699. /* Odometer --
  2700.  
  2701. This routine keeps track of the #bytes transferred, when we are running
  2702. locally.  We must be called at the beginning of each xfer, whenever bytes
  2703. are successfully transferred, and at the end.  In addition, there is a
  2704. special entrance called when a transaction is aborted, to disable the odometer.
  2705.  
  2706. */
  2707.  
  2708. odometer: procedure(n);
  2709.  
  2710. dcl n  fixed; /* INPUT: action  0:disable  1:stop  2:start  3:advance */
  2711. dcl m  fixed static init(-1); /* the 'odometer', initially off */
  2712.  
  2713. do case(n+1); /* branch on action */
  2714. if m>=0 then do; /* 0: DISABLE ODOMETER (called via 'signal(abort)') */
  2715.    m = -1; /* stop it */
  2716.    put file($screen) skip; /* advance to next line */
  2717. end;
  2718. if m>=0 then do; /* 1: TURN OFF ODOMETER (xfer complete) */
  2719.    put file($screen) skip(0) edit(m,' bytes transferred')(f(7),a); /* final #byt
  2720. es */
  2721.    put file($screen) skip(1); /* advance to next line */
  2722.    m = -1; /* turn off the odometer */
  2723. end;
  2724. do; /* 2: TURN ON ODOMETER (start of xfer) */
  2725.    if local & ~debuging /* must be local, but if debugging plenty of output anyw
  2726. ay... */
  2727.    then m = 0; /* start counter by setting nonnegative */
  2728. end;
  2729. if m>=0 then do; /* 3: ADVANCE ODOMETER (bytes xferred) */
  2730.    m = m + length($buf); /* bump counter ($buf always contains the bytes xferred
  2731. ) */
  2732.    if ($n&3)=0 then do; /* update screen every 4 packets... */
  2733.       put file($screen) skip(0) edit(m,' bytes transferred')(f(7),a);
  2734.       call flush($screen); /* update display immediately */
  2735.    end;
  2736. end;
  2737. end;
  2738.  
  2739. end odometer;
  2740. %subtitle 'Protocol Machine -- Subroutines';
  2741.  
  2742.  
  2743. /* bump $n (modulo 64) and zero $r */
  2744.  
  2745. next: procedure();
  2746.  
  2747. $n = ($n + 1) & 63; /* bump mod 64 */
  2748. $r = 0; /* zero retry count */
  2749.  
  2750. end next;
  2751.  
  2752.  
  2753. /* bump $r, checking for retry threshold */
  2754.  
  2755. retry: procedure();
  2756.  
  2757. $r = $r + 1; /* bump retry count */
  2758. if $r>params.maxretry /* if too many retries... */
  2759. then signal cond(abort); /* then abort the transaction */
  2760.  
  2761. end retry;
  2762.  
  2763.  
  2764. /* handle protocol error */
  2765.  
  2766. error: procedure(msg);
  2767.  
  2768. dcl msg char var; /* the error message */
  2769.  
  2770. if local /* if a local Kermit... */
  2771. then $errmsg = msg; /* then put in canonic spot */
  2772. else call putp('E',$n,(msg)); /* else send an Error packet */
  2773.  
  2774. signal cond(abort); /* then abort the transaction */
  2775.  
  2776. end error;
  2777.  
  2778.  
  2779. /* Unquote '$buf' and write to '$dataf'  */
  2780.  
  2781. putb: procedure();
  2782.  
  2783. linesize($dataf) = 0; /* turn off linewrap */
  2784. call unquote; /* unquote '$buf' */
  2785. put file($dataf) edit($buf)(a); /* write data to output file */
  2786.  
  2787. end putb;
  2788.  
  2789.  
  2790. /* choose checksum type: called after an S-parameter exchange */
  2791.  
  2792. choose: procedure();
  2793.  
  2794. if params.iwant~=0 /* if I've expressed a preference... */
  2795.  & params.iwant=params.uwant /* ...and if you've agreed... */
  2796. then params.chkt = params.iwant; /* then lets use it */
  2797. else params.chkt = 1; /* else use default (1-char) checksums */
  2798.  
  2799. end choose;
  2800.  
  2801. end protocol;
  2802. %subtitle 'Protocol Machine -- Default Parameters';
  2803.  
  2804. /* Default --
  2805.  
  2806. Sets up the default values of the protocol parameters, which are defined
  2807. by the protocol.
  2808.  
  2809. */
  2810.  
  2811. default: procedure();
  2812.  
  2813. %list off;%include 'params';%list on;
  2814.  
  2815. params.maxl = 80; /* max packet length */
  2816. params.time = 5; /* 5 timeout in seconds */
  2817. params.npad = 0; /* #padding characters */
  2818. params.padc = 0; /* pad with NULLs */
  2819. params.eol  = ascii('cr'); /* terminate packets with an CR */
  2820. params.qctl = ascii('#'); /* quote control chars with '#' */
  2821. params.qbin = 0; /* don't quote 8th bits */
  2822. params.chkt = 1; /* single character checksums */
  2823. params.rept = 0; /* don't use repeat counts */
  2824. params.A    = '0'b; /* don't use A(ttribute) packets */
  2825. params.fncnv= '0'b; /* don't map filenames */
  2826. params.overwrit = ''b; /* don't overwrite existing output files by default */
  2827. params.maxretry = 5; /* max retry count */
  2828. params.iwant    = 0; /* this Kermit has not expressed a checksum preference */
  2829. params.uwant    = 0; /* nor has other end */
  2830.  
  2831. end default;
  2832. %subtitle 'Transmission -- Putp';
  2833.  
  2834. /* Putp --
  2835.  
  2836. Write a packet.  We assemble the padding characters, headers, checksum,
  2837. trailing character, and ship it off.  The data is assumed to be already
  2838. quoted, if necessary.
  2839.  
  2840. If the write operation fails, we signal the 'abort' condition, which causes
  2841. the protocol machine to abort the current transaction.  Otherwise, if we
  2842. return, the packet has left DCTS successfully.
  2843.  
  2844. */
  2845.  
  2846. (nosubscriptrange, nostringrange):
  2847.  
  2848. putp: procedure(type,seqn,data);
  2849.  
  2850. %list off;%include 'params';%list on;
  2851.  
  2852. dcl type     char(1); /* INPUT: packet type */
  2853. dcl seqn     fixed; /* INPUT: sequence# */
  2854. dcl data     dstr; /* INPUT: data field */
  2855.  
  2856. dcl iobuf    char(150); /* I/O buffer */
  2857. dcl ld       fixed; /* length(data) */
  2858. dcl cks      fixed; /* checksum */
  2859. dcl (i,j)    fixed; /* temps */
  2860. dcl (m1,m2)  fixed static; /* MME parameters */
  2861. dcl regs(0:11) fixed; /* MME parameters */
  2862.  
  2863. dcl checksum entry(char(150),fixed,fixed); /* to compute checksums */
  2864. dcl debugp   entry(char(150),char var); /* to log debugging information */
  2865. dcl lostconn entry(fixed); /* called when connection lost */
  2866.  
  2867. ld = length(data); /* get length of packet data field */
  2868.  
  2869. /* assemble the characters to be written */
  2870.  
  2871. do j = 1 to params.npad; /* first, the padding chars */
  2872.    substr(iobuf,j,1) = chr(params.padc); /* pack in a padding char */
  2873. end;
  2874. substr(iobuf,j+0,1)  = chr(01); /* the mark */
  2875. substr(iobuf,j+1,1)  = chr(ld+2+params.chkt+32); /* length of packet */
  2876. substr(iobuf,j+2,1)  = chr(seqn+32); /* sequence# */
  2877. substr(iobuf,j+3,1)  = type; /* packet type */
  2878. substr(iobuf,j+4,ld) = data; /* then the data */
  2879. j = j + (ld + 3); /* #chars packed into iobuf so far */
  2880.  
  2881. /* append checksum to packet */
  2882.  
  2883. call checksum(iobuf,j,cks); /* compute checksum */
  2884. cks = cks - params.npad*params.padc; /* don't checksum padding characters! */
  2885. do case(params.chkt); /* branch on checksum type */
  2886. do; /* type 1: single character */
  2887.   substr(iobuf,j+1,1) = chr(((cks+shr(cks&192,6))&63)+32); /* compute per protoc
  2888. ol */
  2889. end;
  2890. do; /* type 2: double character */
  2891.   substr(iobuf,j+1,1) = chr((shr(cks,6)&63)+32); /* bits 007700b3 of checksum */
  2892.   substr(iobuf,j+2,1) = chr((cks&63)+32); /* bits 000077b3 of checksum */
  2893. end;
  2894. end;
  2895. j = j + params.chkt; /* adjust running sum of length */
  2896.  
  2897. /* log packet if debugging */
  2898.  
  2899. if debuging then call debugp(iobuf,'sent');
  2900.  
  2901. /* append terminating EOL */
  2902.  
  2903. substr(iobuf,j+1,1) = chr(params.eol); /* the terminator */
  2904. j = j + 1; /* count the terminator */
  2905.  
  2906. /* write the packet using a MME, so we can indicate its exact character length *
  2907. /
  2908.  
  2909. m1 = waddr(iobuf); /* point to the I/O buffer */
  2910. m2 = shr(j+3,2); /* get word length of packet */
  2911. unspec(regs) = ''b; /* clear the registers */
  2912. regs(1) = waddr(m1); /* X1: ptr to ptr to buffer */
  2913. regs(2) = frn($comf); /* X2: frn of destination */
  2914. regs(4) = (-j)&3; /* X4: #padding characters, in destination field */
  2915. regs(7) = waddr(m2); /* X7: ptr to #words to write */
  2916. do i = 1 to params.maxretry; /* loop retrying bad statuses */
  2917.    $timeout = - params.time; /* set the timer running */
  2918.    call mme(500234b3,regs); /* Write the buffer */
  2919.    $timeout = 0; /* stop the timer */
  2920.    if regs(10)=0 then return; /* exit if good status */
  2921. end;
  2922.  
  2923. call lostconn(regs(10)); /* lost connection if cannot write after several retrie
  2924. s */
  2925.  
  2926. end putp;
  2927. %subtitle 'Transmission -- Getp';
  2928.  
  2929. /* Getp --
  2930.  
  2931. Input a packet.  We verify the checksum and unpack it into the component
  2932. fields of interest to higher levels.  Most errors detectible at this level,
  2933. such as timeouts, short packets, bad checksums, etc result in the return of
  2934. a fake 'T' packet, with sequence#=$n.  If we get an error ("E") packet, all
  2935. we do is save the error message; the caller is responsible for signalling
  2936. abort, if he wants to.  True I/O errors (that seem fatal) cause "abort" to
  2937. be signalled, at least.  The data is NOT unquoted.
  2938.  
  2939. */
  2940.  
  2941. (nostringrange, nosubscriptrange):
  2942.  
  2943. getp: procedure(type,seqn,data);
  2944.  
  2945. %list off;%include 'params';%list on;
  2946.  
  2947. dcl type     char(1); /* OUTPUT: packet type */
  2948. dcl seqn     fixed; /* OUTPUT: sequence# */
  2949. dcl data     dstr; /* OUTPUT: quoted data field */
  2950.  
  2951. dcl iobuf    char(150); /* I/O buffer */
  2952. dcl cks      fixed; /* checksum */
  2953. dcl pl       fixed; /* packet length in chars */
  2954. dcl (i,j)    fixed; /* temps */
  2955.  
  2956. dcl checksum entry(char(150),fixed,fixed); /* to compute checksums */
  2957. dcl debugp   entry(char(150),char var); /* debugging output */
  2958. dcl lostconn entry(fixed); /* called if connection lost */
  2959.  
  2960. /* loop issueing the read, retrying until we get solid data, error, or timeout *
  2961. /
  2962.  
  2963. i, j = 0; /* initialize position of mark (001) and retry count */
  2964. do while(j=0); /* loop until we get valid data */
  2965.    $timeout = - params.time; /* set clock ticking */
  2966.    pl = matread($comf,iobuf,wlen(iobuf)) * 4; /* read the packet, set pl:=#bytes
  2967.  */
  2968.    $timeout = 0; /* stop the clock */
  2969.    j = index(substr(iobuf,1,max(pl,0)),chr(1)); /* look for start of packet */
  2970.    if j=0 then do; /* if no mark character (001) in data read... */
  2971.       if pl<0 then do; /* I/O error: trouble! */
  2972.          if stw1($comf)=20000000b3 /* if reset status... */
  2973.          then call T('timeout'); /* then say we got a timeout */
  2974.          else call lostconn(stw1($comf)); /* signal abort on other bad statuses
  2975. */
  2976.       end;
  2977.       i = i + 1; /* bump retry count */
  2978.       if i>params.maxretry then call T('too many retries');
  2979.    end;
  2980. end;
  2981.  
  2982. /* trim off leading and trailing junk */
  2983.  
  2984. if j>1 then do; /* if junk preceeds packet in data read... */
  2985.    pl = pl - j + 1; /* adjust length of packet by #bytes preceeding 001 */
  2986.    if pl<5 then call T('too short'); /* minimum packet size in 5 chars */
  2987.    substr(iobuf,1,pl) = substr(iobuf,j,pl); /* move down in buffer */
  2988. end;
  2989. j = byte(iobuf,2) - 32 + 2; /* get logical packet length */
  2990. if j<5 | j>96 then call T('bogus packet len'); /* valid length field? */
  2991. if j>pl then call T('truncated'); /* return error if entire packet not read */
  2992. pl = j; /* forget trailing junk (the CR, NULLs, etc) */
  2993.  
  2994. /* Heuristic: infer checksum method from packet received (suggested by protocol)
  2995.  */
  2996.  
  2997. if      substr(iobuf,4,1)='S' then params.chkt = 1; /* S packets always type 1 *
  2998. /
  2999. else if substr(iobuf,4,1)='N' then do; /* N packets are 'universal synchronizers
  3000. ' */
  3001.      if (pl-4)>0 & (pl-4)<3 /* data field of N packets always null... */
  3002.      then params.chkt = pl - 4; /* so infer checksum length from packet length..
  3003. . */
  3004. end; /* (as long as latter is reasonable!) */
  3005.  
  3006. /* log packet if debugging is enabled */
  3007.  
  3008. if debuging then call debugp(iobuf,'rcvd');
  3009.  
  3010. /* Checksum data */
  3011.  
  3012. do case(params.chkt); /* branch on checksum type */
  3013. cks = byte(iobuf,pl)-32; /* type 1: get single byte checksum */
  3014. cks = (byte(iobuf,pl-1)-32)*64 + byte(iobuf,pl)-32; /* type 2: double byte */
  3015. end;
  3016. pl = pl - params.chkt; /* strip checksum off input string */
  3017. if pl<4 then call T('malformed'); /* too short? */
  3018. call checksum(iobuf,pl,i); /* compute checksum */
  3019. do case(params.chkt); /* branch on checksum type once again */
  3020.   if cks~=((i+shr(i&192,6))&63) then call T('chks err 1'); /* type 1 */
  3021.   if cks~=(i&7777b3) then call T('chks err 2'); /* type 2 */
  3022. end;
  3023.  
  3024. /* extract fields, save message if 'E', and return */
  3025.  
  3026. seqn = byte(iobuf,3) - 32; /* get sequence# */
  3027. data = substr(iobuf,5,pl-4); /* get data */
  3028. type = substr(iobuf,4,1); /* get packet type */
  3029.  
  3030. if type='E' then $errmsg = data; /* save message if error packet */
  3031.  
  3032. ERR: return; /* done */
  3033.  
  3034.  
  3035. /* return T packet on error */
  3036.  
  3037. T: proc(msg);
  3038.  
  3039. dcl msg char var; /* INPUT: error message */
  3040.  
  3041. if debuging /* log reception errors if debugging */
  3042. then call debugp(iobuf,'rcvd in error ('||msg||') stw1:'||octal(stw1($comf)));
  3043.  
  3044. type = 'T'; /* set type to generic error */
  3045. seqn = $n; /* set seq# to what is expected (this is required) */
  3046. data = ''; /* nullify data field */
  3047. goto ERR; /* return to getp's caller */
  3048.  
  3049. end T;
  3050.  
  3051. end getp;
  3052. %subtitle 'Transmission -- Lostconn';
  3053.  
  3054. /* Lostconn --
  3055.  
  3056. Called from GETP and PUTP, when we get a bad status on the MME, other than
  3057. commfile busy (6) and reset status (20), which are usually retried.  The connect
  3058. ion
  3059. appears to be dead.  If in server mode, all communication has been lost with
  3060. the world, so we can only terminate.  If local, we print an error message and
  3061. signal "abort", which will cause us to return to command level and report the
  3062. problem.
  3063.  
  3064. */
  3065.  
  3066. lostconn: procedure(stw1);
  3067.  
  3068. %list off;%include 'params';%list on;
  3069.  
  3070. dcl stw1 fixed; /* INPUT: stw1 from mme */
  3071. dcl logf entry(char var); /* to log messages */
  3072.  
  3073. call logf('Connection closed due to communication error ('||octal(stw1)||')');
  3074.  
  3075. if serving /* if in server mode... */
  3076. then stop; /* then there's no recovery possible */
  3077. else signal cond(abort); /* else abort protocol, back to command processing */
  3078.  
  3079. end lostconn;
  3080. %subtitle 'Transmission -- Debugp';
  3081.  
  3082. /* Debugp --
  3083.  
  3084. Print debugging information regarding packet transmission.  Called from
  3085. the lowest level, ie 'getp' and 'putp'.  The packet we are passed may
  3086. contain leading and trailing padding, but has otherwise been checked for
  3087. gross format consistenncy (except for error msgs).  Debugging mode should be ON.
  3088.  
  3089. */
  3090.  
  3091. debugp: procedure(packet,leader);
  3092.  
  3093. %list off;%include 'params';%list on;
  3094.  
  3095. dcl packet   char(150); /* INPUT: the packet buffer */
  3096. dcl leader   char var; /* message header ('rcvd', 'sent', or error msg) */
  3097. dcl f        file variable; /* temp */
  3098. dcl (i,j)    fixed; /* temps */
  3099.  
  3100. if      local   then f = $screen; /* output to screen if local */
  3101. else if logging then f = $log; /* else output to logfile if enabled */
  3102. else return; /* else nowhere to write our message! */
  3103.  
  3104. if length(leader)>5 /* if (long) error message... */
  3105. then put file(f) line('*pkt ',leader); /* then just log */
  3106. else do; /* normal xmit, log packet contents */
  3107.    i = index(packet,chr(1)); /* find start of packet in buffer */
  3108.    j = byte(packet,i+1)-32-2-params.chkt; /* length of data field */
  3109.    linesize(f) = 0; /* make sure lines don't wrap */
  3110.    put file(f) edit('*pkt ',leader,substr(packet,i+3,1),byte(packet,i+2)-32,
  3111.                 substr(packet,i+4+j,params.chkt),'"',substr(packet,i+4,j),'"')
  3112.        (a,a(5),a,f(3),x(1),a(2),x(1),a,a,a);
  3113.    put file(f) skip;
  3114. end;
  3115.  
  3116. call flush(f); /* write it out now */
  3117.  
  3118. end debugp;
  3119. %subtitle 'Transmission -- Checksum';
  3120.  
  3121. /* Checksum --
  3122.  
  3123. Compute the packet checksum, which is defined to be the sum of its
  3124. bytes.  We KNOW that no 400b3 bit is set in any byte, since when sending
  3125. one of our files the quoting mechanism strips them off, and the front
  3126. ends guarantee these bits to be 0 when receiving someone else's file.
  3127. This lets us be slightly tricky, and compute the checksums in semi-
  3128. parallel, because we know that we can add any two bytes of data without
  3129. carrying out of a 9-bit byte.
  3130.  
  3131. We assume we are passed the MARK character (001), which is not added into
  3132. the checksum, and that we are not passed the checksum itself.
  3133.  
  3134. The checksum returned will have garbage in its upper half.
  3135.  
  3136. */
  3137.  
  3138. (nosubscriptrange, nostringrange):
  3139.  
  3140. checksum: procedure(data,len,cks);
  3141.  
  3142. dcl data     char(150); /* INPUT: the packet buffer */
  3143. dcl len      fixed; /* INPUT: the packet length, minus the checksum field */
  3144. dcl cks      fixed; /* OUTPUT: the sum of each byte, minus 1 (for the mark) */
  3145.  
  3146. dcl word(0)  fixed based; /* used to access data by the word */
  3147. dcl p        ptr; /* addr(data), for use w 'word' */
  3148. dcl nulls    char(3) static init(chr(0)||chr(0)||chr(0)); /* for null padding */
  3149. dcl (i,j)    fixed; /* temps */
  3150.  
  3151. substr(data,len+1,3) = nulls; /* null pad last word */
  3152. i = shr(len+3,2); /* get word length of packet */
  3153. p = addr(data); /* get address of data */
  3154.  
  3155. j = -1; /* initialize checksum to account for mark character */
  3156. do while(i>0); /* loop over each word of packet */
  3157.    j = j + ((p->word(i)+shr(p->word(i),9))&000777000777b3); /* sum both halves *
  3158. /
  3159.    i = i - 1; /* next word, if any */
  3160. end;
  3161.  
  3162. cks = shr(j,18) + j; /* checksum is sum of upper and lower sums */
  3163.  
  3164. end checksum;
  3165. %subtitle 'Transmission -- Ticktock';
  3166.  
  3167. /* Ticktock --
  3168.  
  3169. This is a task, spawned during initialization, that wakes up every 5 seconds
  3170. and bumps the $timeout timer, if it is running (ie, if it is negative.)  If
  3171. it goes zero, a Reset Status is done on file $comf.  This is the way we
  3172. timeout reads and writes of packets.
  3173.  
  3174. */
  3175.  
  3176. ticktock: procedure;
  3177.  
  3178. %list off;%include 'params';%list on;
  3179.  
  3180. %dcl N lit '5'; /* ticktock period, in seconds */
  3181.  
  3182. dcl reset    entry(fixed); /* to issue reset status */
  3183.  
  3184. do while(1); /* loop forever */
  3185. call wait(N); /* wait for N seconds */
  3186. if $timeout<0 then do; /* if the clock is running... */
  3187.    $timeout = $timeout + N; /* then bump by N seconds */
  3188.    if $timeout>=0 then do; /* did it run out? */
  3189.       if $timeout~=(N-1) /* give any timer longer than 1 sec at least 2 ticks */
  3190.       then $timeout = -1; /* (otherwise it might go off almost instantly) */
  3191.       else call reset($comffrn); /* reset comfile, timing out the read */
  3192.    end;
  3193. end;
  3194. end;
  3195.  
  3196. end ticktock;
  3197. %subtitle 'Transmission -- Terminal Emulation';
  3198.  
  3199. /* Terminal Emulation --
  3200.  
  3201. This is the external procedure spawned by the CONNECT command, to
  3202. do terminal emulation.  All that's required to "emulate" a terminal,
  3203. at least in our simplistic view, is to read the outbound connection and
  3204. write what we get to the screen, and visa versa.  We do this by setting
  3205. ourselves up as a global on unit for the Interrupt condition, and responding
  3206. to interrupts.  We look for unrequested input specials; when one comes in,
  3207. we read the data and write it to the other end.
  3208.  
  3209. The "real" terminal is file $screen, usually controlled by TCFACE.  The
  3210. outbound connection to the remote system is file $comf, always controlled
  3211. by NETFACE.
  3212.  
  3213. There are two complications.  First, TCFACE will discard pending input when
  3214. it receives output.  If we write to $screen after getting an URI special,
  3215. but before reading it, then the read will hang when finally issued.  We handle
  3216. this by clearing pending URIs when we write (it is IMPOSSIBLE to tell if a
  3217. TCFACE read will hang-- believe me, I implemented comfiles in the Exec!).
  3218. In effect, TCFACE is throwing away typeahead.  This makes it very difficult
  3219. to simulate terminals in full duplex (ie, with the system echoing the chars
  3220. typed), since inevitably the chars sent by the system to echo what is being
  3221. types will pass characters being sent to the system as they are typed, in
  3222. which case the chars typed will be discarded by TCFACE as typeahead!  We handle
  3223. this by NEVER DRIVING THE SCREEN INTO CHARACTER-BY-CHARACTER MODE, since doing
  3224. so will almost certainly result in typed chars being lost.  UNIX, and many
  3225. other systems, drive c-b-c mode, but we just keep the screen in l-b-l mode.
  3226. This works OK in most cases; UNIX won't get any input until the CR is typed,
  3227. but for most simple applications on UNIX this is (barely) adequate.
  3228.  
  3229. The other complication occurs when we get a zero status reading a TCFACE
  3230. terminal in build mode (for instance); we can't tell whether there is more
  3231. input pending, and therefore whether we should reissue the read.  We handle
  3232. this by not rereading.  Note that NETFACE does not discard pending input
  3233. the way TCFACE does, and it always sends URIs if input arrives wo a read
  3234. outstanding, so it is easier to deal with than TCFACE.
  3235.  
  3236. Terminal emulation continues until the user types "@@<ret>" on the real
  3237. terminal, at which point we drive both ends back to line-by-line mode and
  3238. unblock the main program.
  3239.  
  3240. What we don't do correctly is return to the state the emulation was in
  3241. if this is not the first communication with the network connection.
  3242. For example, say we connect to a UNIX.  They will drive the connection
  3243. into char-by-char, no-echo, no-append-LF mode.  When the user escapes
  3244. from the emulation, to transfer a file or something, we drive the files
  3245. back to line-by-line mode, and drive $screen even further (turning on
  3246. echo and LF-echo.)  Now, if the user does not put UNIX in server mode,
  3247. when he re-connects we'll leave the files in line-by-line mode etc.
  3248. Wrong!  The fix is to keep track of the current state, ad re-drive it
  3249. when re-emulating.  The trouble is, this is hard to do, but should be
  3250. done.
  3251.  
  3252. This is, by far, the most system-dependent and obscure part of Kermit.
  3253. Mostly because of TCFACE's typeahead throwaway, it CANNOT work entirely
  3254. satisfactorily.  We try to get it to work adequately for all systems.
  3255.  
  3256. */
  3257.  
  3258. terminal: procedure(); /* first a task, then a global On Unit */
  3259.  
  3260. %list off;%include 'params';%list on;
  3261.  
  3262. dcl drive     entry(file,fixed); /* to issue a drive MME */
  3263. dcl nothing   entry; /* do-nothing On Unit for Interrupt */
  3264.  
  3265. dcl emulating bit(1) static init(''b); /* true when terminal emulating */
  3266. dcl termwait  fixed  ext; /* Q main task uses to wait for us to finish */
  3267. dcl f(2)      file   static; /* used to switch between $screen and $comf */
  3268. dcl (m1,m2)   fixed  static; /* MME parameters */
  3269.  
  3270. dcl (i,j)     fixed; /* temps */
  3271. dcl regs(0:11)fixed; /* MME registers */
  3272. dcl buf       char(2048); /* I/O buffer */
  3273. dcl n         fixed; /* file# 1:$screen  2:$comf  */
  3274. dcl ws        bit(1); /* true if this task has written screen */
  3275. dcl 1 spec, /* special interrupt template */
  3276.     2 bits    bit(9), /* slave end's bits */
  3277.     2 type    unsigned(9), /* special type */
  3278.     2 frn     unsigned(18), /* frn */
  3279.     2 data    fixed; /* length, etc. */
  3280.  
  3281.  
  3282. /* if not emulating, this is the original (spawned) task, so initialize */
  3283.  
  3284. if ~emulating then do; /* if just spawned by CONNECT command... */
  3285.    f(1) = $screen; /* initialize file index */
  3286.    f(2) = $comf; /* used for I/O switch */
  3287.    put file($screen) line('[Connecting to remote host, type "@@<cr>" to return.'
  3288. );
  3289.    put file($screen) line(' Remember, typeahead is not possible, so wait for out
  3290. put');
  3291.    put file($screen) line(' to complete before typing (and do not type a BREAK).
  3292. ]');
  3293.    call flush($screen); /* flush the above message */
  3294.    emulating = '1'b; /* ok, we're set to go... */
  3295.    call drive($screen,000006b3); /* drive TCFACE into Line-by-Line wo LF echo */
  3296.    on interrupt global(terminal); /* set ourselves up as an on unit */
  3297.    signal interrupt; /* then handle the stacked specials (if any) */
  3298.    return; /* thats all until next special comes in */
  3299. end;
  3300.  
  3301.  
  3302. /* loop on each special */
  3303.  
  3304. ws = ''b; /* haven't written screen yet */
  3305.  
  3306. do while(emulating); /* return when out of specials or escaped */
  3307. unspec(spec) = oninterrupt(); /* get next special, if any */
  3308. if unspec(spec)=''b then return; /* if no more, wait for next */
  3309. if spec.type=1 /* if an unrequested input special... */
  3310. then call read; /* then handle */
  3311. else; /* else ignore others */
  3312. end;
  3313.  
  3314. EXIT: return; /* terminate task if escaped out of terminal emulation */
  3315.  
  3316.  
  3317. /* handle unrequested input */
  3318.  
  3319. read: procedure();
  3320.  
  3321. dcl n   fixed; /* file#  1:$screen   2:$comf  */
  3322. dcl frn builtin;
  3323.  
  3324. if spec.frn=frn($screen) /* $screen? */
  3325. then n = 1; /* yes, remember file index */
  3326. else if spec.frn=frn($comf) /* remote system? */
  3327. then n = 2; /* yes */
  3328. else return; /* ignore others (huh?) */
  3329.  
  3330. if debuging then call debug('URI spec  f:'||n||'  ms:'||ws||'  wds:'||octal(spec
  3331. .data));
  3332.  
  3333. if n=1 & ws then return; /* don't read TCFACE after writing to it! */
  3334.  
  3335. /* Must read via Read MME, in order to set the substatus and metatext
  3336.    enable bits in X4, so we are enabled for reading "metatext", or
  3337.    incoming drive information from the network.  The 100 bit in the upper
  3338.    byte of stw1 on the trap will be set if we get metatext. */
  3339.  
  3340. m1 = waddr(buf); /* ptr to I/O buffer */
  3341. m2 = wlen(buf); /* #words to read */
  3342. unspec(regs) = ''b; /* zero the registers */
  3343. regs(0) = spec.frn; /* X0: frn */
  3344. regs(3) = waddr(m1); /* X3: ptr to buffer ptr */
  3345. regs(4) = 140000b3; /* set substatus-request and metatext-enable bits */
  3346. regs(7) = waddr(m2); /* X7: ptr to #words to read */
  3347. call issue(500233b3); /* issue Read and handle comfile busy etc */
  3348. if debuging then call debug('read  stw1:'||octal(regs(10))||' '||octal(regs(11))
  3349. );
  3350. if (shr(regs(10),18)&776b3)~=0 then return; /* ignore bad statuses */
  3351. i = m2 + regs(11); /* get #words transferred */
  3352.  
  3353. if i<0 then return; /* ignore bad reads (BUT echo 0-len reads!) */
  3354. j = shr(regs(10),27); /* get substatus bits from read */
  3355.  
  3356. if (j&100b3)~=0 then do; /* if metatext bit set... */
  3357.    call metatext(n,i); /* process the information */
  3358.    return; /* don't write to other end! */
  3359. end;
  3360.  
  3361. if (n=1) & (i>0) & (substr(buf,1,3)='@@'||chr(ascii('cr'))) then do; /* escape s
  3362. equence? */
  3363.    call escape; /* turn off terminal emulation */
  3364.    return; /* done */
  3365. end;
  3366.  
  3367. if debuging then call debug('f:'||n||'  data:'||substr(buf,1,min((i*4)-(j&3),60)
  3368. ));
  3369.  
  3370. /* must write via Write MME in order to set character residue, since some
  3371.    systems (rightfully) object to getting spurious trailing nulls */
  3372.  
  3373. j = 0; /* initialize count of trailing nulls */
  3374. do while(i>0 & j<3 & substr(buf,i*4-j,1)=chr(0)); /* loop discarding null fill o
  3375. f last word */
  3376.    j = j + 1; /* up to 3 trailing nulls */
  3377. end;
  3378. m2 = i; /* #words read */
  3379. regs(1) = waddr(m1); /* X1: ptr to ptr to buffer */
  3380. regs(2) = frn(f(3-n)); /* X2: frn of destination */
  3381. regs(4) = j; /* X4: set character-residue in destination field */
  3382. call issue(500234b3); /* issue Write, ignore status (except retry 6s) */
  3383.  
  3384. if n=2 then do; /* if reading $comf and writing $screen... */
  3385.    ws = '1'b; /* then remember not to try to read $screen */
  3386.    if logging & ~debuging /* if logging... (data already written if debugging) *
  3387. /
  3388.    then j = matwrite($log,buf,i); /* then log output to screen */
  3389. end;
  3390.  
  3391. end read;
  3392.  
  3393.  
  3394. /* escape sequence typed on screen, so shut down terminal emulation */
  3395.  
  3396. escape: procedure();
  3397.  
  3398. on interrupt global(nothing); /* don't react to further interrupts (but keep the
  3399. m stacked) */
  3400.  
  3401. call drive($screen,000000b3); /* set line-by-line mode */
  3402. call drive($screen,000020b3); /* turn on echo */
  3403. call drive($screen,003030b3); /* echo LF after CR, output messages and warnings
  3404. */
  3405.  
  3406. put file($screen) line('[Connection suspended, back at DCTS]');
  3407.  
  3408. emulating = ''b; /* no longer emulating */
  3409. call unblock(termwait); /* unblock main task */
  3410.  
  3411. end escape;
  3412.  
  3413.  
  3414. /* handle incoming metatext (ie, drives): echo to the other side */
  3415.  
  3416. metatext: procedure(file,words);
  3417.  
  3418. dcl file     fixed; /* INPUT: the file# sending metatext */
  3419. dcl words    fixed; /* INPUT: #words of data read */
  3420. dcl (i,j,n)  fixed; /* temps */
  3421.  
  3422. i = 1; /* first byte to examine in input */
  3423.  
  3424. do while(i<=words*4); /* loop until input exhausted */
  3425.    j = byte(buf,i); /* get length of next 'record' in KSP metatext */
  3426.    if j=0 then return; /* done when we reach last-record-flag */
  3427.    n = byte(buf,i+1) - 'A0'b4; /* get drive type and normalize */
  3428.    i = i + j; /* advance pointer into input buffer */
  3429.    if n>=0 & n<37b3 & n~=12b3 then do;/* if drive looks ok... (do NOT drive char
  3430. -by-char!) */
  3431.       call drive(f(3-file),n); /* then do it to the other side */
  3432.       if debuging then call debug('drive  f:'||file||'  mod:'||substr(octal(n),7
  3433. ));
  3434.    end;
  3435.    else do; /* we'll ignore this drive */
  3436.       if debuging then call debug('drive  f:'||file||'  mod:'||substr(octal(n),7
  3437. )||' NOT echoed');
  3438.    end;
  3439. end;
  3440.  
  3441. end metatext;
  3442.  
  3443.  
  3444. /* debugging mode output */
  3445.  
  3446. debug: procedure(txt);
  3447.  
  3448. dcl txt char var; /* UPDATE: line of output */
  3449. dcl prt char var; /* the printable chars */
  3450. dcl i,j;
  3451.  
  3452. if logging then do;/* make sure we're logging (bad idea to write to $screen) */
  3453. prt = substr(collate(),32,95); /* get the printable chars */
  3454. i = verify(txt,prt); /* any control chars, DELs, or chars w 8th bits set? */
  3455. do while(i~=0); /* loop over each ctl char */
  3456. j = byte(txt,i) & 177b3; /* yes, get it and mask off parity bits */
  3457. if j=177b3 /* delete? */
  3458. then j = 77b3; /* we'll print DEL as '^?' */
  3459. else j = j + 64; /* else, print NULL as '^@', etc */
  3460. txt = substr(txt,1,i-1)||'^'||chr(j)||substr(txt,i+1);
  3461. i = verify(txt,prt); /* another control char in string? */
  3462. end;
  3463. put file($log) line('### ',txt);
  3464. end;
  3465.  
  3466. end debug;
  3467.  
  3468.  
  3469. /* issue MME and handle commfile-busy statuses */
  3470.  
  3471. issue: procedure(n);
  3472.  
  3473. dcl n fixed; /* INPUT: mme# */
  3474. dcl i fixed; /* loop index */
  3475.  
  3476. do i = 1 to params.maxretry; /* loop retrying 6s */
  3477.    call mme(n,regs); /* issue the mme */
  3478.    if (shr(regs(10),18)&777b3)>=200b3 then do; /* XR0 or XR2 errors mean comfile
  3479.  closed */
  3480.       call escape; /* "escape" out of terminal emulation */
  3481.       put file($screen) line('Connection closed.');
  3482.       goto EXIT; /* abort emulation */
  3483.    end;
  3484.    if regs(10)~=6000000b3 /* if not the dreaded commfile-busy... */
  3485.    then return; /* then done */
  3486. end;
  3487.  
  3488. end issue;
  3489.  
  3490. end terminal;
  3491. %subtitle 'Transmission -- Nothing';
  3492.  
  3493. /* Nothing --
  3494.  
  3495. When we escape from terminal emulation, this procedure is set up as the
  3496. global On Unit for Interrupt.  We do nothing, but allow specials to accumulate
  3497. in case we re-"connect" to the terminal emulation session, in which case the
  3498. pending specials will still be there (NETFACE does not discard typeahead.)
  3499.  
  3500. */
  3501.  
  3502. nothing: procedure();
  3503.  
  3504. end nothing;
  3505.