Add routine to run daughter process and collect its output
[xboard.git] / usystem.c
1 /*
2  * usystem.c -- X-free, but Unix-like code for XBoard front end
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Free
9  * Software Foundation, Inc.
10  *
11  * The following terms apply to Digital Equipment Corporation's copyright
12  * interest in XBoard:
13  * ------------------------------------------------------------------------
14  * All Rights Reserved
15  *
16  * Permission to use, copy, modify, and distribute this software and its
17  * documentation for any purpose and without fee is hereby granted,
18  * provided that the above copyright notice appear in all copies and that
19  * both that copyright notice and this permission notice appear in
20  * supporting documentation, and that the name of Digital not be
21  * used in advertising or publicity pertaining to distribution of the
22  * software without specific, written prior permission.
23  *
24  * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
25  * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
26  * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
27  * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
28  * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
29  * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
30  * SOFTWARE.
31  * ------------------------------------------------------------------------
32  *
33  * The following terms apply to the enhanced version of XBoard
34  * distributed by the Free Software Foundation:
35  * ------------------------------------------------------------------------
36  *
37  * GNU XBoard is free software: you can redistribute it and/or modify
38  * it under the terms of the GNU General Public License as published by
39  * the Free Software Foundation, either version 3 of the License, or (at
40  * your option) any later version.
41  *
42  * GNU XBoard is distributed in the hope that it will be useful, but
43  * WITHOUT ANY WARRANTY; without even the implied warranty of
44  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
45  * General Public License for more details.
46  *
47  * You should have received a copy of the GNU General Public License
48  * along with this program. If not, see http://www.gnu.org/licenses/.  *
49  *
50  *------------------------------------------------------------------------
51  ** See the file ChangeLog for a revision history.  */
52
53 #include "config.h"
54
55 #include <stdio.h>
56 #include <ctype.h>
57 #include <signal.h>
58 #include <errno.h>
59 #include <sys/types.h>
60 #include <sys/stat.h>
61 #include <pwd.h>
62 #include <math.h>
63
64 #if !OMIT_SOCKETS
65 # if HAVE_SYS_SOCKET_H
66 #  include <sys/socket.h>
67 #  include <netinet/in.h>
68 #  include <netdb.h>
69 # else /* not HAVE_SYS_SOCKET_H */
70 #  if HAVE_LAN_SOCKET_H
71 #   include <lan/socket.h>
72 #   include <lan/in.h>
73 #   include <lan/netdb.h>
74 #  else /* not HAVE_LAN_SOCKET_H */
75 #   define OMIT_SOCKETS 1
76 #  endif /* not HAVE_LAN_SOCKET_H */
77 # endif /* not HAVE_SYS_SOCKET_H */
78 #endif /* !OMIT_SOCKETS */
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 #else /* not STDC_HEADERS */
84 extern char *getenv();
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if HAVE_SYS_SYSTEMINFO_H
101 # include <sys/systeminfo.h>
102 #endif /* HAVE_SYS_SYSTEMINFO_H */
103
104 #if TIME_WITH_SYS_TIME
105 # include <sys/time.h>
106 # include <time.h>
107 #else
108 # if HAVE_SYS_TIME_H
109 #  include <sys/time.h>
110 # else
111 #  include <time.h>
112 # endif
113 #endif
114
115 #if HAVE_UNISTD_H
116 # include <unistd.h>
117 #endif
118
119 #if HAVE_SYS_WAIT_H
120 # include <sys/wait.h>
121 #endif
122
123 #if HAVE_DIRENT_H
124 # include <dirent.h>
125 # define NAMLEN(dirent) strlen((dirent)->d_name)
126 # define HAVE_DIR_STRUCT
127 #else
128 # define dirent direct
129 # define NAMLEN(dirent) (dirent)->d_namlen
130 # if HAVE_SYS_NDIR_H
131 #  include <sys/ndir.h>
132 #  define HAVE_DIR_STRUCT
133 # endif
134 # if HAVE_SYS_DIR_H
135 #  include <sys/dir.h>
136 #  define HAVE_DIR_STRUCT
137 # endif
138 # if HAVE_NDIR_H
139 #  include <ndir.h>
140 #  define HAVE_DIR_STRUCT
141 # endif
142 #endif
143
144 #if ENABLE_NLS
145 #include <locale.h>
146 #endif
147
148 // [HGM] bitmaps: put before incuding the bitmaps / pixmaps, to know how many piece types there are.
149 #include "common.h"
150
151 #include "frontend.h"
152 #include "backend.h"
153 #include "childio.h"
154 #include "menus.h"
155 #include "usystem.h"
156 #include "gettext.h"
157
158
159 #ifdef __EMX__
160 #ifndef HAVE_USLEEP
161 #define HAVE_USLEEP
162 #endif
163 #define usleep(t)   _sleep2(((t)+500)/1000)
164 #endif
165
166 #ifdef ENABLE_NLS
167 # define  _(s) gettext (s)
168 # define N_(s) gettext_noop (s)
169 #else
170 # define  _(s) (s)
171 # define N_(s)  s
172 #endif
173
174 static int get_term_width P(());
175
176 static char *cnames[9] = { "black", "red", "green", "yellow", "blue",
177                              "magenta", "cyan", "white" };
178 TextColors textColors[(int)NColorClasses];
179
180 /* String is: "fg, bg, attr". Which is 0, 1, 2 */
181 static int
182 parse_color (char *str, int which)
183 {
184     char *p, buf[100], *d;
185     int i;
186
187     if (strlen(str) > 99)       /* watch bounds on buf */
188       return -1;
189
190     p = str;
191     d = buf;
192     for (i=0; i<which; ++i) {
193         p = strchr(p, ',');
194         if (!p)
195           return -1;
196         ++p;
197     }
198
199     /* Could be looking at something like:
200        black, , 1
201        .. in which case we want to stop on a comma also */
202     while (*p && *p != ',' && !isalpha(*p) && !isdigit(*p))
203       ++p;
204
205     if (*p == ',') {
206         return -1;              /* Use default for empty field */
207     }
208
209     if (which == 2 || isdigit(*p))
210       return atoi(p);
211
212     while (*p && isalpha(*p))
213       *(d++) = *(p++);
214
215     *d = 0;
216
217     for (i=0; i<8; ++i) {
218         if (!StrCaseCmp(buf, cnames[i]))
219           return which? (i+40) : (i+30);
220     }
221     if (!StrCaseCmp(buf, "default")) return -1;
222
223     fprintf(stderr, _("%s: unrecognized color %s\n"), programName, buf);
224     return -2;
225 }
226
227 static int
228 parse_cpair (ColorClass cc, char *str)
229 {
230     if ((textColors[(int)cc].fg=parse_color(str, 0)) == -2) {
231         fprintf(stderr, _("%s: can't parse foreground color in '%s'\n"),
232                 programName, str);
233         return -1;
234     }
235
236     /* bg and attr are optional */
237     textColors[(int)cc].bg = parse_color(str, 1);
238     if ((textColors[(int)cc].attr = parse_color(str, 2)) < 0) {
239         textColors[(int)cc].attr = 0;
240     }
241     return 0;
242 }
243
244 void
245 ParseIcsTextColors ()
246 {   // [HGM] tken out of main(), so it can be called from ICS-Options dialog
247     if (parse_cpair(ColorShout, appData.colorShout) < 0 ||
248         parse_cpair(ColorSShout, appData.colorSShout) < 0 ||
249         parse_cpair(ColorChannel1, appData.colorChannel1) < 0  ||
250         parse_cpair(ColorChannel, appData.colorChannel) < 0  ||
251         parse_cpair(ColorKibitz, appData.colorKibitz) < 0 ||
252         parse_cpair(ColorTell, appData.colorTell) < 0 ||
253         parse_cpair(ColorChallenge, appData.colorChallenge) < 0  ||
254         parse_cpair(ColorRequest, appData.colorRequest) < 0  ||
255         parse_cpair(ColorSeek, appData.colorSeek) < 0  ||
256         parse_cpair(ColorNormal, appData.colorNormal) < 0)
257       {
258           if (appData.colorize) {
259               fprintf(stderr,
260                       _("%s: can't parse color names; disabling colorization\n"),
261                       programName);
262           }
263           appData.colorize = FALSE;
264       }
265     textColors[ColorNone].fg = textColors[ColorNone].bg = -1;
266     textColors[ColorNone].attr = 0;
267     SetTextColor(cnames, textColors[ColorNormal].fg - 30, textColors[ColorNormal].bg - 40, -2); // kludge to announce background color to front-end 
268 }
269
270 char *oldICSInteractionTitle;
271
272 void
273 ShutDownFrontEnd ()
274 {
275     if (appData.icsActive && oldICSInteractionTitle != NULL) {
276         DisplayIcsInteractionTitle(oldICSInteractionTitle);
277     }
278     if (saveSettingsOnExit) SaveSettings(settingsFileName);
279     unlink(gameCopyFilename);
280     unlink(gamePasteFilename);
281     EchoOn();
282 }
283
284 void
285 RunCommand (char *buf)
286 {
287     system(buf);
288 }
289
290 void
291 Colorize (ColorClass cc, int continuation)
292 {
293     char buf[MSG_SIZ];
294     int count, outCount, error;
295
296     SetTextColor(cnames, textColors[(int)cc].fg - 30, textColors[(int)cc].bg - 40, textColors[(int)cc].attr); // for GTK widget
297
298     if (textColors[(int)cc].bg > 0) {
299         if (textColors[(int)cc].fg > 0) {
300           snprintf(buf, MSG_SIZ, "\033[0;%d;%d;%dm", textColors[(int)cc].attr,
301                    textColors[(int)cc].fg, textColors[(int)cc].bg);
302         } else {
303           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
304                    textColors[(int)cc].bg);
305         }
306     } else {
307         if (textColors[(int)cc].fg > 0) {
308           snprintf(buf, MSG_SIZ, "\033[0;%d;%dm", textColors[(int)cc].attr,
309                     textColors[(int)cc].fg);
310         } else {
311           snprintf(buf, MSG_SIZ, "\033[0;%dm", textColors[(int)cc].attr);
312         }
313     }
314     count = strlen(buf);
315     outCount = OutputToProcess(NoProc, buf, count, &error);
316     if (outCount < count) {
317         DisplayFatalError(_("Error writing to display"), error, 1);
318     }
319
320     if (continuation) return;
321     PlaySoundForColor(cc);
322 }
323
324 char *
325 UserName ()
326 {
327     return getpwuid(getuid())->pw_name;
328 }
329
330 char *
331 ExpandPathName (char *path)
332 {
333     static char static_buf[4*MSG_SIZ];
334     char *d, *s, buf[4*MSG_SIZ];
335     struct passwd *pwd;
336
337     s = path;
338     d = static_buf;
339
340     while (*s && isspace(*s))
341       ++s;
342
343     if (!*s) {
344         *d = 0;
345         return static_buf;
346     }
347
348     if (*s == '~') {
349         if(s[1] == '~') { // use ~~ for XBoard's private data directory
350           snprintf(d, 4*MSG_SIZ, "%s%s", dataDir, s+2);
351         } else
352         if (*(s+1) == '/') {
353           safeStrCpy(d, getpwuid(getuid())->pw_dir, 4*MSG_SIZ );
354           strcat(d, s+1);
355         }
356         else {
357           safeStrCpy(buf, s+1, sizeof(buf)/sizeof(buf[0]) );
358           { char *p; if(p = strchr(buf, '/')) *p = 0; }
359           pwd = getpwnam(buf);
360           if (!pwd)
361             {
362               fprintf(stderr, _("ERROR: Unknown user %s (in path %s)\n"),
363                       buf, path);
364               return NULL;
365             }
366           safeStrCpy(d, pwd->pw_dir, 4*MSG_SIZ );
367           strcat(d, strchr(s+1, '/'));
368         }
369     }
370     else
371       safeStrCpy(d, s, 4*MSG_SIZ );
372
373     return static_buf;
374 }
375
376 int
377 MySearchPath (char *installDir, char *name, char *fullname)
378 { // just append installDir and name. Perhaps ExpandPath should be used here?
379   name = ExpandPathName(name);
380   if(name && name[0] == '/')
381     safeStrCpy(fullname, name, MSG_SIZ );
382   else {
383     sprintf(fullname, "%s%c%s", installDir, '/', name);
384   }
385   return 1;
386 }
387
388 int
389 MyGetFullPathName (char *name, char *fullname)
390 { // should use ExpandPath?
391   name = ExpandPathName(name);
392   safeStrCpy(fullname, name, MSG_SIZ );
393   return 1;
394 }
395
396 char *
397 HostName ()
398 {
399     static char host_name[MSG_SIZ];
400
401 #if HAVE_GETHOSTNAME
402     gethostname(host_name, MSG_SIZ);
403     return host_name;
404 #else  /* not HAVE_GETHOSTNAME */
405 # if HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H
406     sysinfo(SI_HOSTNAME, host_name, MSG_SIZ);
407     return host_name;
408 # else /* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
409     return "localhost";
410 # endif/* not (HAVE_SYSINFO && HAVE_SYS_SYSTEMINFO_H) */
411 #endif /* not HAVE_GETHOSTNAME */
412 }
413
414
415 int
416 StartChildProcess (char *cmdLine, char *dir, ProcRef *pr)
417 {
418     char *argv[64], *p;
419     int i, pid;
420     int to_prog[2], from_prog[2];
421     ChildProc *cp;
422     char buf[MSG_SIZ];
423
424     if (appData.debugMode) {
425         fprintf(debugFP, "StartChildProcess (dir=\"%s\") %s\n",dir, cmdLine);
426     }
427
428     /* We do NOT feed the cmdLine to the shell; we just
429        parse it into blank-separated arguments in the
430        most simple-minded way possible.
431        */
432     i = 0;
433     safeStrCpy(buf, cmdLine, sizeof(buf)/sizeof(buf[0]) );
434     p = buf;
435     for (;;) {
436         while(*p == ' ') p++;
437         argv[i++] = p;
438         if(*p == '"' || *p == '\'')
439              p = strchr(++argv[i-1], *p);
440         else p = strchr(p, ' ');
441         if (p == NULL) break;
442         *p++ = NULLCHAR;
443     }
444     argv[i] = NULL;
445
446     SetUpChildIO(to_prog, from_prog);
447
448     if ((pid = fork()) == 0) {
449         /* Child process */
450         // [HGM] PSWBTM: made order resistant against case where fd of created pipe was 0 or 1
451         close(to_prog[1]);     // first close the unused pipe ends
452         close(from_prog[0]);
453         dup2(to_prog[0], 0);   // to_prog was created first, nd is the only one to use 0 or 1
454         dup2(from_prog[1], 1);
455         if(to_prog[0] >= 2) close(to_prog[0]); // if 0 or 1, the dup2 already cosed the original
456         close(from_prog[1]);                   // and closing again loses one of the pipes!
457         if(fileno(stderr) >= 2) // better safe than sorry...
458                 dup2(1, fileno(stderr)); /* force stderr to the pipe */
459
460         if (dir[0] != NULLCHAR && chdir(dir) != 0) {
461             perror(dir);
462             exit(1);
463         }
464
465         nice(appData.niceEngines); // [HGM] nice: adjust priority of engine proc
466
467         execvp(argv[0], argv);
468
469         /* If we get here, exec failed */
470         perror(argv[0]);
471         exit(1);
472     }
473
474     /* Parent process */
475     close(to_prog[0]);
476     close(from_prog[1]);
477
478     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
479     cp->kind = CPReal;
480     cp->pid = pid;
481     cp->fdFrom = from_prog[0];
482     cp->fdTo = to_prog[1];
483     *pr = (ProcRef) cp;
484     return 0;
485 }
486
487 // [HGM] kill: implement the 'hard killing' of AS's Winboard_x
488 static int pid;
489
490 static RETSIGTYPE
491 AlarmCallBack (int n)
492 {
493     kill(pid, SIGKILL); // kill forcefully
494     return;
495 }
496
497 void
498 DestroyChildProcess (ProcRef pr, int signalType)
499 {
500     ChildProc *cp = (ChildProc *) pr;
501
502     if (cp->kind != CPReal) return;
503     cp->kind = CPNone;
504     if (signalType & 1) {
505             kill(cp->pid, signalType == 9 ? SIGKILL : SIGTERM); // [HGM] kill: for 9 hard-kill immediately
506     }
507     signal(SIGALRM, AlarmCallBack);
508     pid = cp->pid;
509     if(signalType & 4) alarm(1 + appData.delayAfterQuit); // [HGM] kill: schedule hard kill if so requested
510     /* Process is exiting either because of the kill or because of
511        a quit command sent by the backend; either way, wait for it to die.
512     */
513     wait((int *) 0);
514     alarm(0); // cancel alarm if still pending
515     close(cp->fdFrom);
516     close(cp->fdTo);
517 }
518
519 char *
520 BufferCommandOutput (char *command, int size)
521 {
522     ChildProc *pr;
523     char *res = (char *) calloc(1, size);
524     if(res) {
525         int count;
526         StartChildProcess(command, ".", (ProcRef) &pr);    // run command in daughter process
527         count = read(pr->fdFrom, res, size-1);  // read its output
528         res[count > 0 ? count : 0] = NULLCHAR;  
529         DestroyChildProcess((ProcRef) pr, 9);
530         free(pr);
531     }
532     return res; // return buffer with output
533 }
534
535 void
536 InterruptChildProcess (ProcRef pr)
537 {
538     ChildProc *cp = (ChildProc *) pr;
539
540     if (cp->kind != CPReal) return;
541     (void) kill(cp->pid, SIGINT); /* stop it thinking */
542 }
543
544 int
545 OpenTelnet (char *host, char *port, ProcRef *pr)
546 {
547     char cmdLine[MSG_SIZ];
548
549     if (port[0] == NULLCHAR) {
550       snprintf(cmdLine, sizeof(cmdLine), "%s %s", appData.telnetProgram, host);
551     } else {
552       snprintf(cmdLine, sizeof(cmdLine), "%s %s %s", appData.telnetProgram, host, port);
553     }
554     return StartChildProcess(cmdLine, "", pr);
555 }
556
557 int
558 OpenTCP (char *host, char *port, ProcRef *pr)
559 {
560 #if OMIT_SOCKETS
561     DisplayFatalError(_("Socket support is not configured in"), 0, 2);
562 #else  /* !OMIT_SOCKETS */
563     struct addrinfo hints;
564     struct addrinfo *ais, *ai;
565     int error;
566     int s=0;
567     ChildProc *cp;
568
569     memset(&hints, 0, sizeof(hints));
570     hints.ai_family = AF_UNSPEC;
571     hints.ai_socktype = SOCK_STREAM;
572
573     error = getaddrinfo(host, port, &hints, &ais);
574     if (error != 0) {
575       /* a getaddrinfo error is not an errno, so can't return it */
576       fprintf(debugFP, "getaddrinfo(%s, %s): %s\n",
577               host, port, gai_strerror(error));
578       return ENOENT;
579     }
580
581     for (ai = ais; ai != NULL; ai = ai->ai_next) {
582       if ((s = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol)) < 0) {
583         error = errno;
584         continue;
585       }
586       if (connect(s, ai->ai_addr, ai->ai_addrlen) < 0) {
587         error = errno;
588         continue;
589       }
590       error = 0;
591       break;
592     }
593     freeaddrinfo(ais);
594
595     if (error != 0) {
596       return error;
597     }
598
599     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
600     cp->kind = CPSock;
601     cp->pid = 0;
602     cp->fdFrom = s;
603     cp->fdTo = s;
604     *pr = (ProcRef) cp;
605 #endif /* !OMIT_SOCKETS */
606
607     return 0;
608 }
609
610 int
611 OpenCommPort (char *name, ProcRef *pr)
612 {
613     int fd;
614     ChildProc *cp;
615
616     fd = open(name, 2, 0);
617     if (fd < 0) return errno;
618
619     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
620     cp->kind = CPComm;
621     cp->pid = 0;
622     cp->fdFrom = fd;
623     cp->fdTo = fd;
624     *pr = (ProcRef) cp;
625
626     return 0;
627 }
628
629 int
630 OpenLoopback (ProcRef *pr)
631 {
632     ChildProc *cp;
633     int to[2], from[2];
634
635     SetUpChildIO(to, from);
636
637     cp = (ChildProc *) calloc(1, sizeof(ChildProc));
638     cp->kind = CPLoop;
639     cp->pid = 0;
640     cp->fdFrom = to[0];         /* note not from[0]; we are doing a loopback */
641     cp->fdTo = to[1];
642     *pr = (ProcRef) cp;
643
644     return 0;
645 }
646
647 int
648 OpenRcmd (char *host, char *user, char *cmd, ProcRef *pr)
649 {
650     DisplayFatalError(_("internal rcmd not implemented for Unix"), 0, 1);
651     return -1;
652 }
653
654 Boolean stdoutClosed = FALSE;
655
656 int
657 OutputToProcess (ProcRef pr, char *message, int count, int *outError)
658 {
659     static int line = 0;
660     ChildProc *cp = (ChildProc *) pr;
661     int outCount = count;
662
663     if (pr == NoProc)
664     {
665         if (appData.noJoin || !appData.useInternalWrap) {
666             if(!stdoutClosed) outCount = fwrite(message, 1, count, stdout);
667         } else
668         {
669             int width = get_term_width();
670             int len = wrap(NULL, message, count, width, &line);
671             char *msg = malloc(len);
672             int dbgchk;
673
674             if (!msg)
675                 outCount = fwrite(message, 1, count, stdout);
676             else
677             {
678                 dbgchk = wrap(msg, message, count, width, &line);
679                 if (dbgchk != len && appData.debugMode)
680                     fprintf(debugFP, "wrap(): dbgchk(%d) != len(%d)\n", dbgchk, len);
681                 outCount = fwrite(msg, 1, dbgchk, stdout);
682                 free(msg);
683             }
684         }
685         if(*message != '\033') ConsoleWrite(message, count);
686     }
687     else
688       outCount = write(cp->fdTo, message, count);
689
690     if (outCount == -1)
691       *outError = errno;
692     else
693       *outError = 0;
694
695     return outCount;
696 }
697
698 /* Output message to process, with "ms" milliseconds of delay
699    between each character. This is needed when sending the logon
700    script to ICC, which for some reason doesn't like the
701    instantaneous send. */
702 int
703 OutputToProcessDelayed (ProcRef pr, char *message, int count, int *outError, long msdelay)
704 {
705     ChildProc *cp = (ChildProc *) pr;
706     int outCount = 0;
707     int r;
708
709     while (count--) {
710         r = write(cp->fdTo, message++, 1);
711         if (r == -1) {
712             *outError = errno;
713             return outCount;
714         }
715         ++outCount;
716         if (msdelay >= 0)
717           TimeDelay(msdelay);
718     }
719
720     return outCount;
721 }
722
723 int
724 ICSInitScript ()
725 {
726   /* try to open the icsLogon script, either in the location given
727    * or in the users HOME directory
728    */
729
730   FILE *f;
731   char buf[MSG_SIZ];
732   char *homedir;
733
734   f = fopen(appData.icsLogon, "r");
735   if (f == NULL)
736     {
737       homedir = getenv("HOME");
738       if (homedir != NULL)
739         {
740           safeStrCpy(buf, homedir, sizeof(buf)/sizeof(buf[0]) );
741           strncat(buf, "/", MSG_SIZ - strlen(buf) - 1);
742           strncat(buf, appData.icsLogon,  MSG_SIZ - strlen(buf) - 1);
743           f = fopen(buf, "r");
744         }
745     }
746
747   if (f != NULL) {
748     ProcessICSInitScript(f);
749     return TRUE;
750   } else
751     printf("Warning: Couldn't open icsLogon file (checked %s and %s).\n", appData.icsLogon, buf);
752
753   return FALSE;
754 }
755
756 void
757 ResetFrontEnd ()
758 {
759     CommentPopDown();
760     TagsPopDown();
761     return;
762 }
763
764 #include <sys/ioctl.h>
765 static int
766 get_term_width ()
767 {
768     int fd, default_width;
769
770     fd = STDIN_FILENO;
771     default_width = 79; // this is FICS default anyway...
772
773 #if !defined(TIOCGWINSZ) && defined(TIOCGSIZE)
774     struct ttysize win;
775     if (!ioctl(fd, TIOCGSIZE, &win))
776         default_width = win.ts_cols;
777 #elif defined(TIOCGWINSZ)
778     struct winsize win;
779     if (!ioctl(fd, TIOCGWINSZ, &win))
780         default_width = win.ws_col;
781 #endif
782     return default_width;
783 }
784
785 void
786 update_ics_width ()
787 {
788   static int old_width = 0;
789   int new_width = get_term_width();
790
791   if (old_width != new_width)
792     ics_printf("set width %d\n", new_width);
793   old_width = new_width;
794 }
795
796 void
797 NotifyFrontendLogin ()
798 {
799     update_ics_width();
800 }