Added -check/-nocheck switches to sortm(1). Also removed obsolete
[mmh] / uip / msh.c
1
2 /*
3  * msh.c -- The nmh shell
4  *
5  * This code is Copyright (c) 2002, by the authors of nmh.  See the
6  * COPYRIGHT file in the root directory of the nmh distribution for
7  * complete copyright information.
8  */
9
10 /*
11  * TODO:
12  *    Keep more status information in maildrop map
13  */
14
15 #include <h/mh.h>
16 #include <fcntl.h>
17 #include <h/signals.h>
18 #include <h/dropsbr.h>
19 #include <h/fmt_scan.h>
20 #include <h/scansbr.h>
21 #include <h/tws.h>
22 #include <h/mts.h>
23 #include <h/utils.h>
24
25 #include <termios.h>
26
27 #include <pwd.h>
28 #include <setjmp.h>
29 #include <signal.h>
30 #include <h/msh.h>
31 #include <h/vmhsbr.h>
32
33 #define QUOTE   '\\'            /* sigh */
34
35 static struct swit switches[] = {
36 #define IDSW                  0
37     { "idstart number", -7 },           /* interface from bbc */
38 #define FDSW                  1
39     { "idstop number", -6 },            /*  .. */
40 #define QDSW                  2
41     { "idquit number", -6 },            /*  .. */
42 #define NMSW                  3
43     { "idname BBoard", -6 },            /*  .. */
44 #define PRMPTSW               4
45     { "prompt string", 0 },
46 #define SCANSW                5
47     { "scan", 0 },
48 #define NSCANSW               6
49     { "noscan", 0 },
50 #define READSW                7
51     { "vmhread fd", -7 },
52 #define WRITESW               8
53     { "vmhwrite fd", -8 },      
54 #define PREADSW               9
55     { "popread fd", -7 },
56 #define PWRITSW              10
57     { "popwrite fd", -8 },
58 #define TCURSW               11
59     { "topcur", 0 },
60 #define NTCURSW              12
61     { "notopcur", 0 },
62 #define VERSIONSW            13
63     { "version", 0 },
64 #define HELPSW               14
65     { "help", 0 },
66     { NULL, 0 }
67 };
68
69 static int mbx_style = MMDF_FORMAT;
70
71 /*
72  * FOLDER
73  */
74 char*fmsh = NULL;                       /* folder instead of file              */
75 int modified;                           /* command modified folder             */
76 struct msgs *mp;                        /* used a lot                          */
77 static int nMsgs = 0;
78 struct Msg *Msgs = NULL;                /* Msgs[0] not used                    */
79 static FILE *fp;                        /* input file                          */
80 static FILE *yp = NULL;                 /* temporary file                      */
81 static int mode;                        /* mode of file                        */
82 static int numfds = 0;                  /* number of files cached              */
83 static int maxfds = 0;                  /* number of files cached to be cached */
84 static time_t mtime = (time_t) 0;       /* mtime of file                       */
85
86 /*
87  * VMH
88  */
89 #define ALARM   ((unsigned int) 10)
90 #define ttyN(c) ttyNaux ((c), NULL)
91
92 static int vmh = 0;
93
94 static int vmhpid = OK;
95 static int vmhfd0;
96 static int vmhfd1;
97 static int vmhfd2;
98
99 static int vmhtty = NOTOK;
100
101 #define SCAN    1
102 #define STATUS  2
103 #define DISPLAY 3
104 #define NWIN    DISPLAY
105
106 static int topcur = 0;
107
108 static int numwins = 0;
109 static int windows[NWIN + 1];
110
111 static jmp_buf peerenv;
112
113 /*
114  * PARENT
115  */
116 static int pfd = NOTOK;         /* fd parent is reading from */
117 static int ppid = 0;            /* pid of parent             */
118
119 /*
120  * COMMAND
121  */
122 int interactive;                /* running from a /dev/tty */
123 int redirected;                 /* re-directing output     */
124 FILE *sp = NULL;                /* original stdout         */
125
126 char *cmd_name;                 /* command being run   */
127 char myfilter[BUFSIZ];          /* path to mhl.forward */
128
129 static char *myprompt = "(%s) ";/* prompting string */
130
131 /*
132  * BBOARDS
133  */
134 static int gap;                 /* gap in BBoard-ID:s */
135 static char *myname = NULL;     /* BBoard name        */
136 char *BBoard_ID = "BBoard-ID";  /* BBoard-ID constant */
137
138 /*
139  * SIGNALS
140  */
141 SIGNAL_HANDLER istat;           /* original SIGINT  */
142 static SIGNAL_HANDLER pstat;    /* current SIGPIPE  */
143 SIGNAL_HANDLER qstat;           /* original SIGQUIT */
144
145 #ifdef SIGTSTP
146 SIGNAL_HANDLER tstat;           /* original SIGTSTP */
147 #endif
148
149 int interrupted;                /* SIGINT detected  */
150 int broken_pipe;                /* SIGPIPE detected */
151 int told_to_quit;               /* SIGQUIT detected */
152
153 /*
154  * prototypes
155  */
156 void fsetup (char *);
157 void setup (char *);
158 void readids (int);
159 int readid (int);
160 void display_info (int);
161 int expand (char *);
162 void m_reset (void);
163 void seq_setcur (struct msgs *, int);
164 void padios (char *, char *, ...);
165 void padvise (char *, char *, ...);
166
167
168 /*
169  * static prototypes
170  */
171 static void msh (int);
172 static int read_map (char *, long);
173 static int read_file (long, int);
174
175 static void m_gMsgs (int);
176 static int check_folder (int);
177 static void scanrange (int, int);
178 static void scanstring (char *);
179 static void write_ids (void);
180 static void quit (void);
181 static int getargs (char *, struct swit *, struct Cmd *);
182 static int getcmds (struct swit *, struct Cmd *, int);
183 static int parse (char *, struct Cmd *);
184 static int init_io (struct Cmd *, int);
185 static int initaux_io (struct Cmd *);
186 static void fin_io (struct Cmd *, int);
187 static void finaux_io (struct Cmd *);
188 static void m_init (void);
189 static void intrser (int);
190 static void pipeser (int);
191 static void quitser (int);
192 static void alrmser (int);
193 static int pINI (void);
194 static int pQRY (char *, int);
195 static int pQRY1 (int);
196 static int pQRY2 (void);
197 static int pCMD (char *, struct swit *, struct Cmd *);
198 static int pFIN (void);
199 static int peerwait (void);
200 static int ttyNaux (struct Cmd *, char *);
201 static int ttyR (struct Cmd *);
202 static int winN (struct Cmd *, int, int);
203 static int winR (struct Cmd *);
204 static int winX (int);
205
206
207 int
208 main (int argc, char **argv)
209 {
210     int id = 0, scansw = 0, vmh1 = 0, vmh2 = 0;
211     char *cp, *file = NULL, *folder = NULL;
212     char **argp, **arguments, buf[BUFSIZ];
213
214 #ifdef LOCALE
215     setlocale(LC_ALL, "");
216 #endif
217     invo_name = r1bindex (argv[0], '/');
218
219     /* read user profile/context */
220     context_read();
221
222     mts_init (invo_name);
223     arguments = getarguments (invo_name, argc,argv, 1);
224     argp = arguments;
225
226     while ((cp = *argp++)) {
227         if (*cp == '-')
228             switch (smatch (++cp, switches)) {
229                 case AMBIGSW: 
230                     ambigsw (cp, switches);
231                     done (1);
232                 case UNKWNSW: 
233                     adios (NULL, "-%s unknown", cp);
234
235                 case HELPSW: 
236                     snprintf (buf, sizeof(buf), "%s [switches] file", invo_name);
237                     print_help (buf, switches, 1);
238                     done (0);
239                 case VERSIONSW:
240                     print_version(invo_name);
241                     done (0);
242
243                 case IDSW: 
244                     if (!(cp = *argp++) || *cp == '-')
245                         adios (NULL, "missing argument to %s", argp[-2]);
246                     if ((id = atoi (cp)) < 1)
247                         adios (NULL, "bad argument %s %s", argp[-2], cp);
248                     continue;
249                 case FDSW: 
250                     if (!(cp = *argp++) || *cp == '-')
251                         adios (NULL, "missing argument to %s", argp[-2]);
252                     if ((pfd = atoi (cp)) <= 1)
253                         adios (NULL, "bad argument %s %s", argp[-2], cp);
254                     continue;
255                 case QDSW: 
256                     if (!(cp = *argp++) || *cp == '-')
257                         adios (NULL, "missing argument to %s", argp[-2]);
258                     if ((ppid = atoi (cp)) <= 1)
259                         adios (NULL, "bad argument %s %s", argp[-2], cp);
260                     continue;
261                 case NMSW:
262                     if (!(myname = *argp++) || *myname == '-')
263                         adios (NULL, "missing argument to %s", argp[-2]);
264                     continue;
265
266                 case SCANSW: 
267                     scansw++;
268                     continue;
269                 case NSCANSW: 
270                     scansw = 0;
271                     continue;
272
273                 case PRMPTSW:
274                     if (!(myprompt = *argp++) || *myprompt == '-')
275                         adios (NULL, "missing argument to %s", argp[-2]);
276                     continue;
277
278                 case READSW: 
279                     if (!(cp = *argp++) || *cp == '-')
280                         adios (NULL, "missing argument to %s", argp[-2]);
281                     if ((vmh1 = atoi (cp)) < 1)
282                         adios (NULL, "bad argument %s %s", argp[-2], cp);
283                     continue;
284                 case WRITESW: 
285                     if (!(cp = *argp++) || *cp == '-')
286                         adios (NULL, "missing argument to %s", argp[-2]);
287                     if ((vmh2 = atoi (cp)) < 1)
288                         adios (NULL, "bad argument %s %s", argp[-2], cp);
289                     continue;
290
291                 case PREADSW: 
292                     if (!(cp = *argp++) || *cp == '-')
293                         adios (NULL, "missing argument to %s", argp[-2]);
294                     continue;
295                 case PWRITSW: 
296                     if (!(cp = *argp++) || *cp == '-')
297                         adios (NULL, "missing argument to %s", argp[-2]);
298                     continue;
299
300                 case TCURSW:
301                     topcur++;
302                     continue;
303                 case NTCURSW:
304                     topcur = 0;
305                     continue;
306             }
307         if (*cp == '+' || *cp == '@') {
308             if (folder)
309                 adios (NULL, "only one folder at a time!");
310             else
311                 folder = pluspath (cp);
312         }
313         else
314             if (file)
315                 adios (NULL, "only one file at a time!");
316             else
317                 file = cp;
318     }
319
320     if (!file && !folder)
321         file = "./msgbox";
322     if (file && folder)
323         adios (NULL, "use a file or a folder, not both");
324     strncpy (myfilter, etcpath (mhlforward), sizeof(myfilter));
325 #ifdef FIOCLEX
326     if (pfd > 1)
327         ioctl (pfd, FIOCLEX, NULL);
328 #endif /* FIOCLEX */
329
330     istat = SIGNAL2 (SIGINT, intrser);
331     qstat = SIGNAL2 (SIGQUIT, quitser);
332
333     sc_width ();                /* MAGIC... */
334
335     if ((vmh = vmh1 && vmh2)) {
336         rcinit (vmh1, vmh2);
337         pINI ();
338         SIGNAL (SIGINT, SIG_IGN);
339         SIGNAL (SIGQUIT, SIG_IGN);
340 #ifdef SIGTSTP
341         tstat = SIGNAL (SIGTSTP, SIG_IGN);
342 #endif /* SIGTSTP */
343     }
344
345     if (folder)
346         fsetup (folder);
347     else
348         setup (file);
349     readids (id);
350     display_info (id > 0 ? scansw : 0);
351
352     msh (id > 0 ? scansw : 0);
353
354     m_reset ();
355     
356     done (0);
357     return 1;
358 }
359
360
361 static struct swit mshcmds[] = {
362 #define ADVCMD  0
363     { "advance", -7 },
364 #define ALICMD  1
365     { "ali", 0 },
366 #define EXPLCMD 2
367     { "burst", 0 },
368 #define COMPCMD 3
369     { "comp", 0 },
370 #define DISTCMD 4
371     { "dist", 0 },
372 #define EXITCMD 5
373     { "exit", 0 },
374 #define FOLDCMD 6
375     { "folder", 0 },
376 #define FORWCMD 7
377     { "forw", 0 },
378 #define HELPCMD 8
379     { "help", 0 },
380 #define INCMD   9
381     { "inc", 0 },
382 #define MARKCMD 10
383     { "mark", 0 },
384 #define MAILCMD 11
385     { "mhmail", 0 },
386 #define MHNCMD  12
387     { "mhn", 0 },
388 #define MSGKCMD 13
389     { "msgchk", 0 },
390 #define NEXTCMD 14
391     { "next", 0 },
392 #define PACKCMD 15
393     { "packf", 0 },
394 #define PICKCMD 16
395     { "pick", 0 },
396 #define PREVCMD 17
397     { "prev", 0 },
398 #define QUITCMD 18
399     { "quit", 0 },
400 #define FILECMD 19
401     { "refile", 0 },
402 #define REPLCMD 20
403     { "repl", 0 },
404 #define RMMCMD  21
405     { "rmm", 0 },
406 #define SCANCMD 22
407     { "scan", 0 },
408 #define SENDCMD 23
409     { "send", 0 },
410 #define SHOWCMD 24
411     { "show", 0 },
412 #define SORTCMD 25
413     { "sortm", 0 },
414 #define WHATCMD 26
415     { "whatnow", 0 },
416 #define WHOMCMD 27
417     { "whom", 0 },
418     { NULL, 0 }
419 };
420
421
422 static void
423 msh (int scansw)
424 {
425     int i;
426     register char *cp, **ap;
427     char prompt[BUFSIZ], *vec[MAXARGS];
428     struct Cmd typein;
429     register struct Cmd *cmdp;
430     static int once_only = ADVCMD;
431
432     snprintf (prompt, sizeof(prompt), myprompt, invo_name);
433     cmdp = &typein;
434
435     for (;;) {
436         if (yp) {
437             fclose (yp);
438             yp = NULL;
439         }
440         if (vmh) {
441             if ((i = getcmds (mshcmds, cmdp, scansw)) == EOF) {
442                 rcdone ();
443                 return;
444             }
445         } else {
446             check_folder (scansw);
447             if ((i = getargs (prompt, mshcmds, cmdp)) == EOF) {
448                 putchar ('\n');
449                 return;
450             }
451         }
452         cmd_name = mshcmds[i].sw;
453
454         switch (i) {
455             case QUITCMD: 
456                 quit ();
457                 return;
458
459             case ADVCMD:
460                 if (once_only == ADVCMD)
461                     once_only = i = SHOWCMD;
462                 else
463                     i = mp->curmsg != mp->hghmsg ? NEXTCMD : EXITCMD;
464                 cmd_name = mshcmds[i].sw;
465                 /* and fall... */
466
467             case EXITCMD:
468             case EXPLCMD: 
469             case FOLDCMD: 
470             case FORWCMD:       /* sigh */
471             case MARKCMD: 
472             case NEXTCMD: 
473             case PACKCMD: 
474             case PICKCMD: 
475             case PREVCMD: 
476             case RMMCMD: 
477             case SHOWCMD: 
478             case SCANCMD: 
479             case SORTCMD: 
480                 if ((cp = context_find (cmd_name))) {
481                     cp = getcpy (cp);
482                     ap = brkstring (cp, " ", "\n");
483                     ap = copyip (ap, vec, MAXARGS);
484                 } else {
485                     ap = vec;
486                 }
487                 break;
488
489             default: 
490                 cp = NULL;
491                 ap = vec;
492                 break;
493         }
494         copyip (cmdp->args + 1, ap, MAXARGS);
495
496         m_init ();
497
498         if (!vmh && init_io (cmdp, vmh) == NOTOK) {
499             if (cp != NULL)
500                 free (cp);
501             continue;
502         }
503         modified = 0;
504         redirected = vmh || cmdp->direction != STDIO;
505
506         switch (i) {
507             case ALICMD: 
508             case COMPCMD: 
509             case INCMD: 
510             case MAILCMD: 
511             case MSGKCMD: 
512             case SENDCMD: 
513             case WHATCMD: 
514             case WHOMCMD: 
515                 if (!vmh || ttyN (cmdp) != NOTOK)
516                     forkcmd (vec, cmd_name);
517                 break;
518
519             case DISTCMD: 
520                 if (!vmh || ttyN (cmdp) != NOTOK)
521                     distcmd (vec);
522                 break;
523
524             case EXPLCMD: 
525                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
526                     explcmd (vec);
527                 break;
528
529             case FILECMD: 
530                 if (!vmh 
531                         || (filehak (vec) == OK ? ttyN (cmdp)
532                                         : winN (cmdp, DISPLAY, 1)) != NOTOK)
533                     filecmd (vec);
534                 break;
535
536             case FOLDCMD: 
537                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
538                     foldcmd (vec);
539                 break;
540
541             case FORWCMD: 
542                 if (!vmh || ttyN (cmdp) != NOTOK)
543                     forwcmd (vec);
544                 break;
545
546             case HELPCMD: 
547                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
548                     helpcmd (vec);
549                 break;
550
551             case EXITCMD:
552             case MARKCMD: 
553                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
554                     markcmd (vec);
555                 break;
556
557             case MHNCMD:
558                 if (!vmh || ttyN (cmdp) != NOTOK)
559                     mhncmd (vec);
560                 break;
561
562             case NEXTCMD: 
563             case PREVCMD: 
564             case SHOWCMD: 
565                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
566                     showcmd (vec);
567                 break;
568
569             case PACKCMD: 
570                 if (!vmh 
571                         || (packhak (vec) == OK ? ttyN (cmdp)
572                                         : winN (cmdp, DISPLAY, 1)) != NOTOK)
573                     packcmd (vec);
574                 break;
575
576             case PICKCMD: 
577                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
578                     pickcmd (vec);
579                 break;
580
581             case REPLCMD: 
582                 if (!vmh || ttyN (cmdp) != NOTOK)
583                     replcmd (vec);
584                 break;
585
586             case RMMCMD: 
587                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
588                     rmmcmd (vec);
589                 break;
590
591             case SCANCMD: 
592                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
593                     scancmd (vec);
594                 break;
595
596             case SORTCMD: 
597                 if (!vmh || winN (cmdp, DISPLAY, 1) != NOTOK)
598                     sortcmd (vec);
599                 break;
600
601             default: 
602                 padios (NULL, "no dispatch for %s", cmd_name);
603         }
604
605         if (vmh) {
606             if (vmhtty != NOTOK)
607                 ttyR (cmdp);
608             if (vmhpid > OK)
609                 winR (cmdp);
610         }
611         else
612             fin_io (cmdp, vmh);
613         if (cp != NULL)
614             free (cp);
615         if (i == EXITCMD) {
616             quit ();
617             return;
618         }
619     }
620 }
621
622
623 void
624 fsetup (char *folder)
625 {
626     register int msgnum;
627     char *maildir;
628     struct stat st;
629
630     maildir = m_maildir (folder);
631     if (chdir (maildir) == NOTOK)
632         padios (maildir, "unable to change directory to");
633
634     /* read folder and create message structure */
635     if (!(mp = folder_read (folder)))
636         padios (NULL, "unable to read folder %s", folder);
637
638     /* check for empty folder */
639     if (mp->nummsg == 0)
640         padios (NULL, "no messages in %s", folder);
641
642     mode = m_gmprot ();
643     mtime = stat (mp->foldpath, &st) != NOTOK ? st.st_mtime : 0;
644
645     m_gMsgs (mp->hghmsg);
646
647     for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) {
648         Msgs[msgnum].m_bboard_id = 0;
649         Msgs[msgnum].m_top = NOTOK;
650         Msgs[msgnum].m_start = Msgs[msgnum].m_stop = 0L;
651         Msgs[msgnum].m_scanl = NULL;
652     }
653
654     m_init ();
655
656     fmsh = getcpy (folder);
657
658     maxfds = OPEN_MAX / 2;
659
660     if ((maxfds -= 2) < 1)
661         maxfds = 1;
662 }
663
664
665 void
666 setup (char *file)
667 {
668     int i, msgp;
669     struct stat st;
670     if ((fp = fopen (file, "r")) == NULL)
671         padios (file, "unable to read");
672 #ifdef FIOCLEX
673     ioctl (fileno (fp), FIOCLEX, NULL);
674 #endif /* FIOCLEX */
675     if (fstat (fileno (fp), &st) != NOTOK) {
676         mode = (int) (st.st_mode & 0777), mtime = st.st_mtime;
677         msgp = read_map (file, (long) st.st_size);
678     }
679     else {
680         mode = m_gmprot (), mtime = 0;
681         msgp = 0;
682     }
683
684     if ((msgp = read_file (msgp ? Msgs[msgp].m_stop : 0L, msgp + 1)) < 1)
685         padios (NULL, "no messages in %s", myname ? myname : file);
686
687     if (!(mp = (struct msgs  *) calloc ((size_t) 1, sizeof(*mp))))
688         padios (NULL, "unable to allocate folder storage");
689
690     if (!(mp->msgstats = calloc ((size_t) msgp + 3, sizeof(*(mp->msgstats)))))
691         padios (NULL, "unable to allocate message status storage");
692
693     mp->hghmsg = msgp;
694     mp->nummsg = msgp;
695     mp->lowmsg = 1;
696     mp->curmsg = 0;
697     mp->foldpath = getcpy (myname ? myname : file);
698     clear_folder_flags (mp);
699
700         stat (file, &st);
701         if (st.st_uid != getuid () || access (file, W_OK) == NOTOK)
702             set_readonly (mp);
703
704     mp->lowoff = 1;
705     mp->hghoff = mp->hghmsg + 1;
706
707     for (i = mp->lowmsg; i <= mp->hghmsg; i++) {
708         clear_msg_flags (mp, i);
709         set_exists (mp, i);
710     }
711     m_init ();
712
713     mp->msgattrs[0] = getcpy ("unseen");
714     mp->msgattrs[1] = NULL;
715
716     m_unknown (fp);             /* the MAGIC invocation */    
717     if (fmsh) {
718         free (fmsh);
719         fmsh = NULL;
720     }
721 }
722
723
724 static int
725 read_map (char *file, long size)
726 {
727     register int i, msgp;
728     register struct drop *dp, *mp;
729     struct drop *rp;
730
731     if ((i = map_read (file, size, &rp, 1)) == 0)
732         return 0;
733
734     m_gMsgs (i);
735
736     msgp = 1;
737     for (dp = rp + 1; i-- > 0; msgp++, dp++) {
738         mp = &Msgs[msgp].m_drop;
739         mp->d_id = dp->d_id;
740         mp->d_size = dp->d_size;
741         mp->d_start = dp->d_start;
742         mp->d_stop = dp->d_stop;
743         Msgs[msgp].m_scanl = NULL;
744     }
745     free ((char *) rp);
746
747     return (msgp - 1);
748 }
749
750
751 static int
752 read_file (long pos, int msgp)
753 {
754     register int i;
755     register struct drop *dp, *mp;
756     struct drop *rp;
757
758     if ((i = mbx_read (fp, pos, &rp, 1)) <= 0)
759         return (msgp - 1);
760
761     m_gMsgs ((msgp - 1) + i);
762
763     for (dp = rp; i-- > 0; msgp++, dp++) {
764         mp = &Msgs[msgp].m_drop;
765         mp->d_id = 0;
766         mp->d_size = dp->d_size;
767         mp->d_start = dp->d_start;
768         mp->d_stop = dp->d_stop;
769         Msgs[msgp].m_scanl = NULL;
770     }
771     free ((char *) rp);
772
773     return (msgp - 1);
774 }
775
776
777 static void
778 m_gMsgs (int n)
779 {
780     int nmsgs;
781
782     if (Msgs == NULL) {
783         nMsgs = n + MAXFOLDER / 2;
784         Msgs = (struct Msg *) calloc ((size_t) (nMsgs + 2), sizeof *Msgs);
785         if (Msgs == NULL)
786             padios (NULL, "unable to allocate Msgs structure");
787         return;
788     }
789
790     if (nMsgs >= n)
791         return;
792
793     nmsgs = nMsgs + n + MAXFOLDER / 2;
794     Msgs = (struct Msg *) mh_xrealloc ((char *) Msgs, (size_t) (nmsgs + 2) * sizeof *Msgs);
795     memset((char *) (Msgs + nMsgs + 2), 0, (size_t) ((nmsgs - nMsgs) * sizeof *Msgs));
796
797     nMsgs = nmsgs;
798 }
799
800
801 FILE *
802 msh_ready (int msgnum, int full)
803 {
804     register int msgp;
805     int fd;
806     char *cp;
807     NMH_UNUSED (full);
808
809     if (yp) {
810         fclose (yp);
811         yp = NULL;
812     }
813
814     if (fmsh) {
815         if ((fd = Msgs[msgnum].m_top) == NOTOK) {
816             if (numfds >= maxfds)
817                 for (msgp = mp->lowmsg; msgp <= mp->hghmsg; msgp++)
818                     if (Msgs[msgp].m_top != NOTOK) {
819                         close (Msgs[msgp].m_top);
820                         Msgs[msgp].m_top = NOTOK;
821                         numfds--;
822                         break;
823                     }
824
825             if ((fd = open (cp = m_name (msgnum), O_RDONLY)) == NOTOK)
826                 padios (cp, "unable to open message");
827             Msgs[msgnum].m_top = fd;
828             numfds++;
829         }
830
831         if ((fd = dup (fd)) == NOTOK)
832             padios ("cached message", "unable to dup");
833         if ((yp = fdopen (fd, "r")) == NULL)
834             padios (NULL, "unable to fdopen cached message");
835         fseek (yp, 0L, SEEK_SET);
836         return yp;
837     }
838
839     m_eomsbr ((int (*)()) 0);   /* XXX */
840     fseek (fp, Msgs[msgnum].m_start, SEEK_SET);
841     return fp;
842 }
843
844
845 static int
846 check_folder (int scansw)
847 {
848     int seqnum, i, low, hgh, msgp;
849     struct stat st;
850
851     if (fmsh) {
852         if (stat (mp->foldpath, &st) == NOTOK)
853             padios (mp->foldpath, "unable to stat");
854         if (mtime == st.st_mtime)
855             return 0;
856         mtime = st.st_mtime;
857
858         low = mp->hghmsg + 1;
859         folder_free (mp);               /* free folder/message structure */
860
861         if (!(mp = folder_read (fmsh)))
862             padios (NULL, "unable to re-read folder %s", fmsh);
863
864         hgh = mp->hghmsg;
865
866         for (msgp = mp->lowmsg; msgp <= mp->hghmsg; msgp++) {
867             if (Msgs[msgp].m_top != NOTOK) {
868                 close (Msgs[msgp].m_top);
869                 Msgs[msgp].m_top = NOTOK;
870                 numfds--;
871             }
872             if (Msgs[msgp].m_scanl) {
873                 free (Msgs[msgp].m_scanl);
874                 Msgs[msgp].m_scanl = NULL;
875             }
876         }
877
878         m_init ();
879
880         if (modified || low > hgh)
881             return 1;
882         goto check_vmh;
883     }
884     if (fstat (fileno (fp), &st) == NOTOK)
885         padios (mp->foldpath, "unable to fstat");
886     if (mtime == st.st_mtime)
887         return 0;
888     mode = (int) (st.st_mode & 0777);
889     mtime = st.st_mtime;
890
891     if ((msgp = read_file (Msgs[mp->hghmsg].m_stop, mp->hghmsg + 1)) < 1)
892         padios (NULL, "no messages in %s", mp->foldpath);       /* XXX */
893     if (msgp >= MAXFOLDER)
894         padios (NULL, "more than %d messages in %s", MAXFOLDER,
895                 mp->foldpath);
896     if (msgp <= mp->hghmsg)
897         return 0;               /* XXX */
898
899     if (!(mp = folder_realloc (mp, mp->lowoff, msgp)))
900         padios (NULL, "unable to allocate folder storage");
901
902     low = mp->hghmsg + 1, hgh = msgp;
903     seqnum = scansw ? seq_getnum (mp, "unseen") : -1;
904     for (i = mp->hghmsg + 1; i <= msgp; i++) {
905         set_exists(mp, i);
906         if (seqnum != -1)
907             add_sequence(mp, seqnum, i);
908         mp->nummsg++;
909     }
910     mp->hghmsg = msgp;
911     m_init ();
912
913 check_vmh: ;
914     if (vmh)
915         return 1;
916
917     advise (NULL, "new messages have arrived!\007");
918     if (scansw)
919         scanrange (low, hgh);
920
921     return 1;
922 }
923
924
925 static void
926 scanrange (int low, int hgh)
927 {
928     char buffer[BUFSIZ];
929
930     snprintf (buffer, sizeof(buffer), "%d-%d", low, hgh);
931     scanstring (buffer);
932 }
933
934
935 static void
936 scanstring (char *arg)
937 {
938     char *cp, **ap, *vec[MAXARGS];
939
940     /*
941      * This should be replace with a call to getarguments()
942      */
943     if ((cp = context_find (cmd_name = "scan"))) {
944         cp = getcpy (cp);
945         ap = brkstring (cp, " ", "\n");
946         ap = copyip (ap, vec, MAXARGS);
947     } else {
948         ap = vec;
949     }
950     *ap++ = arg;
951     *ap = NULL;
952     m_init ();
953     scancmd (vec);
954     if (cp != NULL)
955         free (cp);
956 }
957
958
959 void
960 readids (int id)
961 {
962     register int cur, seqnum, i=0, msgnum;
963
964     if (mp->curmsg == 0)
965         seq_setcur (mp, mp->lowmsg);
966     if (id <= 0 || (seqnum = seq_getnum (mp, "unseen")) == -1)
967         return;
968
969     for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
970         add_sequence(mp, seqnum, msgnum);
971
972     if (id != 1) {
973         cur = mp->curmsg;
974
975         for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
976           if (does_exist(mp, msgnum))           /* FIX */
977             if ((i = readid (msgnum)) > 0 && i < id) {
978                 cur = msgnum + 1;
979                 clear_sequence(mp, seqnum, msgnum);
980                 break;
981             }
982         for (i = mp->lowmsg; i < msgnum; i++)
983             clear_sequence(mp, seqnum, i);
984
985         if (cur > mp->hghmsg)
986             cur = mp->hghmsg;
987
988         seq_setcur (mp, cur);
989     }
990
991     if ((gap = 1 < id && id < (i = readid (mp->lowmsg)) ? id : 0) && !vmh)
992         advise (NULL, "gap in ID:s, last seen %d, lowest present %d\n",
993                 id - 1, i);
994 }
995
996
997 int
998 readid (int msgnum)
999 {
1000     int i, state;
1001     char *bp, buf[BUFSIZ], name[NAMESZ];
1002     register FILE *zp;
1003
1004     if (Msgs[msgnum].m_bboard_id)
1005         return Msgs[msgnum].m_bboard_id;
1006
1007     zp = msh_ready (msgnum, 0);
1008     for (state = FLD;;)
1009         switch (state = m_getfld (state, name, buf, sizeof(buf), zp)) {
1010             case FLD: 
1011             case FLDEOF: 
1012             case FLDPLUS: 
1013                 if (!mh_strcasecmp (name, BBoard_ID)) {
1014                     bp = getcpy (buf);
1015                     while (state == FLDPLUS) {
1016                         state = m_getfld (state, name, buf, sizeof(buf), zp);
1017                         bp = add (buf, bp);
1018                     }
1019                     i = atoi (bp);
1020                     free (bp);
1021                     if (i > 0)
1022                         return (Msgs[msgnum].m_bboard_id = i);
1023                     else
1024                         continue;
1025                 }
1026                 while (state == FLDPLUS)
1027                     state = m_getfld (state, name, buf, sizeof(buf), zp);
1028                 if (state != FLDEOF)
1029                     continue;
1030
1031             default: 
1032                 return 0;
1033         }
1034 }
1035
1036
1037 void
1038 display_info (int scansw)
1039 {
1040     int seqnum, sd;
1041
1042     interactive = isatty (fileno (stdout));
1043     if (sp == NULL) {
1044         if ((sd = dup (fileno (stdout))) == NOTOK)
1045             padios ("standard output", "unable to dup");
1046 #ifdef FIOCLEX
1047         ioctl (sd, FIOCLEX, NULL);
1048 #endif /* FIOCLEX */
1049         if ((sp = fdopen (sd, "w")) == NULL)
1050             padios ("standard output", "unable to fdopen");
1051     }
1052
1053     m_putenv ("mhfolder", mp->foldpath);
1054     if (vmh)
1055         return;
1056
1057     if (myname) {
1058         printf ("Reading ");
1059         if (SOprintf ("%s", myname))
1060             printf ("%s", myname);
1061         printf (", currently at message %d of %d\n",
1062                 mp->curmsg, mp->hghmsg);
1063     }
1064     else {
1065         printf ("Reading ");
1066         if (fmsh)
1067             printf ("+%s", fmsh);
1068         else
1069             printf ("%s", mp->foldpath);
1070         printf (", currently at message %d of %d\n",
1071                 mp->curmsg, mp->hghmsg);
1072     }
1073
1074     if (((seqnum = seq_getnum (mp, "unseen")) != -1)
1075             && scansw
1076             && in_sequence(mp, seqnum, mp->hghmsg))
1077         scanstring ("unseen");
1078 }
1079
1080
1081 static void
1082 write_ids (void)
1083 {
1084     int i = 0, seqnum, msgnum;
1085     char buffer[80];
1086
1087     if (pfd <= 1)
1088         return;
1089
1090     if ((seqnum = seq_getnum (mp, "unseen")) != -1)
1091         for (msgnum = mp->hghmsg; msgnum >= mp->lowmsg; msgnum--)
1092             if (!in_sequence(mp, seqnum, msgnum)) {
1093                 if (Msgs[msgnum].m_bboard_id == 0)
1094                     readid (msgnum);
1095                 if ((i = Msgs[msgnum].m_bboard_id) > 0)
1096                     break;
1097             }
1098
1099     snprintf (buffer, sizeof(buffer), "%d %d\n", i, Msgs[mp->hghmsg].m_bboard_id);
1100     write (pfd, buffer, sizeof(buffer));
1101     close (pfd);
1102     pfd = NOTOK;
1103 }
1104
1105
1106 static void
1107 quit (void)
1108 {
1109     int i, md, msgnum;
1110     char *cp, tmpfil[BUFSIZ];
1111     char map1[BUFSIZ], map2[BUFSIZ];
1112     struct stat st;
1113     FILE *dp;
1114
1115     if (!(mp->msgflags & MODIFIED) || is_readonly(mp) || fmsh) {
1116             if (vmh)
1117                 rc2peer (RC_FIN, 0, NULL);
1118         return;
1119     }
1120
1121     if (vmh) 
1122         ttyNaux (NULLCMD, "FAST");
1123     cp = NULL;
1124     if ((dp = lkfopen (mp->foldpath, "r")) == NULL) {
1125         advise (mp->foldpath, "unable to lock");
1126         if (vmh) {
1127             ttyR (NULLCMD);
1128             pFIN ();
1129         }       
1130         return;
1131     }
1132     if (fstat (fileno (dp), &st) == NOTOK) {
1133         advise (mp->foldpath, "unable to stat");
1134         goto release;
1135     }
1136     if (mtime != st.st_mtime) {
1137         advise (NULL, "new messages have arrived, no update");
1138         goto release;
1139     }
1140     mode = (int) (st.st_mode & 0777);
1141
1142     if (mp->nummsg == 0) {
1143         cp = concat ("Zero file \"", mp->foldpath, "\"? ", NULL);
1144         if (getanswer (cp)) {
1145             if ((i = creat (mp->foldpath, mode)) != NOTOK)
1146                 close (i);
1147             else
1148                 advise (mp->foldpath, "error zero'ing");
1149             unlink (map_name (mp->foldpath));/* XXX */
1150         }
1151         goto release;
1152     }
1153
1154     cp = concat ("Update file \"", mp->foldpath, "\"? ", NULL);
1155     if (!getanswer (cp))
1156         goto release;
1157     strncpy (tmpfil, m_backup (mp->foldpath), sizeof(tmpfil));
1158     if ((md = mbx_open (tmpfil, mbx_style, st.st_uid, st.st_gid, mode)) == NOTOK) {
1159         advise (tmpfil, "unable to open");
1160         goto release;
1161     }
1162
1163     for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++)
1164         if (does_exist(mp, msgnum) && pack (tmpfil, md, msgnum) == NOTOK) {
1165             mbx_close (tmpfil, md);
1166             unlink (tmpfil);
1167             unlink (map_name (tmpfil));
1168             goto release;
1169         }
1170     mbx_close (tmpfil, md);
1171
1172     if (rename (tmpfil, mp->foldpath) == NOTOK)
1173         admonish (mp->foldpath, "unable to rename %s to", tmpfil);
1174     else {
1175         strncpy (map1, map_name (tmpfil), sizeof(map1));
1176         strncpy (map2, map_name (mp->foldpath), sizeof(map2));
1177
1178         if (rename (map1, map2) == NOTOK) {
1179             admonish (map2, "unable to rename %s to", map1);
1180             unlink (map1);
1181             unlink (map2);
1182         }
1183     }
1184
1185 release: ;
1186     if (cp)
1187         free (cp);
1188     lkfclose (dp, mp->foldpath);
1189     if (vmh) {
1190         ttyR (NULLCMD);
1191         pFIN ();
1192     }
1193 }
1194
1195
1196 static int
1197 getargs (char *prompt, struct swit *sw, struct Cmd *cmdp)
1198 {
1199     int i;
1200     char *cp;
1201     static char buffer[BUFSIZ];
1202
1203     told_to_quit = 0;
1204     for (;;) {
1205         interrupted = 0;
1206         if (interactive) {
1207             printf ("%s", prompt);
1208             fflush (stdout);
1209         }
1210         for (cp = buffer; (i = getchar ()) != '\n';) {
1211             if (interrupted && !told_to_quit) {
1212                 buffer[0] = '\0';
1213                 putchar ('\n');
1214                 break;
1215             }
1216             if (told_to_quit || i == EOF) {
1217                 if (ppid > 0)
1218 #ifdef SIGEMT
1219                     kill (ppid, SIGEMT);
1220 #else
1221                     kill (ppid, SIGTERM);
1222 #endif
1223                 return EOF;
1224             }
1225             if (cp < &buffer[sizeof buffer - 2])
1226                 *cp++ = i;
1227         }
1228         *cp = 0;
1229
1230         if (buffer[0] == 0)
1231             continue;
1232         if (buffer[0] == '?') {
1233             printf ("commands:\n");
1234             print_sw (ALL, sw, "", stdout);
1235             printf ("type CTRL-D or use ``quit'' to leave %s\n",
1236                     invo_name);
1237             continue;
1238         }
1239
1240         if (parse (buffer, cmdp) == NOTOK)
1241             continue;
1242
1243         switch (i = smatch (cmdp->args[0], sw)) {
1244             case AMBIGSW: 
1245                 ambigsw (cmdp->args[0], sw);
1246                 continue;
1247             case UNKWNSW: 
1248                 printf ("say what: ``%s'' -- type ? (or help) for help\n",
1249                         cmdp->args[0]);
1250                 continue;
1251             default: 
1252                 return i;
1253         }
1254     }
1255 }
1256
1257
1258 static int
1259 getcmds (struct swit *sw, struct Cmd *cmdp, int scansw)
1260 {
1261     int i;
1262     struct record rcs, *rc;
1263
1264     rc = &rcs;
1265     initrc (rc);
1266
1267     for (;;)
1268         switch (peer2rc (rc)) {
1269             case RC_QRY: 
1270                 pQRY (rc->rc_data, scansw);
1271                 break;
1272
1273             case RC_CMD: 
1274                 if ((i = pCMD (rc->rc_data, sw, cmdp)) != NOTOK)
1275                     return i;
1276                 break;
1277
1278             case RC_FIN: 
1279                 if (ppid > 0)
1280 #ifdef SIGEMT
1281                     kill (ppid, SIGEMT);
1282 #else
1283                     kill (ppid, SIGTERM);
1284 #endif
1285                 return EOF;
1286
1287             case RC_XXX: 
1288                 padios (NULL, "%s", rc->rc_data);
1289
1290             default: 
1291                 fmt2peer (RC_ERR, "pLOOP protocol screw-up");
1292                 done (1);
1293         }
1294 }
1295
1296
1297 static int
1298 parse (char *buffer, struct Cmd *cmdp)
1299 {
1300     int argp = 0;
1301     unsigned char c, *cp;
1302     char *pp;
1303
1304     cmdp->line[0] = 0;
1305     pp = cmdp->args[argp++] = cmdp->line;
1306     cmdp->redirect = NULL;
1307     cmdp->direction = STDIO;
1308     cmdp->stream = NULL;
1309
1310     for (cp = buffer; (c = *cp); cp++) {
1311         if (!isspace (c))
1312             break;
1313     }
1314     if (c == '\0') {
1315         if (vmh)
1316             fmt2peer (RC_EOF, "null command");
1317         return NOTOK;
1318     }
1319
1320     while ((c = *cp++)) {
1321         if (isspace (c)) {
1322             while (isspace (c))
1323                 c = *cp++;
1324             if (c == 0)
1325                 break;
1326             *pp++ = 0;
1327             cmdp->args[argp++] = pp;
1328             *pp = 0;
1329         }
1330
1331         switch (c) {
1332             case '"': 
1333                 for (;;) {
1334                     switch (c = *cp++) {
1335                         case 0: 
1336                             padvise (NULL, "unmatched \"");
1337                             return NOTOK;
1338                         case '"': 
1339                             break;
1340                         case QUOTE: 
1341                             if ((c = *cp++) == 0)
1342                                 goto no_quoting;
1343                         default: 
1344                             *pp++ = c;
1345                             continue;
1346                     }
1347                     break;
1348                 }
1349                 continue;
1350
1351             case QUOTE: 
1352                 if ((c = *cp++) == 0) {
1353             no_quoting: ;
1354                     padvise (NULL, "the newline character can not be quoted");
1355                     return NOTOK;
1356                 }
1357
1358             default: ;
1359                 *pp++ = c;
1360                 continue;
1361
1362             case '>': 
1363             case '|': 
1364                 if (pp == cmdp->line) {
1365                     padvise (NULL, "invalid null command");
1366                     return NOTOK;
1367                 }
1368                 if (*cmdp->args[argp - 1] == 0)
1369                     argp--;
1370                 cmdp->direction = c == '>' ? CRTIO : PIPIO;
1371                 if (cmdp->direction == CRTIO && (c = *cp) == '>') {
1372                     cmdp->direction = APPIO;
1373                     cp++;
1374                 }
1375                 cmdp->redirect = pp + 1;/* sigh */
1376                 for (; (c = *cp); cp++)
1377                     if (!isspace (c))
1378                         break;
1379                 if (c == 0) {
1380                     padvise (NULL, cmdp->direction != PIPIO
1381                             ? "missing name for redirect"
1382                             : "invalid null command");
1383                     return NOTOK;
1384                 }
1385                 strcpy (cmdp->redirect, cp);
1386                 if (cmdp->direction != PIPIO) {
1387                     for (; *cp; cp++)
1388                         if (isspace (*cp)) {
1389                             padvise (NULL, "bad name for redirect");
1390                             return NOTOK;
1391                         }
1392                     if (expand (cmdp->redirect) == NOTOK)
1393                         return NOTOK;
1394                 }
1395                 break;
1396         }
1397         break;
1398     }
1399
1400     *pp++ = 0;
1401     cmdp->args[argp] = NULL;
1402
1403     return OK;
1404 }
1405
1406
1407 int
1408 expand (char *redirect)
1409 {
1410     char *cp, *pp;
1411     char path[BUFSIZ];
1412     struct passwd  *pw;
1413
1414     if (*redirect != '~')
1415         return OK;
1416
1417     if ((cp = strchr(pp = redirect + 1, '/')))
1418         *cp++ = 0;
1419     if (*pp == 0)
1420         pp = mypath;
1421     else
1422         if ((pw = getpwnam (pp)))
1423             pp = pw->pw_dir;
1424         else {
1425             padvise (NULL, "unknown user: %s", pp);
1426             return NOTOK;
1427         }
1428
1429     snprintf (path, sizeof(path), "%s/%s", pp, cp ? cp : "");
1430     strcpy (redirect, path);
1431     return OK;
1432 }
1433
1434
1435 static int
1436 init_io (struct Cmd *cmdp, int vio)
1437 {
1438     int io, result;
1439
1440     io = vmh;
1441
1442     vmh = vio;
1443     result = initaux_io (cmdp);
1444     vmh = io;
1445
1446     return result;
1447 }
1448
1449
1450 static int
1451 initaux_io (struct Cmd *cmdp)
1452 {
1453     char *mode;
1454
1455     switch (cmdp->direction) {
1456         case STDIO: 
1457             return OK;
1458
1459         case CRTIO: 
1460         case APPIO: 
1461             mode = cmdp->direction == CRTIO ? "write" : "append";
1462             if ((cmdp->stream = fopen (cmdp->redirect, mode)) == NULL) {
1463                 padvise (cmdp->redirect, "unable to %s ", mode);
1464                 cmdp->direction = STDIO;
1465                 return NOTOK;
1466             }
1467             break;
1468
1469         case PIPIO: 
1470             if ((cmdp->stream = popen (cmdp->redirect, "w")) == NULL) {
1471                 padvise (cmdp->redirect, "unable to pipe");
1472                 cmdp->direction = STDIO;
1473                 return NOTOK;
1474             }
1475             SIGNAL (SIGPIPE, pipeser);
1476             broken_pipe = 0;
1477             break;
1478
1479         default: 
1480             padios (NULL, "unknown redirection for command");
1481     }
1482
1483     fflush (stdout);
1484     if (dup2 (fileno (cmdp->stream), fileno (stdout)) == NOTOK)
1485         padios ("standard output", "unable to dup2");
1486     clearerr (stdout);
1487
1488     return OK;
1489 }
1490
1491
1492 static void
1493 fin_io (struct Cmd *cmdp, int vio)
1494 {
1495     int io;
1496
1497     io = vmh;
1498     vmh = vio;
1499     finaux_io (cmdp);
1500     vmh = io;
1501 }
1502
1503
1504 static void
1505 finaux_io (struct Cmd *cmdp)
1506 {
1507     switch (cmdp->direction) {
1508         case STDIO: 
1509             return;
1510
1511         case CRTIO: 
1512         case APPIO: 
1513             fflush (stdout);
1514             close (fileno (stdout));
1515             if (ferror (stdout))
1516                 padvise (NULL, "problems writing %s", cmdp->redirect);
1517             fclose (cmdp->stream);
1518             break;
1519
1520         case PIPIO: 
1521             fflush (stdout);
1522             close (fileno (stdout));
1523             pclose (cmdp->stream);
1524             SIGNAL (SIGPIPE, SIG_DFL);
1525             break;
1526
1527         default: 
1528             padios (NULL, "unknown redirection for command");
1529     }
1530
1531     if (dup2 (fileno (sp), fileno (stdout)) == NOTOK)
1532         padios ("standard output", "unable to dup2");
1533     clearerr (stdout);
1534
1535     cmdp->direction = STDIO;
1536 }
1537
1538
1539 static void
1540 m_init (void)
1541 {
1542     int msgnum;
1543
1544     for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++)
1545         unset_selected (mp, msgnum);
1546     mp->lowsel = mp->hghsel = mp->numsel = 0;
1547 }
1548
1549
1550 void
1551 m_reset (void)
1552 {
1553     write_ids ();
1554     folder_free (mp);   /* free folder/message structure */
1555     myname = NULL;
1556 }
1557
1558
1559 void
1560 seq_setcur (struct msgs *mp, int msgnum)
1561 {
1562     if (mp->curmsg == msgnum)
1563         return;
1564
1565     if (mp->curmsg && Msgs[mp->curmsg].m_scanl) {
1566         free (Msgs[mp->curmsg].m_scanl);
1567         Msgs[mp->curmsg].m_scanl = NULL;
1568     }
1569     if (Msgs[msgnum].m_scanl) {
1570         free (Msgs[msgnum].m_scanl);
1571         Msgs[msgnum].m_scanl = NULL;
1572     }
1573
1574     mp->curmsg = msgnum;
1575 }
1576
1577
1578
1579 static void
1580 intrser (int i)
1581 {
1582     NMH_UNUSED (i);
1583     discard (stdout);
1584     interrupted++;
1585 }
1586
1587
1588 static void
1589 pipeser (int i)
1590 {
1591     NMH_UNUSED (i);
1592     if (broken_pipe++ == 0)
1593         fprintf (stderr, "broken pipe\n");
1594     told_to_quit++;
1595     interrupted++;
1596 }
1597
1598
1599 static void
1600 quitser (int i)
1601 {
1602     NMH_UNUSED (i);
1603     told_to_quit++;
1604     interrupted++;
1605 }
1606
1607
1608 static void
1609 alrmser (int i)
1610 {
1611     NMH_UNUSED (i);
1612     longjmp (peerenv, DONE);
1613 }
1614
1615
1616 static int
1617 pINI (void)
1618 {
1619     int i, vrsn;
1620     unsigned char *bp;
1621     struct record rcs, *rc;
1622
1623     rc = &rcs;
1624     initrc (rc);
1625
1626     switch (peer2rc (rc)) {
1627         case RC_INI: 
1628             bp = rc->rc_data;
1629             while (isspace (*bp))
1630                 bp++;
1631             if (sscanf (bp, "%d", &vrsn) != 1) {
1632         bad_init: ;
1633                 fmt2peer (RC_ERR, "bad init \"%s\"", rc->rc_data);
1634                 done (1);
1635             }
1636             if (vrsn != RC_VRSN) {
1637                 fmt2peer (RC_ERR, "version %d unsupported", vrsn);
1638                 done (1);
1639             }
1640
1641             while (*bp && !isspace (*bp))
1642                 bp++;
1643             while (isspace (*bp))
1644                 bp++;
1645             if (sscanf (bp, "%d", &numwins) != 1 || numwins <= 0)
1646                 goto bad_init;
1647             if (numwins > NWIN)
1648                 numwins = NWIN;
1649
1650             for (i = 1; i <= numwins; i++) {
1651                 while (*bp && !isspace (*bp))
1652                     bp++;
1653                 while (isspace (*bp))
1654                     bp++;
1655                 if (sscanf (bp, "%d", &windows[i]) != 1 || windows[i] <= 0)
1656                     goto bad_init;
1657             }
1658             rc2peer (RC_ACK, 0, NULL);
1659             return OK;
1660
1661         case RC_XXX: 
1662             padios (NULL, "%s", rc->rc_data);
1663
1664         default: 
1665             fmt2peer (RC_ERR, "pINI protocol screw-up");
1666             done (1);           /* NOTREACHED */
1667     }
1668
1669     return 1;  /* dead code to satisfy the compiler */
1670 }
1671
1672
1673 static int
1674 pQRY (char *str, int scansw)
1675 {
1676     NMH_UNUSED (str);
1677     if (pQRY1 (scansw) == NOTOK || pQRY2 () == NOTOK)
1678         return NOTOK;
1679
1680     rc2peer (RC_EOF, 0, NULL);
1681     return OK;
1682 }
1683         
1684
1685 static int
1686 pQRY1 (int scansw)
1687 {
1688     int oldhgh;
1689     static int lastlow = 0,
1690                lastcur = 0,
1691                lasthgh = 0,
1692                lastnum = 0;
1693
1694     oldhgh = mp->hghmsg;
1695     if (check_folder (scansw) && oldhgh < mp->hghmsg) {
1696         switch (winX (STATUS)) {
1697             case NOTOK: 
1698                 return NOTOK;
1699
1700             case OK: 
1701                 printf ("new messages have arrived!");
1702                 fflush (stdout);
1703                 fflush (stderr);
1704                 _exit (0);      /* NOTREACHED */
1705
1706             default: 
1707                 lastlow = lastcur = lasthgh = lastnum = 0;
1708                 break;
1709         }
1710
1711         switch (winX (DISPLAY)) {
1712             case NOTOK: 
1713                 return NOTOK;
1714
1715             case OK: 
1716                 scanrange (oldhgh + 1, mp->hghmsg);
1717                 fflush (stdout);
1718                 fflush (stderr);
1719                 _exit (0);      /* NOTREACHED */
1720
1721             default: 
1722                 break;
1723         }
1724         return OK;
1725     }
1726
1727     if (gap)
1728         switch (winX (STATUS)) {
1729             case NOTOK:
1730                 return NOTOK;
1731
1732             case OK:
1733                 printf ("%s: gap in ID:s, last seen %d, lowest present %d\n",
1734                     myname ? myname : fmsh ? fmsh : mp->foldpath, gap - 1,
1735                     readid (mp->lowmsg));
1736                 fflush (stdout);
1737                 fflush (stderr);
1738                 _exit (0);      /* NOTREACHED */
1739
1740             default:
1741                 gap = 0;
1742                 return OK;
1743         }
1744
1745     if (mp->lowmsg != lastlow
1746             || mp->curmsg != lastcur
1747             || mp->hghmsg != lasthgh
1748             || mp->nummsg != lastnum)
1749         switch (winX (STATUS)) {
1750             case NOTOK: 
1751                 return NOTOK;
1752
1753             case OK: 
1754                 foldcmd (NULL);
1755                 fflush (stdout);
1756                 fflush (stderr);
1757                 _exit (0);      /* NOTREACHED */
1758
1759             default: 
1760                 lastlow = mp->lowmsg;
1761                 lastcur = mp->curmsg;
1762                 lasthgh = mp->hghmsg;
1763                 lastnum = mp->nummsg;
1764                 return OK;
1765         }
1766
1767     return OK;
1768 }
1769
1770
1771 static int
1772 pQRY2 (void)
1773 {
1774     int i, j, k, msgnum, n;
1775     static int cur = 0,
1776                num = 0,
1777                lo = 0,
1778                hi = 0;
1779
1780     if (mp->nummsg == 0 && mp->nummsg != num)
1781         switch (winX (SCAN)) {
1782             case NOTOK: 
1783                 return NOTOK;
1784
1785             case OK: 
1786                 printf ("empty!");
1787                 fflush (stdout);
1788                 fflush (stderr);
1789                 _exit (0);      /* NOTREACHED */
1790
1791             default: 
1792                 num = mp->nummsg;
1793                 return OK;
1794         }
1795     num = mp->nummsg;
1796
1797     i = 0;
1798     j = (k = windows[SCAN]) / 2;
1799     for (msgnum = mp->curmsg; msgnum <= mp->hghmsg; msgnum++)
1800         if (does_exist (mp, msgnum))
1801             i++;
1802     if (i-- > 0) {
1803         if (topcur)
1804             k = i >= k ? 1 : k - i;
1805         else
1806             k -= i > j ? j : i;
1807     }
1808
1809     i = j = 0;
1810     n = 1;
1811     for (msgnum = mp->curmsg; msgnum >= mp->lowmsg; msgnum--)
1812         if (does_exist (mp, msgnum)) {
1813             i = msgnum;
1814             if (j == 0)
1815                 j = msgnum;
1816             if (n++ >= k)
1817                 break;
1818         }
1819     for (msgnum = mp->curmsg + 1; msgnum <= mp->hghmsg; msgnum++)
1820         if (does_exist (mp, msgnum)) {
1821             if (i == 0)
1822                 i = msgnum;
1823             j = msgnum;
1824             if (n++ >= windows[SCAN])
1825                 break;
1826         }
1827     if (!topcur
1828             && lo > 0
1829             && hi > 0
1830             && does_exist (mp, lo)
1831             && does_exist (mp, hi)
1832             && (lo < mp->curmsg
1833                     || (lo == mp->curmsg && lo == mp->lowmsg))
1834             && (mp->curmsg < hi
1835                     || (hi == mp->curmsg && hi == mp->hghmsg))
1836             && hi - lo == j - i)
1837         i = lo, j = hi;
1838
1839     if (mp->curmsg != cur || modified)
1840         switch (winN (NULLCMD, SCAN, 0)) {
1841             case NOTOK: 
1842                 return NOTOK;
1843
1844             case OK:
1845                 return OK;
1846
1847             default: 
1848                 scanrange (lo = i, hi = j);
1849                 cur = mp->curmsg;
1850                 winR (NULLCMD);
1851                 return OK;
1852         }
1853
1854     return OK;
1855 }
1856
1857
1858 static int
1859 pCMD (char *str, struct swit *sw, struct Cmd *cmdp)
1860 {
1861     int i;
1862
1863     if (*str == '?')
1864         switch (winX (DISPLAY)) {
1865             case NOTOK: 
1866                 return NOTOK;
1867
1868             case OK: 
1869                 printf ("commands:\n");
1870                 print_sw (ALL, sw, "", stdout);
1871                 printf ("type ``quit'' to leave %s\n", invo_name);
1872                 fflush (stdout);
1873                 fflush (stderr);
1874                 _exit (0);      /* NOTREACHED */
1875
1876             default: 
1877                 rc2peer (RC_EOF, 0, NULL);
1878                 return NOTOK;
1879         }
1880
1881     if (parse (str, cmdp) == NOTOK)
1882         return NOTOK;
1883
1884     switch (i = smatch (cmdp->args[0], sw)) {
1885         case AMBIGSW: 
1886             switch (winX (DISPLAY)) {
1887                 case NOTOK: 
1888                     return NOTOK;
1889
1890                 case OK: 
1891                     ambigsw (cmdp->args[0], sw);
1892                     fflush (stdout);
1893                     fflush (stderr);
1894                     _exit (0);  /* NOTREACHED */
1895
1896                 default: 
1897                     rc2peer (RC_EOF, 0, NULL);
1898                     return NOTOK;
1899             }
1900
1901         case UNKWNSW: 
1902             fmt2peer (RC_ERR,
1903                     "say what: ``%s'' -- type ? (or help) for help",
1904                     cmdp->args[0]);
1905             return NOTOK;
1906
1907         default: 
1908             return i;
1909     }
1910 }
1911
1912
1913 static int
1914 pFIN (void)
1915 {
1916     int status;
1917
1918     switch (setjmp (peerenv)) {
1919         case OK: 
1920             SIGNAL (SIGALRM, alrmser);
1921             alarm (ALARM);
1922
1923             status = peerwait ();
1924
1925             alarm (0);
1926             return status;
1927
1928         default: 
1929             return NOTOK;
1930     }
1931 }
1932
1933
1934 static int
1935 peerwait (void)
1936 {
1937     struct record rcs, *rc;
1938
1939     rc = &rcs;
1940     initrc (rc);
1941
1942     switch (peer2rc (rc)) {
1943         case RC_QRY: 
1944         case RC_CMD: 
1945             rc2peer (RC_FIN, 0, NULL);
1946             return OK;
1947
1948         case RC_XXX: 
1949             advise (NULL, "%s", rc->rc_data);
1950             return NOTOK;
1951
1952         default: 
1953             fmt2peer (RC_FIN, "pLOOP protocol screw-up");
1954             return NOTOK;
1955     }
1956 }
1957
1958
1959 static int
1960 ttyNaux (struct Cmd *cmdp, char *s)
1961 {
1962     struct record rcs, *rc;
1963
1964     rc = &rcs;
1965     initrc (rc);
1966
1967     if (cmdp && init_io (cmdp, vmh) == NOTOK)
1968         return NOTOK;
1969
1970     /* XXX: fseek() too tricky for our own good */
1971     if (!fmsh)
1972         fseek (fp, 0L, SEEK_SET);
1973
1974     vmhtty = NOTOK;
1975     switch (rc2rc (RC_TTY, s ? strlen (s) : 0, s, rc)) {
1976         case RC_ACK: 
1977             vmhtty = OK;        /* fall */
1978         case RC_ERR: 
1979             break;
1980
1981         case RC_XXX: 
1982             padios (NULL, "%s", rc->rc_data);/* NOTREACHED */
1983
1984         default: 
1985             fmt2peer (RC_ERR, "pTTY protocol screw-up");
1986             done (1);           /* NOTREACHED */
1987     }
1988
1989 #ifdef SIGTSTP
1990     SIGNAL (SIGTSTP, tstat);
1991 #endif
1992     return vmhtty;
1993 }
1994
1995
1996 static int
1997 ttyR (struct Cmd *cmdp)
1998 {
1999     struct record rcs, *rc;
2000
2001     rc = &rcs;
2002
2003 #ifdef SIGTSTP
2004     SIGNAL (SIGTSTP, SIG_IGN);
2005 #endif
2006
2007     if (vmhtty != OK)
2008         return NOTOK;
2009
2010     initrc (rc);
2011
2012     if (cmdp)
2013         fin_io (cmdp, 0);
2014
2015     vmhtty = NOTOK;
2016     switch (rc2rc (RC_EOF, 0, NULL, rc)) {
2017         case RC_ACK: 
2018             rc2peer (RC_EOF, 0, NULL);
2019             return OK;
2020
2021         case RC_XXX: 
2022             padios (NULL, "%s", rc->rc_data);/* NOTREACHED */
2023
2024         default: 
2025             fmt2peer (RC_ERR, "pTTY protocol screw-up");
2026             done (1);           /* NOTREACHED */
2027     }
2028
2029     return 1;  /* dead code to satisfy compiler */
2030 }
2031
2032
2033 static int
2034 winN (struct Cmd *cmdp, int n, int eof)
2035 {
2036     int i, pd[2];
2037     char buffer[BUFSIZ];
2038     struct record rcs, *rc;
2039
2040     rc = &rcs;
2041     if (vmhpid == NOTOK)
2042         return OK;
2043
2044     initrc (rc);
2045
2046     /* XXX: fseek() too tricky for our own good */
2047     if (!fmsh)
2048         fseek (fp, 0L, SEEK_SET);
2049
2050     vmhpid = OK;
2051
2052     snprintf (buffer, sizeof(buffer), "%d", n);
2053     switch (str2rc (RC_WIN, buffer, rc)) {
2054         case RC_ACK: 
2055             break;
2056
2057         case RC_ERR: 
2058             return NOTOK;
2059
2060         case RC_XXX: 
2061             padios (NULL, "%s", rc->rc_data);
2062
2063         default: 
2064             fmt2peer (RC_ERR, "pWIN protocol screw-up");
2065             done (1);
2066     }
2067
2068     if (pipe (pd) == NOTOK) {
2069         err2peer (RC_ERR, "pipe", "unable to");
2070         return NOTOK;
2071     }
2072
2073     switch (vmhpid = fork()) {
2074         case NOTOK: 
2075             err2peer (RC_ERR, "fork", "unable to");
2076             close (pd[0]);
2077             close (pd[1]);
2078             return NOTOK;
2079
2080         case OK: 
2081             close (pd[1]);
2082             SIGNAL (SIGPIPE, SIG_IGN);
2083             while ((i = read (pd[0], buffer, sizeof buffer)) > 0)
2084                 switch (rc2rc (RC_DATA, i, buffer, rc)) {
2085                     case RC_ACK: 
2086                         break;
2087
2088                     case RC_ERR: 
2089                         _exit (1);
2090
2091                     case RC_XXX: 
2092                         advise (NULL, "%s", rc->rc_data);
2093                         _exit (2);
2094
2095                     default: 
2096                         fmt2peer (RC_ERR, "pWIN protocol screw-up");
2097                         _exit (2);
2098                 }
2099             if (i == OK)
2100                 switch (rc2rc (RC_EOF, 0, NULL, rc)) {
2101                     case RC_ACK: 
2102                         if (eof)
2103                             rc2peer (RC_EOF, 0, NULL);
2104                         i = 0;
2105                         break;
2106
2107                     case RC_XXX: 
2108                         advise (NULL, "%s", rc->rc_data);
2109                         i = 2;
2110                         break;
2111
2112                     default: 
2113                         fmt2peer (RC_ERR, "pWIN protocol screw-up");
2114                         i = 2;
2115                         break;
2116                 }
2117             if (i == NOTOK)
2118                 err2peer (RC_ERR, "pipe", "error reading from");
2119             close (pd[0]);
2120             _exit (i != NOTOK ? i : 1);
2121
2122         default: 
2123             if ((vmhfd0 = dup (fileno (stdin))) == NOTOK)
2124                 padios ("standard input", "unable to dup");
2125             if ((vmhfd1 = dup (fileno (stdout))) == NOTOK)
2126                 padios ("standard output", "unable to dup");
2127             if ((vmhfd2 = dup (fileno (stderr))) == NOTOK)
2128                 padios ("diagnostic output", "unable to dup");
2129
2130             close (0);
2131             if ((i = open ("/dev/null", O_RDONLY)) != NOTOK && i != fileno (stdin)) {
2132                 dup2 (i, fileno (stdin));
2133                 close (i);
2134             }
2135
2136             fflush (stdout);
2137             if (dup2 (pd[1], fileno (stdout)) == NOTOK)
2138                 padios ("standard output", "unable to dup2");
2139             clearerr (stdout);
2140
2141             fflush (stderr);
2142             if (dup2 (pd[1], fileno (stderr)) == NOTOK)
2143                 padios ("diagnostic output", "unable to dup2");
2144             clearerr (stderr);
2145
2146             if (cmdp && init_io (cmdp, 0) == NOTOK)
2147                 return NOTOK;
2148             pstat = SIGNAL (SIGPIPE, pipeser);
2149             broken_pipe = 1;
2150
2151             close (pd[0]);
2152             close (pd[1]);
2153
2154             return vmhpid;
2155     }
2156 }
2157
2158
2159 static int
2160 winR (struct Cmd *cmdp)
2161 {
2162     int status;
2163
2164     if (vmhpid <= OK)
2165         return NOTOK;
2166
2167     if (cmdp)
2168         fin_io (cmdp, 0);
2169
2170     if (dup2 (vmhfd0, fileno (stdin)) == NOTOK)
2171         padios ("standard input", "unable to dup2");
2172     clearerr (stdin);
2173     close (vmhfd0);
2174
2175     fflush (stdout);
2176     if (dup2 (vmhfd1, fileno (stdout)) == NOTOK)
2177         padios ("standard output", "unable to dup2");
2178     clearerr (stdout);
2179     close (vmhfd1);
2180
2181     fflush (stderr);
2182     if (dup2 (vmhfd2, fileno (stderr)) == NOTOK)
2183         padios ("diagnostic output", "unable to dup2");
2184     clearerr (stderr);
2185     close (vmhfd2);
2186
2187     SIGNAL (SIGPIPE, pstat);
2188
2189     if ((status = pidwait (vmhpid, OK)) == 2)
2190         done (1);
2191
2192     vmhpid = OK;
2193     return (status == 0 ? OK : NOTOK);
2194 }
2195
2196
2197 static int
2198 winX (int n)
2199 {
2200     int i, pid, pd[2];
2201     char buffer[BUFSIZ];
2202     struct record rcs, *rc;
2203
2204     rc = &rcs;
2205     initrc (rc);
2206
2207     /* XXX: fseek() too tricky for our own good */
2208     if (!fmsh)
2209         fseek (fp, 0L, SEEK_SET);
2210
2211     snprintf (buffer, sizeof(buffer), "%d", n);
2212     switch (str2rc (RC_WIN, buffer, rc)) {
2213         case RC_ACK: 
2214             break;
2215
2216         case RC_ERR: 
2217             return NOTOK;
2218
2219         case RC_XXX: 
2220             padios (NULL, "%s", rc->rc_data);
2221
2222         default: 
2223             fmt2peer (RC_ERR, "pWIN protocol screw-up");
2224             done (1);
2225     }
2226
2227     if (pipe (pd) == NOTOK) {
2228         err2peer (RC_ERR, "pipe", "unable to");
2229         return NOTOK;
2230     }
2231
2232     switch (pid = fork ()) {
2233         case NOTOK: 
2234             err2peer (RC_ERR, "fork", "unable to");
2235             close (pd[0]);
2236             close (pd[1]);
2237             return NOTOK;
2238
2239         case OK: 
2240             close (fileno (stdin));
2241             if ((i = open ("/dev/null", O_RDONLY)) != NOTOK && i != fileno (stdin)) {
2242                 dup2 (i, fileno (stdin));
2243                 close (i);
2244             }
2245             dup2 (pd[1], fileno (stdout));
2246             dup2 (pd[1], fileno (stderr));
2247             close (pd[0]);
2248             close (pd[1]);
2249             vmhpid = NOTOK;
2250             return OK;
2251
2252         default: 
2253             close (pd[1]);
2254             while ((i = read (pd[0], buffer, sizeof buffer)) > 0)
2255                 switch (rc2rc (RC_DATA, i, buffer, rc)) {
2256                     case RC_ACK: 
2257                         break;
2258
2259                     case RC_ERR: 
2260                         close (pd[0]);
2261                         pidwait (pid, OK);
2262                         return NOTOK;
2263
2264                     case RC_XXX: 
2265                         padios (NULL, "%s", rc->rc_data);
2266
2267                     default: 
2268                         fmt2peer (RC_ERR, "pWIN protocol screw-up");
2269                         done (1);
2270                 }
2271             if (i == OK)
2272                 switch (rc2rc (RC_EOF, 0, NULL, rc)) {
2273                     case RC_ACK: 
2274                         break;
2275
2276                     case RC_XXX: 
2277                         padios (NULL, "%s", rc->rc_data);
2278
2279                     default: 
2280                         fmt2peer (RC_ERR, "pWIN protocol screw-up");
2281                         done (1);
2282                 }
2283             if (i == NOTOK)
2284                 err2peer (RC_ERR, "pipe", "error reading from");
2285
2286             close (pd[0]);
2287             pidwait (pid, OK);
2288             return (i != NOTOK ? pid : NOTOK);
2289     }
2290 }
2291
2292
2293 void
2294 padios (char *what, char *fmt, ...)
2295 {
2296     va_list ap;
2297
2298     va_start(ap, fmt);
2299     if (vmh) {
2300         verr2peer (RC_FIN, what, fmt, ap);
2301         rcdone ();
2302     } else {
2303         advertise (what, NULL, fmt, ap);
2304     }
2305     va_end(ap);
2306
2307     done (1);
2308 }
2309
2310
2311 void
2312 padvise (char *what, char *fmt, ...)
2313 {
2314     va_list ap;
2315
2316     va_start(ap, fmt);
2317     if (vmh) {
2318         verr2peer (RC_ERR, what, fmt, ap);
2319     } else {
2320         advertise (what, NULL, fmt, ap);
2321     }
2322     va_end(ap);
2323 }