1 /* sim_tmxr.c: Telnet terminal multiplexor library
3 Copyright (c) 2001-2007, Robert M Supnik
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:
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
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.
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.
26 Based on the original DZ11 simulator by Thord Nilson, as updated by
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
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)
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
49 This library includes:
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
71 All routines are OS-independent.
79 /* Telnet protocol constants - negatives are for init'ing signed char data */
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 */
95 /* Telnet line states */
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 */
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
);
108 extern int32 sim_switches
;
109 extern char sim_name
[];
110 extern FILE *sim_log
;
111 extern uint32
sim_os_msec (void);
113 /* Poll for new connection
115 Called from unit service routine to test for new connection
118 *mp = pointer to terminal multiplexor descriptor
120 line number activated, -1 if none
123 int32
tmxr_poll_conn (TMXR
*mp
)
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
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? */
143 if (i
>= mp
->lines
) { /* all busy? */
144 tmxr_msg (newsock
, "All connections busy\r\n");
145 sim_close_sock (newsock
, 0);
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 */
165 } /* end if newsock */
171 void tmxr_reset_ln (TMLN
*lp
)
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;
184 /* Get character from specific line
187 *lp = pointer to terminal line descriptor
189 valid + char, 0 if line
192 int32
tmxr_getc_ln (TMLN
*lp
)
197 if (lp
->conn
&& lp
->rcve
) { /* conn & enb? */
198 j
= lp
->rxbpi
- lp
->rxbpr
; /* # input chrs */
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 */
206 if (lp
->rxbpi
== lp
->rxbpr
) /* empty? zero ptrs */
207 lp
->rxbpi
= lp
->rxbpr
= 0;
214 *mp = pointer to terminal multiplexor descriptor
218 void tmxr_poll_rx (TMXR
*mp
)
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 */
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
;
243 /* Examine new data, remove TELNET cruft before making input available */
245 for (; j
< lp
->rxbpi
; ) { /* loop thru char */
246 signed char tmp
= lp
->rxb
[j
]; /* get char */
247 switch (lp
->tsta
) { /* case tlnt state */
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 */
255 if ((tmp
== TN_CR
) && lp
->dstb
) /* CR, no bin */
256 lp
->tsta
= TNS_CRPAD
; /* skip pad char */
257 j
= j
+ 1; /* advance j */
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 */
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 */
273 if (tmp
== TN_WILL
) /* IAC + WILL? */
275 else if (tmp
== TN_WONT
) /* IAC + WONT? */
277 else lp
->tsta
= TNS_SKIP
; /* IAC + other */
278 tmxr_rmvrc (lp
, j
); /* remove char */
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;
287 /* Negotiation with the HP terminal emulator "QCTerm" is not working.
288 QCTerm says "WONT BIN" but sends bare CRs. RFC 854 says:
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.
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
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 */
308 case TNS_SKIP
: default: /* skip char */
309 lp
->tsta
= TNS_NORM
; /* next normal */
310 tmxr_rmvrc (lp
, j
); /* remove char */
312 } /* end case state */
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 */
324 /* Return count of available characters for line */
326 int32
tmxr_rqln (TMLN
*lp
)
328 return (lp
->rxbpi
- lp
->rxbpr
);
331 /* Remove character p (and matching status) from line l input buffer */
333 void tmxr_rmvrc (TMLN
*lp
, int32 p
)
335 for ( ; p
< lp
->rxbpi
; p
++) {
336 lp
->rxb
[p
] = lp
->rxb
[p
+ 1];
337 lp
->rbr
[p
] = lp
->rbr
[p
+ 1];
339 lp
->rxbpi
= lp
->rxbpi
- 1;
343 /* Store character in line buffer
346 *lp = pointer to line descriptor
349 status = ok, connection lost, or stall
352 t_stat
tmxr_putc_ln (TMLN
*lp
, int32 chr
)
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? */
365 if (tmxr_tqln (lp
) > (TMXR_MAXBUF
- TMXR_GUARD
)) /* near full? */
366 lp
->xmte
= 0; /* disable line */
367 return SCPE_OK
; /* char sent */
369 lp
->xmte
= 0; /* no room, dsbl line */
370 return SCPE_STALL
; /* char not sent */
376 *mp = pointer to terminal multiplexor descriptor
381 void tmxr_poll_tx (TMXR
*mp
)
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 */
395 /* Send buffered data across network
398 *lp = pointer to line descriptor
400 returns number of bytes still buffered
403 int32
tmxr_send_buffered_data (TMLN
*lp
)
405 int32 nbytes
, sbytes
;
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
;
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
;
429 } /* end if nbytes */
433 /* Return count of buffered characters for line */
435 int32
tmxr_tqln (TMLN
*lp
)
437 return (lp
->txbpi
- lp
->txbpr
+ ((lp
->txbpi
< lp
->txbpr
)? TMXR_MAXBUF
: 0));
440 /* Open master socket */
442 t_stat
tmxr_open_master (TMXR
*mp
, char *cptr
)
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 */
460 lp
->conn
= lp
->tsta
= 0;
461 lp
->rxbpi
= lp
->rxbpr
= 0;
462 lp
->txbpi
= lp
->txbpr
= 0;
463 lp
->rxcnt
= lp
->txcnt
= 0;
470 /* Attach unit to master socket */
472 t_stat
tmxr_attach (TMXR
*mp
, UNIT
*uptr
, char *cptr
)
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 */
484 strcpy (tptr
, cptr
); /* copy port */
485 uptr
->filename
= tptr
; /* save */
486 uptr
->flags
= uptr
->flags
| UNIT_ATT
; /* no more errors */
490 /* Close master socket */
492 t_stat
tmxr_close_master (TMXR
*mp
)
497 for (i
= 0; i
< mp
->lines
; i
++) { /* loop thru conn */
500 tmxr_linemsg (lp
, "\r\nDisconnected from the ");
501 tmxr_linemsg (lp
, sim_name
);
502 tmxr_linemsg (lp
, " simulator\r\n\n");
506 sim_close_sock (mp
->master
, 1); /* close master socket */
511 /* Detach unit from master socket */
513 t_stat
tmxr_detach (TMXR
*mp
, UNIT
*uptr
)
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 */
523 /* Stub examine and deposit */
525 t_stat
tmxr_ex (t_value
*vptr
, t_addr addr
, UNIT
*uptr
, int32 sw
)
530 t_stat
tmxr_dep (t_value val
, t_addr addr
, UNIT
*uptr
, int32 sw
)
535 /* Output message to socket or line descriptor */
537 void tmxr_msg (SOCKET sock
, char *msg
)
539 if (sock
) sim_write_sock (sock
, msg
, strlen (msg
));
543 void tmxr_linemsg (TMLN
*lp
, char *msg
)
547 for (len
= strlen (msg
); len
> 0; --len
)
548 tmxr_putc_ln (lp
, *msg
++);
552 /* Print connections - used only in named SHOW command */
554 void tmxr_fconns (FILE *st
, TMLN
*lp
, int32 ln
)
556 if (ln
>= 0) fprintf (st
, "line %d: ", ln
);
558 int32 o1
, o2
, o3
, o4
, hr
, mn
, sc
;
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;
567 mn
= (ctime
/ 60) % 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
);
572 else fprintf (st
, "line disconnected\n");
573 if (lp
->txlog
) fprintf (st
, "Logging to %s\n", lp
->txlogname
);
577 /* Print statistics - used only in named SHOW command */
579 void tmxr_fstats (FILE *st
, TMLN
*lp
, int32 ln
)
581 static const char *enab
= "on";
582 static const char *dsab
= "off";
584 if (ln
>= 0) fprintf (st
, "line %d: ", ln
);
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
);
593 else fprintf (st
, "line disconnected\n");
597 /* Disconnect line */
599 t_stat
tmxr_dscln (UNIT
*uptr
, int32 val
, char *cptr
, void *desc
)
601 TMXR
*mp
= (TMXR
*) desc
;
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
;
614 lp
= tmxr_find_ldsc (uptr
, 0, mp
);
615 if (lp
== NULL
) return SCPE_IERR
;
618 tmxr_linemsg (lp
, "\r\nOperator disconnected line\r\n\n");
624 /* Enable logging for line */
626 t_stat
tmxr_set_log (UNIT
*uptr
, int32 val
, char *cptr
, void *desc
)
628 TMXR
*mp
= (TMXR
*) desc
;
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 */
646 /* Disable logging for line */
648 t_stat
tmxr_set_nolog (UNIT
*uptr
, int32 val
, char *cptr
, void *desc
)
650 TMXR
*mp
= (TMXR
*) desc
;
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 */
660 lp
->txlogname
= NULL
;
665 /* Show logging status for line */
667 t_stat
tmxr_show_log (FILE *st
, UNIT
*uptr
, int32 val
, void *desc
)
669 TMXR
*mp
= (TMXR
*) desc
;
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");
679 /* Find line descriptor */
681 TMLN
*tmxr_find_ldsc (UNIT
*uptr
, int32 val
, TMXR
*mp
)
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 # */
688 if ((val
< 0) || (val
>= mp
->lines
)) return NULL
; /* invalid line? */
689 return mp
->ldsc
+ val
; /* line descriptor */