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