First Commit of my working state
[simh.git] / sim_tmxr.c
1 /* sim_tmxr.c: Telnet terminal multiplexor library
2
3 Copyright (c) 2001-2007, Robert M Supnik
4
5 Permission is hereby granted, free of charge, to any person obtaining a
6 copy of this software and associated documentation files (the "Software"),
7 to deal in the Software without restriction, including without limitation
8 the rights to use, copy, modify, merge, publish, distribute, sublicense,
9 and/or sell copies of the Software, and to permit persons to whom the
10 Software is furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 ROBERT M SUPNIK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22 Except as contained in this notice, the name of Robert M Supnik shall not be
23 used in advertising or otherwise to promote the sale, use or other dealings
24 in this Software without prior written authorization from Robert M Supnik.
25
26 Based on the original DZ11 simulator by Thord Nilson, as updated by
27 Arthur Krewat.
28
29 11-Apr-07 JDB Worked around Telnet negotiation problem with QCTerm
30 16-Aug-05 RMS Fixed C++ declaration and cast problems
31 29-Jun-05 RMS Extended tmxr_dscln to support unit array devices
32 Fixed bug in SET LOG/NOLOG
33 04-Jan-04 RMS Changed TMXR ldsc to be pointer to linedesc array
34 Added tmxr_linemsg, circular output pointers, logging
35 (from Mark Pizzolato)
36 29-Dec-03 RMS Added output stall support
37 01-Nov-03 RMS Cleaned up attach routine
38 09-Mar-03 RMS Fixed bug in SHOW CONN
39 22-Dec-02 RMS Fixed bugs in IAC+IAC receive and transmit sequences
40 Added support for received break (all from by Mark Pizzolato)
41 Fixed bug in attach
42 31-Oct-02 RMS Fixed bug in 8b (binary) support
43 22-Aug-02 RMS Added tmxr_open_master, tmxr_close_master
44 30-Dec-01 RMS Added tmxr_fstats, tmxr_dscln, renamed tmxr_fstatus
45 03-Dec-01 RMS Changed tmxr_fconns for extended SET/SHOW
46 20-Oct-01 RMS Fixed bugs in read logic (found by Thord Nilson).
47 Added tmxr_rqln, tmxr_tqln
48
49 This library includes:
50
51 tmxr_poll_conn - poll for connection
52 tmxr_reset_ln - reset line
53 tmxr_getc_ln - get character for line
54 tmxr_poll_rx - poll receive
55 tmxr_putc_ln - put character for line
56 tmxr_poll_tx - poll transmit
57 tmxr_open_master - open master connection
58 tmxr_close_master - close master connection
59 tmxr_attach - attach terminal multiplexor
60 tmxr_detach - detach terminal multiplexor
61 tmxr_ex - (null) examine
62 tmxr_dep - (null) deposit
63 tmxr_msg - send message to socket
64 tmxr_linemsg - send message to line
65 tmxr_fconns - output connection status
66 tmxr_fstats - output connection statistics
67 tmxr_dscln - disconnect line (SET routine)
68 tmxr_rqln - number of available characters for line
69 tmxr_tqln - number of buffered characters for line
70
71 All routines are OS-independent.
72 */
73
74 #include "sim_defs.h"
75 #include "sim_sock.h"
76 #include "sim_tmxr.h"
77 #include <ctype.h>
78
79 /* Telnet protocol constants - negatives are for init'ing signed char data */
80
81 #define TN_IAC -1 /* protocol delim */
82 #define TN_DONT -2 /* dont */
83 #define TN_DO -3 /* do */
84 #define TN_WONT -4 /* wont */
85 #define TN_WILL -5 /* will */
86 #define TN_BRK -13 /* break */
87 #define TN_BIN 0 /* bin */
88 #define TN_ECHO 1 /* echo */
89 #define TN_SGA 3 /* sga */
90 #define TN_LINE 34 /* line mode */
91 #define TN_CR 015 /* carriage return */
92 #define TN_LF 012 /* line feed */
93 #define TN_NUL 000 /* null */
94
95 /* Telnet line states */
96
97 #define TNS_NORM 000 /* normal */
98 #define TNS_IAC 001 /* IAC seen */
99 #define TNS_WILL 002 /* WILL seen */
100 #define TNS_WONT 003 /* WONT seen */
101 #define TNS_SKIP 004 /* skip next cmd */
102 #define TNS_CRPAD 005 /* CR padding */
103
104 void tmxr_rmvrc (TMLN *lp, int32 p);
105 int32 tmxr_send_buffered_data (TMLN *lp);
106 TMLN *tmxr_find_ldsc (UNIT *uptr, int32 val, TMXR *mp);
107
108 extern int32 sim_switches;
109 extern char sim_name[];
110 extern FILE *sim_log;
111 extern uint32 sim_os_msec (void);
112
113 /* Poll for new connection
114
115 Called from unit service routine to test for new connection
116
117 Inputs:
118 *mp = pointer to terminal multiplexor descriptor
119 Outputs:
120 line number activated, -1 if none
121 */
122
123 int32 tmxr_poll_conn (TMXR *mp)
124 {
125 SOCKET newsock;
126 TMLN *lp;
127 int32 i;
128 uint32 ipaddr;
129 static char mantra[] = {
130 TN_IAC, TN_WILL, TN_LINE,
131 TN_IAC, TN_WILL, TN_SGA,
132 TN_IAC, TN_WILL, TN_ECHO,
133 TN_IAC, TN_WILL, TN_BIN,
134 TN_IAC, TN_DO, TN_BIN
135 };
136
137 newsock = sim_accept_conn (mp->master, &ipaddr); /* poll connect */
138 if (newsock != INVALID_SOCKET) { /* got a live one? */
139 for (i = 0; i < mp->lines; i++) { /* find avail line */
140 lp = mp->ldsc + i; /* ptr to ln desc */
141 if (lp->conn == 0) break; /* available? */
142 }
143 if (i >= mp->lines) { /* all busy? */
144 tmxr_msg (newsock, "All connections busy\r\n");
145 sim_close_sock (newsock, 0);
146 }
147 else {
148 lp = mp->ldsc + i; /* get line desc */
149 lp->conn = newsock; /* record connection */
150 lp->ipad = ipaddr; /* ip address */
151 lp->cnms = sim_os_msec (); /* time of conn */
152 lp->rxbpr = lp->rxbpi = 0; /* init buf pointers */
153 lp->txbpr = lp->txbpi = 0;
154 lp->rxcnt = lp->txcnt = 0; /* init counters */
155 lp->tsta = 0; /* init telnet state */
156 lp->xmte = 1; /* enable transmit */
157 lp->dstb = 0; /* default bin mode */
158 sim_write_sock (newsock, mantra, 15);
159 tmxr_linemsg (lp, "\n\r\nConnected to the ");
160 tmxr_linemsg (lp, sim_name);
161 tmxr_linemsg (lp, " simulator\r\n\n");
162 tmxr_poll_tx (mp); /* flush output */
163 return i;
164 }
165 } /* end if newsock */
166 return -1;
167 }
168
169 /* Reset line */
170
171 void tmxr_reset_ln (TMLN *lp)
172 {
173 if (lp->txlog) fflush (lp->txlog); /* dump log */
174 tmxr_send_buffered_data (lp); /* send buffered data */
175 sim_close_sock (lp->conn, 0); /* reset conn */
176 lp->conn = lp->tsta = 0; /* reset state */
177 lp->rxbpr = lp->rxbpi = 0;
178 lp->txbpr = lp->txbpi = 0;
179 lp->xmte = 1;
180 lp->dstb = 0;
181 return;
182 }
183
184 /* Get character from specific line
185
186 Inputs:
187 *lp = pointer to terminal line descriptor
188 Output:
189 valid + char, 0 if line
190 */
191
192 int32 tmxr_getc_ln (TMLN *lp)
193 {
194 int32 j, val = 0;
195 uint32 tmp;
196
197 if (lp->conn && lp->rcve) { /* conn & enb? */
198 j = lp->rxbpi - lp->rxbpr; /* # input chrs */
199 if (j) { /* any? */
200 tmp = lp->rxb[lp->rxbpr]; /* get char */
201 val = TMXR_VALID | (tmp & 0377); /* valid + chr */
202 if (lp->rbr[lp->rxbpr]) val = val | SCPE_BREAK; /* break? */
203 lp->rxbpr = lp->rxbpr + 1; /* adv pointer */
204 }
205 } /* end if conn */
206 if (lp->rxbpi == lp->rxbpr) /* empty? zero ptrs */
207 lp->rxbpi = lp->rxbpr = 0;
208 return val;
209 }
210
211 /* Poll for input
212
213 Inputs:
214 *mp = pointer to terminal multiplexor descriptor
215 Outputs: none
216 */
217
218 void tmxr_poll_rx (TMXR *mp)
219 {
220 int32 i, nbytes, j;
221 TMLN *lp;
222
223 for (i = 0; i < mp->lines; i++) { /* loop thru lines */
224 lp = mp->ldsc + i; /* get line desc */
225 if (!lp->conn || !lp->rcve) continue; /* skip if !conn */
226
227 nbytes = 0;
228 if (lp->rxbpi == 0) /* need input? */
229 nbytes = sim_read_sock (lp->conn, /* yes, read */
230 &(lp->rxb[lp->rxbpi]), /* leave spc for */
231 TMXR_MAXBUF - TMXR_GUARD); /* Telnet cruft */
232 else if (lp->tsta) /* in Telnet seq? */
233 nbytes = sim_read_sock (lp->conn, /* yes, read to end */
234 &(lp->rxb[lp->rxbpi]),
235 TMXR_MAXBUF - lp->rxbpi);
236 if (nbytes < 0) tmxr_reset_ln (lp); /* closed? reset ln */
237 else if (nbytes > 0) { /* if data rcvd */
238 j = lp->rxbpi; /* start of data */
239 memset (&lp->rbr[j], 0, nbytes); /* clear status */
240 lp->rxbpi = lp->rxbpi + nbytes; /* adv pointers */
241 lp->rxcnt = lp->rxcnt + nbytes;
242
243 /* Examine new data, remove TELNET cruft before making input available */
244
245 for (; j < lp->rxbpi; ) { /* loop thru char */
246 signed char tmp = lp->rxb[j]; /* get char */
247 switch (lp->tsta) { /* case tlnt state */
248
249 case TNS_NORM: /* normal */
250 if (tmp == TN_IAC) { /* IAC? */
251 lp->tsta = TNS_IAC; /* change state */
252 tmxr_rmvrc (lp, j); /* remove char */
253 break;
254 }
255 if ((tmp == TN_CR) && lp->dstb) /* CR, no bin */
256 lp->tsta = TNS_CRPAD; /* skip pad char */
257 j = j + 1; /* advance j */
258 break;
259
260 case TNS_IAC: /* IAC prev */
261 if ((tmp == TN_IAC) & !lp->dstb) { /* IAC + IAC, bin? */
262 lp->tsta = TNS_NORM; /* treat as normal */
263 j = j + 1; /* advance j */
264 break; /* keep IAC */
265 }
266 if (tmp == TN_BRK) { /* IAC + BRK? */
267 lp->tsta = TNS_NORM; /* treat as normal */
268 lp->rxb[j] = 0; /* char is null */
269 lp->rbr[j] = 1; /* flag break */
270 j = j + 1; /* advance j */
271 break;
272 }
273 if (tmp == TN_WILL) /* IAC + WILL? */
274 lp->tsta = TNS_WILL;
275 else if (tmp == TN_WONT) /* IAC + WONT? */
276 lp->tsta = TNS_WONT;
277 else lp->tsta = TNS_SKIP; /* IAC + other */
278 tmxr_rmvrc (lp, j); /* remove char */
279 break;
280
281 case TNS_WILL: case TNS_WONT: /* IAC+WILL/WONT prev */
282 if (tmp == TN_BIN) { /* BIN? */
283 if (lp->tsta == TNS_WILL) lp->dstb = 0;
284 else lp->dstb = 1;
285 }
286
287 /* Negotiation with the HP terminal emulator "QCTerm" is not working.
288 QCTerm says "WONT BIN" but sends bare CRs. RFC 854 says:
289
290 Note that "CR LF" or "CR NUL" is required in both directions
291 (in the default ASCII mode), to preserve the symmetry of the
292 NVT model. ...The protocol requires that a NUL be inserted
293 following a CR not followed by a LF in the data stream.
294
295 Until full negotiation is implemented, we work around the problem
296 by checking the character following the CR in non-BIN mode and
297 strip it only if it is LF or NUL. This should not affect
298 conforming clients.
299 */
300
301 case TNS_CRPAD: /* only LF or NUL should follow CR */
302 lp->tsta = TNS_NORM; /* next normal */
303 if ((tmp == TN_LF) || /* CR + LF ? */
304 (tmp == TN_NUL)) /* CR + NUL? */
305 tmxr_rmvrc (lp, j); /* remove it */
306 break;
307
308 case TNS_SKIP: default: /* skip char */
309 lp->tsta = TNS_NORM; /* next normal */
310 tmxr_rmvrc (lp, j); /* remove char */
311 break;
312 } /* end case state */
313 } /* end for char */
314 } /* end else nbytes */
315 } /* end for lines */
316 for (i = 0; i < mp->lines; i++) { /* loop thru lines */
317 lp = mp->ldsc + i; /* get line desc */
318 if (lp->rxbpi == lp->rxbpr) /* if buf empty, */
319 lp->rxbpi = lp->rxbpr = 0; /* reset pointers */
320 } /* end for */
321 return;
322 }
323
324 /* Return count of available characters for line */
325
326 int32 tmxr_rqln (TMLN *lp)
327 {
328 return (lp->rxbpi - lp->rxbpr);
329 }
330
331 /* Remove character p (and matching status) from line l input buffer */
332
333 void tmxr_rmvrc (TMLN *lp, int32 p)
334 {
335 for ( ; p < lp->rxbpi; p++) {
336 lp->rxb[p] = lp->rxb[p + 1];
337 lp->rbr[p] = lp->rbr[p + 1];
338 }
339 lp->rxbpi = lp->rxbpi - 1;
340 return;
341 }
342
343 /* Store character in line buffer
344
345 Inputs:
346 *lp = pointer to line descriptor
347 chr = characters
348 Outputs:
349 status = ok, connection lost, or stall
350 */
351
352 t_stat tmxr_putc_ln (TMLN *lp, int32 chr)
353 {
354 if (lp->txlog) fputc (chr, lp->txlog); /* log if available */
355 if (lp->conn == 0) return SCPE_LOST; /* no conn? lost */
356 if (tmxr_tqln (lp) < (TMXR_MAXBUF - 1)) { /* room for char (+ IAC)? */
357 lp->txb[lp->txbpi] = (char) chr; /* buffer char */
358 lp->txbpi = lp->txbpi + 1; /* adv pointer */
359 if (lp->txbpi >= TMXR_MAXBUF) lp->txbpi = 0; /* wrap? */
360 if ((char) chr == TN_IAC) { /* IAC? */
361 lp->txb[lp->txbpi] = (char) chr; /* IAC + IAC */
362 lp->txbpi = lp->txbpi + 1; /* adv pointer */
363 if (lp->txbpi >= TMXR_MAXBUF) lp->txbpi = 0; /* wrap? */
364 }
365 if (tmxr_tqln (lp) > (TMXR_MAXBUF - TMXR_GUARD)) /* near full? */
366 lp->xmte = 0; /* disable line */
367 return SCPE_OK; /* char sent */
368 }
369 lp->xmte = 0; /* no room, dsbl line */
370 return SCPE_STALL; /* char not sent */
371 }
372
373 /* Poll for output
374
375 Inputs:
376 *mp = pointer to terminal multiplexor descriptor
377 Outputs:
378 none
379 */
380
381 void tmxr_poll_tx (TMXR *mp)
382 {
383 int32 i, nbytes;
384 TMLN *lp;
385
386 for (i = 0; i < mp->lines; i++) { /* loop thru lines */
387 lp = mp->ldsc + i; /* get line desc */
388 if (lp->conn == 0) continue; /* skip if !conn */
389 nbytes = tmxr_send_buffered_data (lp); /* buffered bytes */
390 if (nbytes == 0) lp->xmte = 1; /* buf empty? enab line */
391 } /* end for */
392 return;
393 }
394
395 /* Send buffered data across network
396
397 Inputs:
398 *lp = pointer to line descriptor
399 Outputs:
400 returns number of bytes still buffered
401 */
402
403 int32 tmxr_send_buffered_data (TMLN *lp)
404 {
405 int32 nbytes, sbytes;
406
407 nbytes = tmxr_tqln(lp); /* avail bytes */
408 if (nbytes) { /* >0? write */
409 if (lp->txbpr < lp->txbpi) /* no wrap? */
410 sbytes = sim_write_sock (lp->conn, /* write all data */
411 &(lp->txb[lp->txbpr]), nbytes);
412 else sbytes = sim_write_sock (lp->conn, /* write to end buf */
413 &(lp->txb[lp->txbpr]), TMXR_MAXBUF - lp->txbpr);
414 if (sbytes != SOCKET_ERROR) { /* ok? */
415 lp->txbpr = (lp->txbpr + sbytes); /* update remove ptr */
416 if (lp->txbpr >= TMXR_MAXBUF) lp->txbpr = 0; /* wrap? */
417 lp->txcnt = lp->txcnt + sbytes; /* update counts */
418 nbytes = nbytes - sbytes;
419 }
420 if (nbytes && (lp->txbpr == 0)) { /* more data and wrap? */
421 sbytes = sim_write_sock (lp->conn, lp->txb, nbytes);
422 if (sbytes != SOCKET_ERROR) { /* ok */
423 lp->txbpr = (lp->txbpr + sbytes); /* update remove ptr */
424 if (lp->txbpr >= TMXR_MAXBUF) lp->txbpr = 0;/* wrap? */
425 lp->txcnt = lp->txcnt + sbytes; /* update counts */
426 nbytes = nbytes - sbytes;
427 }
428 }
429 } /* end if nbytes */
430 return nbytes;
431 }
432
433 /* Return count of buffered characters for line */
434
435 int32 tmxr_tqln (TMLN *lp)
436 {
437 return (lp->txbpi - lp->txbpr + ((lp->txbpi < lp->txbpr)? TMXR_MAXBUF: 0));
438 }
439
440 /* Open master socket */
441
442 t_stat tmxr_open_master (TMXR *mp, char *cptr)
443 {
444 int32 i, port;
445 SOCKET sock;
446 TMLN *lp;
447 t_stat r;
448
449 port = (int32) get_uint (cptr, 10, 65535, &r); /* get port */
450 if ((r != SCPE_OK) || (port == 0)) return SCPE_ARG;
451 sock = sim_master_sock (port); /* make master socket */
452 if (sock == INVALID_SOCKET) return SCPE_OPENERR; /* open error */
453 printf ("Listening on port %d (socket %d)\n", port, sock);
454 if (sim_log) fprintf (sim_log,
455 "Listening on port %d (socket %d)\n", port, sock);
456 mp->port = port; /* save port */
457 mp->master = sock; /* save master socket */
458 for (i = 0; i < mp->lines; i++) { /* initialize lines */
459 lp = mp->ldsc + i;
460 lp->conn = lp->tsta = 0;
461 lp->rxbpi = lp->rxbpr = 0;
462 lp->txbpi = lp->txbpr = 0;
463 lp->rxcnt = lp->txcnt = 0;
464 lp->xmte = 1;
465 lp->dstb = 0;
466 }
467 return SCPE_OK;
468 }
469
470 /* Attach unit to master socket */
471
472 t_stat tmxr_attach (TMXR *mp, UNIT *uptr, char *cptr)
473 {
474 char* tptr;
475 t_stat r;
476
477 tptr = (char *) malloc (strlen (cptr) + 1); /* get string buf */
478 if (tptr == NULL) return SCPE_MEM; /* no more mem? */
479 r = tmxr_open_master (mp, cptr); /* open master socket */
480 if (r != SCPE_OK) { /* error? */
481 free (tptr); /* release buf */
482 return SCPE_OPENERR;
483 }
484 strcpy (tptr, cptr); /* copy port */
485 uptr->filename = tptr; /* save */
486 uptr->flags = uptr->flags | UNIT_ATT; /* no more errors */
487 return SCPE_OK;
488 }
489
490 /* Close master socket */
491
492 t_stat tmxr_close_master (TMXR *mp)
493 {
494 int32 i;
495 TMLN *lp;
496
497 for (i = 0; i < mp->lines; i++) { /* loop thru conn */
498 lp = mp->ldsc + i;
499 if (lp->conn) {
500 tmxr_linemsg (lp, "\r\nDisconnected from the ");
501 tmxr_linemsg (lp, sim_name);
502 tmxr_linemsg (lp, " simulator\r\n\n");
503 tmxr_reset_ln (lp);
504 } /* end if conn */
505 } /* end for */
506 sim_close_sock (mp->master, 1); /* close master socket */
507 mp->master = 0;
508 return SCPE_OK;
509 }
510
511 /* Detach unit from master socket */
512
513 t_stat tmxr_detach (TMXR *mp, UNIT *uptr)
514 {
515 if (!(uptr->flags & UNIT_ATT)) return SCPE_OK; /* attached? */
516 tmxr_close_master (mp); /* close master socket */
517 free (uptr->filename); /* free port string */
518 uptr->filename = NULL;
519 uptr->flags = uptr->flags & ~UNIT_ATT; /* not attached */
520 return SCPE_OK;
521 }
522
523 /* Stub examine and deposit */
524
525 t_stat tmxr_ex (t_value *vptr, t_addr addr, UNIT *uptr, int32 sw)
526 {
527 return SCPE_NOFNC;
528 }
529
530 t_stat tmxr_dep (t_value val, t_addr addr, UNIT *uptr, int32 sw)
531 {
532 return SCPE_NOFNC;
533 }
534
535 /* Output message to socket or line descriptor */
536
537 void tmxr_msg (SOCKET sock, char *msg)
538 {
539 if (sock) sim_write_sock (sock, msg, strlen (msg));
540 return;
541 }
542
543 void tmxr_linemsg (TMLN *lp, char *msg)
544 {
545 int32 len;
546
547 for (len = strlen (msg); len > 0; --len)
548 tmxr_putc_ln (lp, *msg++);
549 return;
550 }
551
552 /* Print connections - used only in named SHOW command */
553
554 void tmxr_fconns (FILE *st, TMLN *lp, int32 ln)
555 {
556 if (ln >= 0) fprintf (st, "line %d: ", ln);
557 if (lp->conn) {
558 int32 o1, o2, o3, o4, hr, mn, sc;
559 uint32 ctime;
560
561 o1 = (lp->ipad >> 24) & 0xFF;
562 o2 = (lp->ipad >> 16) & 0xFF;
563 o3 = (lp->ipad >> 8) & 0xFF;
564 o4 = (lp->ipad) & 0xFF;
565 ctime = (sim_os_msec () - lp->cnms) / 1000;
566 hr = ctime / 3600;
567 mn = (ctime / 60) % 60;
568 sc = ctime % 60;
569 fprintf (st, "IP address %d.%d.%d.%d", o1, o2, o3, o4);
570 if (ctime) fprintf (st, ", connected %02d:%02d:%02d\n", hr, mn, sc);
571 }
572 else fprintf (st, "line disconnected\n");
573 if (lp->txlog) fprintf (st, "Logging to %s\n", lp->txlogname);
574 return;
575 }
576
577 /* Print statistics - used only in named SHOW command */
578
579 void tmxr_fstats (FILE *st, TMLN *lp, int32 ln)
580 {
581 static const char *enab = "on";
582 static const char *dsab = "off";
583
584 if (ln >= 0) fprintf (st, "line %d: ", ln);
585 if (lp->conn) {
586 fprintf (st, "input (%s) queued/total = %d/%d, ",
587 (lp->rcve? enab: dsab),
588 lp->rxbpi - lp->rxbpr, lp->rxcnt);
589 fprintf (st, "output (%s) queued/total = %d/%d\n",
590 (lp->xmte? enab: dsab),
591 lp->txbpi - lp->txbpr, lp->txcnt);
592 }
593 else fprintf (st, "line disconnected\n");
594 return;
595 }
596
597 /* Disconnect line */
598
599 t_stat tmxr_dscln (UNIT *uptr, int32 val, char *cptr, void *desc)
600 {
601 TMXR *mp = (TMXR *) desc;
602 TMLN *lp;
603 int32 ln;
604 t_stat r;
605
606 if (mp == NULL) return SCPE_IERR;
607 if (val) { /* = n form */
608 if (cptr == NULL) return SCPE_ARG;
609 ln = (int32) get_uint (cptr, 10, mp->lines - 1, &r);
610 if (r != SCPE_OK) return SCPE_ARG;
611 lp = mp->ldsc + ln;
612 }
613 else {
614 lp = tmxr_find_ldsc (uptr, 0, mp);
615 if (lp == NULL) return SCPE_IERR;
616 }
617 if (lp->conn) {
618 tmxr_linemsg (lp, "\r\nOperator disconnected line\r\n\n");
619 tmxr_reset_ln (lp);
620 }
621 return SCPE_OK;
622 }
623
624 /* Enable logging for line */
625
626 t_stat tmxr_set_log (UNIT *uptr, int32 val, char *cptr, void *desc)
627 {
628 TMXR *mp = (TMXR *) desc;
629 TMLN *lp;
630
631 if (cptr == NULL) return SCPE_2FARG; /* no file name? */
632 lp = tmxr_find_ldsc (uptr, val, mp); /* find line desc */
633 if (lp == NULL) return SCPE_IERR;
634 if (lp->txlog) tmxr_set_nolog (NULL, val, NULL, desc); /* close existing log */
635 lp->txlogname = (char *) calloc (CBUFSIZE, sizeof (char)); /* alloc namebuf */
636 if (lp->txlogname == NULL) return SCPE_MEM; /* can't? */
637 strncpy (lp->txlogname, cptr, CBUFSIZE); /* save file name */
638 lp->txlog = fopen (cptr, "ab"); /* open log */
639 if (lp->txlog == NULL) { /* error? */
640 free (lp->txlogname); /* free buffer */
641 return SCPE_OPENERR;
642 }
643 return SCPE_OK;
644 }
645
646 /* Disable logging for line */
647
648 t_stat tmxr_set_nolog (UNIT *uptr, int32 val, char *cptr, void *desc)
649 {
650 TMXR *mp = (TMXR *) desc;
651 TMLN *lp;
652
653 if (cptr) return SCPE_2MARG; /* no arguments */
654 lp = tmxr_find_ldsc (uptr, val, mp); /* find line desc */
655 if (lp == NULL) return SCPE_IERR;
656 if (lp->txlog) { /* logging? */
657 fclose (lp->txlog); /* close log */
658 free (lp->txlogname); /* free namebuf */
659 lp->txlog = NULL;
660 lp->txlogname = NULL;
661 }
662 return SCPE_OK;
663 }
664
665 /* Show logging status for line */
666
667 t_stat tmxr_show_log (FILE *st, UNIT *uptr, int32 val, void *desc)
668 {
669 TMXR *mp = (TMXR *) desc;
670 TMLN *lp;
671
672 lp = tmxr_find_ldsc (uptr, val, mp); /* find line desc */
673 if (lp == NULL) return SCPE_IERR;
674 if (lp->txlog) fprintf (st, "logging to %s", lp->txlogname);
675 else fprintf (st, "no logging");
676 return SCPE_OK;
677 }
678
679 /* Find line descriptor */
680
681 TMLN *tmxr_find_ldsc (UNIT *uptr, int32 val, TMXR *mp)
682 {
683 if (uptr) { /* called from SET? */
684 DEVICE *dptr = find_dev_from_unit (uptr); /* find device */
685 if (dptr == NULL) return NULL; /* what?? */
686 val = (int32) (uptr - dptr->units); /* implicit line # */
687 }
688 if ((val < 0) || (val >= mp->lines)) return NULL; /* invalid line? */
689 return mp->ldsc + val; /* line descriptor */
690 }