1 /* pick.c -- search for messages by content
2  *
3  * This code is Copyright (c) 2002, 2008, 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 <h/tws.h>
10 #include <h/picksbr.h>
11 #include <h/utils.h>
12 #include "../sbr/m_maildir.h"
13 
14 #define PICK_SWITCHES \
15     X("reverse", 0, REVSW) \
16     X("and", 0, ANDSW) \
17     X("or", 0, ORSW) \
18     X("not", 0, NOTSW) \
19     X("lbrace", 0, LBRSW) \
20     X("rbrace", 0, RBRSW) \
21     X("cc  pattern", 0, CCSW) \
22     X("date  pattern", 0, DATESW) \
23     X("from  pattern", 0, FROMSW) \
24     X("search  pattern", 0, SRCHSW) \
25     X("subject  pattern", 0, SUBJSW) \
26     X("to  pattern", 0, TOSW) \
27     X("-othercomponent  pattern", 0, OTHRSW) \
28     X("after date", 0, AFTRSW) \
29     X("before date", 0, BEFRSW) \
30     X("datefield field", 5, DATFDSW) \
31     X("sequence name", 0, SEQSW) \
32     X("nosequence", 0, NSEQSW) \
33     X("public", 0, PUBLSW) \
34     X("nopublic", 0, NPUBLSW) \
35     X("zero", 0, ZEROSW) \
36     X("nozero", 0, NZEROSW) \
37     X("list", 0, LISTSW) \
38     X("nolist", 0, NLISTSW) \
39     X("debug", 0, DEBUGSW) \
40     X("version", 0, VERSIONSW) \
41     X("help", 0, HELPSW) \
42 
43 #define X(sw, minchars, id) id,
44 DEFINE_SWITCH_ENUM(PICK);
45 #undef X
46 
47 #define X(sw, minchars, id) { sw, minchars, id },
48 DEFINE_SWITCH_ARRAY(PICK, switches);
49 #undef X
50 
51 static int listsw = -1;
52 
53 static void putzero_done (int) NORETURN;
54 
55 int
main(int argc,char ** argv)56 main (int argc, char **argv)
57 {
58     int publicsw = -1, zerosw = 1, vecp = 0;
59     size_t seqp = 0;
60     int msgnum;
61     char *maildir, *folder = NULL, buf[100];
62     char *cp, **argp, **arguments;
63     svector_t seqs = svector_create (0);
64     char *vec[MAXARGS];
65     struct msgs_array msgs = { 0, 0, NULL };
66     struct msgnum_array nums = { 0, 0, NULL };
67     struct msgs *mp, *mp2;
68     FILE *fp;
69     int debug = 0;
70     int reverse = 0;
71     int start, end, inc;
72 
73     if (nmh_init(argv[0], 1)) { return 1; }
74 
75     done=putzero_done;
76 
77     /* Deprecated.  Use -debug instead. */
78     if ((cp = getenv ("MHPDEBUG")) && *cp)
79 	++debug;
80 
81     arguments = getarguments (invo_name, argc, argv, 1);
82     argp = arguments;
83 
84     while ((cp = *argp++)) {
85 	if (*cp == '-') {
86 	    if (*++cp == '-') {
87 		vec[vecp++] = --cp;
88 		goto pattern;
89 	    }
90 	    switch (smatch (cp, switches)) {
91 	    case AMBIGSW:
92 		ambigsw (cp, switches);
93 		listsw = 0;	/* HACK */
94 		done (1);
95 	    case UNKWNSW:
96 		adios (NULL, "-%s unknown", cp);
97 
98 	    case HELPSW:
99 		snprintf (buf, sizeof(buf), "%s [+folder] [msgs] [switches]",
100 			  invo_name);
101 		print_help (buf, switches, 1);
102 		listsw = 0;	/* HACK */
103 		done (0);
104 	    case VERSIONSW:
105 		print_version(invo_name);
106 		listsw = 0;	/* HACK */
107 		done (0);
108 
109             case REVSW:
110                 reverse = 1;
111                 continue;
112 
113 	    case CCSW:
114 	    case DATESW:
115 	    case FROMSW:
116 	    case SUBJSW:
117 	    case TOSW:
118 	    case DATFDSW:
119 	    case AFTRSW:
120 	    case BEFRSW:
121 	    case SRCHSW:
122 		vec[vecp++] = --cp;
123 	    pattern:
124 		if (!(cp = *argp++))/* allow -xyz arguments */
125 		    adios (NULL, "missing argument to %s", argp[-2]);
126 		vec[vecp++] = cp;
127 		continue;
128 	    case OTHRSW:
129 		adios (NULL, "internal error!");
130 
131 	    case ANDSW:
132 	    case ORSW:
133 	    case NOTSW:
134 	    case LBRSW:
135 	    case RBRSW:
136 		vec[vecp++] = --cp;
137 		continue;
138 
139 	    case SEQSW:
140 		if (!(cp = *argp++) || *cp == '-')
141 		    adios (NULL, "missing argument to %s", argp[-2]);
142 
143                 if (!seq_nameok (cp))
144                   done (1);
145 
146 		svector_push_back (seqs, cp);
147 		seqp++;
148 		continue;
149 	    case NSEQSW:
150 		seqp = 0;
151 		continue;
152 	    case PUBLSW:
153 		publicsw = 1;
154 		continue;
155 	    case NPUBLSW:
156 		publicsw = 0;
157 		continue;
158 	    case ZEROSW:
159 		zerosw++;
160 		continue;
161 	    case NZEROSW:
162 		zerosw = 0;
163 		continue;
164 
165 	    case LISTSW:
166 		listsw = 1;
167 		continue;
168 	    case NLISTSW:
169 		listsw = 0;
170 		continue;
171 
172 	    case DEBUGSW:
173 		++debug;
174 		continue;
175 	    }
176 	}
177 	if (*cp == '+' || *cp == '@') {
178 	    if (folder)
179 		adios (NULL, "only one folder at a time!");
180 	    else
181 		folder = pluspath (cp);
182 	} else
183 		app_msgarg(&msgs, cp);
184     }
185     vec[vecp] = NULL;
186 
187     if (!context_find ("path"))
188 	free (path ("./", TFOLDER));
189 
190     /*
191      * If we didn't specify which messages to search,
192      * then search the whole folder.
193      */
194     if (!msgs.size)
195 	app_msgarg(&msgs, "all");
196 
197     if (!folder)
198 	folder = getfolder (1);
199     maildir = m_maildir (folder);
200 
201     if (chdir (maildir) == NOTOK)
202 	adios (maildir, "unable to change directory to");
203 
204     /* read folder and create message structure */
205     if (!(mp = folder_read (folder, 0)))
206 	adios (NULL, "unable to read folder %s", folder);
207 
208     /* check for empty folder */
209     if (mp->nummsg == 0)
210 	adios (NULL, "no messages in %s", folder);
211 
212     /* parse all the message ranges/sequences and set SELECTED */
213     for (msgnum = 0; msgnum < msgs.size; msgnum++)
214 	if (!m_convert (mp, msgs.msgs[msgnum]))
215 	    done (1);
216 
217     /*
218      * If we aren't saving the results to a sequence,
219      * we default to list the results.
220      */
221     if (listsw == -1)
222 	listsw = !seqp;
223 
224     if (publicsw == 1 && is_readonly(mp))
225 	adios (NULL, "folder %s is read-only, so -public not allowed", folder);
226 
227     if (!pcompile (vec, NULL))
228 	done (1);
229 
230     /* If printing message numbers to standard out, force line buffering on.
231      */
232     if (listsw)
233 	setvbuf (stdout, NULL, _IOLBF, 0);
234 
235     /*
236      * Scan through all the SELECTED messages and check for a
237      * match.  If there is NOT a match, then add it to a list to
238      * remove from the final sequence (it will make sense later)
239      */
240     if (!reverse) { /* Overflow or underflow is fine. */
241         start = mp->lowsel;
242         end = mp->hghsel + 1;
243         inc = 1;
244     } else {
245         start = mp->hghsel;
246         end = mp->lowsel - 1;
247         inc = -1;
248     }
249     for (msgnum = start; msgnum != end; msgnum += inc) {
250 	if (is_selected (mp, msgnum)) {
251 	    if ((fp = fopen (cp = m_name (msgnum), "r")) == NULL)
252 		admonish (cp, "unable to read message");
253 	    if (fp && pmatches (fp, msgnum, 0L, 0L, debug)) {
254 		if (listsw)
255 		    puts(m_name (msgnum));
256 	    } else {
257 	    	app_msgnum(&nums, msgnum);
258 	    }
259 	    if (fp)
260 		fclose (fp);
261 	}
262     }
263 
264     if (nums.size >= mp->numsel)
265 	adios (NULL, "no messages match specification");
266 
267     /*
268      * So, what's happening here?
269      *
270      * - Re-read the folder and sequences, but with locking.
271      * - Recreate the original set of selected messages from the command line
272      * - Set the previous sequence
273      * - Remove any messages that did NOT result in hits from the selection
274      * - Add to any new sequences
275      */
276 
277     if (!(mp2 = folder_read (folder, 1)))
278 	adios (NULL, "unable to reread folder %s", folder);
279 
280     for (msgnum = 0; msgnum < msgs.size; msgnum++)
281 	if (!m_convert (mp2, msgs.msgs[msgnum]))
282 	    done (1);
283     seq_setprev (mp2);	/* set the previous-sequence */
284 
285     /*
286      * Nums contains a list of messages that we did NOT match.  Remove
287      * that from the selection.
288      */
289 
290     for (msgnum = 0; msgnum < nums.size; msgnum++) {
291     	unset_selected(mp2, nums.msgnums[msgnum]);
292 	mp2->numsel--;
293     }
294 
295     /*
296      * Add the matching messages to sequences
297      */
298     if (seqp > 0) {
299 	for (seqp = 0; seqp < svector_size (seqs); seqp++)
300 	    if (!seq_addsel (mp2, svector_at (seqs, seqp), publicsw, zerosw))
301 		done (1);
302     }
303 
304     /*
305      * Print total matched if not printing each matched message number.
306      */
307     if (!listsw) {
308 	printf ("%d hit%s\n", mp2->numsel, PLURALS(mp2->numsel));
309     }
310 
311     svector_free (seqs);
312     context_replace (pfolder, folder);	/* update current folder         */
313     seq_save (mp2);			/* synchronize message sequences */
314     context_save ();			/* save the context file         */
315     folder_free (mp);			/* free folder/message structure */
316     folder_free (mp2);
317     done (0);
318     return 1;
319 }
320 
321 
322 static void NORETURN
putzero_done(int status)323 putzero_done (int status)
324 {
325     if (listsw && status && !isatty (fileno (stdout)))
326 	puts("0");
327     exit (status);
328 }
329