Renamed switches variable to ease automatic text extraction.
[mmh] / uip / whatnow.c
1 /*
2 ** whatnow.c -- the WhatNow shell
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 **  The inclusion of attachments is eased by
9 **  using the header field name mechanism added to anno and send.
10 **  The header field name for attachments is predefined.
11 **
12 **  Several commands have been added at the whatnow prompt:
13 **
14 **        cd [ directory ]        This option works just like the shell's
15 **                                cd command and lets the user change the
16 **                                directory from which attachments are
17 **                                taken so that long path names are not
18 **                                needed with every file.
19 **
20 **        ls [ ls-options ]       This option works just like the normal
21 **                                ls command and exists to allow the user
22 **                                to verify file names in the directory.
23 **
24 **        pwd                     This option works just like the normal
25 **                                pwd command and exists to allow the user
26 **                                to verify the directory.
27 **
28 **        attach files            This option attaches the named files to
29 **                                the draft.
30 **
31 **        alist                   This option lists the attachments on the
32 **                                draft.
33 **
34 **        detach numbers          This option removes attachments by
35 **                                attachment number from the draft.
36 */
37
38 #include <h/mh.h>
39 #include <fcntl.h>
40 #include <signal.h>
41 #include <h/mime.h>
42 #include <h/utils.h>
43
44 static struct swit switches[] = {
45 #define EDITRSW  0
46         { "editor editor", 0 },
47 #define PRMPTSW  1
48         { "prompt string", 0 },
49 #define VERSIONSW  2
50         { "Version", 0 },
51 #define HELPSW  3
52         { "help", 0 },
53         { NULL, 0 }
54 };
55
56 /*
57 ** Options at the "whatnow" prompt
58 */
59 static struct swit aleqs[] = {
60 #define EDITSW  0
61         { "edit [<editor> <switches>]", 0 },
62 #define REFILEOPT  1
63         { "refile [<switches>] +folder", 0 },
64 #define DISPSW  2
65         { "display", 0 },
66 #define LISTSW  3
67         { "list", 0 },
68 #define SENDSW  4
69         { "send [<switches>]", 0 },
70 #define QUITSW  5
71         { "quit", 0 },
72 #define DELETESW  6
73         { "delete", 0 },
74 #define CDCMDSW  7
75         { "cd [directory]", 0 },
76 #define PWDCMDSW  8
77         { "pwd", 0 },
78 #define LSCMDSW  9
79         { "ls", 0 },
80 #define ATTACHCMDSW  10
81         { "attach", 0 },
82 #define DETACHCMDSW  11
83         { "detach", 0 },
84 #define ALISTCMDSW  12
85         { "alist", 0 },
86         { NULL, 0 }
87 };
88
89 static char *myprompt = "\nWhat now? ";
90
91 /*
92 ** static prototypes
93 */
94 static int editfile(char **, char **, char *, int);
95 static int sendfile(char **, char *);
96 static int refile(char **, char *);
97 static int removefile(char *);
98 static void writelscmd(char *, int, char **);
99 static void writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp);
100 static FILE* popen_in_dir(const char *dir, const char *cmd, const char *type);
101 static int system_in_dir(const char *dir, const char *cmd);
102
103
104 int
105 main(int argc, char **argv)
106 {
107         int use = 0;
108         char *cp;
109         char *ed = NULL, *drft = NULL;
110         char buf[BUFSIZ], prompt[BUFSIZ];
111         char **argp, **arguments;
112         struct stat st;
113         char cwd[MAXPATHLEN + 1];  /* current working directory */
114         char file[MAXPATHLEN + 1];  /* file name buffer */
115         char shell[MAXPATHLEN + 1];  /* shell response buffer */
116         FILE *f;  /* read pointer for bgnd proc */
117
118         setlocale(LC_ALL, "");
119         invo_name = mhbasename(argv[0]);
120
121         /* read user profile/context */
122         context_read();
123
124         arguments = getarguments(invo_name, argc, argv, 1);
125         argp = arguments;
126
127         /*
128         ** Get the initial current working directory.
129         */
130
131         if (!getcwd(cwd, sizeof (cwd))) {
132                 adios("getcwd", "could not get working directory");
133         }
134
135         while ((cp = *argp++)) {
136                 if (*cp == '-') {
137                         switch (smatch(++cp, switches)) {
138                         case AMBIGSW:
139                                 ambigsw(cp, switches);
140                                 done(1);
141                         case UNKWNSW:
142                                 adios(NULL, "-%s unknown", cp);
143
144                         case HELPSW:
145                                 snprintf(buf, sizeof(buf),
146                                                 "%s [switches] [file]",
147                                                 invo_name);
148                                 print_help(buf, switches, 1);
149                                 done(1);
150                         case VERSIONSW:
151                                 print_version(invo_name);
152                                 done(1);
153
154                         case EDITRSW:
155                                 if (!(ed = *argp++) || *ed == '-')
156                                         adios(NULL, "missing argument to %s",
157                                                         argp[-2]);
158                                 continue;
159
160                         case PRMPTSW:
161                                 if (!(myprompt = *argp++) || *myprompt == '-')
162                                         adios(NULL, "missing argument to %s",
163                                                         argp[-2]);
164                                 continue;
165
166                         }
167                 }
168                 if (drft)
169                         adios(NULL, "only one draft at a time!");
170                 else
171                         drft = cp;
172         }
173
174         if ((!drft && !(drft = getenv("mhdraft"))) || !*drft)
175                 drft = getcpy(m_draft(seq_cur));
176
177         if ((cp = getenv("mhuse")) && *cp)
178                 use = atoi(cp);
179
180         if (!ed && !(ed = getenv("mheditor"))) {
181                 ed = "";  /* Don't initially edit the draft */
182         }
183
184         /* start editing the draft, unless editor is the empty string */
185         if (*ed) {
186                 if (editfile(&ed, NULL, drft, use) <0) {
187                         if (!use) {
188                                 unlink(drft);
189                         }
190                         advise(NULL, "Try again.");
191                         done(1);
192                 }
193         }
194
195         snprintf(prompt, sizeof(prompt), myprompt, invo_name);
196         for (;;) {
197                 if (!(argp = getans(prompt, aleqs))) {
198                         done(1);
199                 }
200                 switch (smatch(*argp, aleqs)) {
201                 case DISPSW:
202                         /* display the msg being replied to or distributed */
203                         if ((cp = getenv("mhaltmsg")) && *cp) {
204                                 snprintf(buf, sizeof buf, "%s '%s'",
205                                                 listproc, cp);
206                                 system(buf);
207                         } else {
208                                 advise(NULL, "no alternate message to display");
209                         }
210                         break;
211
212                 case EDITSW:
213                         /* Call an editor on the draft file */
214                         if (*++argp)
215                                 ed = *argp++;
216                         editfile(&ed, argp, drft, NOUSE);
217                         break;
218
219                 case LISTSW:
220                         /* display the draft file */
221                         snprintf(buf, sizeof buf, "%s '%s'", listproc, drft);
222                         system(buf);
223                         break;
224
225                 case QUITSW:
226                         /* quit */
227                         if (stat(drft, &st) != NOTOK) {
228                                 advise(NULL, "draft left on %s", drft);
229                         }
230                         done(1);
231
232                 case DELETESW:
233                         /* Delete draft and exit */
234                         removefile(drft);
235                         done(1);
236
237                 case SENDSW:
238                         /* Send draft */
239                         sendfile(++argp, drft);
240                         break;
241
242                 case REFILEOPT:
243                         /* Refile the draft */
244                         if (refile(++argp, drft) == 0) {
245                                 done(0);
246                         }
247                         break;
248
249                 case CDCMDSW:
250                         /*
251                         ** Change the working directory for attachments
252                         **
253                         ** Run the directory through the user's shell
254                         ** so that we can take advantage of any syntax
255                         ** that the user is accustomed to.  Read back
256                         ** the absolute path.
257                         */
258
259                         if (*(argp+1) == NULL) {
260                                 sprintf(buf, "$SHELL -c \"cd;pwd\"");
261                         } else {
262                                 writesomecmd(buf, BUFSIZ, "cd", "pwd", argp);
263                         }
264                         if ((f = popen_in_dir(cwd, buf, "r"))) {
265                                 fgets(cwd, sizeof (cwd), f);
266
267                                 if (strchr(cwd, '\n'))
268                                         *strchr(cwd, '\n') = '\0';
269
270                                 pclose(f);
271                         } else {
272                                 advise("popen", "could not get directory");
273                         }
274
275                         break;
276
277                 case PWDCMDSW:
278                         /* Print the working directory for attachments */
279                         printf("%s\n", cwd);
280                         break;
281
282                 case LSCMDSW:
283                         /*
284                         ** List files in the current attachment working
285                         ** directory
286                         **
287                         ** Use the user's shell so that we can take
288                         ** advantage of any syntax that the user is
289                         ** accustomed to.
290                         */
291                         writelscmd(buf, sizeof(buf), argp);
292                         system_in_dir(cwd, buf);
293                         break;
294
295                 case ALISTCMDSW:
296                         /*
297                         ** List attachments on current draft.
298                         */
299                         snprintf(buf, sizeof buf, "anno -list -comp '%s' "
300                                         "-number '%s'", attach_hdr, drft);
301                         if (system(buf) != 0) {
302                                 advise(NULL, "Could not list attachment headers.");
303                         }
304                         break;
305
306                 case ATTACHCMDSW:
307                         /*
308                         ** Attach files to current draft.
309                         */
310
311                         if (*(argp+1) == NULL) {
312                                 advise(NULL, "attach command requires file argument(s).");
313                                 break;
314                         }
315
316                         /*
317                         ** Build a command line that causes the user's
318                         ** shell to list the file name arguments.
319                         ** This handles and wildcard expansion, tilde
320                         ** expansion, etc.
321                         */
322                         writelscmd(buf, sizeof(buf), argp);
323
324                         /*
325                         ** Read back the response from the shell,
326                         ** which contains a number of lines with one
327                         ** file name per line.  Remove off the newline.
328                         ** Determine whether we have an absolute or
329                         ** relative path name.  Prepend the current
330                         ** working directory to relative path names.
331                         ** Add the attachment annotation to the draft.
332                         */
333                         if ((f = popen_in_dir(cwd, buf, "r"))) {
334                                 char buf[BUFSIZ];
335
336                                 while (fgets(shell, sizeof(shell), f)) {
337                                         *(strchr(shell, '\n')) = '\0';
338
339                                         if (*shell == '/')
340                                                 sprintf(file, "%s", shell);
341                                         else {
342                                                 sprintf(file, "%s/%s", cwd,
343                                                                 shell);
344                                         }
345                                         snprintf(buf, sizeof buf,
346                                                         "anno -nodate -append "
347                                                         "-comp '%s' -text '%s'"
348                                                         " '%s'",
349                                                         attach_hdr, file,
350                                                         drft);
351                                         if (system(buf) != 0) {
352                                                 advise(NULL, "Could not add attachment header.");
353                                         }
354                                 }
355
356                                 pclose(f);
357                         } else {
358                                 advise("popen", "could not get file from shell");
359                         }
360
361                         break;
362
363                 case DETACHCMDSW:
364                         /*
365                         ** Detach files from current draft.
366                         **
367                         ** Interpret the arguments as
368                         ** attachment numbers.  Decrement any remaining
369                         ** argument number that is greater than the one
370                         ** just processed after processing each one so
371                         ** that the numbering stays correct.
372                         */
373                         for (arguments=argp+1; *arguments; arguments++) {
374                                 char buf[BUFSIZ];
375                                 int n;
376
377                                 if (**arguments == '\0') {
378                                         continue;
379                                 }
380
381                                 n = atoi(*arguments);
382                                 snprintf(buf, sizeof buf, "anno -delete "
383                                                 "-comp '%s' -number '%d' "
384                                                 "'%s'",
385                                                 attach_hdr, n, drft);
386                                 if (system(buf) != 0) {
387                                         advise(NULL, "Could not delete attachment header.");
388                                 }
389
390                                 for (argp=arguments+1; *argp; argp++) {
391                                         if (atoi(*argp) > n) {
392                                                 if (atoi(*argp) == 1) {
393                                                         *argp = "";
394                                                 } else {
395                                                         sprintf(*argp, "%d", atoi(*argp) - 1);
396                                                 }
397                                         }
398                                 }
399                         }
400                         break;
401
402                 default:
403                         /* Unknown command */
404                         advise(NULL, "say what?");
405                         break;
406                 }
407         }
408         /*NOTREACHED*/
409 }
410
411
412
413 /*
414 ** Build a command line of the form $SHELL -c "cd 'cwd'; cmd argp ... ;
415 ** trailcmd".
416 */
417 static void
418 writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp)
419 {
420         char *cp;
421         /*
422         ** Note that we do not quote -- the argp from the user
423         ** is assumed to be quoted as they desire. (We can't treat
424         ** it as pure literal as that would prevent them using ~,
425         ** wildcards, etc.) The buffer produced by this function
426         ** should be given to popen_in_dir() or system_in_dir() so
427         ** that the current working directory is set correctly.
428         */
429         int ln = snprintf(buf, bufsz, "$SHELL -c \"%s", cmd);
430         /*
431         ** NB that some snprintf() return -1 on overflow rather than the
432         ** new C99 mandated 'number of chars that would have been written'
433         */
434         /*
435         ** length checks here and inside the loop allow for the
436         ** trailing ';', trailcmd, '"' and NUL
437         */
438         int trailln = strlen(trailcmd) + 3;
439         if (ln < 0 || ln + trailln > bufsz)
440                 adios(NULL, "arguments too long");
441
442         cp = buf + ln;
443
444         while (*++argp) {
445                 ln = strlen(*argp);
446                 /* +1 for leading space */
447                 if (ln + trailln + 1 > bufsz - (cp-buf))
448                         adios(NULL, "arguments too long");
449                 *cp++ = ' ';
450                 memcpy(cp, *argp, ln+1);
451                 cp += ln;
452         }
453         if (*trailcmd) {
454                 *cp++ = ';';
455                 strcpy(cp, trailcmd);
456                 cp += trailln - 3;
457         }
458         *cp++ = '"';
459         *cp = 0;
460 }
461
462 /*
463 ** Build a command line that causes the user's shell to list the file name
464 ** arguments.  This handles and wildcard expansion, tilde expansion, etc.
465 */
466 static void
467 writelscmd(char *buf, int bufsz, char **argp)
468 {
469         writesomecmd(buf, bufsz, "ls", "", argp);
470 }
471
472 /*
473 ** Like system(), but run the command in directory dir.
474 ** This assumes the program is single-threaded!
475 */
476 static int
477 system_in_dir(const char *dir, const char *cmd)
478 {
479         char olddir[BUFSIZ];
480         int r;
481         if (getcwd(olddir, sizeof(olddir)) == 0)
482                 adios("getcwd", "could not get working directory");
483         if (chdir(dir) != 0)
484                 adios("chdir", "could not change working directory");
485         r = system(cmd);
486         if (chdir(olddir) != 0)
487                 adios("chdir", "could not change working directory");
488         return r;
489 }
490
491 /* ditto for popen() */
492 static FILE*
493 popen_in_dir(const char *dir, const char *cmd, const char *type)
494 {
495         char olddir[BUFSIZ];
496         FILE *f;
497         if (getcwd(olddir, sizeof(olddir)) == 0)
498                 adios("getcwd", "could not get working directory");
499         if (chdir(dir) != 0)
500                 adios("chdir", "could not change working directory");
501         f = popen(cmd, type);
502         if (chdir(olddir) != 0)
503                 adios("chdir", "could not change working directory");
504         return f;
505 }
506
507
508 /*
509 ** EDIT
510 */
511
512 static char *edsave = NULL;  /* the editor we used previously */
513
514
515 static int
516 editfile(char **ed, char **arg, char *file, int use)
517 {
518         int pid, status, vecp;
519         char *cp, *vec[MAXARGS];
520
521         if (!*ed || !**ed) {
522                 /* We have no explicit editor. */
523                 if (edsave) {
524                         /* Use the previous editor ... */
525                         *ed = edsave;
526                         if (!(cp = mhbasename(*ed)))
527                                 cp = *ed;
528
529                         /* but prefer one specified via "editor-next" */
530                         cp = concat(cp, "-next", NULL);
531                         if ((cp = context_find(cp)))
532                                 *ed = cp;
533                 } else {
534                         /* set initial editor */
535                         *ed = defaulteditor;
536                 }
537         }
538
539         context_save();
540         fflush(stdout);
541
542         switch (pid = fork()) {
543         case NOTOK:
544                 advise("fork", "unable to");
545                 status = NOTOK;
546                 break;
547
548         case OK:
549                 vecp = 0;
550                 vec[vecp++] = mhbasename(*ed);
551                 if (arg)
552                         while (*arg)
553                                 vec[vecp++] = *arg++;
554                 vec[vecp++] = file;
555                 vec[vecp] = NULL;
556
557                 execvp(*ed, vec);
558                 fprintf(stderr, "%s: unable to exec ", invo_name);
559                 perror(*ed);
560                 _exit(-1);
561
562         default:
563                 if ((status = pidwait(pid, NOTOK))) {
564                         if ((status & 0xff00) == 0xff00) {
565                                 /* cmd not found or pidwait() failed */
566                                 status = -1;
567                                 break;
568                         }
569                         if (status & 0x00ff) {
570                                 /* terminated by signal */
571                                 advise(NULL, "%s terminated by signal %d",
572                                                 *ed, status & 0x7f);
573                         } else {
574                                 /* failure exit */
575                                 advise(NULL, "%s exited with return code %d",
576                                                 *ed, (status & 0xff00) >> 8);
577                         }
578                         status = -1;
579                         break;
580                 }
581         }
582
583         /* remember which editor we used */
584         edsave = getcpy(*ed);
585
586         *ed = NULL;
587
588         return status;
589 }
590
591
592 /*
593 ** SEND
594 */
595
596 static int
597 sendfile(char **arg, char *file)
598 {
599         pid_t child_id;
600         int vecp;
601         char *vec[MAXARGS];
602
603         context_save();  /* save the context file */
604         fflush(stdout);
605
606         switch (child_id = fork()) {
607         case NOTOK:
608                 advise(NULL, "unable to fork, so sending directly...");
609                 /* fall */
610         case OK:
611                 vecp = 0;
612                 vec[vecp++] = invo_name;
613                 if (arg)
614                         while (*arg)
615                                 vec[vecp++] = *arg++;
616                 vec[vecp++] = file;
617                 vec[vecp] = NULL;
618
619                 execvp("send", vec);
620                 fprintf(stderr, "%s: unable to exec ", invo_name);
621                 perror("send");
622                 _exit(-1);
623
624         default:
625                 if (pidwait(child_id, OK) == 0)
626                         done(0);
627                 return 1;
628         }
629 }
630
631
632 /*
633 ** refile msg into another folder
634 */
635 static int
636 refile(char **arg, char *file)
637 {
638         pid_t pid;
639         register int vecp;
640         char *vec[MAXARGS];
641
642         vecp = 0;
643         vec[vecp++] = "refile";
644         vec[vecp++] = "-nolink";  /* override bad .mh_profile defaults */
645         vec[vecp++] = "-file";
646         vec[vecp++] = file;
647
648         while (arg && *arg) {
649                 vec[vecp++] = *arg++;
650         }
651         vec[vecp] = NULL;
652
653         context_save();  /* save the context file */
654         fflush(stdout);
655
656         switch (pid = fork()) {
657         case -1:
658                 advise("fork", "unable to");
659                 return -1;
660
661         case 0:
662                 execvp(*vec, vec);
663                 fprintf(stderr, "%s: unable to exec ", invo_name);
664                 perror(*vec);
665                 _exit(-1);
666
667         default:
668                 return (pidwait(pid, -1));
669         }
670 }
671
672
673 /*
674 ** Remove the draft file
675 */
676
677 static int
678 removefile(char *drft)
679 {
680         if (unlink(drft) == NOTOK)
681                 adios(drft, "unable to unlink");
682
683         return OK;
684 }