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