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