[bug #4302] errno is not always an extern int
[mmh] / uip / annosbr.c
1
2 /*
3  * annosbr.c -- prepend annotation to messages
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 #include <h/mh.h>
13 #include <h/tws.h>
14 #include <fcntl.h>
15 #include <errno.h>
16
17
18 /*
19  * static prototypes
20  */
21 static int annosbr (int, char *, char *, char *, int, int, int, int);
22
23
24 int
25 annotate (char *file, char *comp, char *text, int inplace, int datesw, int delete, int append)
26 {
27     int i, fd;
28
29     /* open and lock the file to be annotated */
30     if ((fd = lkopen (file, O_RDWR, 0)) == NOTOK) {
31         switch (errno) {
32             case ENOENT:
33                 break;
34
35             default:
36                 admonish (file, "unable to lock and open");
37                 break;
38         }
39         return 1;
40     }
41
42     i = annosbr (fd, file, comp, text, inplace, datesw, delete, append);
43     lkclose (fd, file);
44     return i;
45 }
46
47 /*
48  *  Produce a listing of all header fields (annotations) whose field name matches
49  *  comp.  Number the listing if number is set.  Treate the field bodies as path
50  *  names and just output the last component unless text is non-NULL.  We don't
51  *  care what text is set to.
52  */
53
54 void
55 annolist(char *file, char *comp, char *text, int number)
56 {
57     int         c;              /* current character */
58     int         count;          /* header field (annotation) counter */
59     char        *cp;            /* miscellaneous character pointer */
60     char        *field;         /* buffer for header field */
61     int         field_size;     /* size of field buffer */
62     FILE        *fp;            /* file pointer made from locked file descriptor */
63     int         length;         /* length of field name */
64     int         n;              /* number of bytes written */
65     char        *sp;            /* another miscellaneous character pointer */
66
67     if ((fp = fopen(file, "r")) == (FILE *)0)
68         adios(file, "unable to open");
69
70     /*
71      *  Allocate a buffer to hold the header components as they're read in.
72      *  This buffer might need to be quite large, so we grow it as needed.
73      */
74
75     if ((field = (char *)malloc(field_size = 256)) == (char *)0)
76         adios(NULL, "can't allocate field buffer.");
77
78     /*
79      *  Get the length of the field name since we use it often.
80      */
81
82     length = strlen(comp);
83
84     count = 0;
85
86     do {
87         /*
88          *      Get a line from the input file, growing the field buffer as needed.  We do this
89          *      so that we can fit an entire line in the buffer making it easy to do a string
90          *      comparison on both the field name and the field body which might be a long path
91          *      name.
92          */
93
94         for (n = 0, cp = field; (c = getc(fp)) != EOF; *cp++ = c) {
95             if (c == '\n' && (c = getc(fp)) != ' ' && c != '\t') {
96                 (void)ungetc(c, fp);
97                 c = '\n';
98                 break;
99             }
100
101             if (++n >= field_size - 1) {
102                 if ((field = (char *)realloc((void *)field, field_size += 256)) == (char *)0)
103                     adios(NULL, "can't grow field buffer.");
104                 
105                 cp = field + n - 1;
106             }
107         }
108
109         /*
110          *      NUL-terminate the field..
111          */
112
113         *cp = '\0';
114
115         if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
116             for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
117                 ;
118
119             if (number)
120                 (void)printf("%d\t", ++count);
121
122             if (text == (char *)0 && (sp = strrchr(cp, '/')) != (char *)0)
123                 cp = sp + 1;
124
125             (void)printf("%s\n", cp);
126         }
127
128     } while (*field != '\0' && *field != '-');
129
130     /*
131      *  Clean up.
132      */
133
134     free(field);
135
136     (void)fclose(fp);
137
138     return;
139 }
140
141
142 static int
143 annosbr (int fd, char *file, char *comp, char *text, int inplace, int datesw, int delete, int append)
144 {
145     int mode, tmpfd;
146     char *cp, *sp;
147     char buffer[BUFSIZ], tmpfil[BUFSIZ];
148     struct stat st;
149     FILE        *tmp;
150     int         c;              /* current character */
151     int         count;          /* header field (annotation) counter */
152     char        *field;         /* buffer for header field */
153     int         field_size;     /* size of field buffer */
154     FILE        *fp;            /* file pointer made from locked file descriptor */
155     int         length;         /* length of field name */
156     int         n;              /* number of bytes written */
157
158     mode = fstat (fd, &st) != NOTOK ? (st.st_mode & 0777) : m_gmprot ();
159
160     strncpy (tmpfil, m_scratch (file, "annotate"), sizeof(tmpfil));
161
162     if ((tmp = fopen (tmpfil, "w")) == NULL) {
163         admonish (tmpfil, "unable to create");
164         return 1;
165     }
166     chmod (tmpfil, mode);
167
168     /*
169      *  We're going to need to copy some of the message file to the temporary
170      *  file while examining the contents.  Convert the message file descriptor
171      *  to a file pointer since it's a lot easier and more efficient to use
172      *  stdio for this.  Also allocate a buffer to hold the header components
173      *  as they're read in.  This buffer is grown as needed later.
174      */
175
176     if (delete >= 0 || append != 0) {
177         if ((fp = fdopen(fd, "r")) == (FILE *)0)
178             adios(NULL, "unable to fdopen file.");
179
180         if ((field = (char *)malloc(field_size = 256)) == (char *)0)
181             adios(NULL, "can't allocate field buffer.");
182     }
183
184     /*
185      *  We're trying to delete a header field (annotation )if the delete flag is
186      *  non-negative.  A  value greater than zero means that we're deleting the
187      *  nth header field that matches the field (component) name.  A value of
188      *  zero means that we're deleting the first field in which both the field
189      *  name matches the component name and the field body matches the text.
190      *  The text is matched in its entirety if it begins with a slash; otherwise
191      *  the text is matched against whatever portion of the field body follows
192      *  the last slash.  This allows matching of both absolute and relative path
193      *  names.  This is because this functionality was added to support attachments.
194      *  It might be worth having a separate flag to indicate path name matching to
195      *  make it more general.
196      */
197
198     if (delete >= 0) {
199         /*
200          *  Get the length of the field name since we use it often.
201          */
202
203         length = strlen(comp);
204
205         /*
206          *  Initialize the field counter.  This is only used if we're deleting by
207          *  number.
208          */
209
210         count = 0;
211
212         /*
213          *  Copy lines from the input file to the temporary file until we either find the one
214          *  that we're looking for (which we don't copy) or we reach the end of the headers.
215          *  Both a blank line and a line beginning with a - terminate the headers so that we
216          *  can handle both drafts and RFC-2822 format messages.
217          */
218
219         do {
220             /*
221              *  Get a line from the input file, growing the field buffer as needed.  We do this
222              *  so that we can fit an entire line in the buffer making it easy to do a string
223              *  comparison on both the field name and the field body which might be a long path
224              *  name.
225              */
226
227             for (n = 0, cp = field; (c = getc(fp)) != EOF; *cp++ = c) {
228                 if (c == '\n' && (c = getc(fp)) != ' ' && c != '\t') {
229                     (void)ungetc(c, fp);
230                     c = '\n';
231                     break;
232                 }
233
234                 if (++n >= field_size - 1) {
235                     if ((field = (char *)realloc((void *)field, field_size *= 2)) == (char *)0)
236                         adios(NULL, "can't grow field buffer.");
237                     
238                     cp = field + n - 1;
239                 }
240             }
241
242             /*
243              *  NUL-terminate the field..
244              */
245
246             *cp = '\0';
247
248             /*
249              *  Check for a match on the field name.  We delete the line by not copying it to the
250              *  temporary file if
251              *
252              *   o  The delete flag is 0, meaning that we're going to delete the first matching
253              *      field, and the text is NULL meaning that we don't care about the field body.
254              *
255              *   o  The delete flag is 0, meaning that we're going to delete the first matching
256              *      field, and the text begins with a / meaning that we're looking for a full
257              *      path name, and the text matches the field body.
258              *
259              *   o  The delete flag is 0, meaning that we're going to delete the first matching
260              *      field, the text does not begin with a / meaning that we're looking for the
261              *      last path name component, and the last path name component matches the text.
262              *
263              *   o  The delete flag is non-zero meaning that we're going to delete the nth field
264              *      with a matching field name, and this is the nth matching field name.
265              */
266
267             if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
268                 if (delete == 0) {
269                     if (text == (char *)0)
270                         break;
271
272                     for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
273                         ;
274
275                     if (*text == '/') {
276                         if (strcmp(cp, text) == 0)
277                                 break;
278                     }
279                     else {
280                         if ((sp = strrchr(cp, '/')) != (char *)0)
281                             cp = sp + 1;
282
283                         if (strcmp(cp, text) == 0)
284                             break;
285                     }
286                 }
287
288                 else if (++count == delete)
289                     break;
290             }
291
292             /*
293              *  This line wasn't a match so copy it to the temporary file.
294              */
295
296             if ((n = fputs(field, tmp)) == EOF || (c == '\n' && fputc('\n', tmp) == EOF))
297                 adios(NULL, "unable to write temporary file.");
298
299         } while (*field != '\0' && *field != '-');
300
301         /*
302          *  Get rid of the field buffer because we're done with it.
303          */
304
305         free((void *)field);
306     }
307
308     else {
309         /*
310          *  Find the end of the headers before adding the annotations if we're
311          *  appending instead of the default prepending.
312          */
313
314         if (append) {
315             /*
316              *  Copy lines from the input file to the temporary file until we
317              *  reach the end of the headers.
318              */
319
320             while ((c = getc(fp)) != EOF) {
321                 (void)putc(c, tmp);
322
323                 if (c == '\n') {
324                     (void)ungetc(c = getc(fp), fp);
325
326                     if (c == '\n' || c == '-')
327                         break;
328                 }
329             }
330         }
331
332         if (datesw)
333             fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
334         if ((cp = text)) {
335             do {
336                 while (*cp == ' ' || *cp == '\t')
337                     cp++;
338                 sp = cp;
339                 while (*cp && *cp++ != '\n')
340                     continue;
341                 if (cp - sp)
342                     fprintf (tmp, "%s: %*.*s", comp, cp - sp, cp - sp, sp);
343             } while (*cp);
344             if (cp[-1] != '\n' && cp != text)
345                 putc ('\n', tmp);
346         }
347     }
348
349     fflush (tmp);
350
351     /*
352      *  We've been messing with the input file position.  Move the input file
353      *  descriptor to the current place in the file because the stock data
354      *  copying routine uses the descriptor, not the pointer.
355      */
356
357     if (append || delete >= 0) {
358         if (lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1)
359             adios(NULL, "can't seek.");
360     }
361
362     cpydata (fd, fileno (tmp), file, tmpfil);
363     fclose (tmp);
364
365     if (inplace) {
366         if ((tmpfd = open (tmpfil, O_RDONLY)) == NOTOK)
367             adios (tmpfil, "unable to open for re-reading");
368
369         lseek (fd, (off_t) 0, SEEK_SET);
370
371         /*
372          *  We're making the file shorter if we're deleting a header field
373          *  so the file has to be truncated or it will contain garbage.
374          */
375
376         if (delete >= 0 && ftruncate(fd, 0) == -1)
377             adios(tmpfil, "unable to truncate.");
378
379         cpydata (tmpfd, fd, tmpfil, file);
380         close (tmpfd);
381         unlink (tmpfil);
382     } else {
383         strncpy (buffer, m_backup (file), sizeof(buffer));
384         if (rename (file, buffer) == NOTOK) {
385             switch (errno) {
386                 case ENOENT:    /* unlinked early - no annotations */
387                     unlink (tmpfil);
388                     break;
389
390                 default:
391                     admonish (buffer, "unable to rename %s to", file);
392                     break;
393             }
394             return 1;
395         }
396         if (rename (tmpfil, file) == NOTOK) {
397             admonish (file, "unable to rename %s to", tmpfil);
398             return 1;
399         }
400     }
401
402     /*
403      *  Close the delete file so that we don't run out of file pointers if
404      *  we're doing piles of files.  Note that this will make the close() in
405      *  lkclose() fail, but that failure is ignored so it's not a problem.
406      */
407
408     if (delete >= 0)
409         (void)fclose(fp);
410
411     return 0;
412 }