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