Put structure into anno(1) and its man page.
authormarkus schnalke <meillo@marmaro.de>
Fri, 30 Mar 2012 16:07:52 +0000 (18:07 +0200)
committermarkus schnalke <meillo@marmaro.de>
Fri, 30 Mar 2012 16:07:52 +0000 (18:07 +0200)
The ``newly'' added features (-list, -delete, ...) were simply added
on top, but without rethinking and adjusting anno(1)'s structure. (I
agree, however, that it was the right decision to add these features
to anno(1) instead of writing another tool.) I've reworked anno now to
give it a clear structure. And I've reworked its man page to propagate
this structure.

man/anno.man1
uip/anno.c

index 1e27cf5..e35899e 100644 (file)
@@ -12,17 +12,37 @@ anno \- annotate messages
 .RI [ msgs ]
 .RB [ \-component
 .IR field ]
-.RB [ \-date " | " \-nodate ]
+.RB [ \-text
+.IR body ]
 .RB [ \-append ]
-.RB [ \-list ]
-.RB [ \-delete ]
-.RB [ \-number
-.IR [ num|all ]]
+.RB [ \-date " | " \-nodate ]
 .RB [ \-preserve " | " \-nopreserve ]
 .RB [ \-version ]
 .RB [ \-help ]
+.HP
+.B anno
+.B \-delete
+.RI [ +folder ]
+.RI [ msgs ]
+.RB [ \-component
+.IR field ]
 .RB [ \-text
 .IR body ]
+.RB [ \-number
+.IR num " | all ]
+.RB [ \-preserve " | " \-nopreserve ]
+.RB [ \-version ]
+.RB [ \-help ]
+.HP
+.B anno
+.B \-list
+.RI [ +folder ]
+.RI [ msgs ]
+.RB [ \-component
+.IR field ]
+.RB [ \-number ]
+.RB [ \-version ]
+.RB [ \-help ]
 .ad
 .SH DESCRIPTION
 .B Anno
@@ -31,15 +51,8 @@ manipulates header fields or
 in messages.
 Header fields consist of a field name and an optional field body
 as defined by RFC-2822.
-The
-.B -component
-option specifies the field name, and the
-.B -text
-option specifies the field body.
-.PP
-The messages are the
-.I msgs
-in the named folder.
+The field name may consist of alphanumerics and dashes only.
+The field body may consist of arbitrary text.
 .PP
 Usually, annotation is performed by the commands
 .BR dist ,
@@ -48,18 +61,48 @@ and
 .BR repl ,
 if they are given the
 .B \-anno
-switch.  This allows you to keep track of your distribution of,
+switch.  This allows you to keep track of your redistribution of,
 forwarding of, and replies to a message.
+The
+.B whatnow
+shell uses annoations to manage attachments, too.
 .PP
 By using
-.BR anno ,
-you can perform arbitrary annotations of your own.
+.BR anno
+manually, you can perform arbitrary annotations of your own.
+.PP
+.B Anno
+has three operation modes: Adding, deleting and listing of header lines.
+
+.SS "Add mode
+.PP
+This is the default mode.
+Historically, it had been the only mode available.
+.PP
 Each message selected will be annotated with the lines
 .PP
-    field:\ date
-    field:\ body
+.RS 5
+.nf
+field:\ date
+field:\ body
+.fi
+.RE
 .PP
 The
+.B \-component
+option specifies the field name.
+If no
+.B \-component
+.I field
+is specified,
+.B anno
+will prompt the user for the name of field for the annotation.
+.PP
+The
+.B \-text
+option specifies the field body.
+If it is missing, only the date annotation will be added.
+The
 .B \-nodate
 switch inhibits the date annotation, leaving only the
 body annotation.
@@ -68,81 +111,88 @@ By default,
 .B anno
 prepends the annotations to the message.
 Annotations are instead appended if the
-.B -append
+.B \-append
 option is specified.
 .PP
-If a
-.B \-component
-.I field
-is not specified when
-.B anno
-is invoked,
-.B anno
-will prompt the user for the name of field for the annotation.
-.PP
-The field specified must be a valid 2822-style message field name,
-which means that it may only consist of alphanumerics and dashes,
-The body specified is arbitrary text.
-.PP
-.B anno
+.B Anno
 always does the annotation inplace in order to preserve
 any links to the message.
 .PP
+By default,
+.B anno
+changes the last-accessed and last-modified times on annotate messages
+to the time at which the annotation occurs.
+.B Anno
+preserves the original times if the
+.B \-preserve
+option is used.
+
+.SS "Delete mode
+.PP
 The
-.B -list
-option produces a listing of the field bodies for header fields with
-names matching the specified component, one per line.
-The listing is numbered, starting at 1, if the
-.B -number
-option is also used.
-A tab character separates the number and the field body.
-The field body is treated as if it is a file name, and only the final
-path name component is listed.
-The complete field body is listed if the
-.B -text
-option is used, the contents of the text are ignored.
+.B \-delete
+mode removes header fields from messages.
+By default, the first header field whose name matches the component
+is deleted.
 .PP
 The
-.B -delete
-option removes header fields from messages.
-The first header field whose name matches the component is deleted if
-no other options are specified.
+.B \-component
+option specifies the field name of headers to delete.
+If no
+.B \-component
+.I field
+is specified,
+.B anno
+will prompt the user for the name.
+.PP
 If the
-.B -text
-option is used in conjunction with the
-.B -delete
-option, the first header field whose name matches the component and
+.B \-text
+option is used,
+the first header field whose name matches the component and
 whose body matches the text is deleted.
-The text is treated as if it was a file name; if it begins with a
+The text is treated as if it was a path name; if it begins with a
 slash, the entire field body must match the text, otherwise just the
 last path name component of the field body must match.
+.PP
 If the
-.B -number
-option is used in conjuction with the
-.B -delete
-option, header field
-.I num
-whose name matches the component is deleted.
-The number matches that which is produced by the
-.B -list
-option.
-The special value
-.B all
-can be used for the number, and causes all components that match the
-name to be deleted.
+.B \-number
+option is used,
+the
+.IR n th
+header field whose name matches the component is deleted.
+The numbers are the same as those produced in
+.B \-list
+mode.
+The special value `all' can be used for the number,
+and causes all components that match the name to be deleted.
 .PP
-By default,
+Either
+.B \-text
+or
+.B \-number
+may be specified, but not both at the same time.
+
+.SS "List mode
+.PP
+The
+.B \-list
+mode produces a listing of the field bodies for header fields with
+matching component names, one per line.
+If the
+.B \-number
+option is also used,
+the listing is numbered, starting at 1.
+.PP
+The
+.B \-component
+option specifies the field name of headers to list.
+If no
+.B \-component
+.I field
+is specified,
 .B anno
-changes the last-accessed and last-modified times on annotate messages
-to the time at which the annotation occurs.
-.B Anno
-preserves the original times if the
-.B -preserve
-option is used.
-A matching
-.B -nopreserve
-option exists that allows time preservation to be turned off if enabled
-in the profile.
+will prompt the user for the name.
+
 .SH FILES
 .fc ^ ~
 .nf
@@ -167,8 +217,20 @@ dist(1), forw(1), repl(1)
 .RI ` +folder "' defaults to the current folder"
 .RI ` msgs "' defaults to cur"
 .RB ` \-date '
+.RB ` \-nopreserve '
 .fi
 
 .SH CONTEXT
 If a folder is given, it will become the current folder.  The first
 message annotated will become the current message.
+
+.SH BUGS
+.PP
+The
+.B \-number
+switch must appear after either the
+.B \-list
+or the
+.B \-delete
+mode switch, on the command line.
+Otherwise it is not possible to determine if it takes an argument.
index 3b1d137..fe0d65f 100644 (file)
@@ -5,38 +5,10 @@
 ** COPYRIGHT file in the root directory of the nmh distribution for
 ** complete copyright information.
 **
-** Three new options have been added: delete, list, and number.
-** Message header fields are used by the new MIME attachment code in
-** the send command.  Adding features to generalize the anno command
-** seemed to be a better approach than the creation of a new command
-** whose features would overlap with those of the anno command.
-**
-** The -delete option deletes header elements that match the -component
-** field name.  If -delete is used without the -text option, the first
-** header field whose field name matches the component name is deleted.
-** If the -delete is used with the -text option, and the -text argument
-** begins with a /, the first header field whose field name matches the
-** component name and whose field body matches the text is deleted.  If
-** the -text argument does not begin with a /, then the text is assumed
-** to be the last component of a path name, and the first header field
-** whose field name matches the component name and a field body whose
-** last path name component matches the text is deleted.  If the -delete
-** option is used with the new -number option described below, the nth
-** header field whose field name matches the component name is deleted.
-** No header fields are deleted if none of the above conditions are met.
-**
-** The -list option outputs the field bodies from each header field whose
-** field name matches the component name, one per line.  If no -text
-** option is specified, only the last path name component of each field
-** body is output.  The entire field body is output if the -text option
-** is used; the contents of the -text argument are ignored.  If the -list
-** option is used in conjuction with the new -number option described
-** below, each line is numbered starting with 1.  A tab separates the
-** number from the field body.
-**
-** The -number option works with both the -delete and -list options as
-** described above.  The -number option takes an optional argument.  A
-** value of 1 is assumed if this argument is absent.
+** Three new options have been added: delete, list, and number. Adding
+** features to generalize the anno command seemed to be a better approach
+** than the creation of a new command whose features would overlap with
+** those of the anno command.
 */
 
 #include <h/mh.h>
@@ -46,6 +18,7 @@
 #include <errno.h>
 #include <utime.h>
 
+static enum { MODE_ADD, MODE_DEL, MODE_LIST } mode = MODE_ADD;
 
 static struct swit switches[] = {
 #define COMPSW 0
@@ -79,9 +52,10 @@ static struct swit switches[] = {
 ** static prototypes
 */
 static void make_comp(unsigned char **);
-static int annosbr(int, char *, char *, char *, int, int, int);
-static int annotate(char *, char *, char *, int, int, int, int);
-static void annolist(char *, char *, char *, int);
+static int annotate(char *, unsigned char *, char *, int, int, int, int);
+static void annolist(char *, unsigned char *, int);
+static void dodel(int, unsigned char *, char *, FILE *, int);
+static void doadd(int, unsigned char *, char *, FILE *, int, int);
 
 
 int
@@ -98,17 +72,12 @@ main(int argc, char **argv)
        struct msgs_array msgs = { 0, 0, NULL };
        struct msgs *mp;
        int append = 0;  /* append annotations instead of default prepend */
-       int delete = -2;  /* delete header element if set */
-       int list = 0;  /* list header elements if set */
        int number = 0; /* delete specific number of like elements if set */
-       int havemsgs = 0;
 
 #ifdef LOCALE
        setlocale(LC_ALL, "");
 #endif
        invo_name = mhbasename(argv[0]);
-
-       /* read user profile/context */
        context_read();
 
        arguments = getarguments(invo_name, argc, argv, 1);
@@ -133,6 +102,14 @@ main(int argc, char **argv)
                                print_version(invo_name);
                                done(1);
 
+                       case DELETESW:  /* delete annotations */
+                               mode = MODE_DEL;
+                               continue;
+
+                       case LISTSW:  /* produce a listing */
+                               mode = MODE_LIST;
+                               continue;
+
                        case COMPSW:
                                if (comp)
                                        adios(NULL, "only one component at a time!");
@@ -141,13 +118,6 @@ main(int argc, char **argv)
                                                        argp[-2]);
                                continue;
 
-                       case DATESW:
-                               datesw++;
-                               continue;
-                       case NDATESW:
-                               datesw = 0;
-                               continue;
-
                        case TEXTSW:
                                if (text)
                                        adios(NULL, "only one body at a time!");
@@ -156,34 +126,39 @@ main(int argc, char **argv)
                                                        argp[-2]);
                                continue;
 
-                       case DELETESW:  /* delete annotations */
-                               delete = 0;
-                               continue;
-
-                       case LISTSW:  /* produce a listing */
-                               list = 1;
-                               continue;
-
                        case NUMBERSW: /* number listing or delete by number */
-                               if (number != 0)
-                                       adios(NULL, "only one number at a time!");
-
-                               if (argp-arguments == argc-1 || **argp == '-')
+                               if (mode == MODE_ADD) {
+                                       adios(NULL, "-number switch must appear after -list or -delete, only.");
+                               }
+                               if (mode == MODE_LIST) {
                                        number = 1;
-
-                               else {
-                                       if (strcmp(*argp, "all") == 0)
-                                               number = -1;
-                                       else if (!(number = atoi(*argp)))
-                                               /* FIXME: fails for
-                                               ** `-list -number l:10'
-                                               ** but okay if we add `all'.
-                                               */
-                                               adios(NULL, "missing argument to %s", argp[-1]);
+                                       continue;
+                               }
+                               /* MODE_DEL */
+                               if (number) {
+                                       adios(NULL, "only one number at a time!");
+                               }
+                               if (*argp && strcmp(*argp, "all")==0) {
+                                       number = -1;
                                        argp++;
+                                       continue;
+                               }
+                               if (!*argp || !(number = atoi(*argp))) {
+                                       adios(NULL, "missing argument to %s",
+                                                       argp[-1]);
+                               }
+                               if (number < 0) {
+                                       adios(NULL, "invalid number (%d).",
+                                                       number);
                                }
+                               argp++;
+                               continue;
 
-                               delete = number;
+                       case DATESW:
+                               datesw++;
+                               continue;
+                       case NDATESW:
+                               datesw = 0;
                                continue;
 
                        case APPENDSW:
@@ -210,22 +185,25 @@ main(int argc, char **argv)
                        file = cp;
                } else {
                        app_msgarg(&msgs, cp);
-                       havemsgs = 1;
                }
        }
 
-       if (file && (folder || havemsgs)) {
+       if (file && (folder || !msgs.size)) {
                adios(NULL, "Don't intermix files and messages.");
        }
-
-       make_comp(&comp);
+       if (!datesw && !text) {
+               adios(NULL, "-nodate without -text is a no-op.");
+       }
+       if (number && text) {
+               adios(NULL, "Don't combine -number with -text.");
+       }
 
        if (file) {
-               if (list)
-                       annolist(file, comp, text, number);
+               if (mode == MODE_LIST)
+                       annolist(file, comp, number);
                else
-                       annotate(file, comp, text,
-                                       datesw, delete, append, preserve);
+                       annotate(file, comp, text, datesw, number,
+                                       append, preserve);
                done(0);
        }
 
@@ -254,11 +232,11 @@ main(int argc, char **argv)
        /* annotate all the SELECTED messages */
        for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
                if (is_selected(mp, msgnum)) {
-                       if (list)
-                               annolist(m_name(msgnum), comp, text, number);
+                       if (mode == MODE_LIST)
+                               annolist(m_name(msgnum), comp, number);
                        else
                                annotate(m_name(msgnum), comp, text, datesw,
-                                               delete, append, preserve);
+                                               number, append, preserve);
                }
        }
 
@@ -274,20 +252,21 @@ main(int argc, char **argv)
 static void
 make_comp(unsigned char **ap)
 {
-       register unsigned char *cp;
+       unsigned char *cp;
        char buffer[BUFSIZ];
 
-       if (*ap == NULL) {
+       if (!*ap) {
                printf("Enter component name: ");
                fflush(stdout);
 
-               if (fgets(buffer, sizeof buffer, stdin) == NULL)
+               if (!fgets(buffer, sizeof buffer, stdin)) {
                        done(1);
+               }
                *ap = trimcpy(buffer);
        }
 
        if ((cp = *ap + strlen(*ap) - 1) > *ap && *cp == ':')
-               *cp = 0;
+               *cp = '\0';
        if (strlen(*ap) == 0)
                adios(NULL, "null component name");
        if (**ap == '-')
@@ -301,61 +280,21 @@ make_comp(unsigned char **ap)
 }
 
 
-static int
-annotate(char *file, char *comp, char *text, int datesw,
-       int delete, int append, int preserve)
-{
-       int i, fd;
-       struct utimbuf b;
-       struct stat s;
-
-       /* open and lock the file to be annotated */
-       if ((fd = lkopen(file, O_RDWR, 0)) == NOTOK) {
-               switch (errno) {
-               case ENOENT:
-                       break;
-               default:
-                       admonish(file, "unable to lock and open");
-                       break;
-               }
-               return 1;
-       }
-
-       if (stat(file, &s) == -1) {
-               advise("can't get access and modification times for %s", file);
-               preserve = 0;
-       }
-
-       b.actime = s.st_atime;
-       b.modtime = s.st_mtime;
-
-       i = annosbr(fd, file, comp, text, datesw, delete, append);
-
-       if (preserve && utime(file, &b) == -1) {
-               advise("can't set access and modification times for %s", file);
-       }
-       lkclose(fd, file);
-       return i;
-}
-
 /*
 **  Produce a listing of all header fields (annotations) whose field
-**  name matches comp.  Number the listing if number is set.  Treat the
-**  field bodies as path names and just output the last component unless
-**  text is non-NULL.  We don't care what text is set to.
+**  name matches comp.  Number the listing if number is set.
 */
 static void
-annolist(char *file, char *comp, char *text, int number)
+annolist(char *file, unsigned char *comp, int number)
 {
        int c;
-       int count;  /* header field (annotation) counter */
+       int count = 1;  /* header field (annotation) counter */
        char *cp;
        char *field;
        int field_size;
        FILE *fp;
        int length;
        int n;  /* number of bytes written */
-       char *sp;
 
        if ((fp = fopen(file, "r")) == NULL) {
                adios(file, "unable to open");
@@ -364,8 +303,8 @@ annolist(char *file, char *comp, char *text, int number)
        /* We'll grow this buffer as needed. */
        field = (char *)mh_xmalloc(field_size = 256);
 
+       make_comp(&comp);
        length = strlen(comp); /* Convenience copy. */
-       count = 0;
 
        do {
                /*
@@ -396,10 +335,7 @@ annolist(char *file, char *comp, char *text, int number)
                                continue;
                        }
                        if (number) {
-                               printf("%d\t", ++count);
-                       }
-                       if (!text && (sp = strrchr(cp, '/'))) {
-                               cp = sp + 1;
+                               printf("%d\t", count++);
                        }
                        printf("%s\n", cp);
                }
@@ -412,28 +348,104 @@ annolist(char *file, char *comp, char *text, int number)
        return;
 }
 
+
 static int
-annosbr(int fd, char *file, char *comp, char *text, int datesw, int delete,
-               int append)
+annotate(char *file, unsigned char *comp, char *text, int datesw,
+               int number, int append, int preserve)
 {
-       int mode, tmpfd;
-       char *cp, *sp;
+       int fd;
+       struct utimbuf b;
+       int perms, tmpfd;
        char tmpfil[BUFSIZ];
        struct stat st;
        FILE *tmp;
-       int c;
-       int count;  /* header field (annotation) counter */
-       char *field = NULL;
-       int field_size = 0;
-       FILE *fp = NULL;
-       int length;
-       int n;  /* number of bytes written */
 
-       mode = fstat(fd, &st) != NOTOK ? (int)(st.st_mode & 0777) : m_gmprot();
+       /* open and lock the file to be annotated */
+       if ((fd = lkopen(file, O_RDWR, 0)) == NOTOK) {
+               switch (errno) {
+               case ENOENT:
+                       break;
+               default:
+                       admonish(file, "unable to lock and open");
+                       break;
+               }
+               return 1;
+       }
+
+       if (stat(file, &st) == -1) {
+               advise("can't get access and modification times for %s", file);
+               preserve = 0;
+       }
+       b.actime = st.st_atime;
+       b.modtime = st.st_mtime;
+
+       perms = fstat(fd, &st) != NOTOK ?
+                       (int)(st.st_mode & 0777) : m_gmprot();
 
        strncpy(tmpfil, m_mktemp2(file, "annotate", NULL, &tmp),
                        sizeof(tmpfil));
-       chmod(tmpfil, mode);
+       chmod(tmpfil, perms);
+
+       make_comp(&comp);
+
+       if (mode == MODE_DEL) {
+               dodel(fd, comp, text, tmp, number);
+       }
+       if (mode == MODE_ADD) {
+               doadd(fd, comp, text, tmp, datesw, append);
+       }
+
+       cpydata(fd, fileno(tmp), file, tmpfil);
+       fclose(tmp);
+
+       if ((tmpfd = open(tmpfil, O_RDONLY)) == NOTOK) {
+               adios(tmpfil, "unable to open for re-reading");
+       }
+       lseek(fd, (off_t) 0, SEEK_SET);
+
+       /*
+       **  We're making the file shorter if we're deleting a header field
+       **  so the file has to be truncated or it will contain garbage.
+       */
+       if (mode == MODE_DEL && ftruncate(fd, 0) == -1) {
+               adios(tmpfil, "unable to truncate.");
+       }
+       cpydata(tmpfd, fd, tmpfil, file);
+       close(tmpfd);
+       unlink(tmpfil);
+
+       if (preserve && utime(file, &b) == -1) {
+               advise("can't set access and modification times for %s", file);
+       }
+       lkclose(fd, file);
+       return 0;
+}
+
+/*
+** We're trying to delete a header field (annotation).
+**
+** - If number is greater than zero,
+**   we're deleting the nth header field that matches
+**   the field (component) name.
+** - If number is zero and text is NULL,
+**   we're deleting the first field in which the field name
+**   matches the component name.
+** - If number is zero and text is set,
+**   we're deleting the first field in which both the field name
+**   matches the component name and the field body matches the text.
+** - If number is -1,
+**   we delete all matching fields.
+*/
+static void
+dodel(int fd, unsigned char *comp, char *text, FILE *tmp, int number)
+{
+       int length = strlen(comp);  /* convenience copy */
+       int count = 1;  /* Number of matching header line. */
+       int c, n;
+       char *cp;
+       char *field = NULL;
+       int field_size = 256;
+       FILE *fp;
 
        /*
        ** We're going to need to copy some of the message file to the
@@ -443,187 +455,176 @@ annosbr(int fd, char *file, char *comp, char *text, int datesw, int delete,
        ** a buffer to hold the header components as they're read in.
        ** This buffer is grown as needed later.
        */
-       if (delete >= -1 || append != 0) {
-               if ((fp = fdopen(fd, "r")) == NULL) {
-                       adios(NULL, "unable to fdopen file.");
-               }
-               field = (char *)mh_xmalloc(field_size = 256);
+       if ((fp = fdopen(fd, "r")) == NULL) {
+               adios(NULL, "unable to fdopen file.");
        }
+       field = (char *)mh_xmalloc(field_size);
 
        /*
-       ** We're trying to delete a header field (annotation) if the
-       ** delete flag is greater -2.  A value greater than zero
-       ** means that we're deleting the nth header field that matches
-       ** the field (component) name.  A value of zero means that
-       ** we're deleting the first field in which both the field name
-       ** matches the component name and the field body matches the text.
-       ** The text is matched in its entirety if it begins with a slash;
-       ** otherwise the text is matched against whatever portion of the
-       ** field body follows the last slash.  This allows matching of
-       ** both absolute and relative path names.  This is because this
-       ** functionality was added to support attachments.  It might be
-       ** worth having a separate flag to indicate path name matching
-       ** to make it more general.  A value of -1 means to delete all
-       ** matching fields.
+       **  Copy lines from the input file to the temporary file
+       **  until we either find the one that we're looking
+       **  for (which we don't copy) or we reach the end of
+       **  the headers.  Both a blank line and a line beginning
+       **  with a - terminate the headers so that we can handle
+       **  both drafts and RFC-2822 format messages.
        */
-       if (delete >= -1) {
-               length = strlen(comp);  /* convenience copy */
-               count = 0; /* Only used if we're deleting by number. */
-
+       do {
                /*
-               **  Copy lines from the input file to the temporary file
-               **  until we either find the one that we're looking
-               **  for (which we don't copy) or we reach the end of
-               **  the headers.  Both a blank line and a line beginning
-               **  with a - terminate the headers so that we can handle
-               **  both drafts and RFC-2822 format messages.
+               ** Get a line from the input file, growing the
+               ** field buffer as needed.  We do this so that
+               ** we can fit an entire line in the buffer making
+               ** it easy to do a string comparison on both the
+               ** field name and the field body which might be
+               ** a long path name.
                */
-               do {
-                       /*
-                       ** Get a line from the input file, growing the
-                       ** field buffer as needed.  We do this so that
-                       ** we can fit an entire line in the buffer making
-                       ** it easy to do a string comparison on both the
-                       ** field name and the field body which might be
-                       ** a long path name.
-                       */
-                       for (n=0, cp=field; (c=getc(fp)) != EOF; *cp++ = c) {
-                               if (c == '\n' && (c = getc(fp)) != ' ' &&
-                                               c != '\t') {
-                                       ungetc(c, fp);
-                                       c = '\n';
-                                       break;
-                               }
+               for (n=0, cp=field; (c=getc(fp)) != EOF; *cp++ = c) {
+                       if (c == '\n' && (c = getc(fp)) != ' ' &&
+                                       c != '\t') {
+                               ungetc(c, fp);
+                               c = '\n';
+                               break;
+                       }
 
-                               if (++n >= field_size - 1) {
-                                       field = (char *) mh_xrealloc(field,
-                                                       field_size *= 2);
-                                       cp = field + n - 1;
-                               }
+                       if (++n >= field_size - 1) {
+                               field = (char *) mh_xrealloc(field,
+                                               field_size *= 2);
+                               cp = field + n - 1;
                        }
-                       *cp = '\0';
+               }
+               *cp = '\0';
 
+               if (strncasecmp(field, comp, length)==0 &&
+                               field[length] == ':') {
                        /*
-                       ** Check for a match on the field name.  We delete
-                       ** the line by not copying it to the temporary
-                       ** file if
-                       **
-                       **  o  The delete flag is 0, meaning that we're
-                       **     going to delete the first matching
-                       **     field, and the text is NULL meaning that
-                       **     we don't care about the field body.
-                       **
-                       **  o  The delete flag is 0, meaning that we're
-                       **     going to delete the first matching
-                       **     field, and the text begins with a / meaning
-                       **     that we're looking for a full path name,
-                       **     and the text matches the field body.
-                       **
-                       **  o  The delete flag is 0, meaning that we're
-                       **     going to delete the first matching
-                       **     field, the text does not begin with a /
-                       **     meaning that we're looking for the last
-                       **     path name component, and the last path
-                       **     name component matches the text.
-                       **
-                       **  o  The delete flag is positive meaning that
-                       **     we're going to delete the nth field
-                       **     with a matching field name, and this is
-                       **     the nth matching field name.
-                       **
-                       **  o  The delete flag is -1 meaning that we're
-                       **     going to delete all fields with a
-                       **     matching field name.
+                       ** This component matches and thus is a candidate.
+                       ** We delete the line by not copying it to the
+                       ** temporary file. Thus:
+                       ** - Break if we've found the one to delete.
+                       ** - Continue if this is one to delete, but
+                       **   there'll be further ones.
                        */
-                       if (strncasecmp(field, comp, length)==0 &&
-                                       field[length] == ':') {
-                               if (!delete) {
-                                       if (!text) {
-                                               break;
-                                       }
-                                       for (cp=field+length+1;
-                                                       *cp==' ' || *cp=='\t';
-                                                       cp++) {
-                                               continue;
-                                       }
-                                       if (*text == '/' &&
-                                                       strcmp(cp, text)==0) {
-                                               break;
-                                       } else {
-                                               if ((sp = strrchr(cp, '/'))) {
-                                                       cp = sp + 1;
-                                               }
-                                               if (strcmp(cp, text)==0) {
-                                                       break;
-                                               }
-                                       }
-                               } else if (delete == -1) {
-                                       continue;
-                               } else if (++count == delete) {
-                                       break;
-                               }
+
+                       if (!number && !text) {
+                               /* this first one is it */
+                               break;
+                       }
+
+                       if (number == -1) {
+                               /* delete all of them */
+                               continue;
+                       } else if (number == count++) {
+                               /* delete this specific one */
+                               break;
                        }
 
+                       if (text) {
+                               /* delete the first matching one */
+                               cp = field+length+1;
+                               while (*cp==' ' || *cp=='\t') {
+                                       cp++;  /* eat leading whitespace */
+                               }
+                               if (*text == '/' && strcmp(text, cp)==0) {
+                                       break;  /* full path matches */
+                               } else if (strcmp(text, mhbasename(cp))==0) {
+                                       break;  /* basename matches */
+                               }
+                       }
                        /*
-                       ** This line wasn't a match so copy it to the
-                       ** temporary file.
+                       ** Although the compoment name mached, it
+                       ** wasn't the right one.
                        */
-                       if ((n = fputs(field, tmp)) == EOF ||
-                                       (c=='\n' && fputc('\n', tmp)==EOF)) {
-                               adios(NULL, "unable to write temporary file.");
-                       }
-               } while (*field && *field != '-');
-               free(field);
+               }
+
+               /* Copy it. */
+               if ((n = fputs(field, tmp)) == EOF ||
+                               (c=='\n' && fputc('\n', tmp)==EOF)) {
+                       adios(NULL, "unable to write temporary file.");
+               }
+
+       } while (*field && *field != '-');
+
+       free(field);
+
+       fflush(tmp);
+       fflush(fp); /* The underlying fd will be closed by lkclose() */
+
+       /*
+       ** We've been messing with the input file position.  Move the
+       ** input file descriptor to the current place in the file
+       ** because the stock data copying routine uses the descriptor,
+       ** not the pointer.
+       */
+       if (lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1) {
+               adios(NULL, "can't seek.");
+       }
+}
 
-       } else {
+
+static void
+doadd(int fd, unsigned char *comp, char *text, FILE *tmp, int datesw,
+               int append)
+{
+       char *cp, *sp;
+       int c;
+       FILE *fp = NULL;
+
+       if (append) {
                /*
-               **  Find the end of the headers before adding the
-               **  annotations if we're appending instead of the default
-               **  prepending.  A special check for no headers is needed
-               **  if appending.
+               ** We're going to need to copy some of the message
+               ** file to the temporary file while examining the
+               ** contents.  Convert the message file descriptor to
+               ** a file pointer since it's a lot easier and more
+               ** efficient to use stdio for this.  Also allocate
+               ** a buffer to hold the header components as they're
+               ** read in.  This buffer is grown as needed later.
                */
-               if (append) {
+               if ((fp = fdopen(fd, "r")) == NULL) {
+                       adios(NULL, "unable to fdopen file.");
+               }
+               /* Find the end of the headers. */
+               if ((c = getc(fp)) == '\n') {
+                       /* Special check for no headers is needed. */
+                       rewind(fp);
+               } else {
                        /*
-                       ** Copy lines from the input file to the temporary
-                       ** file until we reach the end of the headers.
+                       ** Copy lines from the input file to the
+                       ** temporary file until we reach the end
+                       ** of the headers.
                        */
-                       if ((c = getc(fp)) == '\n') {
-                               rewind(fp);
-                       } else {
+                       putc(c, tmp);
+                       while ((c = getc(fp)) != EOF) {
                                putc(c, tmp);
-                               while ((c = getc(fp)) != EOF) {
-                                       putc(c, tmp);
-                                       if (c == '\n') {
-                                               ungetc(c = getc(fp), fp);
-                                               if (c == '\n' || c == '-') {
-                                                       break;
-                                               }
+                               if (c == '\n') {
+                                       ungetc(c = getc(fp), fp);
+                                       if (c == '\n' || c == '-') {
+                                               break;
                                        }
                                }
                        }
                }
+       }
 
-               if (datesw) {
-                       fprintf(tmp, "%s: %s\n", comp, dtimenow());
-               }
-               if ((cp = text)) {
-                       do {
-                               while (*cp == ' ' || *cp == '\t') {
-                                       cp++;
-                               }
-                               sp = cp;
-                               while (*cp && *cp++ != '\n') {
-                                       continue;
-                               }
-                               if (cp - sp) {
-                                       fprintf(tmp, "%s: %*.*s", comp,
-                                               (int)(cp - sp),
-                                               (int)(cp - sp), sp);
-                               }
-                       } while (*cp);
-                       if (cp[-1] != '\n' && cp != text) {
-                               putc('\n', tmp);
+       if (datesw) {
+               fprintf(tmp, "%s: %s\n", comp, dtimenow());
+       }
+       if ((cp = text)) {
+               /* Add body text header */
+               do {
+                       while (*cp == ' ' || *cp == '\t') {
+                               cp++;
                        }
+                       sp = cp;
+                       while (*cp && *cp++ != '\n') {
+                               continue;
+                       }
+                       if (cp - sp) {
+                               fprintf(tmp, "%s: %*.*s", comp,
+                                       (int)(cp - sp),
+                                       (int)(cp - sp), sp);
+                       }
+               } while (*cp);
+               if (cp[-1] != '\n' && cp != text) {
+                       putc('\n', tmp);
                }
        }
        fflush(tmp);
@@ -634,38 +635,9 @@ annosbr(int fd, char *file, char *comp, char *text, int datesw, int delete,
        ** because the stock data copying routine uses the descriptor,
        ** not the pointer.
        */
-       if (append || delete >= -1) {
+       if (append) {
                if (lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1) {
                        adios(NULL, "can't seek.");
                }
        }
-
-       cpydata(fd, fileno(tmp), file, tmpfil);
-       fclose(tmp);
-
-       if ((tmpfd = open(tmpfil, O_RDONLY)) == NOTOK) {
-               adios(tmpfil, "unable to open for re-reading");
-       }
-       lseek(fd, (off_t) 0, SEEK_SET);
-
-       /*
-       **  We're making the file shorter if we're deleting a header field
-       **  so the file has to be truncated or it will contain garbage.
-       */
-       if (delete >= -1 && ftruncate(fd, 0) == -1) {
-               adios(tmpfil, "unable to truncate.");
-       }
-       cpydata(tmpfd, fd, tmpfil, file);
-       close(tmpfd);
-       unlink(tmpfil);
-
-       /*
-       ** Close the delete file so that we don't run out of file pointers if
-       ** we're doing piles of files.  Note that this will make the close() in
-       ** lkclose() fail, but that failure is ignored so it's not a problem.
-       */
-       if (delete >= -1) {
-               fclose(fp);
-       }
-       return 0;
 }