]> diplodocus.org Git - nmh/blob - uip/scansbr.c
28c382fe1180b480ec7fa2c1e8b25f8ed0e8479c
[nmh] / uip / scansbr.c
1 /* scansbr.c -- routines to help scan along...
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/m_name.h"
10 #include "sbr/m_gmprot.h"
11 #include "sbr/m_getfld.h"
12 #include "sbr/getcpy.h"
13 #include "sbr/error.h"
14 #include "h/addrsbr.h"
15 #include "h/fmt_scan.h"
16 #include "h/scansbr.h"
17 #include "h/tws.h"
18 #include "h/utils.h"
19 #include "sbr/terminal.h"
20
21 static struct format *fmt;
22 static struct comp *datecomp; /* pntr to "date" comp */
23 static struct comp *bodycomp; /* pntr to "body" pseudo-comp *
24 * (if referenced) */
25 static int ncomps = 0; /* # of interesting components */
26 static char **compbuffers = 0; /* buffers for component text */
27 static struct comp **used_buf = 0; /* stack for comp that use buffers */
28
29 static int dat[5]; /* aux. data for format routine */
30
31 static m_getfld_state_t gstate; /* for accessor functions below */
32
33 #define DIEWRERR() adios (scnmsg, "write error on")
34
35 #define PUTC(c) \
36 if (putc((c), scnout) == EOF) \
37 DIEWRERR();
38
39 #define FPUTS(buf) {\
40 if (fputs(buf,scnout) == EOF)\
41 DIEWRERR();\
42 }
43
44 /* outnum determines how the input from inb is copied. If positive then
45 * it is the number of the message to create, e.g. for inc(1), and all
46 * of the email is copied into that message, with some tweaks. If 0,
47 * e.g. `scan 42', then reading inb can dubiously stop after a whole
48 * buffer of body, even though this might not be enough to fulfill the
49 * scan format and width. Or if -1 then no copy is being created, but
50 * all of inb must be read because the next message must be found, e.g.
51 * `scan -file foo.mbox'. */
52
53 int
54 scan (FILE *inb, int innum, int outnum, char *nfs, int width, int curflg,
55 int unseen, char *folder, long size, int noisy, charstring_t *scanl)
56 {
57 static bool deja_vu;
58 static int tty_width;
59 int i, compnum, encrypted, state;
60 char *cp, *tmpbuf, *startbody, **nxtbuf;
61 char *saved_c_text = NULL;
62 struct comp *cptr;
63 struct comp **savecomp;
64 char *scnmsg = NULL;
65 FILE *scnout = NULL;
66 char name[NAMESZ];
67 int bufsz;
68 static int rlwidth, slwidth;
69
70 /* first-time only initialization, which will always happen the
71 way the code is now, with callers initializing *scanl to NULL.
72 scanl used to be a global. */
73 if (! *scanl) {
74 if (width == -1) {
75 if (!deja_vu) {
76 deja_vu = true;
77 tty_width = sc_width();
78 }
79
80 width = max(tty_width, WIDTH / 2);
81 } else if (width == 0) {
82 /* Unlimited width. */
83 width = INT_MAX;
84 }
85 dat[3] = slwidth = width;
86 *scanl = charstring_create (min(width, NMH_BUFSIZ));
87 if (outnum)
88 umask(~m_gmprot());
89
90 /* Compile format string */
91 ncomps = fmt_compile (nfs, &fmt, 1) + 2;
92
93 bodycomp = fmt_findcomp("body");
94 datecomp = fmt_findcomp("date");
95 cptr = fmt_findcomp("folder");
96 if (cptr && folder)
97 cptr->c_text = mh_xstrdup(folder);
98 if (fmt_addcompentry("encrypted")) {
99 ncomps++;
100 }
101 cptr = fmt_findcomp("dtimenow");
102 if (cptr)
103 cptr->c_text = getcpy(dtimenow (0));
104
105 /*
106 * In other programs I got rid of this complicated buffer switching,
107 * but since scan reads lots of messages at once and this complicated
108 * memory management, I decided to keep it; otherwise there was
109 * the potential for a lot of malloc() and free()s, and I could
110 * see the malloc() pool really getting fragmented. Maybe it
111 * wouldn't be an issue in practice; perhaps this will get
112 * revisited someday.
113 *
114 * So, some notes for what's going on:
115 *
116 * nxtbuf is an array of pointers that contains malloc()'d buffers
117 * to hold our component text. used_buf is an array of struct comp
118 * pointers that holds pointers to component structures we found while
119 * processing a message.
120 *
121 * We read in the message with m_getfld(), using "tmpbuf" as our
122 * input buffer. tmpbuf is set at the start of message processing
123 * to the first buffer in our buffer pool (nxtbuf).
124 *
125 * Every time we find a component we care about, we set that component's
126 * text buffer to the current value of tmpbuf, and then switch tmpbuf
127 * to the next buffer in our pool. We also add that component to
128 * our used_buf pool.
129 *
130 * When we're done, we go back and zero out all of the component
131 * text buffer pointers that we saved in used_buf.
132 *
133 * Note that this means c_text memory is NOT owned by the fmt_module
134 * and it's our responsibility to free it.
135 */
136
137 nxtbuf = compbuffers = mh_xcalloc(ncomps, sizeof *nxtbuf);
138 used_buf = mh_xcalloc(ncomps + 1, sizeof *used_buf);
139 used_buf += ncomps+1; *--used_buf = 0;
140 rlwidth = NMH_BUFSIZ;
141 for (i = ncomps; i--; )
142 *nxtbuf++ = mh_xmalloc(rlwidth);
143 }
144
145 /*
146 * each-message initialization
147 */
148 nxtbuf = compbuffers;
149 savecomp = used_buf;
150 tmpbuf = *nxtbuf++;
151 startbody = NULL;
152 dat[0] = innum ? innum : outnum;
153 dat[1] = curflg;
154 dat[4] = unseen;
155
156 /*
157 * Get the first field. If the message is non-empty
158 * and we're doing an "inc", open the output file.
159 */
160 bufsz = rlwidth;
161 m_getfld_state_reset (&gstate);
162 if ((state = m_getfld (&gstate, name, tmpbuf, &bufsz, inb)) == FILEEOF) {
163 if (ferror(inb)) {
164 advise("read", "unable to"); /* "read error" */
165 return SCNFAT;
166 }
167 return SCNEOF;
168 }
169
170 if (outnum > 0) {
171 scnmsg = m_name (outnum);
172 if (*scnmsg == '?') /* msg num out of range */
173 return SCNNUM;
174 if ((scnout = fopen (scnmsg, "w")) == NULL)
175 adios (scnmsg, "unable to write");
176 }
177
178 /* scan - main loop */
179 for (compnum = 1; ;
180 bufsz = rlwidth, state = m_getfld (&gstate, name, tmpbuf, &bufsz, inb)) {
181 switch (state) {
182 case FLD:
183 case FLDPLUS:
184 compnum++;
185 if (scnout) {
186 FPUTS (name);
187 PUTC(':');
188 FPUTS (tmpbuf);
189 }
190 /*
191 * if we're interested in this component, save a pointer
192 * to the component text, then start using our next free
193 * buffer as the component temp buffer (buffer switching
194 * saves an extra copy of the component text).
195 */
196 if ((cptr = fmt_findcasecomp(name))) {
197 if (! cptr->c_text) {
198 cptr->c_text = tmpbuf;
199 for (cp = tmpbuf + strlen (tmpbuf) - 1;
200 cp >= tmpbuf; cp--)
201 if (isspace ((unsigned char) *cp))
202 *cp = 0;
203 else
204 break;
205 *--savecomp = cptr;
206 tmpbuf = *nxtbuf++;
207 }
208 }
209
210 while (state == FLDPLUS) {
211 bufsz = rlwidth;
212 state = m_getfld (&gstate, name, tmpbuf, &bufsz, inb);
213 if (scnout)
214 FPUTS (tmpbuf);
215 }
216 break;
217
218 case BODY:
219 /*
220 * A slight hack ... if we have less than rlwidth characters
221 * in the buffer, call m_getfld again.
222 */
223
224 if ((i = strlen(tmpbuf)) < rlwidth) {
225 bufsz = rlwidth - i;
226 state = m_getfld (&gstate, name, tmpbuf + i, &bufsz, inb);
227 }
228
229 if (outnum == 0) {
230 state = FILEEOF; /* stop now if scan cmd */
231 if (bodycomp && startbody == NULL)
232 startbody = tmpbuf;
233 goto finished;
234 }
235 if (scnout) {
236 PUTC('\n');
237 FPUTS (tmpbuf);
238 }
239 /*
240 * The previous code here used to call m_getfld() using
241 * pointers to the underlying output stdio buffers to
242 * avoid the extra copy. Tests by Markus Schnalke show
243 * no noticeable performance loss on larger mailboxes
244 * if we incur an extra copy, and messing around with
245 * internal stdio buffers is becoming more and more
246 * unportable as times go on. So from now on just deal
247 * with the overhead of an extra copy.
248 *
249 * Subtle change - with the previous code tmpbuf wasn't
250 * used, so we could reuse it for the {body} component.
251 * Now since we're using tmpbuf as our read buffer we
252 * need to save the beginning of the body for later.
253 * See the above (and below) use of startbody.
254 */
255 body:;
256 if (bodycomp && startbody == NULL) {
257 startbody = tmpbuf;
258 tmpbuf = *nxtbuf++;
259 }
260
261 while (state == BODY) {
262 bufsz = rlwidth;
263 state = m_getfld (&gstate, name, tmpbuf, &bufsz, inb);
264 if (scnout)
265 FPUTS(tmpbuf);
266 }
267 goto finished;
268
269 case LENERR:
270 case FMTERR:
271 if (innum)
272 fprintf (stderr, "??Format error (message %d) in ",
273 outnum ? outnum : innum);
274 else
275 fprintf (stderr, "??Format error in ");
276
277 fprintf (stderr, "component %d\n", compnum);
278
279 if (scnout) {
280 FPUTS ("\n\nBAD MSG:\n");
281 FPUTS (name);
282 PUTC('\n');
283 state = BODY;
284 goto body;
285 }
286 goto finished;
287
288 case FILEEOF:
289 goto finished;
290
291 default:
292 die("getfld() returned %d", state);
293 }
294 }
295
296 /*
297 * format and output the scan line.
298 */
299 finished:
300 if (ferror(inb)) {
301 advise("read", "unable to"); /* "read error" */
302 return SCNFAT;
303 }
304
305 /* Save and restore buffer so we don't trash our dynamic pool! */
306 if (bodycomp) {
307 saved_c_text = bodycomp->c_text;
308 bodycomp->c_text = startbody;
309 }
310
311 if (size)
312 dat[2] = size;
313 else if (scnout)
314 {
315 dat[2] = ftell(scnout);
316 if (dat[2] == EOF) DIEWRERR();
317 }
318
319 if ((datecomp && !datecomp->c_text) || (!size && !outnum)) {
320 struct stat st;
321
322 fstat (fileno(inb), &st);
323 if (!size && !outnum)
324 dat[2] = st.st_size;
325 if (datecomp) {
326 if (! datecomp->c_text) {
327 if (datecomp->c_tws == NULL)
328 NEW0(datecomp->c_tws);
329 *datecomp->c_tws = *dlocaltime ((time_t *) &st.st_mtime);
330 datecomp->c_flags |= CF_DATEFAB|CF_TRUE;
331 } else {
332 datecomp->c_flags &= ~CF_DATEFAB;
333 }
334 }
335 }
336
337 fmt_scan (fmt, *scanl, slwidth, dat, NULL);
338
339 if (bodycomp)
340 bodycomp->c_text = saved_c_text;
341
342 if (noisy)
343 fputs (charstring_buffer (*scanl), stdout);
344
345 cptr = fmt_findcomp ("encrypted");
346 encrypted = cptr && cptr->c_text;
347
348 /* return dynamically allocated buffers to pool */
349 while ((cptr = *savecomp++)) {
350 cptr->c_text = NULL;
351 }
352
353 if (scnout && (ferror(scnout) || fclose (scnout) == EOF))
354 DIEWRERR();
355
356 return state != FILEEOF ? SCNERR : encrypted ? SCNENC : SCNMSG;
357 }
358
359
360 /* The following two functions allow access to the global gstate above. */
361 void
362 scan_finished(void)
363 {
364 m_getfld_state_destroy (&gstate);
365 }
366
367 void
368 scan_detect_mbox_style (FILE *f)
369 {
370 m_unknown (&gstate, f);
371 }