mhshow: Automatically invoke (one) pager for the whole message, if on TTY.
[mmh] / uip / mhshow.c
1 /*
2 ** mhshow.c -- display the contents of MIME messages
3 **
4 ** This code is Copyright (c) 2002, by the authors of nmh.  See the
5 ** COPYRIGHT file in the root directory of the nmh distribution for
6 ** complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include <fcntl.h>
11 #include <h/signals.h>
12 #include <errno.h>
13 #include <signal.h>
14 #include <h/tws.h>
15 #include <h/mime.h>
16 #include <h/mhparse.h>
17 #include <h/utils.h>
18
19 static struct swit switches[] = {
20 #define VERBSW  0
21         { "verbose", 0 },
22 #define NVERBSW  1
23         { "noverbose", 0 },
24 #define FILESW  2  /* interface from show */
25         { "file file", 0 },
26 #define FORMSW  3
27         { "form formfile", 0 },
28 #define PARTSW  4
29         { "part number", 0 },
30 #define TYPESW  5
31         { "type content", 0 },
32 #define VERSIONSW  6
33         { "version", 0 },
34 #define HELPSW  7
35         { "help", 0 },
36 #define DEBUGSW  8
37         { "debug", -5 },
38         { NULL, 0 }
39 };
40
41
42 /* mhparse.c */
43 extern char *tmp;  /* directory to place temp files */
44
45 /* mhshowsbr.c */
46 extern int nolist;
47 extern char *formsw;
48
49 /* mhmisc.c */
50 extern int npart;
51 extern int ntype;
52 extern char *parts[NPARTS + 1];
53 extern char *types[NTYPES + 1];
54 extern int userrs;
55
56 int debugsw = 0;
57 int verbosw = 0;
58
59 #define quitser pipeser
60
61 /* mhparse.c */
62 CT parse_mime(char *);
63
64 /* mhmisc.c */
65 int part_ok(CT, int);
66 int type_ok(CT, int);
67 void set_endian(void);
68 void flush_errors(void);
69
70 /* mhshowsbr.c */
71 void show_all_messages(CT *);
72
73 /* mhfree.c */
74 void free_content(CT);
75 extern CT *cts;
76 void freects_done(int) NORETURN;
77
78 /*
79 ** static prototypes
80 */
81 static void pipeser(int);
82 static void m_popen(char *);
83 static void m_pclose(void);
84
85
86 int
87 main(int argc, char **argv)
88 {
89         int msgnum;
90         char *cp, *file = NULL, *folder = NULL;
91         char *maildir, buf[100], **argp;
92         char **arguments;
93         struct msgs_array msgs = { 0, 0, NULL };
94         struct msgs *mp = NULL;
95         CT ct, *ctp;
96         FILE *fp;
97         int ontty = 0;
98
99         done=freects_done;
100
101 #ifdef LOCALE
102         setlocale(LC_ALL, "");
103 #endif
104         invo_name = mhbasename(argv[0]);
105
106         /* read user profile/context */
107         context_read();
108
109         arguments = getarguments(invo_name, argc, argv, 1);
110         argp = arguments;
111
112         /*
113         ** Parse arguments
114         */
115         while ((cp = *argp++)) {
116                 if (*cp == '-') {
117                         switch (smatch(++cp, switches)) {
118                         case AMBIGSW:
119                                 ambigsw(cp, switches);
120                                 done(1);
121                         case UNKWNSW:
122                                 adios(NULL, "-%s unknown", cp);
123
124                         case HELPSW:
125                                 snprintf(buf, sizeof(buf), "%s [+folder] [msgs] [switches]", invo_name);
126                                 print_help(buf, switches, 1);
127                                 done(1);
128                         case VERSIONSW:
129                                 print_version(invo_name);
130                                 done(1);
131
132                         case PARTSW:
133                                 if (!(cp = *argp++) || *cp == '-')
134                                         adios(NULL, "missing argument to %s",
135                                                         argp[-2]);
136                                 if (npart >= NPARTS)
137                                         adios(NULL, "too many parts (starting with %s), %d max", cp, NPARTS);
138                                 parts[npart++] = cp;
139                                 continue;
140
141                         case TYPESW:
142                                 if (!(cp = *argp++) || *cp == '-')
143                                         adios(NULL, "missing argument to %s",
144                                                         argp[-2]);
145                                 if (ntype >= NTYPES)
146                                         adios(NULL, "too many types (starting with %s), %d max", cp, NTYPES);
147                                 types[ntype++] = cp;
148                                 continue;
149
150                         case FILESW:
151                                 if (!(cp = *argp++) || (*cp == '-' && cp[1]))
152                                         adios(NULL, "missing argument to %s",
153                                                         argp[-2]);
154                                 file = *cp == '-' ? cp : getcpy(expanddir(cp));
155                                 continue;
156
157                         case FORMSW:
158                                 if (!(cp = *argp++) || *cp == '-')
159                                         adios(NULL, "missing argument to %s",
160                                                         argp[-2]);
161                                 if (formsw)
162                                         free(formsw);
163                                 formsw = getcpy(etcpath(cp));
164                                 continue;
165
166                         case VERBSW:
167                                 verbosw = 1;
168                                 continue;
169                         case NVERBSW:
170                                 verbosw = 0;
171                                 continue;
172                         case DEBUGSW:
173                                 debugsw = 1;
174                                 continue;
175                         }
176                 }
177                 if (*cp == '+' || *cp == '@') {
178                         if (folder)
179                                 adios(NULL, "only one folder at a time!");
180                         else
181                                 folder = getcpy(expandfol(cp));
182                 } else
183                         app_msgarg(&msgs, cp);
184         }
185
186         /* null terminate the list of acceptable parts/types */
187         parts[npart] = NULL;
188         types[ntype] = NULL;
189
190         set_endian();
191
192         if ((cp = getenv("MM_NOASK")) && strcmp(cp, "1")==0) {
193                 nolist  = 1;
194         }
195
196         /*
197         ** Check if we've specified an additional profile
198         */
199         if ((cp = getenv("MHSHOW"))) {
200                 if ((fp = fopen(cp, "r"))) {
201                         readconfig((struct node **) 0, fp, cp, 0);
202                         fclose(fp);
203                 } else {
204                         admonish("", "unable to read $MHSHOW profile (%s)",
205                                         cp);
206                 }
207         }
208
209         /*
210         ** Read the standard profile setup
211         */
212         if ((fp = fopen(cp = etcpath("mhn.defaults"), "r"))) {
213                 readconfig((struct node **) 0, fp, cp, 0);
214                 fclose(fp);
215         }
216
217         /*
218         ** Check for storage directory.  If specified,
219         ** then store temporary files there.  Else we
220         ** store them in standard nmh directory.
221         */
222         if ((cp = context_find(nmhstorage)) && *cp)
223                 tmp = concat(cp, "/", invo_name, NULL);
224         else
225                 tmp = getcpy(toabsdir(invo_name));
226
227         if (file && msgs.size)
228                 adios(NULL, "cannot specify msg and file at same time!");
229
230         /*
231         ** check if message is coming from file
232         */
233         if (file) {
234                 if (!(cts = (CT *) calloc((size_t) 2, sizeof(*cts))))
235                         adios(NULL, "out of memory");
236                 ctp = cts;
237
238                 if ((ct = parse_mime(file)))
239                         *ctp++ = ct;
240         } else {
241                 /*
242                 ** message(s) are coming from a folder
243                 */
244                 if (!msgs.size)
245                         app_msgarg(&msgs, seq_cur);
246                 if (!folder)
247                         folder = getcurfol();
248                 maildir = toabsdir(folder);
249
250                 if (chdir(maildir) == NOTOK)
251                         adios(maildir, "unable to change directory to");
252
253                 /* read folder and create message structure */
254                 if (!(mp = folder_read(folder)))
255                         adios(NULL, "unable to read folder %s", folder);
256
257                 /* check for empty folder */
258                 if (mp->nummsg == 0)
259                         adios(NULL, "no messages in %s", folder);
260
261                 /* parse all the message ranges/sequences and set SELECTED */
262                 for (msgnum = 0; msgnum < msgs.size; msgnum++)
263                         if (!m_convert(mp, msgs.msgs[msgnum]))
264                                 done(1);
265
266                 /*
267                 ** Set the SELECT_UNSEEN bit for all the SELECTED messages,
268                 ** since we will use that as a tag to know which messages
269                 ** to remove from the "unseen" sequence.
270                 */
271                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++)
272                         if (is_selected(mp, msgnum))
273                                 set_unseen(mp, msgnum);
274
275                 seq_setprev(mp);  /* set the Previous-Sequence */
276                 seq_setunseen(mp, 0);  /* unset unseen seqs for shown msgs */
277
278                 if (!(cts = (CT *) calloc((size_t) (mp->numsel + 1),
279                                 sizeof(*cts))))
280                         adios(NULL, "out of memory");
281                 ctp = cts;
282
283                 /*
284                 ** Parse all the SELECTED messages.
285                 */
286                 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
287                         if (is_selected(mp, msgnum)) {
288                                 char *msgnam;
289
290                                 msgnam = m_name(msgnum);
291                                 if ((ct = parse_mime(msgnam)))
292                                         *ctp++ = ct;
293                         }
294                 }
295         }
296
297         if (!*cts)
298                 done(1);
299
300         userrs = 1;
301         SIGNAL(SIGQUIT, quitser);
302         SIGNAL(SIGPIPE, pipeser);
303
304         /*
305         ** Get the associated umask for the relevant contents.
306         */
307         for (ctp = cts; *ctp; ctp++) {
308                 struct stat st;
309
310                 ct = *ctp;
311                 if (type_ok(ct, 1) && !ct->c_umask) {
312                         if (stat(ct->c_file, &st) != NOTOK)
313                                 ct->c_umask = ~(st.st_mode & 0777);
314                         else
315                                 ct->c_umask = ~m_gmprot();
316                 }
317         }
318
319         if ((ontty = isatty(fileno(stdout)))) {
320                 m_popen(defaultpager);
321         }
322
323         /*
324         ** Show the message content
325         */
326         show_all_messages(cts);
327
328         if (ontty) {
329                 m_pclose();
330         }
331
332         /* Now free all the structures for the content */
333         for (ctp = cts; *ctp; ctp++)
334                 free_content(*ctp);
335
336         free((char *) cts);
337         cts = NULL;
338
339         /* If reading from a folder, do some updating */
340         if (mp) {
341                 context_replace(curfolder, folder); /* update current folder */
342                 seq_setcur(mp, mp->hghsel);        /* update current message */
343                 seq_save(mp);                      /* synchronize sequences */
344                 context_save();                    /* save the context file */
345         }
346
347         done(0);
348         return 1;
349 }
350
351
352 static void
353 pipeser(int i)
354 {
355         if (i == SIGQUIT) {
356                 unlink("core");
357                 fflush(stdout);
358                 fprintf(stderr, "\n");
359                 fflush(stderr);
360         }
361
362         done(1);
363         /* NOTREACHED */
364 }
365
366
367 static int m_pid = NOTOK;
368 static int sd = NOTOK;
369
370
371 static void
372 m_popen(char *name)
373 {
374         int pd[2];
375
376         if ((sd = dup(fileno(stdout))) == NOTOK)
377                 adios("standard output", "unable to dup()");
378
379         if (pipe(pd) == NOTOK)
380                 adios("pipe", "unable to");
381
382         switch (m_pid = fork()) {
383         case NOTOK:
384                 adios("fork", "unable to");
385
386         case OK:
387                 SIGNAL(SIGINT, SIG_DFL);
388                 SIGNAL(SIGQUIT, SIG_DFL);
389
390                 close(pd[1]);
391                 if (pd[0] != fileno(stdin)) {
392                         dup2(pd[0], fileno(stdin));
393                         close(pd[0]);
394                 }
395                 execlp(name, mhbasename(name), NULL);
396                 fprintf(stderr, "unable to exec ");
397                 perror(name);
398                 _exit(-1);
399
400         default:
401                 close(pd[0]);
402                 if (pd[1] != fileno(stdout)) {
403                         dup2(pd[1], fileno(stdout));
404                         close(pd[1]);
405                 }
406         }
407 }
408
409
410 void
411 m_pclose(void)
412 {
413         if (m_pid == NOTOK)
414                 return;
415
416         if (sd != NOTOK) {
417                 fflush(stdout);
418                 if (dup2(sd, fileno(stdout)) == NOTOK)
419                         adios("standard output", "unable to dup2()");
420
421                 clearerr(stdout);
422                 close(sd);
423                 sd = NOTOK;
424         } else
425                 fclose(stdout);
426
427         pidwait(m_pid, OK);
428         m_pid = NOTOK;
429 }