]>
Commit | Line | Data |
---|---|---|
1eee94d3 GM |
1 | (* TermFile.mod implement the ISO TermFile specification. |
2 | ||
83ffe9cd | 3 | Copyright (C) 2009-2023 Free Software Foundation, Inc. |
1eee94d3 GM |
4 | Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>. |
5 | ||
6 | This file is part of GNU Modula-2. | |
7 | ||
8 | GNU Modula-2 is free software; you can redistribute it and/or modify | |
9 | it under the terms of the GNU General Public License as published by | |
10 | the Free Software Foundation; either version 3, or (at your option) | |
11 | any later version. | |
12 | ||
13 | GNU Modula-2 is distributed in the hope that it will be useful, but | |
14 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
16 | General Public License for more details. | |
17 | ||
18 | Under Section 7 of GPL version 3, you are granted additional | |
19 | permissions described in the GCC Runtime Library Exception, version | |
20 | 3.1, as published by the Free Software Foundation. | |
21 | ||
22 | You should have received a copy of the GNU General Public License and | |
23 | a copy of the GCC Runtime Library Exception along with this program; | |
24 | see the files COPYING3 and COPYING.RUNTIME respectively. If not, see | |
25 | <http://www.gnu.org/licenses/>. *) | |
26 | ||
27 | IMPLEMENTATION MODULE TermFile ; | |
28 | ||
29 | ||
30 | FROM ASCII IMPORT nul, lf, cr ; | |
31 | FROM ChanConsts IMPORT ChanFlags ; | |
32 | FROM RTio IMPORT GetDeviceId ; | |
33 | FROM RTgenif IMPORT GenDevIF, InitGenDevIF ; | |
34 | FROM RTdata IMPORT ModuleId, MakeModuleId, InitData, GetData, KillData ; | |
35 | FROM IOChan IMPORT ChanExceptions, InvalidChan, CurrentFlags ; | |
36 | FROM IOConsts IMPORT ReadResults ; | |
37 | FROM Strings IMPORT Assign ; | |
38 | ||
39 | FROM IOLink IMPORT DeviceId, DeviceTable, DeviceTablePtr, DeviceTablePtrValue, IsDevice, | |
40 | AllocateDeviceId, RAISEdevException, MakeChan, UnMakeChan ; | |
41 | ||
42 | FROM Storage IMPORT ALLOCATE, DEALLOCATE ; | |
43 | FROM Strings IMPORT Append ; | |
44 | ||
45 | ||
46 | FROM SYSTEM IMPORT ADDRESS, ADR, LOC ; | |
47 | FROM errno IMPORT geterrno ; | |
48 | FROM ErrnoCategory IMPORT GetOpenResults ; | |
49 | ||
50 | FROM RTgen IMPORT ChanDev, DeviceType, InitChanDev, | |
51 | doLook, doSkip, doSkipLook, doWriteLn, | |
52 | doReadText, doWriteText, doReadLocs, doWriteLocs, | |
53 | checkErrno ; | |
54 | ||
55 | FROM DynamicStrings IMPORT String, InitStringCharStar, CopyOut, | |
56 | KillString ; | |
57 | ||
58 | FROM termios IMPORT TERMIOS, InitTermios, KillTermios, tcgetattr, | |
59 | tcsetattr, cfmakeraw, tcsnow ; | |
60 | ||
61 | ||
62 | IMPORT libc ; | |
63 | ||
64 | ||
65 | CONST | |
66 | O_RDONLY = 0 ; | |
67 | O_WRONLY = 1 ; | |
68 | ||
69 | TYPE | |
70 | PtrToLoc = POINTER TO LOC ; | |
71 | TermInfo = POINTER TO RECORD | |
72 | fd : INTEGER ; | |
73 | pushed : CHAR ; | |
74 | pushBack: BOOLEAN ; | |
75 | old, new: TERMIOS ; | |
76 | END ; | |
77 | ||
78 | VAR | |
79 | mid: ModuleId ; | |
80 | did: DeviceId ; | |
81 | dev: ChanDev ; | |
82 | ||
83 | ||
84 | (* | |
85 | InitTermInfo - creates a new TermInfo and initializes fields, | |
86 | fd, and, pushed. | |
87 | *) | |
88 | ||
89 | PROCEDURE InitTermInfo (fd: INTEGER) : TermInfo ; | |
90 | VAR | |
91 | t: TermInfo ; | |
92 | BEGIN | |
93 | NEW(t) ; | |
94 | t^.fd := fd ; | |
95 | t^.pushBack := FALSE ; | |
96 | t^.new := InitTermios() ; | |
97 | t^.old := InitTermios() ; | |
98 | RETURN( t ) | |
99 | END InitTermInfo ; | |
100 | ||
101 | ||
102 | (* | |
103 | KillTermInfo - deallocates memory associated with, t. | |
104 | *) | |
105 | ||
106 | PROCEDURE KillTermInfo (t: TermInfo) : TermInfo ; | |
107 | BEGIN | |
108 | WITH t^ DO | |
109 | new := KillTermios(new) ; | |
110 | old := KillTermios(old) | |
111 | END ; | |
112 | DISPOSE(t) ; | |
113 | RETURN( NIL ) | |
114 | END KillTermInfo ; | |
115 | ||
116 | ||
117 | (* | |
118 | getFd - return the file descriptor associated with, t. | |
119 | *) | |
120 | ||
121 | PROCEDURE getFd (t: TermInfo) : INTEGER ; | |
122 | BEGIN | |
123 | RETURN( t^.fd ) | |
124 | END getFd ; | |
125 | ||
126 | ||
127 | (* | |
128 | getPushBackChar - returns TRUE if a previously pushed back | |
129 | character is available. If TRUE then, | |
130 | ch, will be assigned to the pushed back | |
131 | character. | |
132 | *) | |
133 | ||
134 | PROCEDURE getPushBackChar (t: TermInfo; VAR ch: CHAR) : BOOLEAN ; | |
135 | BEGIN | |
136 | WITH t^ DO | |
137 | IF pushBack | |
138 | THEN | |
139 | ch := pushed ; | |
140 | pushBack := FALSE ; | |
141 | RETURN( TRUE ) | |
142 | ELSE | |
143 | RETURN( FALSE ) | |
144 | END | |
145 | END | |
146 | END getPushBackChar ; | |
147 | ||
148 | ||
149 | (* | |
150 | setPushBackChar - attempts to push back, ch. Only one character | |
151 | may be pushed back consecutively. | |
152 | *) | |
153 | ||
154 | PROCEDURE setPushBackChar (t: TermInfo; ch: CHAR) : BOOLEAN ; | |
155 | BEGIN | |
156 | WITH t^ DO | |
157 | IF pushBack | |
158 | THEN | |
159 | RETURN( FALSE ) | |
160 | ELSE | |
161 | pushed := ch ; | |
162 | pushBack := TRUE ; | |
163 | RETURN( TRUE ) | |
164 | END | |
165 | END | |
166 | END setPushBackChar ; | |
167 | ||
168 | ||
169 | PROCEDURE look (d: DeviceTablePtr; | |
170 | VAR ch: CHAR; VAR r: ReadResults) ; | |
171 | BEGIN | |
172 | doLook(dev, d, ch, r) | |
173 | END look ; | |
174 | ||
175 | ||
176 | PROCEDURE skip (d: DeviceTablePtr) ; | |
177 | BEGIN | |
178 | doSkip(dev, d) | |
179 | END skip ; | |
180 | ||
181 | ||
182 | PROCEDURE skiplook (d: DeviceTablePtr; | |
183 | VAR ch: CHAR; VAR r: ReadResults) ; | |
184 | BEGIN | |
185 | doSkipLook(dev, d, ch, r) | |
186 | END skiplook ; | |
187 | ||
188 | ||
189 | PROCEDURE lnwrite (d: DeviceTablePtr) ; | |
190 | BEGIN | |
191 | doWriteLn(dev, d) | |
192 | END lnwrite ; | |
193 | ||
194 | ||
195 | PROCEDURE textread (d: DeviceTablePtr; | |
196 | to: ADDRESS; | |
197 | maxChars: CARDINAL; | |
198 | VAR charsRead: CARDINAL) ; | |
199 | BEGIN | |
200 | doReadText(dev, d, to, maxChars, charsRead) | |
201 | END textread ; | |
202 | ||
203 | ||
204 | PROCEDURE textwrite (d: DeviceTablePtr; | |
205 | from: ADDRESS; | |
206 | charsToWrite: CARDINAL); | |
207 | BEGIN | |
208 | doWriteText(dev, d, from, charsToWrite) | |
209 | END textwrite ; | |
210 | ||
211 | ||
212 | PROCEDURE rawread (d: DeviceTablePtr; | |
213 | to: ADDRESS; | |
214 | maxLocs: CARDINAL; | |
215 | VAR locsRead: CARDINAL) ; | |
216 | BEGIN | |
217 | doReadLocs(dev, d, to, maxLocs, locsRead) | |
218 | END rawread ; | |
219 | ||
220 | ||
221 | PROCEDURE rawwrite (d: DeviceTablePtr; | |
222 | from: ADDRESS; | |
223 | locsToWrite: CARDINAL) ; | |
224 | BEGIN | |
225 | doWriteLocs(dev, d, from, locsToWrite) | |
226 | END rawwrite ; | |
227 | ||
228 | ||
229 | (* | |
230 | doreadchar - returns a CHAR from the file associated with, g. | |
231 | *) | |
232 | ||
233 | PROCEDURE doreadchar (g: GenDevIF; d: DeviceTablePtr) : CHAR ; | |
234 | VAR | |
235 | i : INTEGER ; | |
236 | fd: INTEGER ; | |
237 | t : TermInfo ; | |
238 | ch: CHAR ; | |
239 | BEGIN | |
240 | t := GetData(d, mid) ; | |
241 | WITH d^ DO | |
242 | fd := getFd(t) ; | |
243 | IF NOT getPushBackChar(t, ch) | |
244 | THEN | |
245 | REPEAT | |
246 | i := libc.read(fd, ADR(ch), SIZE(ch)) | |
247 | UNTIL i#0 ; | |
248 | IF i<0 | |
249 | THEN | |
250 | errNum := geterrno() | |
251 | END | |
252 | END ; | |
253 | RETURN( ch ) | |
254 | END | |
255 | END doreadchar ; | |
256 | ||
257 | ||
258 | (* | |
259 | dounreadchar - pushes a CHAR back onto the file associated with, g. | |
260 | *) | |
261 | ||
262 | PROCEDURE dounreadchar (g: GenDevIF; d: DeviceTablePtr; ch: CHAR) : CHAR ; | |
263 | VAR | |
264 | fd: INTEGER ; | |
265 | t : TermInfo ; | |
266 | BEGIN | |
267 | t := GetData(d, mid) ; | |
268 | WITH d^ DO | |
269 | fd := getFd(t) ; | |
270 | IF NOT setPushBackChar(t, ch) | |
271 | THEN | |
272 | RAISEdevException(cid, did, notAvailable, | |
273 | 'TermFile.dounreadchar: cannot push back more than one character consecutively') | |
274 | END ; | |
275 | RETURN( ch ) | |
276 | END | |
277 | END dounreadchar ; | |
278 | ||
279 | ||
280 | (* | |
281 | dogeterrno - returns the errno relating to the generic device. | |
282 | *) | |
283 | ||
284 | PROCEDURE dogeterrno (g: GenDevIF; d: DeviceTablePtr) : INTEGER ; | |
285 | BEGIN | |
286 | RETURN geterrno() | |
287 | END dogeterrno ; | |
288 | ||
289 | ||
290 | (* | |
291 | dorbytes - reads upto, max, bytes setting, actual, and | |
292 | returning FALSE if an error (not due to eof) | |
293 | occurred. | |
294 | *) | |
295 | ||
296 | PROCEDURE dorbytes (g: GenDevIF; d: DeviceTablePtr; | |
297 | to: ADDRESS; | |
298 | max: CARDINAL; | |
299 | VAR actual: CARDINAL) : BOOLEAN ; | |
300 | VAR | |
301 | fd: INTEGER ; | |
302 | t : TermInfo ; | |
303 | p : PtrToLoc ; | |
304 | i : INTEGER ; | |
305 | BEGIN | |
306 | t := GetData(d, mid) ; | |
307 | WITH d^ DO | |
308 | IF max>0 | |
309 | THEN | |
310 | p := to ; | |
311 | IF getPushBackChar(t, p^) | |
312 | THEN | |
313 | actual := 1 ; | |
314 | RETURN( TRUE ) | |
315 | END ; | |
316 | fd := getFd(t) ; | |
317 | i := libc.read(fd, p, max) ; | |
318 | IF i>=0 | |
319 | THEN | |
320 | actual := i ; | |
321 | RETURN( TRUE ) | |
322 | ELSE | |
323 | errNum := geterrno() ; | |
324 | actual := 0 ; | |
325 | RETURN( FALSE ) | |
326 | END | |
327 | END | |
328 | END | |
329 | END dorbytes ; | |
330 | ||
331 | ||
332 | (* | |
333 | dowbytes - | |
334 | *) | |
335 | ||
336 | PROCEDURE dowbytes (g: GenDevIF; d: DeviceTablePtr; | |
337 | from: ADDRESS; | |
338 | nBytes: CARDINAL; | |
339 | VAR actual: CARDINAL) : BOOLEAN ; | |
340 | VAR | |
341 | fd: INTEGER ; | |
342 | t : TermInfo ; | |
343 | i : INTEGER ; | |
344 | BEGIN | |
345 | t := GetData(d, mid) ; | |
346 | WITH d^ DO | |
347 | fd := getFd(t) ; | |
348 | i := libc.write(fd, from, nBytes) ; | |
349 | IF i>=0 | |
350 | THEN | |
351 | actual := i ; | |
352 | RETURN( TRUE ) | |
353 | ELSE | |
354 | errNum := geterrno() ; | |
355 | actual := 0 ; | |
356 | RETURN( FALSE ) | |
357 | END | |
358 | END | |
359 | END dowbytes ; | |
360 | ||
361 | ||
362 | (* | |
363 | dowriteln - attempt to write an end of line marker to the | |
364 | file and returns TRUE if successful. | |
365 | *) | |
366 | ||
367 | PROCEDURE dowriteln (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ; | |
368 | VAR | |
369 | a: ARRAY [0..1] OF CHAR ; | |
370 | i: CARDINAL ; | |
371 | BEGIN | |
372 | a[0] := cr ; | |
373 | a[1] := lf ; | |
374 | RETURN( dowbytes(g, d, ADR(a), SIZE(a), i) AND (i=SIZE(a)) ) | |
375 | END dowriteln ; | |
376 | ||
377 | ||
378 | (* | |
379 | iseof - returns TRUE if end of file is seen. | |
380 | *) | |
381 | ||
382 | PROCEDURE iseof (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ; | |
383 | VAR | |
384 | ch: CHAR ; | |
385 | BEGIN | |
386 | ch := doreadchar(g, d) ; | |
387 | WITH d^ DO | |
388 | IF errNum=0 | |
389 | THEN | |
390 | ch := dounreadchar(g, d, ch) ; | |
391 | RETURN( FALSE ) | |
392 | ELSE | |
393 | RETURN( TRUE ) | |
394 | END | |
395 | END | |
396 | END iseof ; | |
397 | ||
398 | ||
399 | (* | |
400 | iseoln - returns TRUE if end of line is seen. | |
401 | *) | |
402 | ||
403 | PROCEDURE iseoln (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ; | |
404 | VAR | |
405 | ch: CHAR ; | |
406 | BEGIN | |
407 | ch := doreadchar(g, d) ; | |
408 | WITH d^ DO | |
409 | IF errNum=0 | |
410 | THEN | |
411 | ch := dounreadchar(g, d, ch) ; | |
412 | RETURN( ch=lf ) | |
413 | ELSE | |
414 | RETURN( FALSE ) | |
415 | END | |
416 | END | |
417 | END iseoln ; | |
418 | ||
419 | ||
420 | (* | |
421 | iserror - returns TRUE if an error was seen on the device. | |
422 | *) | |
423 | ||
424 | PROCEDURE iserror (g: GenDevIF; d: DeviceTablePtr) : BOOLEAN ; | |
425 | BEGIN | |
426 | RETURN( d^.errNum#0 ) | |
427 | END iserror ; | |
428 | ||
429 | ||
430 | (* | |
431 | getname - assigns, a, to the device name of the terminal. | |
432 | *) | |
433 | ||
434 | PROCEDURE getname (d: DeviceTablePtr; | |
435 | VAR a: ARRAY OF CHAR) ; | |
436 | VAR | |
437 | s: String ; | |
438 | BEGIN | |
439 | s := InitStringCharStar(libc.ttyname(0)) ; | |
440 | CopyOut(a, s) ; | |
441 | s := KillString(s) | |
442 | END getname ; | |
443 | ||
444 | ||
445 | (* | |
446 | freeData - disposes of, t. | |
447 | *) | |
448 | ||
449 | PROCEDURE freeData (t: TermInfo) ; | |
450 | BEGIN | |
451 | t := KillTermInfo(t) | |
452 | END freeData ; | |
453 | ||
454 | ||
455 | (* | |
456 | handlefree - | |
457 | *) | |
458 | ||
459 | PROCEDURE handlefree (d: DeviceTablePtr) ; | |
460 | VAR | |
461 | t : TermInfo ; | |
462 | fd: INTEGER ; | |
463 | i : INTEGER ; | |
464 | BEGIN | |
465 | t := GetData(d, mid) ; | |
466 | fd := getFd(t) ; | |
467 | i := libc.close(fd) ; | |
468 | checkErrno(dev, d) ; | |
469 | KillData(d, mid) | |
470 | END handlefree ; | |
471 | ||
472 | ||
473 | (* | |
474 | termOpen - attempts to open up the terminal device. It fills | |
475 | in any implied flags and returns a result depending | |
476 | whether the open was successful. | |
477 | *) | |
478 | ||
479 | PROCEDURE termOpen (t: TermInfo; VAR flagset: FlagSet; VAR e: INTEGER) : OpenResults ; | |
480 | VAR | |
481 | i: INTEGER ; | |
482 | BEGIN | |
483 | WITH t^ DO | |
484 | IF NOT (rawFlag IN flagset) | |
485 | THEN | |
486 | INCL(flagset, textFlag) | |
487 | END ; | |
488 | IF NOT (echoFlag IN flagset) | |
489 | THEN | |
490 | INCL(flagset, interactiveFlag) | |
491 | END ; | |
492 | IF NOT (writeFlag IN flagset) | |
493 | THEN | |
494 | INCL(flagset, readFlag) | |
495 | END ; | |
496 | IF writeFlag IN flagset | |
497 | THEN | |
498 | fd := libc.open(ADR("/dev/tty"), O_WRONLY, 0600B) | |
499 | ELSE | |
500 | fd := libc.open(ADR("/dev/tty"), O_RDONLY) | |
501 | END ; | |
502 | IF tcgetattr(fd, new)=0 | |
503 | THEN | |
504 | END ; | |
505 | IF tcgetattr(fd, old)=0 | |
506 | THEN | |
507 | IF rawFlag IN flagset | |
508 | THEN | |
509 | cfmakeraw(new) | |
510 | END ; | |
511 | IF tcsetattr(fd, tcsnow(), new)=0 | |
512 | THEN | |
513 | END | |
514 | END ; | |
515 | e := geterrno() ; | |
516 | RETURN( GetOpenResults(e) ) | |
517 | END | |
518 | END termOpen ; | |
519 | ||
520 | ||
521 | (* | |
522 | RestoreTerminalSettings - | |
523 | *) | |
524 | ||
525 | PROCEDURE RestoreTerminalSettings (cid: ChanId) ; | |
526 | VAR | |
527 | d: DeviceTablePtr ; | |
528 | t: TermInfo ; | |
529 | e: INTEGER ; | |
530 | BEGIN | |
531 | d := DeviceTablePtrValue(cid, did) ; | |
532 | t := GetData(d, mid) ; | |
533 | WITH t^ DO | |
534 | IF tcsetattr(fd, tcsnow(), old)=0 | |
535 | THEN | |
536 | END | |
537 | END | |
538 | END RestoreTerminalSettings ; | |
539 | ||
540 | ||
541 | (* | |
542 | Open - attempts to obtain and open a channel connected to | |
543 | the terminal. Without the raw flag, text is implied. | |
544 | Without the echo flag, line mode is requested, | |
545 | otherwise single character mode is requested. | |
546 | If successful, assigns to cid the identity of | |
547 | the opened channel, and assigns the value opened to res. | |
548 | If a channel cannot be opened as required, the value of | |
549 | res indicates the reason, and cid identifies the | |
550 | invalid channel. | |
551 | *) | |
552 | ||
553 | PROCEDURE Open (VAR cid: ChanId; | |
554 | flagset: FlagSet; VAR res: OpenResults) ; | |
555 | VAR | |
556 | d: DeviceTablePtr ; | |
557 | t: TermInfo ; | |
558 | e: INTEGER ; | |
559 | BEGIN | |
560 | MakeChan(did, cid) ; (* create new channel *) | |
561 | d := DeviceTablePtrValue(cid, did) ; | |
562 | t := InitTermInfo(-1) ; | |
563 | res := termOpen(t, flagset, e) ; | |
564 | InitData(d, mid, t, freeData) ; (* attach memory to device and module *) | |
565 | WITH d^ DO | |
566 | flags := flagset ; | |
567 | errNum := e ; | |
568 | doLook := look ; | |
569 | doSkip := skip ; | |
570 | doSkipLook := skiplook ; | |
571 | doLnWrite := lnwrite ; | |
572 | doTextRead := textread ; | |
573 | doTextWrite := textwrite ; | |
574 | doRawRead := rawread ; | |
575 | doRawWrite := rawwrite ; | |
576 | doGetName := getname ; | |
577 | doFree := handlefree | |
578 | END | |
579 | END Open ; | |
580 | ||
581 | ||
582 | (* | |
583 | IsTermFile - tests if the channel identified by cid is open to | |
584 | the terminal. | |
585 | *) | |
586 | ||
587 | PROCEDURE IsTermFile (cid: ChanId) : BOOLEAN ; | |
588 | BEGIN | |
589 | RETURN( (cid # NIL) AND (InvalidChan() # cid) AND | |
590 | (IsDevice(cid, did)) AND | |
591 | ((readFlag IN CurrentFlags(cid)) OR | |
592 | (writeFlag IN CurrentFlags(cid))) ) | |
593 | END IsTermFile ; | |
594 | ||
595 | ||
596 | (* | |
597 | Close - if the channel identified by cid is not open to the | |
598 | terminal, the exception wrongDevice is raised; otherwise | |
599 | closes the channel, and assigns the value identifying | |
600 | the invalid channel to cid. | |
601 | *) | |
602 | ||
603 | PROCEDURE Close (VAR cid: ChanId) ; | |
604 | BEGIN | |
605 | IF IsTermFile(cid) | |
606 | THEN | |
607 | RestoreTerminalSettings(cid) ; | |
608 | UnMakeChan(did, cid) ; | |
609 | cid := InvalidChan() | |
610 | ELSE | |
611 | RAISEdevException(cid, did, wrongDevice, | |
612 | 'TermFile.' + __FUNCTION__ + | |
613 | ': channel is opened to the terminal') | |
614 | END | |
615 | END Close ; | |
616 | ||
617 | ||
618 | (* | |
619 | Init - | |
620 | *) | |
621 | ||
622 | PROCEDURE Init ; | |
623 | VAR | |
624 | gen: GenDevIF ; | |
625 | BEGIN | |
626 | MakeModuleId(mid) ; | |
627 | AllocateDeviceId(did) ; | |
628 | gen := InitGenDevIF(did, | |
629 | doreadchar, dounreadchar, | |
630 | dogeterrno, dorbytes, dowbytes, | |
631 | dowriteln, | |
632 | iseof, iseoln, iserror) ; | |
633 | dev := InitChanDev(term, did, gen) | |
634 | END Init ; | |
635 | ||
636 | ||
637 | BEGIN | |
638 | Init | |
639 | END TermFile. |