1 /*
2 * Copyright (c) 2019 Georg Brein. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are met:
6 *
7 * 1. Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 *
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * 3. Neither the name of the copyright holder nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28 * POSSIBILITY OF SUCH DAMAGE.
29 */
30
31
32 #include <locale.h>
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <stddef.h>
36 #include <string.h>
37 #include <wchar.h>
38 #include <wctype.h>
39 #include <limits.h>
40 #include <errno.h>
41
42 #include <unistd.h>
43
44 #include "tnylpo.h"
45
46
47 /*
48 * global variables containing the configuration information
49 * from the command line resp. from the configuration file
50 */
51 /*
52 * size of the emulated terminal (doesn't change)
53 */
54 int lines = 0;
55 int cols = 0;
56 /*
57 * use terminal emulation (1) or line orientated/batch console (0)
58 */
59 int conf_interactive = (-1);
60 /*
61 * use WordStar (1) or VT52 (0) cursor keys (terminal emulation only)
62 */
63 int altkeys = (-1);
64 /*
65 * reverse the backspace and the delete key (terminal emulation only)
66 */
67 int reverse_bs_del = (-1);
68 /*
69 * seconds to wait before exiting full screen mode (terminal emulation only)
70 */
71 int screen_delay = (-1);
72 /*
73 * CP/M charset in use (0 ... primary, 1 ... secondary);
74 */
75 int charset = 0;
76 /*
77 * primary and secondary character set
78 */
79 wchar_t *conf_charset[256];
80 wchar_t *conf_alt_charset[256];
81 /*
82 * use this character to represent unprintable characters
83 */
84 wchar_t *conf_unprintable = NULL;
85 /*
86 * paths corresponding to the CP/M drives A...P and their read-only flags
87 */
88 char *conf_drives[16];
89 int conf_readonly[16];
90 /*
91 * name of the command file to execute
92 */
93 char *conf_command = NULL;
94 /*
95 * additional command line parameters
96 */
97 int conf_argc = 0;
98 char **conf_argv = NULL;
99 /*
100 * files corresponding to the CP/M character devices LST, PUN, and RDR
101 * and the flags controlling translation to Unix format
102 */
103 char *conf_printer = NULL;
104 int conf_printer_raw = (-1);
105 char *conf_punch = NULL;
106 int conf_punch_raw = (-1);
107 char *conf_reader = NULL;
108 int conf_reader_raw = (-1);
109 /*
110 * path of the log file
111 */
112 char *conf_log = NULL;
113 /*
114 * log level
115 */
116 enum log_level log_level = LL_UNSET;
117 /*
118 * CP/M default drive (0...15 corresponding to A...P)
119 */
120 int default_drive = (-1);
121 /*
122 * flag controlling wether calling BDOS function 19 closes the
123 * corresponding Unix file or not (some programs, e.g. dBase II continue
124 * to use FCBs for file I/O after closing them)
125 */
126 int dont_close = (-1);
127 /*
128 * dump configuration: default is no dump
129 */
130 enum dump conf_dump = 0;
131 /*
132 * emulation delay: insert a pause of delay_nanoseconds every
133 * delay_count instructions (default: no delay)
134 */
135 int delay_count = (-1);
136 int delay_nanoseconds = (-1);
137 /*
138 * save configuration: default is no saving done
139 */
140 const char *conf_save_file = NULL;
141 int conf_save_hex = 0;
142 int conf_save_start = 0;
143 int conf_save_end = 0;
144 /*
145 * use colors in terminal
146 */
147 int conf_color = (-1);
148 int conf_foreground = (-1);
149 int conf_background = (-1);
150
151
152 /*
153 * maximal length of a line in the configuration file
154 */
155 #define L_LINE 1024
156
157
158 /*
159 * variables for communication between the procedures of the
160 * configuration file parser
161 */
162 /*
163 * file pointer, file name, and current line number of the configuration file
164 */
165 static FILE *cf = NULL;
166 static char *cfn = NULL;
167 static int ln = 0;
168 /*
169 * pointer to the current character in current line of the configuration file
170 */
171 static wchar_t *curr_p = NULL;
172 /*
173 * current token and its attributes
174 */
175 static int token = (-1);
176 static unsigned long token_ul = 0;
177 static wchar_t *token_ident = NULL;
178 static wchar_t *token_string = NULL;
179 /*
180 * character class for blank; Solaris < 10 doesn't support
181 * iswblank(), so we use iswctype() instead; for this, we need the
182 * value of wctype("blank"), which we get once and store in this variable
183 */
184 static wctype_t wctype_blank;
185
186
187 /*
188 * built-in character sets: VT52 (ASCII + VT52 graphical character set),
189 * pure ASCII, and ISO-8859-1/Latin 1
190 */
191 enum charset {
192 CS_NONE = (-1),
193 CS_VT52 = 0,
194 CS_ASCII = 1,
195 CS_LATIN1 = 2,
196 CS_TNYLPO = 3
197 };
198
199
200 /*
201 * Unfortunately, some platforms (though supporting UTF-8) have
202 * problems with some characters, e. g. Solaris < 10, which displays
203 * some symbols and the box-drawing characters in double width,
204 * which is not supported by tnylpo.
205 *
206 * The following #ifs are a hack to keep tnylpo useable on these platforms.
207 */
208
209
210 /*
211 * built-in character sets in wchar_t representation (unfortunately,
212 * not all of the VT52 graphical characters are available)
213 */
214 static const wchar_t *default_charset[256][4] = {
215 #if (defined OLD_SOLARIS)
216 /*00*/ { NULL, NULL, NULL, L"·" },
217 { L"·", NULL, NULL, L"·" },
218 { L"·", NULL, NULL, L"·" },
219 { NULL, NULL, NULL, L"·" },
220 { NULL, NULL, NULL, L"·" },
221 { NULL, NULL, NULL, L"·" },
222 { L"°", NULL, NULL, L"·" },
223 { L"±", NULL, NULL, L"·" },
224 { L"·", NULL, NULL, L"·" },
225 { L"·", NULL, NULL, L"·" },
226 { L"÷", NULL, NULL, L"+" },
227 { L"·", NULL, NULL, L"+" },
228 { NULL, NULL, NULL, L"+" },
229 { NULL, NULL, NULL, L"+" },
230 { NULL, NULL, NULL, L"+" },
231 { NULL, NULL, NULL, L"·" },
232 /*10*/ { NULL, NULL, NULL, L"·" },
233 { NULL, NULL, NULL, L"-" },
234 { NULL, NULL, NULL, L"·" },
235 { NULL, NULL, NULL, L"·" },
236 { L"·", NULL, NULL, L"+" },
237 { L"·", NULL, NULL, L"+" },
238 { L"·", NULL, NULL, L"+" },
239 { L"·", NULL, NULL, L"+" },
240 { L"·", NULL, NULL, L"|" },
241 { L"·", NULL, NULL, L"·" },
242 { L"·", NULL, NULL, L"·" },
243 { L"·", NULL, NULL, L"·" },
244 { L"·", NULL, NULL, L"·" },
245 { L"·", NULL, NULL, L"·" },
246 { L"¶", NULL, NULL, L"·" },
247 { L" ", NULL, NULL, L"·" },
248 /*20*/ { L" ", L" ", L" ", L" " },
249 { L"!", L"!", L"!", L"!" },
250 { L"\"", L"\"", L"\"", L"\"" },
251 { L"#", L"#", L"#", L"#" },
252 { L"$", L"$", L"$", L"$" },
253 { L"%", L"%", L"%", L"%" },
254 { L"&", L"&", L"&", L"&" },
255 { L"\'", L"\'", L"\'", L"\'" },
256 { L"(", L"(", L"(", L"(" },
257 { L")", L")", L")", L")" },
258 { L"*", L"*", L"*", L"*" },
259 { L"+", L"+", L"+", L"+" },
260 { L",", L",", L",", L"," },
261 { L"-", L"-", L"-", L"-" },
262 { L".", L".", L".", L"." },
263 { L"/", L"/", L"/", L"/" },
264 /*30*/ { L"0", L"0", L"0", L"0" },
265 { L"1", L"1", L"1", L"1" },
266 { L"2", L"2", L"2", L"2" },
267 { L"3", L"3", L"3", L"3" },
268 { L"4", L"4", L"4", L"4" },
269 { L"5", L"5", L"5", L"5" },
270 { L"6", L"6", L"6", L"6" },
271 { L"7", L"7", L"7", L"7" },
272 { L"8", L"8", L"8", L"8" },
273 { L"9", L"9", L"9", L"9" },
274 { L":", L":", L":", L":" },
275 { L";", L";", L";", L";" },
276 { L"<", L"<", L"<", L"<" },
277 { L"=", L"=", L"=", L"=" },
278 { L">", L">", L">", L">" },
279 { L"?", L"?", L"?", L"?" },
280 /*40*/ { L"@", L"@", L"@", L"@" },
281 { L"A", L"A", L"A", L"A" },
282 { L"B", L"B", L"B", L"B" },
283 { L"C", L"C", L"C", L"C" },
284 { L"D", L"D", L"D", L"D" },
285 { L"E", L"E", L"E", L"E" },
286 { L"F", L"F", L"F", L"F" },
287 { L"G", L"G", L"G", L"G" },
288 { L"H", L"H", L"H", L"H" },
289 { L"I", L"I", L"I", L"I" },
290 { L"J", L"J", L"J", L"J" },
291 { L"K", L"K", L"K", L"K" },
292 { L"L", L"L", L"L", L"L" },
293 { L"M", L"M", L"M", L"M" },
294 { L"N", L"N", L"N", L"N" },
295 { L"O", L"O", L"O", L"O" },
296 /*50*/ { L"P", L"P", L"P", L"P" },
297 { L"Q", L"Q", L"Q", L"Q" },
298 { L"R", L"R", L"R", L"R" },
299 { L"S", L"S", L"S", L"S" },
300 { L"T", L"T", L"T", L"T" },
301 { L"U", L"U", L"U", L"U" },
302 { L"V", L"V", L"V", L"V" },
303 { L"W", L"W", L"W", L"W" },
304 { L"X", L"X", L"X", L"X" },
305 { L"Y", L"Y", L"Y", L"Y" },
306 { L"Z", L"Z", L"Z", L"Z" },
307 { L"[", L"[", L"[", L"[" },
308 { L"\\", L"\\", L"\\", L"\\" },
309 { L"]", L"]", L"]", L"]" },
310 { L"^", L"^", L"^", L"^" },
311 { L"_", L"_", L"_", L"_" },
312 /*60*/ { L"`", L"`", L"`", L"`" },
313 { L"a", L"a", L"a", L"a" },
314 { L"b", L"b", L"b", L"b" },
315 { L"c", L"c", L"c", L"c" },
316 { L"d", L"d", L"d", L"d" },
317 { L"e", L"e", L"e", L"e" },
318 { L"f", L"f", L"f", L"f" },
319 { L"g", L"g", L"g", L"g" },
320 { L"h", L"h", L"h", L"h" },
321 { L"i", L"i", L"i", L"i" },
322 { L"j", L"j", L"j", L"j" },
323 { L"k", L"k", L"k", L"k" },
324 { L"l", L"l", L"l", L"l" },
325 { L"m", L"m", L"m", L"m" },
326 { L"n", L"n", L"n", L"n" },
327 { L"o", L"o", L"o", L"o" },
328 /*70*/ { L"p", L"p", L"p", L"p" },
329 { L"q", L"q", L"q", L"q" },
330 { L"r", L"r", L"r", L"r" },
331 { L"s", L"s", L"s", L"s" },
332 { L"t", L"t", L"t", L"t" },
333 { L"u", L"u", L"u", L"u" },
334 { L"v", L"v", L"v", L"v" },
335 { L"w", L"w", L"w", L"w" },
336 { L"x", L"x", L"x", L"x" },
337 { L"y", L"y", L"y", L"y" },
338 { L"z", L"z", L"z", L"z" },
339 { L"{", L"{", L"{", L"{" },
340 { L"|", L"|", L"|", L"|" },
341 { L"}", L"}", L"}", L"}" },
342 { L"~", L"~", L"~", L"~" },
343 { NULL, NULL, NULL, L"·" },
344 /*80*/ { NULL, NULL, NULL, L"€" },
345 { NULL, NULL, NULL, L"·" },
346 { NULL, NULL, NULL, L"·" },
347 { NULL, NULL, NULL, L"·" },
348 { NULL, NULL, NULL, L"·" },
349 { NULL, NULL, NULL, L"·" },
350 { NULL, NULL, NULL, L"·" },
351 { NULL, NULL, NULL, L"·" },
352 { NULL, NULL, NULL, L"·" },
353 { NULL, NULL, NULL, L"·" },
354 { NULL, NULL, NULL, L"Š" },
355 { NULL, NULL, NULL, L"·" },
356 { NULL, NULL, NULL, L"Œ" },
357 { NULL, NULL, NULL, L"·" },
358 { NULL, NULL, NULL, L"Ž" },
359 { NULL, NULL, NULL, L"·" },
360 /*90*/ { NULL, NULL, NULL, L"·" },
361 { NULL, NULL, NULL, L"·" },
362 { NULL, NULL, NULL, L"·" },
363 { NULL, NULL, NULL, L"·" },
364 { NULL, NULL, NULL, L"·" },
365 { NULL, NULL, NULL, L"·" },
366 { NULL, NULL, NULL, L"·" },
367 { NULL, NULL, NULL, L"·" },
368 { NULL, NULL, NULL, L"·" },
369 { NULL, NULL, NULL, L"·" },
370 { NULL, NULL, NULL, L"š" },
371 { NULL, NULL, NULL, L"·" },
372 { NULL, NULL, NULL, L"œ" },
373 { NULL, NULL, NULL, L"·" },
374 { NULL, NULL, NULL, L"ž" },
375 { NULL, NULL, NULL, L"Ÿ" },
376 /*a0*/ { NULL, NULL, L" ", L" " },
377 { NULL, NULL, L"¡", L"¡" },
378 { NULL, NULL, L"¢", L"¢" },
379 { NULL, NULL, L"£", L"£" },
380 { NULL, NULL, L"¤", L"¤" },
381 { NULL, NULL, L"¥", L"¥" },
382 { NULL, NULL, L"¦", L"¦" },
383 { NULL, NULL, L"§", L"§" },
384 { NULL, NULL, L"¨", L"¨" },
385 { NULL, NULL, L"©", L"©" },
386 { NULL, NULL, L"ª", L"ª" },
387 { NULL, NULL, L"«", L"«" },
388 { NULL, NULL, L"¬", L"¬" },
389 { NULL, NULL, L"-", L"-" },
390 { NULL, NULL, L"®", L"®" },
391 { NULL, NULL, L"¯", L"¯" },
392 /*b0*/ { NULL, NULL, L"°", L"°" },
393 { NULL, NULL, L"±", L"±" },
394 { NULL, NULL, L"²", L"²" },
395 { NULL, NULL, L"³", L"³" },
396 { NULL, NULL, L"´", L"´" },
397 { NULL, NULL, L"µ", L"µ" },
398 { NULL, NULL, L"¶", L"¶" },
399 { NULL, NULL, L"·", L"·" },
400 { NULL, NULL, L"¸", L"¸" },
401 { NULL, NULL, L"¹", L"¹" },
402 { NULL, NULL, L"º", L"º" },
403 { NULL, NULL, L"»", L"»" },
404 { NULL, NULL, L"¼", L"¼" },
405 { NULL, NULL, L"½", L"½" },
406 { NULL, NULL, L"¾", L"¾" },
407 { NULL, NULL, L"¿", L"¿" },
408 /*c0*/ { NULL, NULL, L"À", L"À" },
409 { NULL, NULL, L"Á", L"Á" },
410 { NULL, NULL, L"Â", L"Â" },
411 { NULL, NULL, L"Ã", L"Ã" },
412 { NULL, NULL, L"Ä", L"Ä" },
413 { NULL, NULL, L"Å", L"Å" },
414 { NULL, NULL, L"Æ", L"Æ" },
415 { NULL, NULL, L"Ç", L"Ç" },
416 { NULL, NULL, L"È", L"È" },
417 { NULL, NULL, L"É", L"É" },
418 { NULL, NULL, L"Ê", L"Ê" },
419 { NULL, NULL, L"Ë", L"Ë" },
420 { NULL, NULL, L"Ì", L"Ì" },
421 { NULL, NULL, L"Í", L"Í" },
422 { NULL, NULL, L"Î", L"Î" },
423 { NULL, NULL, L"Ï", L"Ï" },
424 /*d0*/ { NULL, NULL, L"Ð", L"Ð" },
425 { NULL, NULL, L"Ñ", L"Ñ" },
426 { NULL, NULL, L"Ò", L"Ò" },
427 { NULL, NULL, L"Ó", L"Ó" },
428 { NULL, NULL, L"Ô", L"Ô" },
429 { NULL, NULL, L"Õ", L"Õ" },
430 { NULL, NULL, L"Ö", L"Ö" },
431 { NULL, NULL, L"×", L"×" },
432 { NULL, NULL, L"Ø", L"Ø" },
433 { NULL, NULL, L"Ù", L"Ù" },
434 { NULL, NULL, L"Ú", L"Ú" },
435 { NULL, NULL, L"Û", L"Û" },
436 { NULL, NULL, L"Ü", L"Ü" },
437 { NULL, NULL, L"Ý", L"Ý" },
438 { NULL, NULL, L"Þ", L"Þ" },
439 { NULL, NULL, L"ß", L"ß" },
440 /*e0*/ { NULL, NULL, L"à", L"à" },
441 { NULL, NULL, L"á", L"á" },
442 { NULL, NULL, L"â", L"â" },
443 { NULL, NULL, L"ã", L"ã" },
444 { NULL, NULL, L"ä", L"ä" },
445 { NULL, NULL, L"å", L"å" },
446 { NULL, NULL, L"æ", L"æ" },
447 { NULL, NULL, L"ç", L"ç" },
448 { NULL, NULL, L"è", L"è" },
449 { NULL, NULL, L"é", L"é" },
450 { NULL, NULL, L"ê", L"ê" },
451 { NULL, NULL, L"ë", L"ë" },
452 { NULL, NULL, L"ì", L"ì" },
453 { NULL, NULL, L"í", L"í" },
454 { NULL, NULL, L"î", L"î" },
455 { NULL, NULL, L"ï", L"ï" },
456 /*f0*/ { NULL, NULL, L"ð", L"ð" },
457 { NULL, NULL, L"ñ", L"ñ" },
458 { NULL, NULL, L"ò", L"ò" },
459 { NULL, NULL, L"ó", L"ó" },
460 { NULL, NULL, L"ô", L"ô" },
461 { NULL, NULL, L"õ", L"õ" },
462 { NULL, NULL, L"ö", L"ö" },
463 { NULL, NULL, L"÷", L"÷" },
464 { NULL, NULL, L"ø", L"ø" },
465 { NULL, NULL, L"ù", L"ù" },
466 { NULL, NULL, L"ú", L"ú" },
467 { NULL, NULL, L"û", L"û" },
468 { NULL, NULL, L"ü", L"ü" },
469 { NULL, NULL, L"ý", L"ý" },
470 { NULL, NULL, L"þ", L"þ" },
471 { NULL, NULL, L"ÿ", L"ÿ" }
472 #else
473 /*00*/ { NULL, NULL, NULL, L"▄" },
474 { L"█", NULL, NULL, L"█" },
475 { L"⅟", NULL, NULL, L"▐" },
476 { NULL, NULL, NULL, L"▖" },
477 { NULL, NULL, NULL, L"▗" },
478 { NULL, NULL, NULL, L"▘" },
479 { L"°", NULL, NULL, L"▝" },
480 { L"±", NULL, NULL, L"▌" },
481 { L"→", NULL, NULL, L"▀" },
482 { L"…", NULL, NULL, L"▞" },
483 { L"÷", NULL, NULL, L"┘" },
484 { L"↓", NULL, NULL, L"┐" },
485 { NULL, NULL, NULL, L"┌" },
486 { NULL, NULL, NULL, L"└" },
487 { NULL, NULL, NULL, L"┼" },
488 { NULL, NULL, NULL, L"▙" },
489 /*10*/ { NULL, NULL, NULL, L"▛" },
490 { NULL, NULL, NULL, L"─" },
491 { NULL, NULL, NULL, L"▜" },
492 { NULL, NULL, NULL, L"▟" },
493 { L"₀", NULL, NULL, L"├" },
494 { L"₁", NULL, NULL, L"┤" },
495 { L"₂", NULL, NULL, L"┴" },
496 { L"₃", NULL, NULL, L"┬" },
497 { L"₄", NULL, NULL, L"│" },
498 { L"₅", NULL, NULL, L"←" },
499 { L"₆", NULL, NULL, L"↑" },
500 { L"₇", NULL, NULL, L"→" },
501 { L"₈", NULL, NULL, L"↓" },
502 { L"₉", NULL, NULL, L"▚" },
503 { L"¶", NULL, NULL, L"░" },
504 { L" ", NULL, NULL, L"▒" },
505 /*20*/ { L" ", L" ", L" ", L" " },
506 { L"!", L"!", L"!", L"!" },
507 { L"\"", L"\"", L"\"", L"\"" },
508 { L"#", L"#", L"#", L"#" },
509 { L"$", L"$", L"$", L"$" },
510 { L"%", L"%", L"%", L"%" },
511 { L"&", L"&", L"&", L"&" },
512 { L"\'", L"\'", L"\'", L"\'" },
513 { L"(", L"(", L"(", L"(" },
514 { L")", L")", L")", L")" },
515 { L"*", L"*", L"*", L"*" },
516 { L"+", L"+", L"+", L"+" },
517 { L",", L",", L",", L"," },
518 { L"-", L"-", L"-", L"-" },
519 { L".", L".", L".", L"." },
520 { L"/", L"/", L"/", L"/" },
521 /*30*/ { L"0", L"0", L"0", L"0" },
522 { L"1", L"1", L"1", L"1" },
523 { L"2", L"2", L"2", L"2" },
524 { L"3", L"3", L"3", L"3" },
525 { L"4", L"4", L"4", L"4" },
526 { L"5", L"5", L"5", L"5" },
527 { L"6", L"6", L"6", L"6" },
528 { L"7", L"7", L"7", L"7" },
529 { L"8", L"8", L"8", L"8" },
530 { L"9", L"9", L"9", L"9" },
531 { L":", L":", L":", L":" },
532 { L";", L";", L";", L";" },
533 { L"<", L"<", L"<", L"<" },
534 { L"=", L"=", L"=", L"=" },
535 { L">", L">", L">", L">" },
536 { L"?", L"?", L"?", L"?" },
537 /*40*/ { L"@", L"@", L"@", L"@" },
538 { L"A", L"A", L"A", L"A" },
539 { L"B", L"B", L"B", L"B" },
540 { L"C", L"C", L"C", L"C" },
541 { L"D", L"D", L"D", L"D" },
542 { L"E", L"E", L"E", L"E" },
543 { L"F", L"F", L"F", L"F" },
544 { L"G", L"G", L"G", L"G" },
545 { L"H", L"H", L"H", L"H" },
546 { L"I", L"I", L"I", L"I" },
547 { L"J", L"J", L"J", L"J" },
548 { L"K", L"K", L"K", L"K" },
549 { L"L", L"L", L"L", L"L" },
550 { L"M", L"M", L"M", L"M" },
551 { L"N", L"N", L"N", L"N" },
552 { L"O", L"O", L"O", L"O" },
553 /*50*/ { L"P", L"P", L"P", L"P" },
554 { L"Q", L"Q", L"Q", L"Q" },
555 { L"R", L"R", L"R", L"R" },
556 { L"S", L"S", L"S", L"S" },
557 { L"T", L"T", L"T", L"T" },
558 { L"U", L"U", L"U", L"U" },
559 { L"V", L"V", L"V", L"V" },
560 { L"W", L"W", L"W", L"W" },
561 { L"X", L"X", L"X", L"X" },
562 { L"Y", L"Y", L"Y", L"Y" },
563 { L"Z", L"Z", L"Z", L"Z" },
564 { L"[", L"[", L"[", L"[" },
565 { L"\\", L"\\", L"\\", L"\\" },
566 { L"]", L"]", L"]", L"]" },
567 { L"^", L"^", L"^", L"^" },
568 { L"_", L"_", L"_", L"_" },
569 /*60*/ { L"`", L"`", L"`", L"`" },
570 { L"a", L"a", L"a", L"a" },
571 { L"b", L"b", L"b", L"b" },
572 { L"c", L"c", L"c", L"c" },
573 { L"d", L"d", L"d", L"d" },
574 { L"e", L"e", L"e", L"e" },
575 { L"f", L"f", L"f", L"f" },
576 { L"g", L"g", L"g", L"g" },
577 { L"h", L"h", L"h", L"h" },
578 { L"i", L"i", L"i", L"i" },
579 { L"j", L"j", L"j", L"j" },
580 { L"k", L"k", L"k", L"k" },
581 { L"l", L"l", L"l", L"l" },
582 { L"m", L"m", L"m", L"m" },
583 { L"n", L"n", L"n", L"n" },
584 { L"o", L"o", L"o", L"o" },
585 /*70*/ { L"p", L"p", L"p", L"p" },
586 { L"q", L"q", L"q", L"q" },
587 { L"r", L"r", L"r", L"r" },
588 { L"s", L"s", L"s", L"s" },
589 { L"t", L"t", L"t", L"t" },
590 { L"u", L"u", L"u", L"u" },
591 { L"v", L"v", L"v", L"v" },
592 { L"w", L"w", L"w", L"w" },
593 { L"x", L"x", L"x", L"x" },
594 { L"y", L"y", L"y", L"y" },
595 { L"z", L"z", L"z", L"z" },
596 { L"{", L"{", L"{", L"{" },
597 { L"|", L"|", L"|", L"|" },
598 { L"}", L"}", L"}", L"}" },
599 { L"~", L"~", L"~", L"~" },
600 { NULL, NULL, NULL, L"▓" },
601 /*80*/ { NULL, NULL, NULL, L"€" },
602 { NULL, NULL, NULL, L"≠" },
603 { NULL, NULL, NULL, L"‚" },
604 { NULL, NULL, NULL, L"ƒ" },
605 { NULL, NULL, NULL, L"„" },
606 { NULL, NULL, NULL, L"…" },
607 { NULL, NULL, NULL, L"†" },
608 { NULL, NULL, NULL, L"‡" },
609 { NULL, NULL, NULL, L"ˆ" },
610 { NULL, NULL, NULL, L"‰" },
611 { NULL, NULL, NULL, L"Š" },
612 { NULL, NULL, NULL, L"‹" },
613 { NULL, NULL, NULL, L"Œ" },
614 { NULL, NULL, NULL, L"IJ" },
615 { NULL, NULL, NULL, L"Ž" },
616 { NULL, NULL, NULL, L"≤" },
617 /*90*/ { NULL, NULL, NULL, L"≥" },
618 { NULL, NULL, NULL, L"‘" },
619 { NULL, NULL, NULL, L"’" },
620 { NULL, NULL, NULL, L"“" },
621 { NULL, NULL, NULL, L"”" },
622 { NULL, NULL, NULL, L"•" },
623 { NULL, NULL, NULL, L"–" },
624 { NULL, NULL, NULL, L"—" },
625 { NULL, NULL, NULL, L"˜" },
626 { NULL, NULL, NULL, L"™" },
627 { NULL, NULL, NULL, L"š" },
628 { NULL, NULL, NULL, L"›" },
629 { NULL, NULL, NULL, L"œ" },
630 { NULL, NULL, NULL, L"ij" },
631 { NULL, NULL, NULL, L"ž" },
632 { NULL, NULL, NULL, L"Ÿ" },
633 /*a0*/ { NULL, NULL, L" ", L" " },
634 { NULL, NULL, L"¡", L"¡" },
635 { NULL, NULL, L"¢", L"¢" },
636 { NULL, NULL, L"£", L"£" },
637 { NULL, NULL, L"¤", L"¤" },
638 { NULL, NULL, L"¥", L"¥" },
639 { NULL, NULL, L"¦", L"¦" },
640 { NULL, NULL, L"§", L"§" },
641 { NULL, NULL, L"¨", L"¨" },
642 { NULL, NULL, L"©", L"©" },
643 { NULL, NULL, L"ª", L"ª" },
644 { NULL, NULL, L"«", L"«" },
645 { NULL, NULL, L"¬", L"¬" },
646 { NULL, NULL, L"–", L"–" },
647 { NULL, NULL, L"®", L"®" },
648 { NULL, NULL, L"¯", L"¯" },
649 /*b0*/ { NULL, NULL, L"°", L"°" },
650 { NULL, NULL, L"±", L"±" },
651 { NULL, NULL, L"²", L"²" },
652 { NULL, NULL, L"³", L"³" },
653 { NULL, NULL, L"´", L"´" },
654 { NULL, NULL, L"µ", L"µ" },
655 { NULL, NULL, L"¶", L"¶" },
656 { NULL, NULL, L"·", L"·" },
657 { NULL, NULL, L"¸", L"¸" },
658 { NULL, NULL, L"¹", L"¹" },
659 { NULL, NULL, L"º", L"º" },
660 { NULL, NULL, L"»", L"»" },
661 { NULL, NULL, L"¼", L"¼" },
662 { NULL, NULL, L"½", L"½" },
663 { NULL, NULL, L"¾", L"¾" },
664 { NULL, NULL, L"¿", L"¿" },
665 /*c0*/ { NULL, NULL, L"À", L"À" },
666 { NULL, NULL, L"Á", L"Á" },
667 { NULL, NULL, L"Â", L"Â" },
668 { NULL, NULL, L"Ã", L"Ã" },
669 { NULL, NULL, L"Ä", L"Ä" },
670 { NULL, NULL, L"Å", L"Å" },
671 { NULL, NULL, L"Æ", L"Æ" },
672 { NULL, NULL, L"Ç", L"Ç" },
673 { NULL, NULL, L"È", L"È" },
674 { NULL, NULL, L"É", L"É" },
675 { NULL, NULL, L"Ê", L"Ê" },
676 { NULL, NULL, L"Ë", L"Ë" },
677 { NULL, NULL, L"Ì", L"Ì" },
678 { NULL, NULL, L"Í", L"Í" },
679 { NULL, NULL, L"Î", L"Î" },
680 { NULL, NULL, L"Ï", L"Ï" },
681 /*d0*/ { NULL, NULL, L"Ð", L"Ð" },
682 { NULL, NULL, L"Ñ", L"Ñ" },
683 { NULL, NULL, L"Ò", L"Ò" },
684 { NULL, NULL, L"Ó", L"Ó" },
685 { NULL, NULL, L"Ô", L"Ô" },
686 { NULL, NULL, L"Õ", L"Õ" },
687 { NULL, NULL, L"Ö", L"Ö" },
688 { NULL, NULL, L"×", L"×" },
689 { NULL, NULL, L"Ø", L"Ø" },
690 { NULL, NULL, L"Ù", L"Ù" },
691 { NULL, NULL, L"Ú", L"Ú" },
692 { NULL, NULL, L"Û", L"Û" },
693 { NULL, NULL, L"Ü", L"Ü" },
694 { NULL, NULL, L"Ý", L"Ý" },
695 { NULL, NULL, L"Þ", L"Þ" },
696 { NULL, NULL, L"ß", L"ß" },
697 /*e0*/ { NULL, NULL, L"à", L"à" },
698 { NULL, NULL, L"á", L"á" },
699 { NULL, NULL, L"â", L"â" },
700 { NULL, NULL, L"ã", L"ã" },
701 { NULL, NULL, L"ä", L"ä" },
702 { NULL, NULL, L"å", L"å" },
703 { NULL, NULL, L"æ", L"æ" },
704 { NULL, NULL, L"ç", L"ç" },
705 { NULL, NULL, L"è", L"è" },
706 { NULL, NULL, L"é", L"é" },
707 { NULL, NULL, L"ê", L"ê" },
708 { NULL, NULL, L"ë", L"ë" },
709 { NULL, NULL, L"ì", L"ì" },
710 { NULL, NULL, L"í", L"í" },
711 { NULL, NULL, L"î", L"î" },
712 { NULL, NULL, L"ï", L"ï" },
713 /*f0*/ { NULL, NULL, L"ð", L"ð" },
714 { NULL, NULL, L"ñ", L"ñ" },
715 { NULL, NULL, L"ò", L"ò" },
716 { NULL, NULL, L"ó", L"ó" },
717 { NULL, NULL, L"ô", L"ô" },
718 { NULL, NULL, L"õ", L"õ" },
719 { NULL, NULL, L"ö", L"ö" },
720 { NULL, NULL, L"÷", L"÷" },
721 { NULL, NULL, L"ø", L"ø" },
722 { NULL, NULL, L"ù", L"ù" },
723 { NULL, NULL, L"ú", L"ú" },
724 { NULL, NULL, L"û", L"û" },
725 { NULL, NULL, L"ü", L"ü" },
726 { NULL, NULL, L"ý", L"ý" },
727 { NULL, NULL, L"þ", L"þ" },
728 { NULL, NULL, L"ÿ", L"ÿ" }
729 #endif
730 };
731
732
733 /*
734 * three functions for common syntax errors in the configuration file
735 */
736 static void
pexpected(const char * s)737 pexpected(const char *s) {
738 perr("%s(%d): %s expected", cfn, ln, s);
739 }
740
741
742 static void
pinvalid(const char * s)743 pinvalid(const char *s) {
744 perr("%s(%d): invalid %s", cfn, ln, s);
745 }
746
747
748 static void
predefined(const char * s)749 predefined(const char *s) {
750 perr("%s(%d): %s redefined", cfn, ln, s);
751 }
752
753
754 /*
755 * free the current token and its attributes
756 */
757 static void
free_token(void)758 free_token(void) {
759 token = (-1);
760 free(token_string);
761 token_string = NULL;
762 free(token_ident);
763 token_ident = NULL;
764 }
765
766
767 /*
768 * read the next token from the configuration file and return it
769 * and ts attributes via static variables
770 */
771 static void
get_token(void)772 get_token(void) {
773 const wchar_t *start_p;
774 wchar_t *tp;
775 size_t lb, ls;
776 /*
777 * free the last token resp. its remains, set token to invalid (-1)
778 */
779 free_token();
780 /*
781 * skip blanks
782 */
783 while (iswctype(*curr_p, wctype_blank)) curr_p++;
784 /*
785 * test for EOL resp. start of a comment
786 */
787 if (*curr_p == L'\0' || *curr_p == L'#' || *curr_p == L';') {
788 /*
789 * 0 is the token for EOL
790 */
791 token = 0;
792 } else if (iswdigit(*curr_p)) {
793 /*
794 * tokens starting in a decimal digit are numbers;
795 * hexadecimal (0x), octal (0), and decimal (1..9)
796 * numbers are accepted
797 */
798 errno = 0;
799 if (*curr_p == L'0') {
800 if (*(curr_p + 1) == L'x') {
801 token_ul = wcstoul(curr_p + 2, &tp, 16);
802 } else {
803 token_ul = wcstoul(curr_p, &tp, 8);
804 }
805 } else {
806 token_ul = wcstoul(curr_p, &tp, 10);
807 }
808 /*
809 * check for numeric overflow
810 */
811 if (token_ul == ULONG_MAX && errno == ERANGE) {
812 perr("%s(%d): integer out of range", cfn, ln);
813 } else {
814 /*
815 * token is a number
816 */
817 token = '0';
818 curr_p = tp;
819 }
820 } else if (iswalpha(*curr_p)) {
821 /*
822 * identifiers start with an alphabetic character...
823 */
824 start_p = curr_p;
825 /*
826 * and may contain alphabetic and numeric characters
827 * resp. the underscore character
828 */
829 while (iswalnum(*curr_p) || *curr_p == L'_') curr_p++;
830 ls = curr_p - start_p;
831 token_ident = alloc(sizeof (wchar_t) * (ls + 1));
832 memcpy(token_ident, start_p, sizeof (wchar_t) * ls);
833 token_ident[ls] = L'\0';
834 /*
835 * token is an identifier
836 */
837 token = 'i';
838 } else if (*curr_p == L'=') {
839 /*
840 * token is a equal sign
841 */
842 token = '=';
843 curr_p++;
844 } else if (*curr_p == L',') {
845 /*
846 * token is a comma
847 */
848 token = ',';
849 curr_p++;
850 } else if (*curr_p == L'\"') {
851 /*
852 * strings are enclosed in double quotes and may contain
853 * graphical characters and escapes \", \', and \\
854 */
855 lb = 0;
856 ls = 0;
857 curr_p++;
858 for (;;) {
859 if (*curr_p == L'\0') {
860 perr("%s(%d): unterminated string", cfn, ln);
861 free_token();
862 break;
863 }
864 if (*curr_p == L'"') {
865 curr_p++;
866 token_string = resize(token_string,
867 (ls + 1) * sizeof (wchar_t));
868 token_string[ls] = L'\0';
869 token = 's';
870 break;
871 }
872 if (*curr_p == L'\\') {
873 curr_p++;
874 if (*curr_p != L'\"' && *curr_p != L'\\'
875 && *curr_p != L'\'') {
876 pinvalid("escape sequence");
877 free_token();
878 break;
879 }
880 }
881 if (ls == lb) {
882 token_string = resize(token_string,
883 (lb + 16) * sizeof (wchar_t));
884 lb += 16;
885 }
886 token_string[ls++] = *curr_p++;
887 }
888 } else {
889 pinvalid("token");
890 }
891 }
892
893
894 /*
895 * translate drive letters 'a' ... 'p' to drive numbers 0 ... 15
896 */
897 static int
cpm_drive(wchar_t c)898 cpm_drive(wchar_t c) {
899 int rc = (-1);
900 wchar_t *cp;
901 static const wchar_t drives[16] = L"abcdefghijklmnop";
902 cp = wcschr(drives, towlower(c));
903 if (cp) rc = cp - drives;
904 return rc;
905 }
906
907
908 /*
909 * common syntax checks: is the next token a equal sign, a string,
910 * a keyword, or a number?
911 */
912
913 #define CHECK(NAME, TOKEN, MESSAGE) \
914 static int \
915 check_##NAME(int *rc_p) { \
916 if (token != TOKEN) { \
917 pexpected(MESSAGE); \
918 *rc_p = (-1); \
919 return 0; \
920 } \
921 return 1; \
922 }
923
924 CHECK(equal, '=', "=")
925 CHECK(string, 's', "string")
926 CHECK(keyword, 'i', "keyword")
927 CHECK(number, '0', "number")
928
929
930 /*
931 * string in character definition may contain only a single character
932 */
933 static int
check_char(int * rc_p)934 check_char(int *rc_p) {
935 if (! check_string(rc_p)) return 0;
936 if (wcslen(token_string) > 1) {
937 perr("%s(%d): string may contain only one character", cfn, ln);
938 *rc_p = (-1);
939 return 0;
940 }
941 return 1;
942 }
943
944
945 /*
946 * set all undefined character positions of the primary or
947 * secondary character set from the given built-in character set
948 * (the built-in character sets have gaps as well)
949 */
950 static void
set_charset(enum charset cs,wchar_t * charset[256])951 set_charset(enum charset cs, wchar_t *charset[256]) {
952 int i;
953 size_t l;
954 for (i = 0; i < 256; i++) {
955 if (charset[i]) continue;
956 if (! default_charset[i][cs]) continue;
957 l = (wcslen(default_charset[i][cs]) + 1) * sizeof (wchar_t);
958 charset[i] = alloc(l);
959 memcpy(charset[i], default_charset[i][cs], l);
960 }
961 }
962
963
964 /*
965 * parse a screen dimension definition; the dimension may be either "current"
966 * (resulting in (-1) being returned as value) or in the range defined
967 * by the parameters min and max
968 */
969 static int
parse_dim(const char * s,int min,int max,int * lines_p)970 parse_dim(const char *s, int min, int max, int *lines_p) {
971 int rc = 0;
972 get_token();
973 if (! check_equal(&rc)) goto premature_exit;
974 get_token();
975 if (token == 'i' && ! wcscmp(token_ident, L"current")) {
976 *lines_p = (-1);
977 } else {
978 if (! check_number(&rc)) goto premature_exit;
979 if (token_ul < min || token_ul > max) {
980 perr("%s(%d): %s number out of range (%d..%d)",
981 cfn, ln, s, min, max);
982 rc = (-1);
983 goto premature_exit;
984 }
985 *lines_p = (int) token_ul;
986 }
987 get_token();
988 premature_exit:
989 return rc;
990 }
991
992
993 /*
994 * parse a boolean definition: valid values are the
995 * identifiers true or false
996 */
997 static int
parse_boolean(int * value_p)998 parse_boolean(int *value_p) {
999 int rc = 0;
1000 get_token();
1001 if (! check_equal(&rc)) goto premature_exit;
1002 get_token();
1003 if (token == 'i' && ! wcscmp(token_ident, L"true")) {
1004 *value_p = 1;
1005 } else if (token == 'i' && ! wcscmp(token_ident, L"false")) {
1006 *value_p = 0;
1007 } else {
1008 perr("%s(%d): boolean value expected", cfn, ln);
1009 rc = (-1);
1010 goto premature_exit;
1011 }
1012 get_token();
1013 premature_exit:
1014 return rc;
1015 }
1016
1017
1018 /*
1019 * convert a wide character string from the configuration file to
1020 * a multi-byte string
1021 */
1022 static int
unix_path(const wchar_t * wcs,char ** pp)1023 unix_path(const wchar_t *wcs, char **pp) {
1024 int rc = 0;
1025 size_t l;
1026 char *cp;
1027 l = wcslen(wcs) * MB_LEN_MAX + 1;
1028 cp = alloc(l);
1029 l = wcstombs(cp, wcs, l);
1030 if (l == (size_t) (-1)) {
1031 free(cp);
1032 cp = NULL;
1033 rc = (-1);
1034 } else {
1035 cp = resize(cp, l + 1);
1036 }
1037 *pp = cp;
1038 return rc;
1039 }
1040
1041
1042 /*
1043 * parse the configuration for one of the three CP/M character
1044 * devices LST, PUN, or RDR; all three can have both a path
1045 * and a mode (raw or translated)
1046 */
1047 static int
parse_aux(const char * s,char ** name_p,int * raw_p)1048 parse_aux(const char *s, char **name_p, int *raw_p) {
1049 int rc = 0;
1050 get_token();
1051 if (! check_keyword(&rc)) goto premature_exit;
1052 if (! wcscmp(token_ident, L"file")) {
1053 /*
1054 * path definition of the corresponding Unix file
1055 */
1056 if (*name_p) {
1057 perr("%s(%d): %s file redefined", cfn, ln, s);
1058 rc = (-1);
1059 goto premature_exit;
1060 }
1061 get_token();
1062 if (! check_equal(&rc)) goto premature_exit;
1063 get_token();
1064 if (! check_string(&rc)) goto premature_exit;
1065 rc = unix_path(token_string, name_p);
1066 if (rc) {
1067 pinvalid("file name");
1068 goto premature_exit;
1069 }
1070 get_token();
1071 } else if (! wcscmp(token_ident, L"mode")) {
1072 /*
1073 * the mode definition is one of the identifiers raw or text
1074 */
1075 if (*raw_p != (-1)) {
1076 perr("%s(%d): %s mode redefined", cfn, ln, s);
1077 rc = (-1);
1078 goto premature_exit;
1079 }
1080 get_token();
1081 if (! check_equal(&rc)) goto premature_exit;
1082 get_token();
1083 if (! check_keyword(&rc)) goto premature_exit;
1084 if (! wcscmp(token_ident, L"text")) {
1085 *raw_p = 0;
1086 } else if (! wcscmp(token_ident, L"raw")) {
1087 *raw_p = 1;
1088 } else {
1089 pexpected("text or raw");
1090 rc = (-1);
1091 goto premature_exit;
1092 }
1093 get_token();
1094 } else {
1095 pexpected("file or mode");
1096 rc = (-1);
1097 goto premature_exit;
1098 }
1099 premature_exit:
1100 return rc;
1101 }
1102
1103
1104 /*
1105 * parse dump options form configuration file
1106 */
1107 static int
parse_dump(enum dump * dp)1108 parse_dump(enum dump *dp) {
1109 int rc = 0;
1110 get_token();
1111 if (! check_equal(&rc)) goto premature_exit;
1112 if (*dp) {
1113 perr("%s(%d): dump options redefined", cfn, ln);
1114 rc = (-1);
1115 goto premature_exit;
1116 }
1117 do {
1118 get_token();
1119 if (token != 'i') {
1120 pexpected("dump option");
1121 rc = (-1);
1122 goto premature_exit;
1123 }
1124 if (! wcscmp(token_ident, L"all")) {
1125 *dp |= DUMP_ALL;
1126 } else if (! wcscmp(token_ident, L"none")) {
1127 *dp |= DUMP_NONE;
1128 } else if (! wcscmp(token_ident, L"startup")) {
1129 *dp |= DUMP_STARTUP;
1130 } else if (! wcscmp(token_ident, L"signal")) {
1131 *dp |= DUMP_SIGNAL;
1132 } else if (! wcscmp(token_ident, L"exit")) {
1133 *dp |= DUMP_EXIT;
1134 } else if (! wcscmp(token_ident, L"error")) {
1135 *dp |= DUMP_ERROR;
1136 } else {
1137 pexpected("dump option");
1138 rc = (-1);
1139 goto premature_exit;
1140 }
1141 get_token();
1142 } while (token == ',');
1143 /*
1144 * check for illegal combinations
1145 */
1146 if (((*dp & DUMP_ALL) && (*dp & ~DUMP_ALL)) ||
1147 ((*dp & DUMP_NONE) && (*dp & ~DUMP_NONE)) ||
1148 ((*dp & DUMP_ERROR) && (*dp & DUMP_EXIT))) {
1149 perr("%s(%d): illegal dump option combination", cfn, ln);
1150 rc = (-1);
1151 goto premature_exit;
1152 }
1153 /*
1154 * "all" is a macro
1155 */
1156 if (*dp & DUMP_ALL) *dp |= DUMP_STARTUP | DUMP_EXIT | DUMP_SIGNAL;
1157 premature_exit:
1158 return rc;
1159 }
1160
1161
1162 /*
1163 * parse color options line from configuration file:
1164 * "colors" "=" ( "false" | "true" ) [ "," <int 0..7> "," <int 0..7> ]
1165 */
1166 static int
parse_colors(int * colors_p,int * fg_p,int * bg_p)1167 parse_colors(int *colors_p, int *fg_p, int *bg_p) {
1168 int rc = 0;
1169 if (*colors_p != (-1)) {
1170 perr("%s(%d): color options redefined", cfn, ln);
1171 rc = (-1);
1172 goto premature_exit;
1173 }
1174 /*
1175 * check if colors should be used
1176 */
1177 rc = parse_boolean(colors_p);
1178 if (rc) goto premature_exit;
1179 if (token == ',') {
1180 /*
1181 * default colors specified
1182 */
1183 get_token();
1184 /*
1185 * parse foreground color
1186 */
1187 if (token != '0' || token_ul > 7) {
1188 pexpected("foreground color number (0..7)");
1189 rc = (-1);
1190 goto premature_exit;
1191 }
1192 *fg_p = (int) token_ul;
1193 get_token();
1194 /*
1195 * check for separator
1196 */
1197 if (token != ',') {
1198 pexpected(",");
1199 rc = (-1);
1200 goto premature_exit;
1201 }
1202 get_token();
1203 /*
1204 * parse background color
1205 */
1206 if (token != '0' || token_ul > 7) {
1207 pexpected("background color number (0..7)");
1208 rc = (-1);
1209 goto premature_exit;
1210 }
1211 *bg_p = (int) token_ul;
1212 get_token();
1213 }
1214 premature_exit:
1215 return rc;
1216 }
1217
1218
1219 /*
1220 * read parameters from the configuration file; parameters already
1221 * defined on the command line take precedence
1222 */
1223 int
parse_config(void)1224 parse_config(void) {
1225 int rc = 0, alt, n, drive_no, temp_altkeys = (-1),
1226 temp_dont_close = (-1), temp_interactive = (-1),
1227 temp_screen_delay = (-1), temp_default_drive = (-1),
1228 temp_reverse_bs_del = (-1), temp_delay_count = (-1),
1229 temp_delay_nanoseconds = (-1), temp_color = (-1),
1230 temp_foreground = (-1), temp_background = (-1);
1231 enum dump temp_dump = 0;
1232 wchar_t line[L_LINE];
1233 size_t l;
1234 enum charset default_cs[2] = { CS_NONE, CS_NONE };
1235 wchar_t **cs;
1236 enum log_level temp_log_level = LL_UNSET;
1237 /*
1238 * get wctype("blank") once
1239 */
1240 wctype_blank = wctype("blank");
1241 /*
1242 * parse the file
1243 */
1244 for (;;) {
1245 /*
1246 * get next line from configuration file and initialize
1247 * the token scanner
1248 */
1249 ln++;
1250 if (! fgetws(line, L_LINE, cf)) {
1251 if (ferror(cf)) {
1252 perr("error reading %s: %s", cfn,
1253 strerror(errno));
1254 rc = (-1);
1255 goto premature_exit;
1256 }
1257 break;
1258 }
1259 l = wcslen(line) - 1;
1260 if (line[l] != L'\n') {
1261 perr("%s(%d): line too long", cfn, ln);
1262 rc = (-1);
1263 goto premature_exit;
1264 }
1265 line[l] = L'\0';
1266 curr_p = line;
1267 /*
1268 * get first token; skip empty lines and lines containing
1269 * only comments
1270 */
1271 get_token();
1272 if (! token) continue;
1273 /*
1274 * if the first token is the identifier alt, it must be
1275 * followed by another keyword
1276 */
1277 alt = (token == 'i') && (! wcscmp(token_ident, L"alt"));
1278 if (alt) get_token();
1279 if (! check_keyword(&rc)) continue;
1280 if (! wcscmp(token_ident, L"charset")) {
1281 /*
1282 * charset resp. alt charset: define a default for
1283 * characters not explicitly defined in the
1284 * configuration file
1285 */
1286 if (default_cs[alt] != CS_NONE) {
1287 predefined(alt ? "alt charset" : "charset");
1288 rc = (-1);
1289 continue;
1290 }
1291 get_token();
1292 if (! check_equal(&rc)) continue;
1293 get_token();
1294 if (token != 'i') {
1295 pexpected("charset name");
1296 rc = (-1);
1297 continue;
1298 }
1299 /*
1300 * valid options are vt52, ascii, latin1, and tnylpo
1301 */
1302 if (! wcscmp(token_ident, L"vt52")) {
1303 default_cs[alt] = CS_VT52;
1304 } else if (! wcscmp(token_ident, L"ascii")) {
1305 default_cs[alt] = CS_ASCII;
1306 } else if (! wcscmp(token_ident, L"latin1")) {
1307 default_cs[alt] = CS_LATIN1;
1308 } else if (! wcscmp(token_ident, L"tnylpo")) {
1309 default_cs[alt] = CS_TNYLPO;
1310 } else {
1311 pinvalid("charset name");
1312 rc = (-1);
1313 continue;
1314 }
1315 get_token();
1316 } else if (! wcscmp(token_ident, L"char")) {
1317 /*
1318 * char resp. alt char: explicitly define
1319 * a CP/M character
1320 */
1321 get_token();
1322 if (token != '0' || token_ul > 256) {
1323 pexpected("number (0..255)");
1324 rc = (-1);
1325 continue;
1326 }
1327 n = (int) token_ul;
1328 cs = (alt ? conf_alt_charset : conf_charset) + n;
1329 if (*cs) {
1330 predefined(alt ? "alt char" : "char");
1331 rc = (-1);
1332 continue;
1333 }
1334 get_token();
1335 if (! check_equal(&rc)) continue;
1336 /*
1337 * parameter must be a string of length 1
1338 */
1339 get_token();
1340 if (! check_char(&rc)) continue;
1341 *cs = token_string;
1342 token_string = NULL;
1343 get_token();
1344 } else if (alt) {
1345 /*
1346 * all other keywords may not be prefixed by alt
1347 */
1348 perr("%s(%d): keyword alt unexpected", cfn, ln);
1349 rc = (-1);
1350 continue;
1351 } else if (! wcscmp(token_ident, L"cpu")) {
1352 /*
1353 * specify CPU delay: an instruction count
1354 * and a number of nanoseconds, separated by a
1355 * comma
1356 */
1357 get_token();
1358 if (token != 'i' || wcscmp(token_ident, L"delay")) {
1359 pexpected("delay");
1360 rc = (-1);
1361 continue;
1362 }
1363 if (temp_delay_count != (-1)) {
1364 predefined("cpu delay");
1365 rc = (-1);
1366 continue;
1367 }
1368 get_token();
1369 if (! check_equal(&rc)) continue;
1370 get_token();
1371 if (! check_number(&rc)) continue;
1372 if (token_ul < 1 || token_ul > INT_MAX) {
1373 perr("%s(%d): cpu delay count out of range",
1374 cfn, ln);
1375 rc = (-1);
1376 continue;
1377 }
1378 temp_delay_count = (int) token_ul;
1379 get_token();
1380 if (token != ',') {
1381 pexpected(",");
1382 rc = (-1);
1383 continue;
1384 }
1385 get_token();
1386 if (! check_number(&rc)) continue;
1387 if (token_ul < 1 || token_ul > INT_MAX) {
1388 perr("%s(%d): cpu delay nanoseconds out "
1389 "of range", cfn, ln);
1390 rc = (-1);
1391 continue;
1392 }
1393 temp_delay_nanoseconds = (int) token_ul;
1394 get_token();
1395 } else if (! wcscmp(token_ident, L"console")) {
1396 /*
1397 * use emulated terminal (full) or line
1398 * orientated/batch console (line)?
1399 */
1400 if (temp_interactive != (-1)) {
1401 predefined("console");
1402 rc = (-1);
1403 continue;
1404 }
1405 get_token();
1406 if (! check_equal(&rc)) continue;
1407 get_token();
1408 if (! check_keyword(&rc)) continue;
1409 if (! wcscmp(token_ident, L"full")) {
1410 temp_interactive = 1;
1411 } else if (! wcscmp(token_ident, L"line")) {
1412 temp_interactive = 0;
1413 } else {
1414 pexpected("full or line");
1415 rc = (-1);
1416 continue;
1417 }
1418 get_token();
1419 } else if (! wcscmp(token_ident, L"unprintable")) {
1420 /*
1421 * character to be used to represent an
1422 * undefined character written to the console
1423 */
1424 if (conf_unprintable) {
1425 predefined("unprintable char");
1426 rc = (-1);
1427 continue;
1428 }
1429 get_token();
1430 if (! check_equal(&rc)) continue;
1431 get_token();
1432 if (! check_char(&rc)) continue;
1433 conf_unprintable = token_string;
1434 token_string = NULL;
1435 get_token();
1436 } else if (! wcscmp(token_ident, L"close")) {
1437 /*
1438 * close files determines whether BDOS function 19
1439 * actually closes the corresponding Unix file
1440 * or not
1441 */
1442 get_token();
1443 if (token != 'i' || wcscmp(token_ident, L"files")) {
1444 pexpected("files");
1445 rc = (-1);
1446 continue;
1447 }
1448 if (temp_dont_close != (-1)) {
1449 predefined("close files");
1450 rc = (-1);
1451 continue;
1452 }
1453 if (parse_boolean(&temp_dont_close) == (-1)) {
1454 rc = (-1);
1455 continue;
1456 }
1457 temp_dont_close = ! temp_dont_close;
1458
1459 } else if (! wcscmp(token_ident, L"screen")) {
1460 /*
1461 * define a delay in seconds between program
1462 * termination and resetting the terminal emulation
1463 * to allow reading of the final screen contents
1464 * (ncurses restores the previous screen contents
1465 * on exit if possible); a value of key waits for
1466 * a keypress before resetting the terminal.
1467 */
1468 get_token();
1469 if (token != 'i' || wcscmp(token_ident, L"delay")) {
1470 pexpected("delay");
1471 rc = (-1);
1472 continue;
1473 }
1474 if (temp_screen_delay != (-1)) {
1475 predefined("screen delay");
1476 rc = (-1);
1477 continue;
1478 }
1479 get_token();
1480 if (! check_equal(&rc)) continue;
1481 get_token();
1482 if (token == 'i' && ! wcscmp(token_ident, L"key")) {
1483 temp_screen_delay = (-2);
1484 } else {
1485 if (! check_number(&rc)) continue;
1486 if (token_ul > INT_MAX) {
1487 perr("%s(%d): screen delay out of "
1488 "range", cfn, ln);
1489 rc = (-1);
1490 continue;
1491 }
1492 temp_screen_delay = (int) token_ul;
1493 }
1494 get_token();
1495 } else if (! wcscmp(token_ident, L"application")) {
1496 /*
1497 * application cursor determines whether the
1498 * terminal emulation returns WordStar cursor
1499 * movement codes instead of the VT52 cursor
1500 * key sequences
1501 */
1502 get_token();
1503 if (token != 'i' || wcscmp(token_ident, L"cursor")) {
1504 pexpected("cursor");
1505 rc = (-1);
1506 continue;
1507 }
1508 if (temp_dont_close != (-1)) {
1509 predefined("application cursor");
1510 rc = (-1);
1511 continue;
1512 }
1513 if (parse_boolean(&temp_altkeys) == (-1)) {
1514 rc = (-1);
1515 continue;
1516 }
1517 } else if (! wcscmp(token_ident, L"exchange")) {
1518 /*
1519 * exchange delete exchanges delete and backspace
1520 * keys in full screen mode
1521 */
1522 get_token();
1523 if (token != 'i' || wcscmp(token_ident, L"delete")) {
1524 pexpected("delete");
1525 rc = (-1);
1526 continue;
1527 }
1528 if (temp_reverse_bs_del != (-1)) {
1529 predefined("exchange delete");
1530 rc = (-1);
1531 continue;
1532 }
1533 if (parse_boolean(&reverse_bs_del) == (-1)) {
1534 rc = (-1);
1535 continue;
1536 }
1537 } else if (! wcscmp(token_ident, L"default")) {
1538 /*
1539 * defines the default drive
1540 */
1541 get_token();
1542 if (token != 'i' || wcscmp(token_ident, L"drive")) {
1543 pexpected("drive");
1544 rc = (-1);
1545 continue;
1546 }
1547 get_token();
1548 if (! check_equal(&rc)) continue;
1549 get_token();
1550 if (temp_default_drive != (-1)) {
1551 predefined("default drive");
1552 rc = (-1);
1553 goto premature_exit;
1554 }
1555 if (token != 'i' || wcslen(token_ident) != 1) {
1556 temp_default_drive = (-1);
1557 } else {
1558 temp_default_drive = cpm_drive(token_ident[0]);
1559 }
1560 if (temp_default_drive == (-1)) {
1561 pinvalid("drive name");
1562 rc = (-1);
1563 continue;
1564 }
1565 get_token();
1566 } else if (! wcscmp(token_ident, L"drive")) {
1567 /*
1568 * defines one of the 16 CP/M disk drives A...P;
1569 * the drive name is a identifier a, b, c, ..., p;
1570 * the parameter value is a string containing a
1571 * Unix path, optionally preceded by the
1572 * identifier readonly and a comma
1573 */
1574 get_token();
1575 if (token != 'i' || wcslen(token_ident) != 1) {
1576 drive_no = (-1);
1577 } else {
1578 drive_no = cpm_drive(token_ident[0]);
1579 }
1580 if (drive_no == (-1)) {
1581 pinvalid("drive name");
1582 rc = (-1);
1583 continue;
1584 }
1585 if (conf_drives[drive_no]) {
1586 predefined("drive");
1587 rc = (-1);
1588 continue;
1589 }
1590 get_token();
1591 if (! check_equal(&rc)) continue;
1592 get_token();
1593 if (token == 'i') {
1594 /*
1595 * read only?
1596 */
1597 if (wcscmp(token_ident, L"readonly")) {
1598 pexpected("string");
1599 rc = (-1);
1600 continue;
1601 }
1602 conf_readonly[drive_no] = 1;
1603 get_token();
1604 if (token != ',') {
1605 pexpected(",");
1606 rc = (-1);
1607 continue;
1608 }
1609 get_token();
1610 }
1611 if (! check_string(&rc)) continue;
1612 if (unix_path(token_string, conf_drives + drive_no)) {
1613 pinvalid("file name");
1614 rc = (-1);
1615 continue;
1616 }
1617 get_token();
1618 } else if (! wcscmp(token_ident, L"logfile")) {
1619 /*
1620 * the parameter of logfile is a string
1621 * containing a Unix path
1622 */
1623 get_token();
1624 if (! check_equal(&rc)) continue;
1625 get_token();
1626 if (! check_string(&rc)) continue;
1627 if (unix_path(token_string, &conf_log)) {
1628 pinvalid("file name");
1629 rc = (-1);
1630 continue;
1631 }
1632 get_token();
1633 } else if (! wcscmp(token_ident, L"loglevel")) {
1634 /*
1635 * the parameter of loglevel is a number
1636 */
1637 get_token();
1638 if (! check_equal(&rc)) continue;
1639 get_token();
1640 if (! check_number(&rc)) continue;
1641 if (token_ul >= LL_INVALID) {
1642 perr("%s(%d): log level out of range",
1643 cfn, ln);
1644 rc = (-1);
1645 continue;
1646 }
1647 if (temp_log_level != LL_UNSET) {
1648 predefined("log level");
1649 rc = (-1);
1650 continue;
1651 }
1652 temp_log_level = (enum log_level) token_ul;
1653 get_token();
1654 } else if (! wcscmp(token_ident, L"lines")) {
1655 /*
1656 * number of lines used by the VT52 emulation
1657 */
1658 if (parse_dim("line", MIN_LINES, MAX_LINES, &n)) {
1659 rc = (-1);
1660 continue;
1661 }
1662 if (! lines) lines = n;
1663 } else if (! wcscmp(token_ident, L"columns")) {
1664 /*
1665 * number of columns used by the VT52 emulation
1666 */
1667 if (parse_dim("column", MIN_COLS, MAX_COLS, &n)) {
1668 rc = (-1);
1669 continue;
1670 }
1671 if (! cols) cols = n;
1672 } else if (! wcscmp(token_ident, L"printer")) {
1673 /*
1674 * printer file definition
1675 */
1676 if (parse_aux("printer", &conf_printer,
1677 &conf_printer_raw)) {
1678 rc = (-1);
1679 continue;
1680 }
1681 } else if (! wcscmp(token_ident, L"punch")) {
1682 /*
1683 * punch file definition
1684 */
1685 if (parse_aux("punch", &conf_punch,
1686 &conf_punch_raw)) {
1687 rc = (-1);
1688 continue;
1689 }
1690 } else if (! wcscmp(token_ident, L"reader")) {
1691 /*
1692 * reader file definition
1693 */
1694 if (parse_aux("reader", &conf_reader,
1695 &conf_reader_raw)) {
1696 rc = (-1);
1697 continue;
1698 }
1699 } else if (! wcscmp(token_ident, L"dump")) {
1700 /*
1701 * dump definition
1702 */
1703 if (parse_dump(&temp_dump)) {
1704 rc = (-1);
1705 continue;
1706 }
1707 } else if (! wcscmp(token_ident, L"colors")) {
1708 /*
1709 * color definition
1710 */
1711 if (parse_colors(&temp_color, &temp_foreground,
1712 &temp_background)) {
1713 rc = (-1);
1714 continue;
1715 }
1716 }
1717 if (token) {
1718 perr("%s(%d): syntax error", cfn, ln);
1719 rc = (-1);
1720 }
1721 }
1722 /*
1723 * In case of a syntax or semantical error the parser skips
1724 * to the next line in order to check as much as possible
1725 * of the configuration file; if any errors occurred up to
1726 * this point, further processing of the configuration
1727 * is skipped.
1728 */
1729 if (rc) goto premature_exit;
1730 /*
1731 * take values from the configuration file only if
1732 * they have not already been defined on the command line
1733 */
1734 if (log_level == LL_UNSET) log_level = temp_log_level;
1735 if (dont_close == (-1)) dont_close = temp_dont_close;
1736 if (altkeys == (-1)) altkeys = temp_altkeys;
1737 if (reverse_bs_del == (-1)) reverse_bs_del = temp_reverse_bs_del;
1738 if (screen_delay == (-1)) screen_delay = temp_screen_delay;
1739 if (conf_interactive == (-1)) conf_interactive = temp_interactive;
1740 if (default_drive == (-1)) default_drive = temp_default_drive;
1741 if (conf_dump == 0) conf_dump = temp_dump;
1742 if (conf_color == (-1)) conf_color = temp_color;
1743 if (conf_foreground == (-1)) conf_foreground = temp_foreground;
1744 if (conf_background == (-1)) conf_background = temp_background;
1745 if (delay_count == (-1)) {
1746 delay_count = temp_delay_count;
1747 delay_nanoseconds = temp_delay_nanoseconds;
1748 }
1749 /*
1750 * characters and character sets cannot be specified on
1751 * the command line
1752 */
1753 if (default_cs[0] != CS_NONE) {
1754 set_charset(default_cs[0], conf_charset);
1755 } else {
1756 set_charset(CS_VT52, conf_charset);
1757 }
1758 if (default_cs[1] != CS_NONE) {
1759 set_charset(default_cs[1], conf_alt_charset);
1760 } else {
1761 set_charset(CS_VT52, conf_alt_charset);
1762 }
1763 premature_exit:
1764 free_token();
1765 return rc;
1766 }
1767
1768
1769 /*
1770 * read the optional configuration file
1771 */
read_config(char * fn)1772 int read_config(char *fn) {
1773 int rc = 0;
1774 const char *home;
1775 /*
1776 * handle configuration file
1777 */
1778 if (fn) {
1779 /*
1780 * configuration file given on the command line
1781 */
1782 /*
1783 * make configuration file name available to subprograms
1784 */
1785 cfn = fn;
1786 cf = fopen(cfn, "r");
1787 if (! cf) {
1788 perr("cannot open %s: %s", cfn, strerror(errno));
1789 rc = (-1);
1790 goto premature_exit;
1791 }
1792 } else {
1793 /*
1794 * take the configuraton file in the current working directory,
1795 * if one exists
1796 */
1797 cfn = "./.tnylpo.conf";
1798 cf = fopen(cfn, "r");
1799 if (! cf) {
1800 /*
1801 * otherwise, use the configuration file from the
1802 * user's home directory, if present
1803 */
1804 home = getenv("HOME");
1805 if (home) {
1806 cfn = alloc(strlen(home) + 14);
1807 sprintf(cfn, "%s/.tnylpo.conf",home);
1808 cf = fopen(cfn, "r");
1809 }
1810 }
1811 }
1812 /*
1813 * Even if there is no configuration file, this program
1814 * assumes a minimalistic working configuration: one CP/M
1815 * drive (A; the current Unix working directory); VT52
1816 * character set; actually close files; screen size 80x24; use
1817 * VT52 cursor keys (the last two assumptions only have
1818 * an effect if the user passes the -s option on the command
1819 * line, since the console is line orientated by default).
1820 */
1821 if (cf) {
1822 rc = parse_config();
1823 if (rc) goto premature_exit;
1824 } else {
1825 /*
1826 * without a config file, assume the VT52 character set
1827 * both as primary and as secondary character set
1828 */
1829 set_charset(CS_VT52, conf_charset);
1830 set_charset(CS_VT52, conf_alt_charset);
1831 }
1832 premature_exit:
1833 if (cf) {
1834 if (fclose(cf)) {
1835 perr("cannot close %s: %s", cfn, strerror(errno));
1836 rc = (-1);
1837 }
1838 }
1839 return rc;
1840 }
1841