0ec8e41a5211785f6ebf173c68b60d045b9ace45
[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 [-ln]             This option lists the attachments on the
32 **                                draft.  -l gets long listings, -n gets
33 **                                numbered listings.
34 **
35 **        detach files            This option removes attachments from the
36 **        detach -n numbers       draft.  This can be done by file name or
37 **                                by attachment number.
38 */
39
40 #include <h/mh.h>
41 #include <fcntl.h>
42 #include <signal.h>
43 #include <h/mime.h>
44 #include <h/utils.h>
45
46 static struct swit whatnowswitches[] = {
47 #define EDITRSW  0
48         { "editor editor", 0 },
49 #define NEDITSW  1
50         { "noedit", 0 },
51 #define PRMPTSW  2
52         { "prompt string", 4 },
53 #define VERSIONSW  3
54         { "version", 0 },
55 #define HELPSW  4
56         { "help", 0 },
57         { NULL, 0 }
58 };
59
60 /*
61 ** Options at the "whatnow" prompt
62 */
63 static struct swit aleqs[] = {
64 #define EDITSW  0
65         { "edit [<editor> <switches>]", 0 },
66 #define REFILEOPT  1
67         { "refile [<switches>] +folder", 0 },
68 #define DISPSW  2
69         { "display [<switches>]", 0 },
70 #define LISTSW  3
71         { "list [<switches>]", 0 },
72 #define SENDSW  4
73         { "send [<switches>]", 0 },
74 #define PUSHSW  5
75         { "push [<switches>]", 0 },
76 #define QUITSW  6
77         { "quit [-delete]", 0 },
78 #define DELETESW  7
79         { "delete", 0 },
80 #define CDCMDSW  8
81         { "cd [directory]", 0 },
82 #define PWDCMDSW  9
83         { "pwd", 0 },
84 #define LSCMDSW  10
85         { "ls", 0 },
86 #define ATTACHCMDSW  11
87         { "attach", 0 },
88 #define DETACHCMDSW  12
89         { "detach [-n]", 2 },
90 #define ALISTCMDSW  13
91         { "alist [-ln] ", 2 },
92         { NULL, 0 }
93 };
94
95 static char *myprompt = "\nWhat now? ";
96
97 /*
98 ** static prototypes
99 */
100 static int editfile(char **, char **, char *, int, struct msgs *,
101         char *, char *, int);
102 static int sendfile(char **, char *, int);
103 static void sendit(char *, char **, char *, int);
104 static int removefile(char *);
105 static void writelscmd(char *, int, char **);
106 static void writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp);
107 static FILE* popen_in_dir(const char *dir, const char *cmd, const char *type);
108 static int system_in_dir(const char *dir, const char *cmd);
109
110
111 #ifdef HAVE_LSTAT
112 static int copyf(char *, char *);
113 #endif
114
115
116 int
117 main(int argc, char **argv)
118 {
119         int nedit = 0, use = 0;
120         char *cp;
121         char *ed = NULL, *drft = NULL, *msgnam = NULL;
122         char buf[BUFSIZ], prompt[BUFSIZ];
123         char **argp, **arguments;
124         struct stat st;
125         char cwd[MAXPATHLEN + 1];  /* current working directory */
126         char file[MAXPATHLEN + 1];  /* file name buffer */
127         char shell[MAXPATHLEN + 1];  /* shell response buffer */
128         FILE *f;  /* read pointer for bgnd proc */
129         char *l;  /* set on -l to alist  command */
130         int n;  /* set on -n to alist command */
131
132 #ifdef LOCALE
133         setlocale(LC_ALL, "");
134 #endif
135
136         invo_name = mhbasename(argv[0]);
137
138         /* read user profile/context */
139         context_read();
140
141         arguments = getarguments(invo_name, argc, argv, 1);
142         argp = arguments;
143
144         /*
145         ** Get the initial current working directory.
146         */
147
148         if (getcwd(cwd, sizeof (cwd)) == NULL) {
149                 adios("getcwd", "could not get working directory");
150         }
151
152         while ((cp = *argp++)) {
153                 if (*cp == '-') {
154                         switch (smatch(++cp, whatnowswitches)) {
155                         case AMBIGSW:
156                                 ambigsw(cp, whatnowswitches);
157                                 done(1);
158                         case UNKWNSW:
159                                 adios(NULL, "-%s unknown", cp);
160
161                         case HELPSW:
162                                 snprintf(buf, sizeof(buf),
163                                                 "%s [switches] [file]",
164                                                 invo_name);
165                                 print_help(buf, whatnowswitches, 1);
166                                 done(1);
167                         case VERSIONSW:
168                                 print_version(invo_name);
169                                 done(1);
170
171                         case EDITRSW:
172                                 if (!(ed = *argp++) || *ed == '-')
173                                         adios(NULL, "missing argument to %s",
174                                                         argp[-2]);
175                                 nedit = 0;
176                                 continue;
177                         case NEDITSW:
178                                 nedit++;
179                                 continue;
180
181                         case PRMPTSW:
182                                 if (!(myprompt = *argp++) || *myprompt == '-')
183                                         adios(NULL, "missing argument to %s",
184                                                         argp[-2]);
185                                 continue;
186
187                         }
188                 }
189                 if (drft)
190                         adios(NULL, "only one draft at a time!");
191                 else
192                         drft = cp;
193         }
194
195         if ((drft == NULL && (drft = getenv("mhdraft")) == NULL) || *drft == 0)
196                 drft = getcpy(m_draft(seq_cur));
197
198         msgnam = (cp = getenv("mhaltmsg")) && *cp ? getcpy(cp) : NULL;
199
200         if ((cp = getenv("mhuse")) && *cp)
201                 use = atoi(cp);
202
203         if (ed == NULL && ((ed = getenv("mheditor")) == NULL || *ed == 0)) {
204                 ed = NULL;
205                 nedit++;
206         }
207
208         /* start editing the draft, unless -noedit was given */
209         if (!nedit && editfile(&ed, NULL, drft, use, NULL, msgnam, NULL, 1)
210                         < 0)
211                 done(1);
212
213         snprintf(prompt, sizeof(prompt), myprompt, invo_name);
214         for (;;) {
215                 if (!(argp = getans(prompt, aleqs))) {
216                         unlink(altmsglink);
217                         done(1);
218                 }
219                 switch (smatch(*argp, aleqs)) {
220                 case DISPSW:
221                         /*
222                         ** display the message being replied to,
223                         ** or distributed
224                         */
225                         if (msgnam)
226                                 showfile(++argp, msgnam);
227                         else
228                                 advise(NULL, "no alternate message to display");
229                         break;
230
231                 case EDITSW:
232                         /* Call an editor on the draft file */
233                         if (*++argp)
234                                 ed = *argp++;
235                         if (editfile(&ed, argp, drft, NOUSE, NULL,
236                                         msgnam, NULL, 1) == NOTOK)
237                                 done(1);
238                         break;
239
240                 case LISTSW:
241                         /* display the draft file */
242                         showfile(++argp, drft);
243                         break;
244
245                 case QUITSW:
246                         /* Quit, and possibly delete the draft */
247                         if (*++argp && (*argp[0] == 'd' ||
248                                 ((*argp)[0] == '-' && (*argp)[1] == 'd'))) {
249                                 removefile(drft);
250                         } else {
251                                 if (stat(drft, &st) != NOTOK)
252                                         advise(NULL, "draft left on %s", drft);
253                         }
254                         done(1);
255
256                 case DELETESW:
257                         /* Delete draft and exit */
258                         removefile(drft);
259                         done(1);
260
261                 case PUSHSW:
262                         /* Send draft in background */
263                         if (sendfile(++argp, drft, 1))
264                                 done(1);
265                         break;
266
267                 case SENDSW:
268                         /* Send draft */
269                         sendfile(++argp, drft, 0);
270                         break;
271
272                 case REFILEOPT:
273                         /* Refile the draft */
274                         if (refile(++argp, drft) == 0)
275                                 done(0);
276                         break;
277
278                 case CDCMDSW:
279                         /*
280                         ** Change the working directory for attachments
281                         **
282                         ** Run the directory through the user's shell
283                         ** so that we can take advantage of any syntax
284                         ** that the user is accustomed to.  Read back
285                         ** the absolute path.
286                         */
287
288                         if (*(argp+1) == NULL) {
289                                 sprintf(buf, "$SHELL -c \"cd;pwd\"");
290                         } else {
291                                 writesomecmd(buf, BUFSIZ, "cd", "pwd", argp);
292                         }
293                         if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
294                                 fgets(cwd, sizeof (cwd), f);
295
296                                 if (strchr(cwd, '\n') != NULL)
297                                                 *strchr(cwd, '\n') = '\0';
298
299                                 pclose(f);
300                         } else {
301                                 advise("popen", "could not get directory");
302                         }
303
304                         break;
305
306                 case PWDCMDSW:
307                         /* Print the working directory for attachments */
308                         printf("%s\n", cwd);
309                         break;
310
311                 case LSCMDSW:
312                         /*
313                         ** List files in the current attachment working
314                         ** directory
315                         **
316                         ** Use the user's shell so that we can take
317                         ** advantage of any syntax that the user is
318                         ** accustomed to.
319                         */
320                         writelscmd(buf, sizeof(buf), argp);
321                         system_in_dir(cwd, buf);
322                         break;
323
324                 case ALISTCMDSW:
325                         /*
326                         ** List attachments on current draft.  Options are:
327                         **
328                         ** -l    long listing (full path names)
329                         ** -n    numbers listing
330                         */
331
332                         l = NULL;
333                         n = 0;
334
335                         while (*++argp != NULL) {
336                                 if (strcmp(*argp, "-l") == 0)
337                                         l = "/";
338
339                                 else if (strcmp(*argp, "-n") == 0)
340                                         n = 1;
341
342                                 else if (strcmp(*argp, "-ln") == 0 ||
343                                                 strcmp(*argp, "-nl") == 0) {
344                                         l = "/";
345                                         n = 1;
346                                 } else {
347                                         n = -1;
348                                         break;
349                                 }
350                         }
351
352                         if (n == -1)
353                                 advise(NULL, "usage is alist [-ln].");
354
355                         else
356                                 annolist(drft, attach_hdr, l, n);
357
358                         break;
359
360                 case ATTACHCMDSW:
361                         /*
362                         ** Attach files to current draft.
363                         */
364
365                         if (*(argp+1) == NULL) {
366                                 advise(NULL, "attach command requires file argument(s).");
367                                 break;
368                         }
369
370                         /*
371                         ** Build a command line that causes the user's
372                         ** shell to list the file name arguments.
373                         ** This handles and wildcard expansion, tilde
374                         ** expansion, etc.
375                         */
376                         writelscmd(buf, sizeof(buf), argp);
377
378                         /*
379                         ** Read back the response from the shell,
380                         ** which contains a number of lines with one
381                         ** file name per line.  Remove off the newline.
382                         ** Determine whether we have an absolute or
383                         ** relative path name.  Prepend the current
384                         ** working directory to relative path names.
385                         ** Add the attachment annotation to the draft.
386                         */
387                         if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
388                                 while (fgets(shell, sizeof (shell), f)
389                                                 != NULL) {
390                                         *(strchr(shell, '\n')) = '\0';
391
392                                         if (*shell == '/')
393                                                 annotate(drft, attach_hdr,
394                                                                 shell, 1, 0,
395                                                                 -2, 1);
396                                         else {
397                                                 sprintf(file, "%s/%s", cwd,
398                                                                 shell);
399                                                 annotate(drft, attach_hdr,
400                                                                 file, 1, 0,
401                                                                 -2, 1);
402                                         }
403                                 }
404
405                                 pclose(f);
406                         } else {
407                                 advise("popen", "could not get file from shell");
408                         }
409
410                         break;
411
412                 case DETACHCMDSW:
413                         /*
414                         ** Detach files from current draft.
415                         **
416                         ** Scan the arguments for a -n.  Mixed file
417                         ** names and numbers aren't allowed, so this
418                         ** catches a -n anywhere in the argument list.
419                         */
420
421                         for (n = 0, arguments = argp + 1;
422                                         *arguments != NULL;
423                                         arguments++) {
424                                 if (strcmp(*arguments, "-n") == 0) {
425                                                 n = 1;
426                                                 break;
427                                 }
428                         }
429
430                         /*
431                         ** A -n was found so interpret the arguments as
432                         ** attachment numbers.  Decrement any remaining
433                         ** argument number that is greater than the one
434                         ** just processed after processing each one so
435                         ** that the numbering stays correct.
436                         */
437
438                         if (n == 1) {
439                                 for (arguments = argp + 1;
440                                                 *arguments != NULL;
441                                                 arguments++) {
442                                         if (strcmp(*arguments, "-n") == 0)
443                                                 continue;
444
445                                         if (**arguments != '\0') {
446                                                 n = atoi(*arguments);
447                                                 annotate(drft, attach_hdr,
448                                                                 NULL, 1, 0,
449                                                                 n, 1);
450
451                                                 for (argp = arguments + 1; *argp != NULL; argp++) {
452                                                         if (atoi(*argp) > n) {
453                                                                 if (atoi(*argp) == 1)
454                                                                         *argp = "";
455                                                                 else
456                                                                         sprintf(*argp, "%d", atoi(*argp) - 1);
457                                                         }
458                                                 }
459                                         }
460                                 }
461                         }
462
463                         /*
464                         ** The arguments are interpreted as file names.
465                         ** Run them through the user's shell for wildcard
466                         ** expansion and other goodies.  Do this from
467                         ** the current working directory if the argument
468                         ** is not an absolute path name (does not begin
469                         ** with a /).
470                         **
471                         ** We feed all the file names to the shell at
472                         ** once, otherwise you can't provide a file name
473                         ** with a space in it.
474                         */
475                         writelscmd(buf, sizeof(buf), argp);
476                         if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) {
477                                 while (fgets(shell, sizeof (shell), f)
478                                                 != NULL) {
479                                         *(strchr(shell, '\n')) = '\0';
480                                         annotate(drft, attach_hdr, shell,
481                                                         1, 0, 0, 1);
482                                 }
483                                 pclose(f);
484                         } else {
485                                 advise("popen", "could not get file from shell");
486                         }
487
488                         break;
489
490                 default:
491                         /* Unknown command */
492                         advise(NULL, "say what?");
493                         break;
494                 }
495         }
496         /*NOTREACHED*/
497 }
498
499
500
501 /*
502 ** Build a command line of the form $SHELL -c "cd 'cwd'; cmd argp ... ;
503 ** trailcmd".
504 */
505 static void
506 writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp)
507 {
508         char *cp;
509         /*
510         ** Note that we do not quote -- the argp from the user
511         ** is assumed to be quoted as they desire. (We can't treat
512         ** it as pure literal as that would prevent them using ~,
513         ** wildcards, etc.) The buffer produced by this function
514         ** should be given to popen_in_dir() or system_in_dir() so
515         ** that the current working directory is set correctly.
516         */
517         int ln = snprintf(buf, bufsz, "$SHELL -c \"%s", cmd);
518         /*
519         ** NB that some snprintf() return -1 on overflow rather than the
520         ** new C99 mandated 'number of chars that would have been written'
521         */
522         /*
523         ** length checks here and inside the loop allow for the
524         ** trailing ';', trailcmd, '"' and NUL
525         */
526         int trailln = strlen(trailcmd) + 3;
527         if (ln < 0 || ln + trailln > bufsz)
528                 adios(NULL, "arguments too long");
529
530         cp = buf + ln;
531
532         while (*++argp != NULL) {
533                 ln = strlen(*argp);
534                 /* +1 for leading space */
535                 if (ln + trailln + 1 > bufsz - (cp-buf))
536                         adios(NULL, "arguments too long");
537                 *cp++ = ' ';
538                 memcpy(cp, *argp, ln+1);
539                 cp += ln;
540         }
541         if (*trailcmd) {
542                 *cp++ = ';';
543                 strcpy(cp, trailcmd);
544                 cp += trailln - 3;
545         }
546         *cp++ = '"';
547         *cp = 0;
548 }
549
550 /*
551 ** Build a command line that causes the user's shell to list the file name
552 ** arguments.  This handles and wildcard expansion, tilde expansion, etc.
553 */
554 static void
555 writelscmd(char *buf, int bufsz, char **argp)
556 {
557         writesomecmd(buf, bufsz, "ls", "", argp);
558 }
559
560 /*
561 ** Like system(), but run the command in directory dir.
562 ** This assumes the program is single-threaded!
563 */
564 static int
565 system_in_dir(const char *dir, const char *cmd)
566 {
567         char olddir[BUFSIZ];
568         int r;
569         if (getcwd(olddir, sizeof(olddir)) == 0)
570                 adios("getcwd", "could not get working directory");
571         if (chdir(dir) != 0)
572                 adios("chdir", "could not change working directory");
573         r = system(cmd);
574         if (chdir(olddir) != 0)
575                 adios("chdir", "could not change working directory");
576         return r;
577 }
578
579 /* ditto for popen() */
580 static FILE*
581 popen_in_dir(const char *dir, const char *cmd, const char *type)
582 {
583         char olddir[BUFSIZ];
584         FILE *f;
585         if (getcwd(olddir, sizeof(olddir)) == 0)
586                 adios("getcwd", "could not get working directory");
587         if (chdir(dir) != 0)
588                 adios("chdir", "could not change working directory");
589         f = popen(cmd, type);
590         if (chdir(olddir) != 0)
591                 adios("chdir", "could not change working directory");
592         return f;
593 }
594
595
596 /*
597 ** EDIT
598 */
599
600 static int  reedit = 0;  /* have we been here before? */
601 static char *edsave = NULL;  /* the editor we used previously */
602
603
604 static int
605 editfile(char **ed, char **arg, char *file, int use, struct msgs *mp,
606         char *altmsg, char *cwd, int save_editor)
607 {
608         int pid, status, vecp;
609         char altpath[BUFSIZ], linkpath[BUFSIZ];
610         char *cp, *vec[MAXARGS];
611         struct stat st;
612
613 #ifdef HAVE_LSTAT
614         int slinked = 0;
615 #endif /* HAVE_LSTAT */
616
617         /* Was there a previous edit session? */
618         if (reedit) {
619                 if (!*ed) {  /* no explicit editor */
620                         *ed = edsave;  /* so use the previous one */
621                         if ((cp = mhbasename(*ed)) == NULL)
622                                 cp = *ed;
623
624                         /* unless we've specified it via "editor-next" */
625                         cp = concat(cp, "-next", NULL);
626                         if ((cp = context_find(cp)) != NULL)
627                                 *ed = cp;
628                 }
629         } else {
630                 /* set initial editor */
631                 if (*ed == NULL && (*ed = context_find("editor")) == NULL)
632                         *ed = defaulteditor;
633         }
634
635         if (altmsg) {
636                 if (mp == NULL || *altmsg == '/' || cwd == NULL)
637                         strncpy(altpath, altmsg, sizeof(altpath));
638                 else
639                         snprintf(altpath, sizeof(altpath), "%s/%s",
640                                         mp->foldpath, altmsg);
641                 if (cwd == NULL)
642                         strncpy(linkpath, altmsglink, sizeof(linkpath));
643                 else
644                         snprintf(linkpath, sizeof(linkpath), "%s/%s",
645                                         cwd, altmsglink);
646         }
647
648         if (altmsg) {
649                 unlink(linkpath);
650 #ifdef HAVE_LSTAT
651                 if (link(altpath, linkpath) == NOTOK) {
652                         symlink(altpath, linkpath);
653                         slinked = 1;
654                 } else {
655                         slinked = 0;
656                 }
657 #else /* not HAVE_LSTAT */
658                 link(altpath, linkpath);
659 #endif /* not HAVE_LSTAT */
660         }
661
662         context_save();  /* save the context file */
663         fflush(stdout);
664
665         switch (pid = fork()) {
666         case NOTOK:
667                 advise("fork", "unable to");
668                 status = NOTOK;
669                 break;
670
671         case OK:
672                 if (cwd)
673                         chdir(cwd);
674                 if (altmsg) {
675                         if (mp)
676                                 m_putenv("mhfolder", mp->foldpath);
677                         m_putenv("editalt", altpath);
678                 }
679
680                 vecp = 0;
681                 vec[vecp++] = mhbasename(*ed);
682                 if (arg)
683                         while (*arg)
684                                 vec[vecp++] = *arg++;
685                 vec[vecp++] = file;
686                 vec[vecp] = NULL;
687
688                 execvp(*ed, vec);
689                 fprintf(stderr, "unable to exec ");
690                 perror(*ed);
691                 _exit(-1);
692
693         default:
694                 if ((status = pidwait(pid, NOTOK))) {
695                         if (((status & 0xff00) != 0xff00)
696                                 && (!reedit || (status & 0x00ff))) {
697                                 if (!use && (status & 0xff00) && (rename(file, cp = m_backup (file)) != NOTOK)) {
698                                         advise(NULL, "problems with edit--draft left in %s", cp);
699                                 } else {
700                                         advise(NULL, "problems with edit--%s preserved", file);
701                                 }
702                         }
703                         status = -2;  /* maybe "reedit ? -2 : -1"? */
704                         break;
705                 }
706
707                 reedit++;
708 #ifdef HAVE_LSTAT
709                 if (altmsg && mp && !is_readonly(mp) && (slinked ?
710                                 lstat (linkpath, &st) != NOTOK &&
711                                 S_ISREG(st.st_mode) &&
712                                 copyf(linkpath, altpath) == NOTOK :
713                                 stat(linkpath, &st) != NOTOK &&
714                                 st.st_nlink == 1 &&
715                                 (unlink(altpath) == NOTOK ||
716                                 link(linkpath, altpath) == NOTOK)))
717                         advise(linkpath, "unable to update %s from", altmsg);
718 #else /* HAVE_LSTAT */
719                 if (altmsg && mp && !is_readonly(mp) &&
720                                 stat(linkpath, &st) != NOTOK &&
721                                 st.st_nlink == 1 &&
722                                 (unlink(altpath) == NOTOK ||
723                                 link(linkpath, altpath) == NOTOK))
724                         advise(linkpath, "unable to update %s from", altmsg);
725 #endif /* HAVE_LSTAT */
726         }
727
728         /* normally, we remember which editor we used */
729         if (save_editor)
730                 edsave = getcpy(*ed);
731
732         *ed = NULL;
733         if (altmsg)
734                 unlink(linkpath);
735
736         return status;
737 }
738
739
740 #ifdef HAVE_LSTAT
741 static int
742 copyf(char *ifile, char *ofile)
743 {
744         int i, in, out;
745         char buffer[BUFSIZ];
746
747         if ((in = open(ifile, O_RDONLY)) == NOTOK)
748                 return NOTOK;
749         if ((out = open(ofile, O_WRONLY | O_TRUNC)) == NOTOK) {
750                 admonish(ofile, "unable to open and truncate");
751                 close(in);
752                 return NOTOK;
753         }
754
755         while ((i = read(in, buffer, sizeof(buffer))) > OK)
756                 if (write(out, buffer, i) != i) {
757                         advise(ofile, "may have damaged");
758                         i = NOTOK;
759                         break;
760                 }
761
762         close(in);
763         close(out);
764         return i;
765 }
766 #endif /* HAVE_LSTAT */
767
768
769 /*
770 ** SEND
771 */
772
773 static int
774 sendfile(char **arg, char *file, int pushsw)
775 {
776         pid_t child_id;
777         int i, vecp;
778         char *cp, *sp, *vec[MAXARGS];
779
780         /*
781         ** If the sendproc is the nmh command `send', then we call
782         ** those routines directly rather than exec'ing the command.
783         */
784         if (strcmp(sp = mhbasename(sendproc), "send") == 0) {
785                 cp = invo_name;
786                 sendit(invo_name = sp, arg, file, pushsw);
787                 invo_name = cp;
788                 return 1;
789         }
790
791         /* some different sendproc */
792
793         context_save();  /* save the context file */
794         fflush(stdout);
795
796         for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++)
797                 sleep(5);
798         switch (child_id) {
799         case NOTOK:
800                 advise(NULL, "unable to fork, so sending directly...");
801                 /* fall */
802         case OK:
803                 vecp = 0;
804                 vec[vecp++] = invo_name;
805                 if (pushsw)
806                         vec[vecp++] = "-push";
807                 if (arg)
808                         while (*arg)
809                                 vec[vecp++] = *arg++;
810                 vec[vecp++] = file;
811                 vec[vecp] = NULL;
812
813                 execvp(sendproc, vec);
814                 fprintf(stderr, "unable to exec ");
815                 perror(sendproc);
816                 _exit(-1);
817
818         default:
819                 if (pidwait(child_id, OK) == 0)
820                         done(0);
821                 return 1;
822         }
823 }
824
825
826 static struct swit  sendswitches[] = {
827 #define ALIASW  0
828         { "alias aliasfile", 0 },
829 #define DEBUGSW  1
830         { "debug", -5 },
831 #define FILTSW  2
832         { "filter filterfile", 0 },
833 #define NFILTSW  3
834         { "nofilter", 0 },
835 #define FRMTSW  4
836         { "format", 0 },
837 #define NFRMTSW  5
838         { "noformat", 0 },
839 #define FORWSW  6
840         { "forward", 0 },
841 #define NFORWSW  7
842         { "noforward", 0 },
843 #define SPSHSW  8
844         { "push", 0 },
845 #define NSPSHSW  9
846         { "nopush", 0 },
847 #define VERBSW  10
848         { "verbose", 0 },
849 #define NVERBSW  11
850         { "noverbose", 0 },
851 #define WATCSW  12
852         { "watch", 0 },
853 #define NWATCSW  13
854         { "nowatch", 0 },
855 #define WIDTHSW  14
856         { "width columns", 0 },
857 #define SVERSIONSW  15
858         { "version", 0 },
859 #define SHELPSW  16
860         { "help", 0 },
861         { NULL, 0 }
862 };
863
864
865 extern int debugsw;  /* from sendsbr.c */
866 extern int forwsw;
867 extern int inplace;
868 extern int pushsw;
869 extern int verbsw;
870
871 extern char *altmsg;  /*  .. */
872 extern char *annotext;
873 extern char *distfile;
874
875
876 static void
877 sendit(char *sp, char **arg, char *file, int pushed)
878 {
879         int vecp, n = 1;
880         char *cp, buf[BUFSIZ], **argp;
881         char **arguments, *vec[MAXARGS];
882         struct stat st;
883
884 #ifndef lint
885         int distsw = 0;
886 #endif
887
888         /*
889         ** Make sure these are defined.  In particular, we need
890         ** vec[1] to be NULL, in case "arg" is NULL below.  It
891         ** doesn't matter what is the value of vec[0], but we
892         ** set it to NULL, to help catch "off-by-one" errors.
893         */
894         vec[0] = NULL;
895         vec[1] = NULL;
896
897         /*
898         ** Temporarily copy arg to vec, since the brkstring() call in
899         ** getarguments() will wipe it out before it is merged in.
900         ** Also, we skip the first element of vec, since getarguments()
901         ** skips it.  Then we count the number of arguments
902         ** copied.  The value of "n" will be one greater than
903         ** this in order to simulate the standard argc/argv.
904         */
905         if (arg) {
906                 char **bp;
907
908                 copyip(arg, vec+1, MAXARGS-1);
909                 bp = vec+1;
910                 while (*bp++)
911                         n++;
912         }
913
914         /*
915         ** Merge any arguments from command line (now in vec)
916         ** and arguments from profile.
917         */
918         arguments = getarguments(sp, n, vec, 1);
919         argp = arguments;
920
921         debugsw = 0;
922         forwsw = 1;
923         inplace = 1;
924
925         altmsg = NULL;
926         annotext = NULL;
927         distfile = NULL;
928
929         vecp = 1;  /* we'll get the zero'th element later */
930         vec[vecp++] = "-library";
931         vec[vecp++] = getcpy(toabsdir("+"));
932
933         while ((cp = *argp++)) {
934                 if (*cp == '-') {
935                         switch (smatch(++cp, sendswitches)) {
936                         case AMBIGSW:
937                                 ambigsw(cp, sendswitches);
938                                 return;
939                         case UNKWNSW:
940                                 advise(NULL, "-%s unknown\n", cp);
941                                 return;
942
943                         case SHELPSW:
944                                 snprintf(buf, sizeof(buf),
945                                                 "%s [switches]", sp);
946                                 print_help(buf, sendswitches, 1);
947                                 return;
948                         case SVERSIONSW:
949                                 print_version(invo_name);
950                                 return;
951
952                         case SPSHSW:
953                                 pushed++;
954                                 continue;
955                         case NSPSHSW:
956                                 pushed = 0;
957                                 continue;
958
959                         case FORWSW:
960                                 forwsw++;
961                                 continue;
962                         case NFORWSW:
963                                 forwsw = 0;
964                                 continue;
965
966                         case VERBSW:
967                                 verbsw++;
968                                 vec[vecp++] = --cp;
969                                 continue;
970                         case NVERBSW:
971                                 verbsw = 0;
972                                 vec[vecp++] = --cp;
973                                 continue;
974
975                         case DEBUGSW:
976                                 debugsw++;  /* fall */
977                         case NFILTSW:
978                         case FRMTSW:
979                         case NFRMTSW:
980                         case WATCSW:
981                         case NWATCSW:
982                                 vec[vecp++] = --cp;
983                                 continue;
984
985                         case ALIASW:
986                         case FILTSW:
987                         case WIDTHSW:
988                                 vec[vecp++] = --cp;
989                                 if (!(cp = *argp++) || *cp == '-') {
990                                         advise(NULL, "missing argument to %s",
991                                                         argp[-2]);
992                                         return;
993                                 }
994                                 vec[vecp++] = cp;
995                                 continue;
996
997                         }
998                 }
999                 advise(NULL, "usage: %s [switches]", sp);
1000                 return;
1001         }
1002
1003         /* allow Aliasfile: profile entry */
1004         if ((cp = context_find("Aliasfile"))) {
1005                 char **ap, *dp;
1006
1007                 dp = getcpy(cp);
1008                 for (ap = brkstring(dp, " ", "\n"); ap && *ap; ap++) {
1009                         vec[vecp++] = "-alias";
1010                         vec[vecp++] = getcpy(etcpath(*ap));
1011                 }
1012         }
1013
1014         if (!(cp = getenv("SIGNATURE")) || !*cp)
1015                 if ((cp = context_find("signature")) && *cp)
1016                         m_putenv("SIGNATURE", cp);
1017
1018         if ((annotext = getenv("mhannotate")) == NULL || *annotext == 0)
1019                 annotext = NULL;
1020         if ((altmsg = getenv("mhaltmsg")) == NULL || *altmsg == 0)
1021                 altmsg = NULL;
1022         if (annotext && ((cp = getenv("mhinplace")) != NULL && *cp != 0))
1023                 inplace = atoi(cp);
1024
1025         if ((cp = getenv("mhdist")) && *cp
1026 #ifndef lint
1027                         && (distsw = atoi (cp))
1028 #endif /* not lint */
1029                         && altmsg) {
1030                 vec[vecp++] = "-dist";
1031                 distfile = getcpy(m_mktemp2(altmsg, invo_name, NULL, NULL));
1032                 if (link(altmsg, distfile) == NOTOK)
1033                         adios(distfile, "unable to link %s to", altmsg);
1034         } else {
1035                 distfile = NULL;
1036         }
1037
1038         if (altmsg == NULL || stat(altmsg, &st) == NOTOK) {
1039                 st.st_mtime = 0;
1040                 st.st_dev = 0;
1041                 st.st_ino = 0;
1042         }
1043         if ((pushsw = pushed))
1044                 push();
1045
1046         vec[0] = mhbasename(postproc);
1047         closefds(3);
1048
1049         if (sendsbr(vec, vecp, file, &st, 1) == OK)
1050                 done(0);
1051 }
1052
1053
1054 /*
1055 ** Remove the draft file
1056 */
1057
1058 static int
1059 removefile(char *drft)
1060 {
1061         if (unlink(drft) == NOTOK)
1062                 adios(drft, "unable to unlink");
1063
1064         return OK;
1065 }