File: annosbr.c

package info (click to toggle)
nmh 1.8-4
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 7,860 kB
  • sloc: ansic: 50,445; sh: 22,697; makefile: 1,138; lex: 740; perl: 509; yacc: 265
file content (453 lines) | stat: -rw-r--r-- 12,489 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
/* annosbr.c -- prepend annotation to messages
 *
 * 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.
 */

#include "h/mh.h"
#include "sbr/dtime.h"
#include "annosbr.h"
#include "sbr/m_gmprot.h"
#include "sbr/cpydata.h"
#include "sbr/m_backup.h"
#include "sbr/error.h"
#include "h/tws.h"
#include "sbr/utils.h"
#include "sbr/lock_file.h"
#include "sbr/m_mktemp.h"
#include <fcntl.h>
#include <utime.h>


/*
 * static prototypes
 */
static int annosbr (int, char *, char *, char *, bool, bool, int, bool);

/*
 *	This "local" global and the annopreserve() function are a hack that allows additional
 *	functionality to be added to anno without piling on yet another annotate() argument.
 */

static	int	preserve_actime_and_modtime = 0;	/* set to preserve access and modification times on annotated message */

int
annotate (char *file, char *comp, char *text, bool inplace, bool datesw, int delete, bool append)
{
    int			i, fd;
    struct utimbuf	b;
    struct stat		s;
    int			failed_to_lock = 0;

    /* open and lock the file to be annotated */
    if ((fd = lkopendata (file, O_RDWR, 0, &failed_to_lock)) == NOTOK) {
	switch (errno) {
	    case ENOENT:
		break;

	    default:
		if (failed_to_lock) {
		    admonish (file, "unable to lock");
		} else {
		    admonish (file, "unable to open");
		}
		break;
	}
	return 1;
    }

    if (stat(file, &s) == -1) {
        inform("can't get access and modification times for %s", file);
	preserve_actime_and_modtime = 0;
    }

    b.actime = s.st_atime;
    b.modtime = s.st_mtime;

    i = annosbr (fd, file, comp, text, inplace, datesw, delete, append);

    if (preserve_actime_and_modtime && utime(file, &b) == -1)
        inform("can't set access and modification times for %s", file);

    lkclosedata (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.
 */

void
annolist(char *file, char *comp, char *text, int number)
{
    int		c;		/* current character */
    int		count;		/* header field (annotation) counter */
    char	*cp;		/* miscellaneous character pointer */
    char	*field;		/* buffer for header field */
    int		field_size;	/* size of field buffer */
    FILE	*fp;		/* file pointer made from locked file descriptor */
    int		length;		/* length of field name */
    int		n;		/* number of bytes written */
    char	*sp;		/* another miscellaneous character pointer */

    if ((fp = fopen(file, "r")) == NULL)
	adios(file, "unable to open");

    /*
     *  Allocate a buffer to hold the header components as they're read in.
     *  This buffer might need to be quite large, so we grow it as needed.
     */

    field = mh_xmalloc(field_size = 256);

    /*
     *  Get the length of the field name since we use it often.
     */

    length = strlen(comp);

    count = 0;

    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') {
		(void)ungetc(c, fp);
		break;
	    }

	    if (++n >= field_size - 1) {
		field = mh_xrealloc((void *)field, field_size += 256);
		
		cp = field + n - 1;
	    }
	}

	/*
	 *	NUL-terminate the field..
	 */

	*cp = '\0';

	if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
	    for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
		;

	    if (number)
		(void)printf("%d\t", ++count);

	    if (text == NULL && (sp = strrchr(cp, '/')))
		cp = sp + 1;

            puts(cp);
	}

    } while (*field != '\0' && *field != '-');

    /*
     *	Clean up.
     */

    free(field);

    (void)fclose(fp);
}

/*
 *	Set the preserve-times flag.  This hack eliminates the need for an additional argument to annotate().
 */

void
annopreserve(int preserve)
{
	preserve_actime_and_modtime = preserve;
}

static int
annosbr (int fd, char *file, char *comp, char *text, bool inplace, bool datesw, int delete, bool append)
{
    int mode, tmpfd;
    char *cp, *sp;
    char buffer[BUFSIZ], tmpfil[BUFSIZ];
    struct stat st;
    FILE	*tmp;
    int		c;		/* current character */
    int		count;		/* header field (annotation) counter */
    char	*field = NULL;	/* buffer for header field */
    int		field_size = 0;	/* size of field buffer */
    FILE	*fp = NULL;	/* file pointer made from locked file descriptor */
    int		length;		/* length of field name */
    int		n;		/* number of bytes written */

    mode = fstat (fd, &st) != -1 ? (int) (st.st_mode & 0777) : m_gmprot ();

    if ((cp = m_mktemp2(file, "annotate", NULL, &tmp)) == NULL) {
	die("unable to create temporary file");
    }
    strncpy (tmpfil, cp, sizeof(tmpfil) - 1);
    chmod (tmpfil, mode);

    /*
     *  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 (delete >= -1 || append) {
	if ((fp = fdopen(fd, "r")) == NULL)
	    die("unable to fdopen file.");

	field = mh_xmalloc(field_size = 256);
    }

    /*
     *	We're trying to delete a header field (annotation )if the delete flag is
     *	not -2 or less.  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.
     */

    if (delete >= -1) {
	/*
	 *  Get the length of the field name since we use it often.
	 */

	length = strlen(comp);

	/*
	 *  Initialize the field counter.  This is only used if we're deleting by
	 *  number.
	 */

	count = 0;

	/*
	 *  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.
	 */

	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') {
		    (void)ungetc(c, fp);
		    c = '\n';
		    break;
		}

		if (++n >= field_size - 1) {
		    field = mh_xrealloc((void *)field, field_size *= 2);
		
		    cp = field + n - 1;
		}
	    }

	    /*
	     *	NUL-terminate the field..
	     */

	    *cp = '\0';

	    /*
	     *	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.
	     */

	    if (strncasecmp(field, comp, length) == 0 && field[length] == ':') {
		if (delete == 0) {
		    if (text == NULL)
			break;

		    for (cp = field + length + 1; *cp == ' ' || *cp == '\t'; cp++)
			;

		    if (*text == '/') {
			if (strcmp(cp, text) == 0)
				break;
		    } else {
			if ((sp = strrchr(cp, '/')) != NULL)
			    cp = sp + 1;

			if (strcmp(cp, text) == 0)
			    break;
		    }
		}

		else if (delete == -1)
			continue;

		else if (++count == delete)
		    break;
	    }

	    /*
	     *	This line wasn't a match so copy it to the temporary file.
	     */

	    if (fputs(field, tmp) == EOF || (c == '\n' && fputc('\n', tmp) == EOF))
		die("unable to write temporary file.");

	} while (*field != '\0' && *field != '-');

	/*
	 *  Get rid of the field buffer because we're done with it.
	 */

	free(field);
    }

    else {
	/*
	 *  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.
	 */

	if (append) {
	    /*
	     *	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 {
	        (void)putc(c, tmp);

	        while ((c = getc(fp)) != EOF) {
		    (void)putc(c, tmp);

		    if (c == '\n') {
		        (void)ungetc(c = getc(fp), fp);

		        if (c == '\n' || c == '-')
			    break;
		    }
		}
	    }
	}

	if (datesw)
	    fprintf (tmp, "%s: %s\n", comp, dtimenow (0));
	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);
	}
    }

    fflush (tmp);

    /*
     *	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 (fp && lseek(fd, (off_t)ftell(fp), SEEK_SET) == (off_t)-1)
        die("can't seek.");

    cpydata (fd, fileno (tmp), file, tmpfil);
    fclose (tmp);

    if (inplace) {
	if ((tmpfd = open (tmpfil, O_RDONLY)) == -1)
	    adios (tmpfil, "unable to open for re-reading");

	lseek(fd, 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);
	(void) m_unlink (tmpfil);
    } else {
	strncpy (buffer, m_backup (file), sizeof(buffer) - 1);
	if (rename (file, buffer) == -1) {
	    switch (errno) {
		case ENOENT:	/* unlinked early - no annotations */
		    (void) m_unlink (tmpfil);
		    break;

		default:
		    admonish (buffer, "unable to rename %s to", file);
		    break;
	    }
	    return 1;
	}
	if (rename (tmpfil, file) == -1) {
	    admonish (file, "unable to rename %s to", tmpfil);
	    return 1;
	}
    }

    /*
     *	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 (fp)
	(void)fclose(fp);

    return 0;
}