Home | History | Annotate | Line # | Download | only in global
      1 /*	$NetBSD: record.c,v 1.4 2022/10/08 16:12:45 christos Exp $	*/
      2 
      3 /*++
      4 /* NAME
      5 /*	record 3
      6 /* SUMMARY
      7 /*	simple typed record I/O
      8 /* SYNOPSIS
      9 /*	#include <record.h>
     10 /*
     11 /*	int	rec_get(stream, buf, maxsize)
     12 /*	VSTREAM	*stream;
     13 /*	VSTRING	*buf;
     14 /*	ssize_t	maxsize;
     15 /*
     16 /*	int	rec_get_raw(stream, buf, maxsize, flags)
     17 /*	VSTREAM	*stream;
     18 /*	VSTRING	*buf;
     19 /*	ssize_t	maxsize;
     20 /*	int	flags;
     21 /*
     22 /*	int	rec_put(stream, type, data, len)
     23 /*	VSTREAM	*stream;
     24 /*	int	type;
     25 /*	const char *data;
     26 /*	ssize_t	len;
     27 /* AUXILIARY FUNCTIONS
     28 /*	int	rec_put_type(stream, type, offset)
     29 /*	VSTREAM	*stream;
     30 /*	int	type;
     31 /*	long	offset;
     32 /*
     33 /*	int	rec_fprintf(stream, type, format, ...)
     34 /*	VSTREAM	*stream;
     35 /*	int	type;
     36 /*	const char *format;
     37 /*
     38 /*	int	rec_fputs(stream, type, str)
     39 /*	VSTREAM	*stream;
     40 /*	int	type;
     41 /*	const char *str;
     42 /*
     43 /*	int	REC_PUT_BUF(stream, type, buf)
     44 /*	VSTREAM	*stream;
     45 /*	int	type;
     46 /*	VSTRING	*buf;
     47 /*
     48 /*	int	rec_vfprintf(stream, type, format, ap)
     49 /*	VSTREAM	*stream;
     50 /*	int	type;
     51 /*	const char *format;
     52 /*	va_list	ap;
     53 /*
     54 /*	int	rec_goto(stream, where)
     55 /*	VSTREAM	*stream;
     56 /*	const char *where;
     57 /*
     58 /*	int	rec_pad(stream, type, len)
     59 /*	VSTREAM *stream;
     60 /*	int	type;
     61 /*	ssize_t	len;
     62 /*
     63 /*	REC_SPACE_NEED(buflen, reclen)
     64 /*	ssize_t	buflen;
     65 /*	ssize_t	reclen;
     66 /*
     67 /*	REC_GET_HIDDEN_TYPE(type)
     68 /*	int	type;
     69 /* DESCRIPTION
     70 /*	This module reads and writes typed variable-length records.
     71 /*	Each record contains a 1-byte type code (0..255), a length
     72 /*	(1 or more bytes) and as much data as the length specifies.
     73 /*
     74 /*	rec_get_raw() retrieves a record from the named record stream
     75 /*	and returns the record type. The \fImaxsize\fR argument is
     76 /*	zero, or specifies a maximal acceptable record length.
     77 /*	The result is REC_TYPE_EOF when the end of the file was reached,
     78 /*	and REC_TYPE_ERROR in case of a bad record. The result buffer is
     79 /*	null-terminated for convenience. Records may contain embedded
     80 /*	null characters. The \fIflags\fR argument specifies zero or
     81 /*	more of the following:
     82 /* .IP REC_FLAG_FOLLOW_PTR
     83 /*	Follow PTR records, instead of exposing them to the application.
     84 /* .IP REC_FLAG_SKIP_DTXT
     85 /*	Skip "deleted text" records, instead of exposing them to
     86 /*	the application.
     87 /* .IP REC_FLAG_SEEK_END
     88 /*	Seek to the end-of-file upon reading a REC_TYPE_END record.
     89 /* .PP
     90 /*	Specify REC_FLAG_NONE to request no special processing,
     91 /*	and REC_FLAG_DEFAULT for normal use.
     92 /*
     93 /*	rec_get() is a wrapper around rec_get_raw() that always
     94 /*	enables the REC_FLAG_FOLLOW_PTR, REC_FLAG_SKIP_DTXT
     95 /*	and REC_FLAG_SEEK_END features.
     96 /*
     97 /*	REC_GET_HIDDEN_TYPE() is an unsafe macro that returns
     98 /*	non-zero when the specified record type is "not exposed"
     99 /*	by rec_get().
    100 /*
    101 /*	rec_put() stores the specified record and returns the record
    102 /*	type, or REC_TYPE_ERROR in case of problems.
    103 /*
    104 /*	rec_put_type() updates the type field of the record at the
    105 /*	specified file offset.  The result is the new record type,
    106 /*	or REC_TYPE_ERROR in case of trouble.
    107 /*
    108 /*	rec_fprintf() and rec_vfprintf() format their arguments and
    109 /*	write the result to the named stream. The result is the same
    110 /*	as with rec_put().
    111 /*
    112 /*	rec_fputs() writes a record with as contents a copy of the
    113 /*	specified string. The result is the same as with rec_put().
    114 /*
    115 /*	REC_PUT_BUF() is a wrapper for rec_put() that makes it
    116 /*	easier to handle VSTRING buffers. It is an unsafe macro
    117 /*	that evaluates some arguments more than once.
    118 /*
    119 /*	rec_goto() takes the argument of a pointer record and moves
    120 /*	the file pointer to the specified location. A zero position
    121 /*	means do nothing. The result is REC_TYPE_ERROR in case of
    122 /*	failure.
    123 /*
    124 /*	rec_pad() writes a record that occupies the larger of (the
    125 /*	specified amount) or (an implementation-defined minimum).
    126 /*
    127 /*	REC_SPACE_NEED(buflen, reclen) converts the specified buffer
    128 /*	length into a record length. This macro modifies its second
    129 /*	argument.
    130 /* DIAGNOSTICS
    131 /*	Panics: interface violations. Fatal errors: insufficient memory.
    132 /*	Warnings: corrupted file.
    133 /* LICENSE
    134 /* .ad
    135 /* .fi
    136 /*	The Secure Mailer license must be distributed with this software.
    137 /* AUTHOR(S)
    138 /*	Wietse Venema
    139 /*	IBM T.J. Watson Research
    140 /*	P.O. Box 704
    141 /*	Yorktown Heights, NY 10598, USA
    142 /*
    143 /*	Wietse Venema
    144 /*	Google, Inc.
    145 /*	111 8th Avenue
    146 /*	New York, NY 10011, USA
    147 /*--*/
    148 
    149 /* System library. */
    150 
    151 #include <sys_defs.h>
    152 #include <stdlib.h>			/* 44BSD stdarg.h uses abort() */
    153 #include <stdarg.h>
    154 #include <unistd.h>
    155 #include <string.h>
    156 
    157 #ifndef NBBY
    158 #define NBBY 8				/* XXX should be in sys_defs.h */
    159 #endif
    160 
    161 /* Utility library. */
    162 
    163 #include <msg.h>
    164 #include <mymalloc.h>
    165 #include <vstream.h>
    166 #include <vstring.h>
    167 #include <stringops.h>
    168 
    169 /* Global library. */
    170 
    171 #include <off_cvt.h>
    172 #include <rec_type.h>
    173 #include <record.h>
    174 
    175 /* rec_put_type - update record type field */
    176 
    177 int     rec_put_type(VSTREAM *stream, int type, off_t offset)
    178 {
    179     if (type < 0 || type > 255)
    180 	msg_panic("rec_put_type: bad record type %d", type);
    181 
    182     if (msg_verbose > 2)
    183 	msg_info("rec_put_type: %d at %ld", type, (long) offset);
    184 
    185     if (vstream_fseek(stream, offset, SEEK_SET) < 0
    186 	|| VSTREAM_PUTC(type, stream) != type) {
    187 	msg_warn("%s: seek or write error", VSTREAM_PATH(stream));
    188 	return (REC_TYPE_ERROR);
    189     } else {
    190 	return (type);
    191     }
    192 }
    193 
    194 /* rec_put - store typed record */
    195 
    196 int     rec_put(VSTREAM *stream, int type, const char *data, ssize_t len)
    197 {
    198     ssize_t len_rest;
    199     int     len_byte;
    200 
    201     if (type < 0 || type > 255)
    202 	msg_panic("rec_put: bad record type %d", type);
    203 
    204     if (msg_verbose > 2)
    205 	msg_info("rec_put: type %c len %ld data %.10s",
    206 		 type, (long) len, data);
    207 
    208     /*
    209      * Write the record type, one byte.
    210      */
    211     if (VSTREAM_PUTC(type, stream) == VSTREAM_EOF)
    212 	return (REC_TYPE_ERROR);
    213 
    214     /*
    215      * Write the record data length in 7-bit portions, using the 8th bit to
    216      * indicate that there is more. Use as many length bytes as needed.
    217      */
    218     len_rest = len;
    219     do {
    220 	len_byte = len_rest & 0177;
    221 	if (len_rest >>= 7U)
    222 	    len_byte |= 0200;
    223 	if (VSTREAM_PUTC(len_byte, stream) == VSTREAM_EOF) {
    224 	    return (REC_TYPE_ERROR);
    225 	}
    226     } while (len_rest != 0);
    227 
    228     /*
    229      * Write the record data portion. Use as many length bytes as needed.
    230      */
    231     if (len && vstream_fwrite(stream, data, len) != len)
    232 	return (REC_TYPE_ERROR);
    233     return (type);
    234 }
    235 
    236 /* rec_get_raw - retrieve typed record */
    237 
    238 int     rec_get_raw(VSTREAM *stream, VSTRING *buf, ssize_t maxsize, int flags)
    239 {
    240     const char *myname = "rec_get";
    241     int     type;
    242     ssize_t len;
    243     int     len_byte;
    244     unsigned shift;
    245 
    246     /*
    247      * Sanity check.
    248      */
    249     if (maxsize < 0)
    250 	msg_panic("%s: bad record size limit: %ld", myname, (long) maxsize);
    251 
    252     for (;;) {
    253 
    254 	/*
    255 	 * Extract the record type.
    256 	 */
    257 	if ((type = VSTREAM_GETC(stream)) == VSTREAM_EOF)
    258 	    return (REC_TYPE_EOF);
    259 
    260 	/*
    261 	 * Find out the record data length. Return an error result when the
    262 	 * record data length is malformed or when it exceeds the acceptable
    263 	 * limit.
    264 	 */
    265 	for (len = 0, shift = 0; /* void */ ; shift += 7) {
    266 	    if (shift >= (int) (NBBY * sizeof(int))) {
    267 		msg_warn("%s: too many length bits, record type %d",
    268 			 VSTREAM_PATH(stream), type);
    269 		return (REC_TYPE_ERROR);
    270 	    }
    271 	    if ((len_byte = VSTREAM_GETC(stream)) == VSTREAM_EOF) {
    272 		msg_warn("%s: unexpected EOF reading length, record type %d",
    273 			 VSTREAM_PATH(stream), type);
    274 		return (REC_TYPE_ERROR);
    275 	    }
    276 	    len |= (len_byte & 0177) << shift;
    277 	    if ((len_byte & 0200) == 0)
    278 		break;
    279 	}
    280 	if (len < 0 || (maxsize > 0 && len > maxsize)) {
    281 	    msg_warn("%s: illegal length %ld, record type %d",
    282 		     VSTREAM_PATH(stream), (long) len, type);
    283 	    while (len-- > 0 && VSTREAM_GETC(stream) != VSTREAM_EOF)
    284 		 /* void */ ;
    285 	    return (REC_TYPE_ERROR);
    286 	}
    287 
    288 	/*
    289 	 * Reserve buffer space for the result, and read the record data into
    290 	 * the buffer.
    291 	 */
    292 	if (vstream_fread_buf(stream, buf, len) != len) {
    293 	    msg_warn("%s: unexpected EOF in data, record type %d length %ld",
    294 		     VSTREAM_PATH(stream), type, (long) len);
    295 	    return (REC_TYPE_ERROR);
    296 	}
    297 	VSTRING_TERMINATE(buf);
    298 	if (msg_verbose > 2)
    299 	    msg_info("%s: type %c len %ld data %.10s", myname,
    300 		     type, (long) len, vstring_str(buf));
    301 
    302 	/*
    303 	 * Transparency options.
    304 	 */
    305 	if (flags == 0)
    306 	    break;
    307 	if (type == REC_TYPE_PTR && (flags & REC_FLAG_FOLLOW_PTR) != 0
    308 	    && (type = rec_goto(stream, vstring_str(buf))) != REC_TYPE_ERROR)
    309 	    continue;
    310 	if (type == REC_TYPE_DTXT && (flags & REC_FLAG_SKIP_DTXT) != 0)
    311 	    continue;
    312 	if (type == REC_TYPE_END && (flags & REC_FLAG_SEEK_END) != 0
    313 	    && vstream_fseek(stream, (off_t) 0, SEEK_END) < 0) {
    314 	    msg_warn("%s: seek error after reading END record: %m",
    315 		     VSTREAM_PATH(stream));
    316 	    return (REC_TYPE_ERROR);
    317 	}
    318 	break;
    319     }
    320     return (type);
    321 }
    322 
    323 /* rec_goto - follow PTR record */
    324 
    325 int     rec_goto(VSTREAM *stream, const char *buf)
    326 {
    327     off_t   offset;
    328     static char *saved_path;
    329     static off_t saved_offset;
    330     static int reverse_count;
    331 
    332     /*
    333      * Crude workaround for queue file loops. VSTREAMs currently have no
    334      * option to attach application-specific data, so we use global state and
    335      * simple logic to detect if an application switches streams. We trigger
    336      * on reverse jumps only. There's one reverse jump for every inserted
    337      * header, but only one reverse jump for all appended recipients. No-one
    338      * is likely to insert 10000 message headers, but someone might append
    339      * 10000 recipients.
    340      */
    341 #define REVERSE_JUMP_LIMIT	10000
    342 
    343     if (saved_path == 0 || strcmp(saved_path, VSTREAM_PATH(stream)) != 0) {
    344 	if (saved_path)
    345 	    myfree(saved_path);
    346 	saved_path = mystrdup(VSTREAM_PATH(stream));
    347 	reverse_count = 0;
    348 	saved_offset = 0;
    349     }
    350     while (ISSPACE(*buf))
    351 	buf++;
    352     if ((offset = off_cvt_string(buf)) < 0) {
    353 	msg_warn("%s: malformed pointer record value: %s",
    354 		 VSTREAM_PATH(stream), buf);
    355 	return (REC_TYPE_ERROR);
    356     } else if (offset == 0) {
    357 	/* Dummy record. */
    358 	return (0);
    359     } else if (offset <= saved_offset && ++reverse_count > REVERSE_JUMP_LIMIT) {
    360 	msg_warn("%s: too many reverse jump records", VSTREAM_PATH(stream));
    361 	return (REC_TYPE_ERROR);
    362     } else if (vstream_fseek(stream, offset, SEEK_SET) < 0) {
    363 	msg_warn("%s: seek error after pointer record: %m",
    364 		 VSTREAM_PATH(stream));
    365 	return (REC_TYPE_ERROR);
    366     } else {
    367 	saved_offset = offset;
    368 	return (0);
    369     }
    370 }
    371 
    372 /* rec_vfprintf - write formatted string to record */
    373 
    374 int     rec_vfprintf(VSTREAM *stream, int type, const char *format, va_list ap)
    375 {
    376     static VSTRING *vp;
    377 
    378     if (vp == 0)
    379 	vp = vstring_alloc(100);
    380 
    381     /*
    382      * Writing a formatted string involves an extra copy, because we must
    383      * know the record length before we can write it.
    384      */
    385     vstring_vsprintf(vp, format, ap);
    386     return (REC_PUT_BUF(stream, type, vp));
    387 }
    388 
    389 /* rec_fprintf - write formatted string to record */
    390 
    391 int     rec_fprintf(VSTREAM *stream, int type, const char *format,...)
    392 {
    393     int     result;
    394     va_list ap;
    395 
    396     va_start(ap, format);
    397     result = rec_vfprintf(stream, type, format, ap);
    398     va_end(ap);
    399     return (result);
    400 }
    401 
    402 /* rec_fputs - write string to record */
    403 
    404 int     rec_fputs(VSTREAM *stream, int type, const char *str)
    405 {
    406     return (rec_put(stream, type, str, str ? strlen(str) : 0));
    407 }
    408 
    409 /* rec_pad - write padding record */
    410 
    411 int     rec_pad(VSTREAM *stream, int type, ssize_t len)
    412 {
    413     int     width = len - 2;		/* type + length */
    414 
    415     return (rec_fprintf(stream, type, "%*s",
    416 			width < 1 ? 1 : width, "0"));
    417 }
    418