X-Git-Url: http://git.marmaro.de/?p=mmh;a=blobdiff_plain;f=uip%2Fwhatnow.c;h=afe4129333387549b9999f40f315c6443d577661;hp=061c8449e196943ad52c38a8ffec5a0ff6120dd8;hb=fb0594cb913cab5e14e2c295b79eb48cc675bbbf;hpb=ced6090a330d3d83d0bce709f756aa3d7d65fea4 diff --git a/uip/whatnow.c b/uip/whatnow.c index 061c844..afe4129 100644 --- a/uip/whatnow.c +++ b/uip/whatnow.c @@ -1,22 +1,820 @@ /* -** whatnow.c -- the nmh `WhatNow' shell +** whatnow.c -- the WhatNow shell ** ** This code is Copyright (c) 2002, by the authors of nmh. See the ** COPYRIGHT file in the root directory of the nmh distribution for ** complete copyright information. +** +** The inclusion of attachments is eased by +** using the header field name mechanism added to anno and send. +** The header field name for attachments is predefined. +** +** Several commands have been added at the whatnow prompt: +** +** cd [ directory ] This option works just like the shell's +** cd command and lets the user change the +** directory from which attachments are +** taken so that long path names are not +** needed with every file. +** +** ls [ ls-options ] This option works just like the normal +** ls command and exists to allow the user +** to verify file names in the directory. +** +** pwd This option works just like the normal +** pwd command and exists to allow the user +** to verify the directory. +** +** attach files This option attaches the named files to +** the draft. +** +** alist [-ln] This option lists the attachments on the +** draft. -l gets long listings, -n gets +** numbered listings. +** +** detach files This option removes attachments from the +** detach -n numbers draft. This can be done by file name or +** by attachment number. */ #include +#include +#include +#include +#include -/* from whatnowsbr.c */ -int WhatNow (int, char **); +static struct swit whatnowswitches[] = { +#define EDITRSW 0 + { "editor editor", 0 }, +#define NEDITSW 1 + { "noedit", 0 }, +#define PRMPTSW 2 + { "prompt string", 4 }, +#define VERSIONSW 3 + { "version", 0 }, +#define HELPSW 4 + { "help", 0 }, + { NULL, 0 } +}; + +/* +** Options at the "whatnow" prompt +*/ +static struct swit aleqs[] = { +#define EDITSW 0 + { "edit [ ]", 0 }, +#define REFILEOPT 1 + { "refile [] +folder", 0 }, +#define DISPSW 2 + { "display", 0 }, +#define LISTSW 3 + { "list", 0 }, +#define SENDSW 4 + { "send []", 0 }, +#define PUSHSW 5 + { "push []", 0 }, +#define QUITSW 6 + { "quit", 0 }, +#define DELETESW 7 + { "delete", 0 }, +#define CDCMDSW 8 + { "cd [directory]", 0 }, +#define PWDCMDSW 9 + { "pwd", 0 }, +#define LSCMDSW 10 + { "ls", 0 }, +#define ATTACHCMDSW 11 + { "attach", 0 }, +#define DETACHCMDSW 12 + { "detach [-n]", 2 }, +#define ALISTCMDSW 13 + { "alist [-ln] ", 2 }, + { NULL, 0 } +}; + +static char *myprompt = "\nWhat now? "; + +/* +** static prototypes +*/ +static int editfile(char **, char **, char *, int, struct msgs *, + char *, char *, int); +static int sendfile(char **, char *, int); +static int removefile(char *); +static void writelscmd(char *, int, char **); +static void writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp); +static FILE* popen_in_dir(const char *dir, const char *cmd, const char *type); +static int system_in_dir(const char *dir, const char *cmd); + + +#ifdef HAVE_LSTAT +static int copyf(char *, char *); +#endif int -main (int argc, char **argv) +main(int argc, char **argv) { + int nedit = 0, use = 0; + char *cp; + char *ed = NULL, *drft = NULL, *msgnam = NULL; + char buf[BUFSIZ], prompt[BUFSIZ]; + char **argp, **arguments; + struct stat st; + char cwd[MAXPATHLEN + 1]; /* current working directory */ + char file[MAXPATHLEN + 1]; /* file name buffer */ + char shell[MAXPATHLEN + 1]; /* shell response buffer */ + FILE *f; /* read pointer for bgnd proc */ + char *l; /* set on -l to alist command */ + int n; /* set on -n to alist command */ + #ifdef LOCALE setlocale(LC_ALL, ""); #endif - return WhatNow (argc, argv); + + invo_name = mhbasename(argv[0]); + + /* read user profile/context */ + context_read(); + + arguments = getarguments(invo_name, argc, argv, 1); + argp = arguments; + + /* + ** Get the initial current working directory. + */ + + if (getcwd(cwd, sizeof (cwd)) == NULL) { + adios("getcwd", "could not get working directory"); + } + + while ((cp = *argp++)) { + if (*cp == '-') { + switch (smatch(++cp, whatnowswitches)) { + case AMBIGSW: + ambigsw(cp, whatnowswitches); + done(1); + case UNKWNSW: + adios(NULL, "-%s unknown", cp); + + case HELPSW: + snprintf(buf, sizeof(buf), + "%s [switches] [file]", + invo_name); + print_help(buf, whatnowswitches, 1); + done(1); + case VERSIONSW: + print_version(invo_name); + done(1); + + case EDITRSW: + if (!(ed = *argp++) || *ed == '-') + adios(NULL, "missing argument to %s", + argp[-2]); + nedit = 0; + continue; + case NEDITSW: + nedit++; + continue; + + case PRMPTSW: + if (!(myprompt = *argp++) || *myprompt == '-') + adios(NULL, "missing argument to %s", + argp[-2]); + continue; + + } + } + if (drft) + adios(NULL, "only one draft at a time!"); + else + drft = cp; + } + + if ((drft == NULL && (drft = getenv("mhdraft")) == NULL) || *drft == 0) + drft = getcpy(m_draft(seq_cur)); + + msgnam = (cp = getenv("mhaltmsg")) && *cp ? getcpy(cp) : NULL; + + if ((cp = getenv("mhuse")) && *cp) + use = atoi(cp); + + if (ed == NULL && ((ed = getenv("mheditor")) == NULL || *ed == 0)) { + ed = NULL; + nedit++; + } + + /* start editing the draft, unless -noedit was given */ + if (!nedit && editfile(&ed, NULL, drft, use, NULL, msgnam, NULL, 1) + < 0) + done(1); + + snprintf(prompt, sizeof(prompt), myprompt, invo_name); + for (;;) { + if (!(argp = getans(prompt, aleqs))) { + unlink(altmsglink); + done(1); + } + switch (smatch(*argp, aleqs)) { + case DISPSW: + /* display the msg being replied to or distributed */ + if (msgnam) { + snprintf(buf, sizeof buf, "%s '%s'", + lproc, msgnam); + system(buf); + } else { + advise(NULL, "no alternate message to display"); + } + break; + + case EDITSW: + /* Call an editor on the draft file */ + if (*++argp) + ed = *argp++; + if (editfile(&ed, argp, drft, NOUSE, NULL, + msgnam, NULL, 1) == NOTOK) + done(1); + break; + + case LISTSW: + /* display the draft file */ + snprintf(buf, sizeof buf, "%s '%s'", lproc, drft); + system(buf); + break; + + case QUITSW: + /* quit */ + if (stat(drft, &st) != NOTOK) { + advise(NULL, "draft left on %s", drft); + } + done(1); + + case DELETESW: + /* Delete draft and exit */ + removefile(drft); + done(1); + + case PUSHSW: + /* Send draft in background */ + if (sendfile(++argp, drft, 1)) + done(1); + break; + + case SENDSW: + /* Send draft */ + sendfile(++argp, drft, 0); + break; + + case REFILEOPT: + /* Refile the draft */ + if (refile(++argp, drft) == 0) + done(0); + break; + + case CDCMDSW: + /* + ** Change the working directory for attachments + ** + ** Run the directory through the user's shell + ** so that we can take advantage of any syntax + ** that the user is accustomed to. Read back + ** the absolute path. + */ + + if (*(argp+1) == NULL) { + sprintf(buf, "$SHELL -c \"cd;pwd\""); + } else { + writesomecmd(buf, BUFSIZ, "cd", "pwd", argp); + } + if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) { + fgets(cwd, sizeof (cwd), f); + + if (strchr(cwd, '\n') != NULL) + *strchr(cwd, '\n') = '\0'; + + pclose(f); + } else { + advise("popen", "could not get directory"); + } + + break; + + case PWDCMDSW: + /* Print the working directory for attachments */ + printf("%s\n", cwd); + break; + + case LSCMDSW: + /* + ** List files in the current attachment working + ** directory + ** + ** Use the user's shell so that we can take + ** advantage of any syntax that the user is + ** accustomed to. + */ + writelscmd(buf, sizeof(buf), argp); + system_in_dir(cwd, buf); + break; + + case ALISTCMDSW: + /* + ** List attachments on current draft. Options are: + ** + ** -l long listing (full path names) + ** -n numbers listing + */ + + l = NULL; + n = 0; + + while (*++argp != NULL) { + if (strcmp(*argp, "-l") == 0) + l = "/"; + + else if (strcmp(*argp, "-n") == 0) + n = 1; + + else if (strcmp(*argp, "-ln") == 0 || + strcmp(*argp, "-nl") == 0) { + l = "/"; + n = 1; + } else { + n = -1; + break; + } + } + + if (n == -1) + advise(NULL, "usage is alist [-ln]."); + + else + annolist(drft, attach_hdr, l, n); + + break; + + case ATTACHCMDSW: + /* + ** Attach files to current draft. + */ + + if (*(argp+1) == NULL) { + advise(NULL, "attach command requires file argument(s)."); + break; + } + + /* + ** Build a command line that causes the user's + ** shell to list the file name arguments. + ** This handles and wildcard expansion, tilde + ** expansion, etc. + */ + writelscmd(buf, sizeof(buf), argp); + + /* + ** Read back the response from the shell, + ** which contains a number of lines with one + ** file name per line. Remove off the newline. + ** Determine whether we have an absolute or + ** relative path name. Prepend the current + ** working directory to relative path names. + ** Add the attachment annotation to the draft. + */ + if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) { + while (fgets(shell, sizeof (shell), f) + != NULL) { + *(strchr(shell, '\n')) = '\0'; + + if (*shell == '/') + annotate(drft, attach_hdr, + shell, 0, + -2, 1); + else { + sprintf(file, "%s/%s", cwd, + shell); + annotate(drft, attach_hdr, + file, 0, + -2, 1); + } + } + + pclose(f); + } else { + advise("popen", "could not get file from shell"); + } + + break; + + case DETACHCMDSW: + /* + ** Detach files from current draft. + ** + ** Scan the arguments for a -n. Mixed file + ** names and numbers aren't allowed, so this + ** catches a -n anywhere in the argument list. + */ + + for (n = 0, arguments = argp + 1; + *arguments != NULL; + arguments++) { + if (strcmp(*arguments, "-n") == 0) { + n = 1; + break; + } + } + + /* + ** A -n was found so interpret the arguments as + ** attachment numbers. Decrement any remaining + ** argument number that is greater than the one + ** just processed after processing each one so + ** that the numbering stays correct. + */ + + if (n == 1) { + for (arguments = argp + 1; + *arguments != NULL; + arguments++) { + if (strcmp(*arguments, "-n") == 0) + continue; + + if (**arguments != '\0') { + n = atoi(*arguments); + annotate(drft, attach_hdr, + NULL, 0, + n, 1); + + for (argp = arguments + 1; *argp != NULL; argp++) { + if (atoi(*argp) > n) { + if (atoi(*argp) == 1) + *argp = ""; + else + sprintf(*argp, "%d", atoi(*argp) - 1); + } + } + } + } + } + + /* + ** The arguments are interpreted as file names. + ** Run them through the user's shell for wildcard + ** expansion and other goodies. Do this from + ** the current working directory if the argument + ** is not an absolute path name (does not begin + ** with a /). + ** + ** We feed all the file names to the shell at + ** once, otherwise you can't provide a file name + ** with a space in it. + */ + writelscmd(buf, sizeof(buf), argp); + if ((f = popen_in_dir(cwd, buf, "r")) != (FILE *)0) { + while (fgets(shell, sizeof (shell), f) + != NULL) { + *(strchr(shell, '\n')) = '\0'; + annotate(drft, attach_hdr, shell, + 0, 0, 1); + } + pclose(f); + } else { + advise("popen", "could not get file from shell"); + } + + break; + + default: + /* Unknown command */ + advise(NULL, "say what?"); + break; + } + } + /*NOTREACHED*/ +} + + + +/* +** Build a command line of the form $SHELL -c "cd 'cwd'; cmd argp ... ; +** trailcmd". +*/ +static void +writesomecmd(char *buf, int bufsz, char *cmd, char *trailcmd, char **argp) +{ + char *cp; + /* + ** Note that we do not quote -- the argp from the user + ** is assumed to be quoted as they desire. (We can't treat + ** it as pure literal as that would prevent them using ~, + ** wildcards, etc.) The buffer produced by this function + ** should be given to popen_in_dir() or system_in_dir() so + ** that the current working directory is set correctly. + */ + int ln = snprintf(buf, bufsz, "$SHELL -c \"%s", cmd); + /* + ** NB that some snprintf() return -1 on overflow rather than the + ** new C99 mandated 'number of chars that would have been written' + */ + /* + ** length checks here and inside the loop allow for the + ** trailing ';', trailcmd, '"' and NUL + */ + int trailln = strlen(trailcmd) + 3; + if (ln < 0 || ln + trailln > bufsz) + adios(NULL, "arguments too long"); + + cp = buf + ln; + + while (*++argp != NULL) { + ln = strlen(*argp); + /* +1 for leading space */ + if (ln + trailln + 1 > bufsz - (cp-buf)) + adios(NULL, "arguments too long"); + *cp++ = ' '; + memcpy(cp, *argp, ln+1); + cp += ln; + } + if (*trailcmd) { + *cp++ = ';'; + strcpy(cp, trailcmd); + cp += trailln - 3; + } + *cp++ = '"'; + *cp = 0; +} + +/* +** Build a command line that causes the user's shell to list the file name +** arguments. This handles and wildcard expansion, tilde expansion, etc. +*/ +static void +writelscmd(char *buf, int bufsz, char **argp) +{ + writesomecmd(buf, bufsz, "ls", "", argp); +} + +/* +** Like system(), but run the command in directory dir. +** This assumes the program is single-threaded! +*/ +static int +system_in_dir(const char *dir, const char *cmd) +{ + char olddir[BUFSIZ]; + int r; + if (getcwd(olddir, sizeof(olddir)) == 0) + adios("getcwd", "could not get working directory"); + if (chdir(dir) != 0) + adios("chdir", "could not change working directory"); + r = system(cmd); + if (chdir(olddir) != 0) + adios("chdir", "could not change working directory"); + return r; +} + +/* ditto for popen() */ +static FILE* +popen_in_dir(const char *dir, const char *cmd, const char *type) +{ + char olddir[BUFSIZ]; + FILE *f; + if (getcwd(olddir, sizeof(olddir)) == 0) + adios("getcwd", "could not get working directory"); + if (chdir(dir) != 0) + adios("chdir", "could not change working directory"); + f = popen(cmd, type); + if (chdir(olddir) != 0) + adios("chdir", "could not change working directory"); + return f; +} + + +/* +** EDIT +*/ + +static int reedit = 0; /* have we been here before? */ +static char *edsave = NULL; /* the editor we used previously */ + + +static int +editfile(char **ed, char **arg, char *file, int use, struct msgs *mp, + char *altmsg, char *cwd, int save_editor) +{ + int pid, status, vecp; + char altpath[BUFSIZ], linkpath[BUFSIZ]; + char *cp, *vec[MAXARGS]; + struct stat st; + +#ifdef HAVE_LSTAT + int slinked = 0; +#endif /* HAVE_LSTAT */ + + /* Was there a previous edit session? */ + if (reedit) { + if (!*ed) { /* no explicit editor */ + *ed = edsave; /* so use the previous one */ + if ((cp = mhbasename(*ed)) == NULL) + cp = *ed; + + /* unless we've specified it via "editor-next" */ + cp = concat(cp, "-next", NULL); + if ((cp = context_find(cp)) != NULL) + *ed = cp; + } + } else { + /* set initial editor */ + if (*ed == NULL && (*ed = context_find("editor")) == NULL) + *ed = defaulteditor; + } + + if (altmsg) { + if (mp == NULL || *altmsg == '/' || cwd == NULL) + strncpy(altpath, altmsg, sizeof(altpath)); + else + snprintf(altpath, sizeof(altpath), "%s/%s", + mp->foldpath, altmsg); + if (cwd == NULL) + strncpy(linkpath, altmsglink, sizeof(linkpath)); + else + snprintf(linkpath, sizeof(linkpath), "%s/%s", + cwd, altmsglink); + } + + if (altmsg) { + unlink(linkpath); +#ifdef HAVE_LSTAT + if (link(altpath, linkpath) == NOTOK) { + symlink(altpath, linkpath); + slinked = 1; + } else { + slinked = 0; + } +#else /* not HAVE_LSTAT */ + link(altpath, linkpath); +#endif /* not HAVE_LSTAT */ + } + + context_save(); /* save the context file */ + fflush(stdout); + + switch (pid = fork()) { + case NOTOK: + advise("fork", "unable to"); + status = NOTOK; + break; + + case OK: + if (cwd) + chdir(cwd); + if (altmsg) { + if (mp) + m_putenv("mhfolder", mp->foldpath); + m_putenv("editalt", altpath); + } + + vecp = 0; + vec[vecp++] = mhbasename(*ed); + if (arg) + while (*arg) + vec[vecp++] = *arg++; + vec[vecp++] = file; + vec[vecp] = NULL; + + execvp(*ed, vec); + fprintf(stderr, "unable to exec "); + perror(*ed); + _exit(-1); + + default: + if ((status = pidwait(pid, NOTOK))) { + if (((status & 0xff00) != 0xff00) + && (!reedit || (status & 0x00ff))) { + if (!use && (status & 0xff00) && (rename(file, cp = m_backup (file)) != NOTOK)) { + advise(NULL, "problems with edit--draft left in %s", cp); + } else { + advise(NULL, "problems with edit--%s preserved", file); + } + } + status = -2; /* maybe "reedit ? -2 : -1"? */ + break; + } + + reedit++; +#ifdef HAVE_LSTAT + if (altmsg && mp && !is_readonly(mp) && (slinked ? + lstat (linkpath, &st) != NOTOK && + S_ISREG(st.st_mode) && + copyf(linkpath, altpath) == NOTOK : + stat(linkpath, &st) != NOTOK && + st.st_nlink == 1 && + (unlink(altpath) == NOTOK || + link(linkpath, altpath) == NOTOK))) + advise(linkpath, "unable to update %s from", altmsg); +#else /* HAVE_LSTAT */ + if (altmsg && mp && !is_readonly(mp) && + stat(linkpath, &st) != NOTOK && + st.st_nlink == 1 && + (unlink(altpath) == NOTOK || + link(linkpath, altpath) == NOTOK)) + advise(linkpath, "unable to update %s from", altmsg); +#endif /* HAVE_LSTAT */ + } + + /* normally, we remember which editor we used */ + if (save_editor) + edsave = getcpy(*ed); + + *ed = NULL; + if (altmsg) + unlink(linkpath); + + return status; +} + + +#ifdef HAVE_LSTAT +static int +copyf(char *ifile, char *ofile) +{ + int i, in, out; + char buffer[BUFSIZ]; + + if ((in = open(ifile, O_RDONLY)) == NOTOK) + return NOTOK; + if ((out = open(ofile, O_WRONLY | O_TRUNC)) == NOTOK) { + admonish(ofile, "unable to open and truncate"); + close(in); + return NOTOK; + } + + while ((i = read(in, buffer, sizeof(buffer))) > OK) + if (write(out, buffer, i) != i) { + advise(ofile, "may have damaged"); + i = NOTOK; + break; + } + + close(in); + close(out); + return i; +} +#endif /* HAVE_LSTAT */ + + +/* +** SEND +*/ + +static int +sendfile(char **arg, char *file, int pushsw) +{ + pid_t child_id; + int i, vecp; + char *vec[MAXARGS]; + + context_save(); /* save the context file */ + fflush(stdout); + + for (i = 0; (child_id = fork()) == NOTOK && i < 5; i++) + sleep(5); + switch (child_id) { + case NOTOK: + advise(NULL, "unable to fork, so sending directly..."); + /* fall */ + case OK: + vecp = 0; + vec[vecp++] = invo_name; + if (pushsw) + vec[vecp++] = "-push"; + if (arg) + while (*arg) + vec[vecp++] = *arg++; + vec[vecp++] = file; + vec[vecp] = NULL; + + execvp(sendproc, vec); + fprintf(stderr, "unable to exec "); + perror(sendproc); + _exit(-1); + + default: + if (pidwait(child_id, OK) == 0) + done(0); + return 1; + } +} + + +/* +** Remove the draft file +*/ + +static int +removefile(char *drft) +{ + if (unlink(drft) == NOTOK) + adios(drft, "unable to unlink"); + + return OK; }