1 /*************************************************************************
2 * TinyFugue - programmable mud client
3 * Copyright (C) 1993, 1994, 1995, 1996, 1997, 1998, 1999, 2002, 2003, 2004, 2005, 2006-2007 Ken Keys
4 *
5 * TinyFugue (aka "tf") is protected under the terms of the GNU
6 * General Public License. See the file "COPYING" for details.
7 ************************************************************************/
8 static const char RCSid[] = "$Id: tfio.c,v 35004.114 2007/01/13 23:12:39 kkeys Exp $";
9
10
11 /***********************************
12 * TinyFugue "standard" I/O
13 *
14 * Written by Ken Keys
15 *
16 * Provides an interface similar to stdio.
17 ***********************************/
18
19 #include "tfconfig.h"
20 #include <sys/types.h>
21 #if HAVE_SYS_SELECT_H
22 # include <sys/select.h>
23 #endif
24 /* #include <sys/time.h> */ /* for struct timeval, in select() */
25 #include <sys/stat.h>
26
27 #if HAVE_PWD_H
28 # include <pwd.h> /* getpwnam() */
29 #endif
30
31 #include "port.h"
32 #include "tf.h"
33
34 #include "util.h"
35 #include "pattern.h"
36
37 #include "search.h" /* queues */
38 #include "tfio.h"
39 #include "tfselect.h"
40 #include "output.h"
41 #include "attr.h"
42 #include "macro.h" /* macro_body() */
43 #include "history.h"
44 #include "signals.h" /* shell_status() */
45 #include "variable.h" /* getvar() */
46 #include "keyboard.h" /* keyboard_pos */
47 #include "expand.h" /* current_command */
48 #include "cmdlist.h"
49
50 TFILE *loadfile = NULL; /* currently /load'ing file */
51 int loadline = 0; /* line number in /load'ing file */
52 int loadstart = 0; /* line number of start of command in /load'ing file */
53 int read_depth = 0; /* nesting level of user kb reads */
54 int readsafe = 0; /* safe to do a user kb read? */
55 TFILE *tfkeyboard; /* user input (placeholder) */
56 TFILE *tfscreen; /* screen output (placeholder) */
57 TFILE *tfin; /* current input queue file */
58 TFILE *tfout; /* current output queue file */
59 TFILE *tferr; /* current error queue file */
60 TFILE *tfalert; /* current alert queue file */
61 Screen *fg_screen; /* current screen, to which tf writes */
62 Screen *default_screen; /* default screen, used if unconnected or !virtscreen */
63
64 static TFILE *filemap[FD_SETSIZE];
65 static int selectable_tfiles = 0;
66 static List userfilelist[1];
67 static int max_fileid = 0;
68
69 static void fileputs(const char *str, FILE *fp);
70 static void filenputs(const char *str, int n, FILE *fp);
71 static void queueputline(conString *line, TFILE *file);
72
73
init_tfio(void)74 void init_tfio(void)
75 {
76 int i;
77
78 for (i = 0; i < sizeof(filemap)/sizeof(*filemap); i++)
79 filemap[i] = NULL;
80 init_list(userfilelist);
81
82 tfin = tfkeyboard = tfopen("<tfkeyboard>", "q");
83 tfkeyboard->mode = S_IRUSR;
84
85 tfout = tferr = tfalert = tfscreen = tfopen("<tfscreen>", "q");
86 tfscreen->mode = S_IWUSR;
87 tfalert = tfopen("<tfalert>", "q");
88 tfalert->mode = S_IWUSR;
89
90 fg_screen = default_screen = new_screen(1000/*XXX make configurable*/);
91 }
92
93 /* tfname
94 * Use <name> if given, otherwise use body of <macro> as the name. A leading
95 * "~username" followed by '/' or end of string is expanded to <username>'s
96 * home directory; a leading "~" is expanded to the user's home directory.
97 */
tfname(const char * name,const char * macro)98 char *tfname(const char *name, const char *macro)
99 {
100 if (!name || !*name) {
101 if (macro) {
102 if (!(name=macro_body(macro)) || !*name) {
103 eprintf("missing filename, and default macro %s is not defined",
104 macro);
105 }
106 } else {
107 eprintf("missing filename");
108 }
109 }
110 return (name && *name) ? expand_filename(name) : NULL;
111 }
112
expand_filename(const char * str)113 char *expand_filename(const char *str)
114 {
115 const char *dir, *user;
116 STATIC_BUFFER(buffer);
117
118 if (str) {
119 if (*str != '~') return (char *)str;
120 for (user = ++str; *str && *str != '/'; str++);
121 if (str == user) {
122 dir = getvar("HOME");
123 } else {
124
125 #if !(HAVE_GETPWNAM && HAVE_PWD_H)
126 wprintf("\"~user\" filename expansion is not supported.");
127 #else
128 struct passwd *pw;
129 Stringncpy(buffer, user, str - user);
130 if ((pw = getpwnam(buffer->data)))
131 dir = pw->pw_dir;
132 else
133 #endif /* HAVE_GETPWNAM */
134 return (char*)--user;
135 }
136 Stringcpy(buffer, dir ? dir : "");
137 Stringcat(buffer, str);
138 } else {
139 Stringtrunc(buffer, 0);
140 }
141 return buffer->data;
142 }
143
new_screen(long size)144 Screen *new_screen(long size)
145 {
146 Screen *screen;
147
148 screen = XCALLOC(sizeof(Screen));
149 screen->outcount = lines;
150 screen->maxlline = size;
151 screen->scr_wrapflag = wrapflag;
152 screen->scr_wrapsize = wrapsize;
153 screen->scr_wrapspace = wrapspace;
154 init_list(&screen->pline);
155 init_pattern(&screen->filter_pat, NULL, -1);
156 return screen;
157 }
158
free_screen_lines(Screen * screen)159 void free_screen_lines(Screen *screen)
160 {
161 PhysLine *pl;
162
163 while (screen->pline.head) {
164 pl = unlist(screen->pline.head, &screen->pline);
165 if (pl->str) conStringfree(pl->str);
166 pfree(pl, plpool, str);
167 }
168 }
169
free_screen(Screen * screen)170 void free_screen(Screen *screen)
171 {
172 free_screen_lines(screen);
173 free_pattern(&screen->filter_pat);
174 FREE(screen);
175 }
176
177 /* tfopen - opens a TFILE.
178 * Mode "q" will create a TF_QUEUE.
179 * Mode "p" will open a command pipe for reading.
180 * Modes "w", "r", and "a" open a regular file for read, write, or append.
181 * If mode is "r", and the file is not found, will look for a compressed copy.
182 * If tfopen() fails, it will return NULL with errno set as in fopen();
183 * if found file is a directory, tfopen() will return NULL with errno==EISDIR.
184 */
tfopen(const char * name,const char * mode)185 TFILE *tfopen(const char *name, const char *mode)
186 {
187 int type = TF_FILE;
188 FILE *fp;
189 TFILE *result = NULL;
190 const char *prog, *suffix;
191 char *newname = NULL;
192 STATIC_BUFFER(buffer);
193 struct stat buf;
194 MODE_T st_mode = 0;
195
196 if (*mode == 'q') {
197 struct tfile_queue { TFILE file; Queue queue; } *fq;
198 errno = ENOMEM; /* in case malloc fails */
199 if (!(fq = (struct tfile_queue *)MALLOC(sizeof(*fq)))) return NULL;
200 result = &fq->file;
201 result->u.queue = &fq->queue;
202 result->type = TF_QUEUE;
203 result->name = name ? STRDUP(name) : NULL;
204 result->id = -1;
205 result->node = NULL;
206 result->mode = S_IRUSR | S_IWUSR;
207 result->tfmode = *mode;
208 result->autoflush = 1;
209 init_queue(result->u.queue);
210 return result;
211 }
212
213 if (!name || !*name) {
214 errno = ENOENT;
215 return NULL;
216 }
217
218 if (*mode == 'p') {
219 #ifdef __CYGWIN32__
220 eprintf("TF does not support pipes under cygwin32.");
221 errno = EPIPE;
222 return NULL;
223 #else
224 if (restriction >= RESTRICT_SHELL) {
225 errno = EPERM;
226 return NULL;
227 }
228 if (!(fp = popen(name, "r"))) return NULL;
229 result = (TFILE *)XMALLOC(sizeof(TFILE));
230 result->type = TF_PIPE;
231 result->name = STRDUP(name);
232 result->id = -1;
233 result->mode = S_IRUSR;
234 result->tfmode = *mode;
235 result->autoflush = 1;
236 result->node = NULL;
237 result->u.fp = fp;
238 result->off = result->len = 0;
239 filemap[fileno(fp)] = result;
240 selectable_tfiles++;
241 return result;
242 #endif
243 }
244
245 if ((fp = fopen(name, mode)) && fstat(fileno(fp), &buf) == 0) {
246 if (buf.st_mode & S_IFDIR) {
247 fclose(fp);
248 errno = EISDIR; /* must be after fclose() */
249 return NULL;
250 }
251 newname = STRDUP(name);
252 st_mode = buf.st_mode;
253 }
254
255 /* If file did not exist, look for compressed copy. */
256 if (!fp && *mode == 'r' && errno == ENOENT && restriction < RESTRICT_SHELL &&
257 (suffix = macro_body("compress_suffix")) && *suffix &&
258 (prog = macro_body("compress_read")) && *prog)
259 {
260 newname = (char*)XMALLOC(strlen(name) + strlen(suffix) + 1);
261 strcat(strcpy(newname, name), suffix);
262
263 if ((fp = fopen(newname, mode)) != NULL) { /* test readability */
264 fclose(fp);
265 #ifdef PLATFORM_UNIX
266 Sprintf(buffer, "%s %s 2>/dev/null", prog, newname);
267 #endif
268 #ifdef PLATFORM_OS2
269 Sprintf(buffer, "%s %s 2>nul", prog, newname);
270 #endif
271 fp = popen(buffer->data, mode);
272 type = TF_PIPE;
273 }
274 }
275
276 if (fp) {
277 errno = EAGAIN; /* in case malloc fails */
278 if (!(result = (TFILE*)MALLOC(sizeof(TFILE)))) return NULL;
279 result->type = type;
280 result->name = newname;
281 result->id = 0;
282 result->node = NULL;
283 result->tfmode = *mode;
284 result->autoflush = 1;
285 result->u.fp = fp;
286 result->off = result->len = 0;
287 result->mode = st_mode;
288 if (*mode == 'r' || *mode == 'p') {
289 result->mode |= S_IRUSR;
290 result->mode &= ~S_IWUSR;
291 } else {
292 result->mode &= ~S_IRUSR;
293 result->mode |= S_IWUSR;
294 }
295 result->warned = 0;
296 } else {
297 if (newname) FREE(newname);
298 }
299
300 return result;
301 }
302
303 /* tfclose
304 * Close a TFILE created by tfopen().
305 */
tfclose(TFILE * file)306 int tfclose(TFILE *file)
307 {
308 int result;
309 List *list;
310
311 if (!file) return -1;
312 if (file->name) FREE(file->name);
313 switch(file->type) {
314 case TF_QUEUE:
315 list = &file->u.queue->list;
316 while (list->head)
317 Stringfree((String*)unlist(list->head, list));
318 /* FREE(file->u.queue); */ /* queue was allocated with file */
319 result = 0;
320 break;
321 case TF_FILE:
322 result = fclose(file->u.fp);
323 break;
324 case TF_PIPE:
325 filemap[fileno(file->u.fp)] = NULL;
326 selectable_tfiles--;
327 result = shell_status(pclose(file->u.fp));
328 break;
329 default:
330 result = -1;
331 }
332 if (file->node)
333 unlist(file->node, userfilelist);
334 FREE(file);
335 return result;
336 }
337
338 /* tfselect() is like select(), but also checks buffered TFILEs */
tfselect(int nfds,fd_set * readers,fd_set * writers,fd_set * excepts,struct timeval * timeout)339 int tfselect(int nfds, fd_set *readers, fd_set *writers, fd_set *excepts,
340 struct timeval *timeout)
341 {
342 int i, count, tfcount = 0;
343 fd_set tfreaders;
344
345 if (!selectable_tfiles)
346 return select(nfds, readers, writers, excepts, timeout);
347
348 FD_ZERO(&tfreaders);
349
350 for (i = 0; i < nfds; i++) {
351 if (filemap[i] && FD_ISSET(i, readers)) {
352 if (filemap[i]->off < filemap[i]->len) {
353 FD_SET(i, &tfreaders);
354 FD_CLR(i, readers); /* don't check twice */
355 tfcount++;
356 }
357 }
358 }
359
360 if (!tfcount) {
361 return select(nfds, readers, writers, excepts, timeout);
362
363 } else {
364 /* we found at least one; poll the rest, but don't wait */
365 struct timeval zero;
366 zero = tvzero;
367 count = select(nfds, readers, writers, excepts, &zero);
368 if (count < 0) return count;
369 count += tfcount;
370
371 for (i = 0; tfcount && i < nfds; i++) {
372 if (FD_ISSET(i, &tfreaders)) {
373 FD_SET(i, readers);
374 tfcount--;
375 }
376 }
377
378 return count;
379 }
380 }
381
382 /**********
383 * Output *
384 **********/
385
386 /* tfnputs
387 * Print to a TFILE.
388 * Unlike fputs(), tfnputs() always appends a newline when writing to a file.
389 */
tfnputs(const char * str,int n,TFILE * file)390 void tfnputs(const char *str, int n, TFILE *file)
391 {
392 if (!file || file->type == TF_NULL) {
393 /* do nothing */
394 } else if (file->type == TF_QUEUE) {
395 queueputline(CS(Stringnew(str, n, file==tferr ? error_attr : 0)), file);
396 } else {
397 if (n < 0)
398 fileputs(str, file->u.fp);
399 else
400 filenputs(str, n, file->u.fp);
401 if (file->autoflush) tfflush(file);
402 }
403 }
404
405 /* tfputansi
406 * Print to a TFILE, converting embedded ANSI display codes to String attrs.
407 */
tfputansi(const char * str,TFILE * file,attr_t attrs)408 attr_t tfputansi(const char *str, TFILE *file, attr_t attrs)
409 {
410 conString *line;
411
412 if (file && file->type != TF_NULL) {
413 line = CS(decode_ansi(str, attrs, EMUL_ANSI_ATTR, &attrs));
414 line->links++;
415 tfputline(line, file);
416 conStringfree(line);
417 }
418 return attrs;
419 }
420
421 /* tfputline
422 * Print a String to a TFILE, with embedded newline handling.
423 */
tfputline(conString * line,TFILE * file)424 void tfputline(conString *line, TFILE *file)
425 {
426 /* Many callers pass line with links==0, so the ++ and free are vital. */
427 line->links++;
428 if (!file || file->type == TF_NULL) {
429 /* do nothing */
430 } else if (file == tfalert) {
431 alert(line);
432 } else if (file->type == TF_QUEUE) {
433 queueputline(line, file);
434 } else {
435 filenputs(line->data, line->len, file->u.fp);
436 if (file->autoflush) tfflush(file);
437 }
438 conStringfree(line);
439 }
440
queueputline(conString * line,TFILE * file)441 static void queueputline(conString *line, TFILE *file)
442 {
443 /* Many callers pass line with links==0, so the ++ and free are vital. */
444 line->links++;
445 if (file == tfscreen) {
446 record_local(line);
447 record_global(line);
448 screenout(line);
449 } else if (!file) {
450 /* do nothing */
451 } else if (file->type == TF_QUEUE) {
452 line->links++;
453 enqueue(file->u.queue, line);
454 }
455 conStringfree(line);
456 }
457
458 /* print string\n to a file, converting embedded newlines to spaces */
fileputs(const char * str,FILE * fp)459 static void fileputs(const char *str, FILE *fp)
460 {
461 for ( ; *str; str++) {
462 register char c = *str == '\n' ? ' ' : *str;
463 putc(c, fp);
464 }
465 putc('\n', fp);
466 }
467
468 /* print string\n to a file, converting embedded newlines to spaces */
filenputs(const char * str,int n,FILE * fp)469 static void filenputs(const char *str, int n, FILE *fp)
470 {
471 for ( ; *str && n > 0; str++, n--) {
472 register char c = *str == '\n' ? ' ' : *str;
473 putc(c, fp);
474 }
475 putc('\n', fp);
476 }
477
478
479 /* vSprintf
480 * Similar to vsprintf, except:
481 * second arg is a flag, third arg is format.
482 * %S is like %s, but takes a String* argument.
483 * %q takes a char c and a string s; prints s, with \ before each c.
484 * %b is like %q, but nonprinting characters are printed as "\octal".
485 * %s, %S, %q, %b arguments may be NULL.
486 * %q and %b do not support any width or "*" precision.
487 * %A takes an attr_t, and sets the attributes for the whole line
488 * (not implemented: %a sets the attrs for the remainder of the line)
489 * newlines are not allowed in the format string (this is not enforced).
490 */
491
vSprintf(String * buf,int flags,const char * fmt,va_list ap)492 void vSprintf(String *buf, int flags, const char *fmt, va_list ap)
493 {
494 static smallstr spec, tempbuf;
495 const char *q, *sval;
496 char *specptr, quote;
497 const conString *Sval;
498 int len, min, max, leftjust, stars;
499 attr_t attrs = buf->attrs;
500
501 if (!(flags & SP_APPEND) && buf->data) Stringtrunc(buf, 0);
502 while (*fmt) {
503 if (*fmt != '%' || *++fmt == '%') {
504 for (q = fmt + 1; *q && *q != '%'; q++);
505 Stringfncat(buf, fmt, q - fmt);
506 fmt = q;
507 continue;
508 }
509
510 specptr = spec;
511 *specptr++ = '%';
512 for (stars = 0; *fmt && !is_alpha(*fmt); fmt++) {
513 if (*fmt == '*') stars++;
514 *specptr++ = *fmt;
515 }
516 if (*fmt == 'h' || lcase(*fmt) == 'l') *specptr++ = *fmt++;
517 *specptr = *fmt;
518 *++specptr = '\0';
519
520 switch (*fmt) {
521 case 'd': case 'i':
522 case 'x': case 'X': case 'u': case 'o':
523 case 'f': case 'e': case 'E': case 'g': case 'G':
524 case 'p':
525 vsprintf(tempbuf, spec, ap);
526 Stringcat(buf, tempbuf);
527 /* eat the arguments used by vsprintf() */
528 while (stars--) (void)va_arg(ap, int);
529 switch (*fmt) {
530 case 'd': case 'i':
531 (void)va_arg(ap, int); break;
532 case 'x': case 'X': case 'u': case 'o':
533 (void)va_arg(ap, unsigned int); break;
534 case 'f': case 'e': case 'E': case 'g': case 'G':
535 (void)va_arg(ap, double); break;
536 case 'p':
537 (void)va_arg(ap, void *); break;
538 }
539 break;
540 case 'c':
541 Stringadd(buf, (char)va_arg(ap, int));
542 break;
543 case 's':
544 case 'S':
545 sval = NULL;
546 Sval = NULL;
547 min = 0;
548 max = -1;
549
550 specptr = &spec[1];
551 if ((leftjust = (*specptr == '-')))
552 specptr++;
553 if (*specptr == '*') {
554 ++specptr;
555 min = va_arg(ap, int);
556 } else if (is_digit(*specptr)) {
557 min = strtoint(specptr, &specptr);
558 }
559 if (*specptr == '.') {
560 ++specptr;
561 if (*specptr == '*') {
562 ++specptr;
563 max = va_arg(ap, int);
564 } else if (is_digit(*specptr)) {
565 max = strtoint(specptr, &specptr);
566 }
567 }
568
569 if (*fmt == 's') {
570 sval = va_arg(ap, char *);
571 if (flags & SP_CHECK) sval = checkstring(sval);
572 len = sval ? strlen(sval) : 0;
573 } else {
574 Sval = va_arg(ap, const conString *);
575 len = Sval ? Sval->len : 0;
576 }
577
578 if (max >= 0 && len > max) len = max;
579 if (!leftjust && len < min) Stringnadd(buf, ' ', min - len);
580 if (Sval)
581 SStringncat(buf, Sval, len);
582 else
583 Stringfncat(buf, sval ? sval : "", len);
584 if (leftjust && len < min) Stringnadd(buf, ' ', min - len);
585 break;
586 case 'q':
587 case 'b':
588 max = (spec[1] == '.') ? atoi(&spec[2]) : 0x7FFF;
589 if (!(quote = (char)va_arg(ap, int))) break;
590 if (!(sval = va_arg(ap, char *))) break;
591 if (flags & SP_CHECK) sval = checkstring(sval);
592 for ( ; *sval; sval = q) {
593 if (*fmt == 'b' && !is_print(*q)) {
594 if (max < 4) break;
595 max -= 4;
596 sprintf(tempbuf, "\\%03o", *sval++);
597 Stringcat(buf, tempbuf);
598 } else if (*sval == quote || *sval == '\\') {
599 if (max < 2) break;
600 max -= 2;
601 Stringadd(buf, '\\');
602 Stringadd(buf, *sval++);
603 }
604 for (q = sval; max > 0 && *q; q++, max--) {
605 if (*q == quote || *q == '\\') break;
606 if (*fmt == 'b' && !is_print(*q)) break;
607 }
608 Stringfncat(buf, sval, q - sval);
609 }
610 break;
611 case 'A':
612 attrs = (attr_t)va_arg(ap, attr_t);
613 break;
614 default:
615 Stringcat(buf, spec);
616 break;
617 }
618 fmt++;
619 }
620 if (!buf->data) Stringtrunc(buf, 0); /* force buf->data != NULL */
621 buf->attrs = attrs;
622 }
623
624 #ifndef oprintf
625 /* oprintf
626 * A newline will appended. See vSprintf().
627 */
628
oprintf(const char * fmt,...)629 void oprintf(const char *fmt, ...)
630 {
631 va_list ap;
632 String *buffer;
633
634 buffer = Stringnew(NULL, -1, 0);
635 va_start(ap, fmt);
636 vSprintf(buffer, 0, fmt, ap);
637 va_end(ap);
638 oputline(CS(buffer));
639 }
640 #endif /* oprintf */
641
642 /* tfprintf
643 * Print to a TFILE. A newline will appended. See vSprintf().
644 */
645
tfprintf(TFILE * file,const char * fmt,...)646 void tfprintf(TFILE *file, const char *fmt, ...)
647 {
648 va_list ap;
649 String *buffer;
650
651 buffer = Stringnew(NULL, -1, 0);
652 va_start(ap, fmt);
653 vSprintf(buffer, 0, fmt, ap);
654 va_end(ap);
655 tfputline(CS(buffer), file);
656 }
657
658
659 /* Sprintf
660 * Print into a String. See vSprintf().
661 */
Sprintf(String * buf,const char * fmt,...)662 void Sprintf(String *buf, const char *fmt, ...)
663 {
664 va_list ap;
665
666 va_start(ap, fmt);
667 vSprintf(buf, 0, fmt, ap);
668 va_end(ap);
669 }
670
Sappendf(String * buf,const char * fmt,...)671 void Sappendf(String *buf, const char *fmt, ...)
672 {
673 va_list ap;
674
675 va_start(ap, fmt);
676 vSprintf(buf, SP_APPEND, fmt, ap);
677 va_end(ap);
678 }
679
eprefix(String * buffer)680 void eprefix(String *buffer)
681 {
682 extern char current_opt;
683 Stringcpy(buffer, "% ");
684 if (loadfile) {
685 Sappendf(buffer, "%s, line", loadfile->name);
686 if (loadstart == loadline)
687 Sappendf(buffer, " %d: ", loadline);
688 else
689 Sappendf(buffer, "s %d-%d: ", loadstart, loadline);
690 }
691 if (current_command && *current_command != '\b')
692 Sappendf(buffer, current_opt ? "%s -%c: " : "%s: ",
693 current_command, current_opt);
694 }
695
veprintf(int warning,const char * fmt,va_list ap)696 static void veprintf(int warning, const char *fmt, va_list ap)
697 {
698 String *buffer;
699 buffer = Stringnew(NULL, -1, 0);
700 eprefix(buffer);
701 if (warning)
702 Stringcat(buffer, "Warning: ");
703 vSprintf(buffer, SP_APPEND|SP_CHECK, fmt, ap);
704 if (!buffer->attrs)
705 buffer->attrs = warning ? warning_attr : error_attr;
706 tfputline(CS(buffer), tferr);
707 }
708
709 /* print a formatted error message */
eprintf(const char * fmt,...)710 void eprintf(const char *fmt, ...)
711 {
712 va_list ap;
713 va_start(ap, fmt);
714 veprintf(0, fmt, ap);
715 va_end(ap);
716 }
717
718 /* print a formatted warning message */
wprintf(const char * fmt,...)719 void wprintf(const char *fmt, ...)
720 {
721 va_list ap;
722 va_start(ap, fmt);
723 veprintf(1, fmt, ap);
724 va_end(ap);
725 }
726
727 static const char interrmsg[] =
728 "Please report this to the author, and describe what you did.";
729
internal_error(const char * file,int line,const char * fmt,...)730 void internal_error(const char *file, int line, const char *fmt, ...)
731 {
732 va_list ap;
733
734 eprintf("Internal error at %s:%d, %s. %s", file, line, version, interrmsg);
735 if (current_command) eprintf("cmd: \"%.32b\"", '\"', current_command);
736
737 va_start(ap, fmt);
738 veprintf(0, fmt, ap);
739 va_end(ap);
740 }
741
internal_error2(const char * file,int line,const char * file2,int line2,const char * fmt,...)742 void internal_error2(const char *file, int line, const char *file2, int line2,
743 const char *fmt, ...)
744 {
745 va_list ap;
746
747 eprintf("Internal error at %s:%d (%s:%d), %s. %s",
748 file, line, file2, line2, version, interrmsg);
749 if (current_command) eprintf("cmd: \"%.32b\"", '\"', current_command);
750
751 va_start(ap, fmt);
752 veprintf(0, fmt, ap);
753 va_end(ap);
754 }
755
756
757 /*********
758 * Input *
759 *********/
760
761 /* read one char from keyboard, with blocking */
igetchar(void)762 char igetchar(void)
763 {
764 char c;
765 fd_set readers;
766
767 FD_ZERO(&readers);
768 FD_SET(STDIN_FILENO, &readers);
769 while(select(1, &readers, NULL, NULL, NULL) <= 0);
770 read(STDIN_FILENO, &c, 1);
771 return c;
772 }
773
tfreadable(TFILE * file)774 int tfreadable(TFILE *file)
775 {
776 if (!file) {
777 return 1; /* tfread will imeediately return error */
778
779 } else if (file == tfkeyboard) {
780 return 0; /* tfread will not return immediately */
781
782 } else if (file->type == TF_QUEUE) {
783 return 1; /* tfread will immediately return line or EOF */
784
785 } else {
786 struct timeval timeout = tvzero;
787 fd_set readers;
788 int count;
789
790 if (file->len < 0) return 1; /* tfread will return eof or error */
791
792 FD_ZERO(&readers);
793 FD_SET(fileno(file->u.fp), &readers);
794
795 count = select(fileno(file->u.fp) + 1, &readers, NULL, NULL, &timeout);
796 if (count < 0) {
797 return -1;
798 } else if (count == 0) {
799 return 0;
800 }
801 return 1;
802 }
803 }
804
805 /* Unlike fgets, tfgetS() does not retain terminating newline. */
tfgetS(String * str,TFILE * file)806 String *tfgetS(String *str, TFILE *file)
807 {
808 if (!file) {
809 return NULL;
810
811 } else if (file == tfkeyboard) {
812 /* This is a hack. It's a useful feature, but doing it correctly
813 * without blocking tf would require making the macro language
814 * suspendable, which would have required a major redesign. The
815 * nested main_loop() method was easy to add, but leads to a few
816 * quirks, like the odd handling of /dokey newline.
817 */
818 TFILE *oldtfout, *oldtfin;
819
820 if (!readsafe) {
821 eprintf("keyboard can only be read from a command line command.");
822 return NULL;
823 }
824 if (read_depth) wprintf("nested keyboard read");
825 oldtfout = tfout;
826 oldtfin = tfin;
827 tfout = tfscreen;
828 tfin = tfkeyboard;
829 readsafe = 0;
830 read_depth++; update_status_field(NULL, STAT_READ);
831 main_loop();
832 read_depth--; update_status_field(NULL, STAT_READ);
833 readsafe = 1;
834 tfout = oldtfout;
835 tfin = oldtfin;
836 if (interrupted())
837 return NULL;
838
839 SStringcpy(str, CS(keybuf));
840 Stringtrunc(keybuf, keyboard_pos = 0);
841 return str;
842
843 } else if (file->type == TF_QUEUE) {
844 conString *line;
845 do {
846 if (!(line = dequeue(file->u.queue))) return NULL;
847 if (!((line->attrs & F_GAG) && gag)) break;
848 conStringfree(line);
849 } while (1);
850 SStringcpy(str, line); /* TODO: get rid of this copy */
851 conStringfree(line);
852 return str;
853
854 } else {
855 int next;
856
857 if (file->len < 0) return NULL; /* eof or error */
858
859 Stringtrunc(str, 0);
860
861 do {
862 while (file->off < file->len) {
863 next = file->off + 1;
864 if (file->buf[file->off] == '\n') {
865 file->off++;
866 return str;
867 } else if (file->buf[file->off] == '\t') {
868 file->off++;
869 Stringnadd(str, ' ', tabsize - str->len % tabsize);
870 }
871 while (is_print(file->buf[next]) && next < file->len)
872 next++;
873 Stringfncat(str, file->buf + file->off,
874 next - file->off);
875 file->off = next;
876 }
877 file->off = 0;
878 file->len = read(fileno(file->u.fp), file->buf, sizeof(file->buf));
879 } while (file->len > 0);
880
881 file->len = -1; /* note eof */
882 return str->len ? str : NULL;
883 }
884 }
885
886
887 /**************
888 * User level *
889 **************/
890
handle_tfopen_func(const char * name,const char * mode)891 int handle_tfopen_func(const char *name, const char *mode)
892 {
893 TFILE *file;
894
895 if (restriction >= RESTRICT_FILE) {
896 /* RESTRICT_SHELL is checked by tfopen() */
897 eprintf("restricted");
898 return -1;
899 }
900
901 if (mode[1] || !strchr("rwapq", mode[0])) {
902 eprintf("invalid mode '%s'", mode);
903 return -1;
904 }
905 file = tfopen(expand_filename(name), mode);
906 if (!file) {
907 eprintf("%s: %s", name, strerror(errno));
908 return -1;
909 }
910
911 file->node = inlist(file, userfilelist, userfilelist->tail);
912 file->id = ++max_fileid;
913 return file->id;
914 }
915
find_tfile(const char * handle)916 TFILE *find_tfile(const char *handle)
917 {
918 ListEntry *node;
919 int id;
920
921 if (is_alpha(handle[0]) && !handle[1]) {
922 switch(lcase(handle[0])) {
923 case 'i': return tfin;
924 case 'o': return tfout;
925 case 'e': return tferr;
926 case 'a': return tfalert;
927 default: break;
928 }
929 } else {
930 id = atoi(handle);
931 for (node = userfilelist->head; node; node = node->next) {
932 if (((TFILE*)node->datum)->id == id)
933 return (TFILE*)node->datum;
934 }
935 }
936 eprintf("%s: bad handle", handle);
937 return NULL;
938 }
939
find_usable_tfile(const char * handle,int mode)940 TFILE *find_usable_tfile(const char *handle, int mode)
941 {
942 TFILE *tfile;
943
944 if (!(tfile = find_tfile(handle)))
945 return NULL;
946
947 if (mode) {
948 if (!(tfile->mode & mode) ||
949 (mode & S_IRUSR && (tfile == tfout || tfile == tferr || tfile == tfalert)) ||
950 (mode & S_IWUSR && (tfile == tfin))) {
951 eprintf("stream %s is not %sable", handle,
952 mode == S_IRUSR ? "read" : "writ");
953 return NULL;
954 }
955 }
956
957 return tfile;
958 }
959
handle_liststreams_command(String * args,int offset)960 struct Value *handle_liststreams_command(String *args, int offset)
961 {
962 int count = 0;
963 TFILE *file;
964 ListEntry *node;
965
966 if (!userfilelist->head) {
967 oprintf("% No open streams.");
968 return shareval(val_zero);
969 }
970 oprintf("HANDLE MODE FLUSH NAME");
971 for (node = userfilelist->head; node; node = node->next) {
972 file = (TFILE*)node->datum;
973 oprintf("%6d %c %3s %s", file->id, file->tfmode,
974 (file->tfmode == 'w' || file->tfmode == 'a') ?
975 enum_flag[file->autoflush].data : "",
976 file->name ? file->name : "");
977 count++;
978 }
979
980 return newint(count);
981 }
982
983