]> diplodocus.org Git - nmh/blob - uip/refile.c
picksbr.c: fprintf(3) function pointer using `%p' and union.
[nmh] / uip / refile.c
1 /* refile.c -- move or link message(s) from a source folder
2 * -- into one or more destination folders
3 *
4 * This code is Copyright (c) 2002, by the authors of nmh. See the
5 * COPYRIGHT file in the root directory of the nmh distribution for
6 * complete copyright information.
7 */
8
9 #include <h/mh.h>
10 #include "h/done.h"
11 #include <h/utils.h>
12 #include "sbr/m_maildir.h"
13 #include "sbr/m_mktemp.h"
14 #include <fcntl.h>
15
16 #define REFILE_SWITCHES \
17 X("draft", 0, DRAFTSW) \
18 X("link", 0, LINKSW) \
19 X("nolink", 0, NLINKSW) \
20 X("preserve", 0, PRESSW) \
21 X("nopreserve", 0, NPRESSW) \
22 X("retainsequences", 0, RETAINSEQSSW) \
23 X("noretainsequences", 0, NRETAINSEQSSW) \
24 X("unlink", 0, UNLINKSW) \
25 X("nounlink", 0, NUNLINKSW) \
26 X("src +folder", 0, SRCSW) \
27 X("file file", 0, FILESW) \
28 X("rmmproc program", 0, RPROCSW) \
29 X("normmproc", 0, NRPRCSW) \
30 X("version", 0, VERSIONSW) \
31 X("help", 0, HELPSW) \
32
33 #define X(sw, minchars, id) id,
34 DEFINE_SWITCH_ENUM(REFILE);
35 #undef X
36
37 #define X(sw, minchars, id) { sw, minchars, id },
38 DEFINE_SWITCH_ARRAY(REFILE, switches);
39 #undef X
40
41 static char maildir[BUFSIZ];
42
43 struct st_fold {
44 char *f_name;
45 struct msgs *f_mp;
46 };
47
48 /*
49 * static prototypes
50 */
51 static void opnfolds (struct msgs *, struct st_fold *, int);
52 static void clsfolds (struct st_fold *, int);
53 static void remove_files (int, char **);
54 static int m_file (struct msgs *, char *, int, struct st_fold *, int, int, int);
55 static void copy_seqs (struct msgs *, int, struct msgs *, int);
56
57
58 int
59 main (int argc, char **argv)
60 {
61 int linkf = 0, preserve = 0, retainseqs = 0, filep = 0;
62 int foldp = 0, isdf = 0, unlink_msgs = 0;
63 int i, msgnum;
64 char *cp, *folder = NULL, buf[BUFSIZ];
65 char **argp, **arguments;
66 char *filevec[NFOLDERS + 2];
67 char **files = &filevec[1]; /* leave room for remove_files:vec[0] */
68 struct st_fold folders[NFOLDERS + 1];
69 struct msgs_array msgs = { 0, 0, NULL };
70 struct msgs *mp;
71
72 if (nmh_init(argv[0], 1)) { return 1; }
73
74 arguments = getarguments (invo_name, argc, argv, 1);
75 argp = arguments;
76
77 /*
78 * Parse arguments
79 */
80 while ((cp = *argp++)) {
81 if (*cp == '-') {
82 switch (smatch (++cp, switches)) {
83 case AMBIGSW:
84 ambigsw (cp, switches);
85 done (1);
86 case UNKWNSW:
87 adios (NULL, "-%s unknown\n", cp);
88
89 case HELPSW:
90 snprintf (buf, sizeof(buf), "%s [msgs] [switches] +folder ...",
91 invo_name);
92 print_help (buf, switches, 1);
93 done (0);
94 case VERSIONSW:
95 print_version(invo_name);
96 done (0);
97
98 case LINKSW:
99 linkf++;
100 continue;
101 case NLINKSW:
102 linkf = 0;
103 continue;
104
105 case PRESSW:
106 preserve++;
107 continue;
108 case NPRESSW:
109 preserve = 0;
110 continue;
111
112 case RETAINSEQSSW:
113 retainseqs = 1;
114 continue;
115 case NRETAINSEQSSW:
116 retainseqs = 0;
117 continue;
118
119 case UNLINKSW:
120 unlink_msgs++;
121 continue;
122 case NUNLINKSW:
123 unlink_msgs = 0;
124 continue;
125
126 case SRCSW:
127 if (folder)
128 adios (NULL, "only one source folder at a time!");
129 if (!(cp = *argp++) || *cp == '-')
130 adios (NULL, "missing argument to %s", argp[-2]);
131 folder = path (*cp == '+' || *cp == '@' ? cp + 1 : cp,
132 *cp != '@' ? TFOLDER : TSUBCWF);
133 continue;
134 case DRAFTSW:
135 if (filep > NFOLDERS)
136 adios (NULL, "only %d files allowed!", NFOLDERS);
137 isdf = 0;
138 files[filep++] = getcpy (m_draft (NULL, NULL, 1, &isdf));
139 continue;
140 case FILESW:
141 if (filep > NFOLDERS)
142 adios (NULL, "only %d files allowed!", NFOLDERS);
143 if (!(cp = *argp++) || *cp == '-')
144 adios (NULL, "missing argument to %s", argp[-2]);
145 files[filep++] = path (cp, TFILE);
146 continue;
147
148 case RPROCSW:
149 if (!(rmmproc = *argp++) || *rmmproc == '-')
150 adios (NULL, "missing argument to %s", argp[-2]);
151 continue;
152 case NRPRCSW:
153 rmmproc = NULL;
154 continue;
155 }
156 }
157 if (*cp == '+' || *cp == '@') {
158 if (foldp > NFOLDERS)
159 adios (NULL, "only %d folders allowed!", NFOLDERS);
160 folders[foldp++].f_name =
161 pluspath (cp);
162 } else
163 app_msgarg(&msgs, cp);
164 }
165
166 if (!context_find ("path"))
167 free (path ("./", TFOLDER));
168 if (foldp == 0)
169 adios (NULL, "no folder specified");
170
171 /*
172 * We are refiling a file to the folders
173 */
174 if (filep > 0) {
175 if (folder || msgs.size)
176 adios (NULL, "use -file or some messages, not both");
177 opnfolds (NULL, folders, foldp);
178 for (i = 0; i < filep; i++)
179 if (m_file (0, files[i], 0, folders, foldp, preserve, 0))
180 done (1);
181 /* If -nolink, then "remove" files */
182 if (!linkf)
183 remove_files (filep, filevec);
184 done (0);
185 }
186
187 if (!msgs.size)
188 app_msgarg(&msgs, "cur");
189 if (!folder)
190 folder = getfolder (1);
191 strncpy (maildir, m_maildir (folder), sizeof(maildir));
192
193 if (chdir (maildir) == NOTOK)
194 adios (maildir, "unable to change directory to");
195
196 /* read source folder and create message structure */
197 if (!(mp = folder_read (folder, 1)))
198 adios (NULL, "unable to read folder %s", folder);
199
200 /* check for empty folder */
201 if (mp->nummsg == 0)
202 adios (NULL, "no messages in %s", folder);
203
204 /* parse the message range/sequence/name and set SELECTED */
205 for (msgnum = 0; msgnum < msgs.size; msgnum++)
206 if (!m_convert (mp, msgs.msgs[msgnum]))
207 done (1);
208 seq_setprev (mp); /* set the previous-sequence */
209
210 /* create folder structures for each destination folder */
211 opnfolds (mp, folders, foldp);
212
213 /* Link all the selected messages into destination folders.
214 *
215 * This causes the add hook to be run for messages that are
216 * linked into another folder. The refile hook is run for
217 * messages that are moved to another folder.
218 */
219 for (msgnum = mp->lowsel; msgnum <= mp->hghsel; msgnum++) {
220 if (is_selected (mp, msgnum)) {
221 cp = mh_xstrdup(m_name (msgnum));
222 if (m_file (mp, cp, retainseqs ? msgnum : 0, folders, foldp,
223 preserve, !linkf))
224 done (1);
225 free (cp);
226 }
227 }
228
229 /*
230 * Update this now, since folder_delmsgs() will save the context
231 * but doesn't have access to the folder name.
232 */
233
234 context_replace (pfolder, folder); /* update current folder */
235
236 /*
237 * Adjust "cur" if necessary
238 */
239
240 if (mp->hghsel != mp->curmsg
241 && (mp->numsel != mp->nummsg || linkf))
242 seq_setcur (mp, mp->hghsel);
243
244 /*
245 * Close destination folders now; if we are using private sequences
246 * we need to have all of our calls to seq_save() complete before we
247 * call context_save().
248 */
249
250 clsfolds (folders, foldp);
251
252 /* If -nolink, then "remove" messages from source folder.
253 *
254 * Note that folder_delmsgs does not call the delete hook
255 * because the message has already been handled above.
256 */
257 if (!linkf) {
258 folder_delmsgs (mp, unlink_msgs, 1);
259 } else {
260 seq_save (mp); /* synchronize message sequences */
261 context_save (); /* save the context file */
262 }
263
264 folder_free (mp); /* free folder structure */
265 done (0);
266 return 1;
267 }
268
269
270 /*
271 * Read all the destination folders and
272 * create folder structures for all of them.
273 */
274
275 static void
276 opnfolds (struct msgs *src_folder, struct st_fold *folders, int nfolders)
277 {
278 char nmaildir[BUFSIZ];
279 struct st_fold *fp, *ep;
280 struct msgs *mp;
281
282 for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
283 if (chdir (m_maildir ("")) < 0) {
284 advise (m_maildir (""), "chdir");
285 }
286 strncpy (nmaildir, m_maildir (fp->f_name), sizeof(nmaildir));
287
288 /*
289 * Null src_folder indicates that we are refiling a file to
290 * the folders, in which case we don't want to short-circuit
291 * fp->f_mp to any "source folder".
292 */
293 if (! src_folder || strcmp (src_folder->foldpath, nmaildir)) {
294 create_folder (nmaildir, 0, done);
295
296 if (chdir (nmaildir) == NOTOK)
297 adios (nmaildir, "unable to change directory to");
298 if (!(mp = folder_read (fp->f_name, 1)))
299 adios (NULL, "unable to read folder %s", fp->f_name);
300 mp->curmsg = 0;
301
302 fp->f_mp = mp;
303 } else {
304 /* Source and destination folders are the same. */
305 fp->f_mp = src_folder;
306 }
307
308 if (maildir[0] != '\0' && chdir (maildir) < 0) {
309 advise (maildir, "chdir");
310 }
311 }
312 }
313
314
315 /*
316 * Set the Previous-Sequence and then synchronize the
317 * sequence file, for each destination folder.
318 */
319
320 static void
321 clsfolds (struct st_fold *folders, int nfolders)
322 {
323 struct st_fold *fp, *ep;
324 struct msgs *mp;
325
326 for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
327 mp = fp->f_mp;
328 seq_setprev (mp);
329 seq_save (mp);
330 }
331 }
332
333
334 /*
335 * If you have a "rmmproc" defined, we called that
336 * to remove all the specified files. If "rmmproc"
337 * is not defined, then just unlink the files.
338 */
339
340 static void
341 remove_files (int filep, char **files)
342 {
343 int i, vecp;
344 char **vec, *program;
345
346 /* If rmmproc is defined, we use that */
347 if (rmmproc) {
348 vec = argsplit(rmmproc, &program, &vecp);
349 files++; /* Yes, we need to do this */
350 for (i = 0; i < filep; i++)
351 vec[vecp++] = files[i];
352 vec[vecp] = NULL; /* NULL terminate list */
353
354 fflush (stdout);
355 execvp (program, vec);
356 adios (rmmproc, "unable to exec");
357 }
358
359 /* Else just unlink the files */
360 files++; /* advance past filevec[0] */
361 for (i = 0; i < filep; i++) {
362 if (m_unlink (files[i]) == NOTOK)
363 admonish (files[i], "unable to unlink");
364 }
365 }
366
367
368 /*
369 * Link (or copy) the message into each of the destination folders.
370 * If oldmsgnum is not 0, call copy_seqs().
371 */
372
373 static int
374 m_file (struct msgs *mp, char *msgfile, int oldmsgnum,
375 struct st_fold *folders, int nfolders, int preserve, int refile)
376 {
377 int msgnum;
378 struct st_fold *fp, *ep;
379
380 for (fp = folders, ep = folders + nfolders; fp < ep; fp++) {
381 /*
382 * With same source and destination folder, don't indicate that
383 * the new message is selected so that 1) folder_delmsgs() doesn't
384 * delete it later and 2) it is not reflected in mp->hghsel, and
385 * therefore won't be assigned to be the current message.
386 */
387 if ((msgnum = folder_addmsg (&fp->f_mp, msgfile,
388 mp != fp->f_mp,
389 0, preserve, nfolders == 1 && refile,
390 maildir)) == -1)
391 return 1;
392 if (oldmsgnum) copy_seqs (mp, oldmsgnum, fp->f_mp, msgnum);
393 }
394 return 0;
395 }
396
397
398 /*
399 * Copy sequence information for a refiled message to its
400 * new folder. Skip the cur sequence.
401 */
402 static void
403 copy_seqs (struct msgs *oldmp, int oldmsgnum, struct msgs *newmp, int newmsgnum)
404 {
405 char **seq;
406 size_t seqnum;
407
408 for (seq = svector_strs (oldmp->msgattrs), seqnum = 0;
409 *seq && seqnum < svector_size (oldmp->msgattrs);
410 ++seq, ++seqnum) {
411 if (strcmp (current, *seq)) {
412 assert ((int) seqnum == seq_getnum (oldmp, *seq));
413 if (in_sequence (oldmp, seqnum, oldmsgnum)) {
414 seq_addmsg (newmp, *seq, newmsgnum,
415 !is_seq_private (oldmp, seqnum), 0);
416 }
417 }
418 }
419 }