]> diplodocus.org Git - nmh/blob - uip/flist.c
Use va_copy() to get a copy of va_list, instead of using original.
[nmh] / uip / flist.c
1 /* flist.c -- list nmh folders containing messages
2 * -- in a given sequence
3 *
4 * originally by
5 * David Nichols, Xerox-PARC, November, 1992
6 *
7 * Copyright (c) 1994 Xerox Corporation.
8 * Use and copying of this software and preparation of derivative works based
9 * upon this software are permitted. Any distribution of this software or
10 * derivative works must comply with all applicable United States export
11 * control laws. This software is made available AS IS, and Xerox Corporation
12 * makes no warranty about the software, its performance or its conformity to
13 * any specification.
14 */
15
16 #include <h/mh.h>
17 #include <h/utils.h>
18 #include "h/done.h"
19 #include "sbr/m_maildir.h"
20
21 /*
22 * We allocate space to record the names of folders
23 * (foldersToDo array), this number of elements at a time.
24 */
25 #define MAXFOLDERS 100
26
27
28 #define FLIST_SWITCHES \
29 X("sequence name", 0, SEQSW) \
30 X("all", 0, ALLSW) \
31 X("noall", 0, NOALLSW) \
32 X("recurse", 0, RECURSE) \
33 X("norecurse", 0, NORECURSE) \
34 X("showzero", 0, SHOWZERO) \
35 X("noshowzero", 0, NOSHOWZERO) \
36 X("alpha", 0, ALPHASW) \
37 X("noalpha", 0, NOALPHASW) \
38 X("fast", 0, FASTSW) \
39 X("nofast", 0, NOFASTSW) \
40 X("total", -5, TOTALSW) \
41 X("nototal", -7, NOTOTALSW) \
42 X("version", 0, VERSIONSW) \
43 X("help", 0, HELPSW) \
44
45 #define X(sw, minchars, id) id,
46 DEFINE_SWITCH_ENUM(FLIST);
47 #undef X
48
49 #define X(sw, minchars, id) { sw, minchars, id },
50 DEFINE_SWITCH_ARRAY(FLIST, switches);
51 #undef X
52
53 struct Folder {
54 char *name; /* name of folder */
55 int priority;
56 int error; /* error == 1 for unreadable folder */
57 int nMsgs; /* number of messages in folder */
58 ivector_t nSeq; /* number of messages in each sequence */
59 ivector_t private; /* is given sequence, public or private */
60 };
61
62 static struct Folder *orders = NULL;
63 static int nOrders = 0;
64 static int nOrdersAlloced = 0;
65 static struct Folder *folders = NULL;
66 static unsigned int nFolders = 0;
67 static int nFoldersAlloced = 0;
68
69 /* info on folders to search */
70 static char **foldersToDo;
71 static int numfolders;
72 static int maxfolders;
73
74 /* info on sequences to search for */
75 static svector_t sequencesToDo;
76
77 static bool all; /* scan all folders in top level? */
78 static bool alphaOrder; /* want alphabetical order only */
79 static bool recurse; /* show nested folders? */
80 static bool showzero = true; /* show folders even if no messages in seq? */
81 static bool Total = true; /* display info on number of messages in
82 * sequence found, and total num messages */
83
84 static char curfolder[BUFSIZ]; /* name of the current folder */
85 static char *nmhdir; /* base nmh mail directory */
86
87 /*
88 * Type for a compare function for qsort. This keeps
89 * the compiler happy.
90 */
91 typedef int (*qsort_comp) (const void *, const void *);
92
93 /*
94 * static prototypes
95 */
96 static int CompareFolders(struct Folder *, struct Folder *);
97 static void GetFolderOrder(void);
98 static void ScanFolders(void);
99 static int AddFolder(char *, int);
100 static void BuildFolderList(char *, int);
101 static void BuildFolderListRecurse(char *, struct stat *, int);
102 static void PrintFolders(void);
103 static void AllocFolders(struct Folder **, int *, int);
104 static int AssignPriority(char *);
105 static void do_readonly_folders(void);
106
107
108
109 int
110 main(int argc, char **argv)
111 {
112 char *cp, **argp;
113 char **arguments;
114 char buf[BUFSIZ];
115
116 if (nmh_init(argv[0], true, true)) { return 1; }
117
118 /*
119 * If program was invoked with name ending
120 * in `s', then add switch `-all'.
121 */
122 all = has_suffix_c(argv[0], 's');
123
124 arguments = getarguments (invo_name, argc, argv, 1);
125 argp = arguments;
126
127 /* allocate the initial space to record the folder names */
128 numfolders = 0;
129 maxfolders = MAXFOLDERS;
130 foldersToDo = mh_xmalloc ((size_t) (maxfolders * sizeof(*foldersToDo)));
131
132 /* no sequences yet */
133 sequencesToDo = svector_create (0);
134
135 /* parse arguments */
136 while ((cp = *argp++)) {
137 if (*cp == '-') {
138 switch (smatch(++cp, switches)) {
139 case AMBIGSW:
140 ambigsw(cp, switches);
141 done(1);
142 case UNKWNSW:
143 die("-%s unknown", cp);
144
145 case HELPSW:
146 snprintf(buf, sizeof(buf), "%s [+folder1 [+folder2 ...]][switches]",
147 invo_name);
148 print_help(buf, switches, 1);
149 done(0);
150 case VERSIONSW:
151 print_version(invo_name);
152 done (0);
153
154 case SEQSW:
155 if (!(cp = *argp++) || *cp == '-')
156 die("missing argument to %s", argp[-2]);
157
158 svector_push_back (sequencesToDo, cp);
159 break;
160
161 case ALLSW:
162 all = true;
163 break;
164 case NOALLSW:
165 all = false;
166 break;
167
168 case SHOWZERO:
169 showzero = true;
170 break;
171 case NOSHOWZERO:
172 showzero = false;
173 break;
174
175 case ALPHASW:
176 alphaOrder = true;
177 break;
178 case NOALPHASW:
179 alphaOrder = false;
180 break;
181
182 case NOFASTSW:
183 case TOTALSW:
184 Total = true;
185 break;
186
187 case FASTSW:
188 case NOTOTALSW:
189 Total = false;
190 break;
191
192 case RECURSE:
193 recurse = true;
194 break;
195 case NORECURSE:
196 recurse = false;
197 break;
198 }
199 } else {
200 /*
201 * Check if we need to allocate more space
202 * for folder names.
203 */
204 if (numfolders >= maxfolders) {
205 maxfolders += MAXFOLDERS;
206 foldersToDo = mh_xrealloc (foldersToDo,
207 (size_t) (maxfolders * sizeof(*foldersToDo)));
208 }
209 if (*cp == '+' || *cp == '@') {
210 foldersToDo[numfolders++] =
211 pluspath (cp);
212 } else
213 foldersToDo[numfolders++] = cp;
214 }
215 }
216
217 if (!context_find ("path"))
218 free (path ("./", TFOLDER));
219
220 /* get current folder */
221 strncpy (curfolder, getfolder(1), sizeof(curfolder));
222
223 /* get nmh base directory */
224 nmhdir = m_maildir ("");
225
226 /*
227 * If we didn't specify any sequences, we search
228 * for the "Unseen-Sequence" profile entry and use
229 * all the sequences defined there.
230 */
231 if (svector_size (sequencesToDo) == 0) {
232 if ((cp = context_find(usequence)) && *cp) {
233 char **ap, *dp;
234
235 dp = mh_xstrdup(cp);
236 ap = brkstring (dp, " ", "\n");
237 for (; ap && *ap; ap++)
238 svector_push_back (sequencesToDo, *ap);
239 } else {
240 die("no sequence specified or %s profile entry found", usequence);
241 }
242 }
243
244 GetFolderOrder();
245 ScanFolders();
246 qsort(folders, nFolders, sizeof(struct Folder), (qsort_comp) CompareFolders);
247 PrintFolders();
248 svector_free (sequencesToDo);
249 done (0);
250 return 1;
251 }
252
253 /*
254 * Read the Flist-Order profile entry to determine
255 * how to sort folders for output.
256 */
257
258 static void
259 GetFolderOrder(void)
260 {
261 char *p, *s;
262 int priority = 1;
263 struct Folder *o;
264
265 if (!(p = context_find("Flist-Order")))
266 return;
267 for (;;) {
268 while (isspace((unsigned char) *p))
269 ++p;
270 s = p;
271 while (*p && !isspace((unsigned char) *p))
272 ++p;
273 if (p != s) {
274 /* Found one. */
275 AllocFolders(&orders, &nOrdersAlloced, nOrders + 1);
276 o = &orders[nOrders++];
277 o->priority = priority++;
278 o->name = mh_xmalloc(p - s + 1);
279 strncpy(o->name, s, p - s);
280 o->name[p - s] = 0;
281 } else
282 break;
283 }
284 }
285
286 /*
287 * Scan all the necessary folders
288 */
289
290 static void
291 ScanFolders(void)
292 {
293 int i;
294
295 /*
296 * change directory to base of nmh directory
297 */
298 if (chdir (nmhdir) == NOTOK)
299 adios (nmhdir, "unable to change directory to");
300
301 if (numfolders > 0) {
302 /* Update context */
303 strncpy (curfolder, foldersToDo[numfolders - 1], sizeof(curfolder));
304 context_replace (pfolder, curfolder);/* update current folder */
305 context_save (); /* save the context file */
306
307 /*
308 * Scan each given folder. If -all is given,
309 * then also scan the 1st level subfolders under
310 * each given folder.
311 */
312 for (i = 0; i < numfolders; ++i)
313 BuildFolderList(foldersToDo[i], all ? 1 : 0);
314 } else {
315 if (all) {
316 /*
317 * Do the readonly folders
318 */
319 do_readonly_folders();
320
321 /*
322 * Now scan the entire nmh directory for folders
323 */
324 BuildFolderList(".", 0);
325 } else {
326 /*
327 * Else scan current folder
328 */
329 BuildFolderList(curfolder, 0);
330 }
331 }
332 }
333
334 /*
335 * Initial building of folder list for
336 * the top of our search tree.
337 */
338
339 static void
340 BuildFolderList(char *dirName, int searchdepth)
341 {
342 struct stat st;
343
344 /* Make sure we have a directory */
345 if ((stat(dirName, &st) == -1) || !S_ISDIR(st.st_mode))
346 return;
347
348 /*
349 * If base directory, don't add it to the
350 * folder list. We just recurse into it.
351 */
352 if (!strcmp (dirName, ".")) {
353 BuildFolderListRecurse (".", &st, 0);
354 return;
355 }
356
357 /*
358 * Add this folder to the list.
359 * If recursing and directory has subfolders,
360 * then build folder list for subfolders.
361 */
362 if (AddFolder(dirName, showzero) && (recurse || searchdepth) && st.st_nlink > 2)
363 BuildFolderListRecurse(dirName, &st, searchdepth - 1);
364 }
365
366 /*
367 * Recursive building of folder list
368 */
369
370 static void
371 BuildFolderListRecurse(char *dirName, struct stat *s, int searchdepth)
372 {
373 char *base, name[PATH_MAX];
374 char *n;
375 int nlinks;
376 DIR *dir;
377 struct dirent *dp;
378 struct stat st;
379
380 /*
381 * Keep track of number of directories we've seen so we can
382 * stop stat'ing entries in this directory once we've seen
383 * them all. This optimization will fail if you have extra
384 * directories beginning with ".", since we don't bother to
385 * stat them. But that shouldn't generally be a problem.
386 */
387 nlinks = s->st_nlink;
388 if (nlinks == 1) {
389 /* Disable the optimization under conditions where st_nlink
390 is set to 1. That happens on Cygwin, for example:
391 http://cygwin.com/ml/cygwin-apps/2008-08/msg00264.html */
392 nlinks = INT_MAX;
393 }
394
395 if (!(dir = opendir(dirName)))
396 adios(dirName, "can't open directory");
397
398 /*
399 * A hack so that we don't see a
400 * leading "./" in folder names.
401 */
402 base = strcmp (dirName, ".") ? dirName : dirName + 1;
403
404 while (nlinks && (dp = readdir(dir))) {
405 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
406 nlinks--;
407 continue;
408 }
409 if (dp->d_name[0] == '.')
410 continue;
411 /* Check to see if the name of the file is a number
412 * if it is, we assume it's a mail file and skip it
413 */
414 for (n = dp->d_name; isdigit((unsigned char)*n); n++);
415 if (!*n)
416 continue;
417 strncpy (name, base, sizeof(name) - 2);
418 if (*base)
419 strcat(name, "/");
420 strncat(name, dp->d_name, sizeof(name) - strlen(name) - 1);
421 if ((stat(name, &st) != -1) && S_ISDIR(st.st_mode)) {
422 /*
423 * Check if this was really a symbolic link pointing
424 * to a directory. If not, then decrement link count.
425 */
426 if (lstat (name, &st) == -1)
427 nlinks--;
428 /* Add this folder to the list */
429 if (AddFolder(name, showzero) &&
430 (recurse || searchdepth) && st.st_nlink > 2)
431 BuildFolderListRecurse(name, &st, searchdepth - 1);
432 }
433 }
434 closedir(dir);
435 }
436
437 /*
438 * Add this folder to our list, counting the total number of
439 * messages and the number of messages in each sequence.
440 */
441
442 static int
443 AddFolder(char *name, int force)
444 {
445 unsigned int i;
446 int msgnum;
447 bool nonzero;
448 ivector_t seqnum = ivector_create (0), nSeq = ivector_create (0);
449 struct Folder *f;
450 struct msgs *mp;
451 char *cp;
452
453 /* Read folder and create message structure */
454 if (!(mp = folder_read (name, 0))) {
455 /* Oops, error occurred. Record it and continue. */
456 AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
457 f = &folders[nFolders++];
458 f->name = mh_xstrdup(name);
459 f->error = 1;
460 f->priority = AssignPriority(f->name);
461 return 0;
462 }
463
464 for (i = 0; i < svector_size (sequencesToDo); i++) {
465 /* Convert sequences to their sequence numbers */
466 if ((cp = svector_at (sequencesToDo, i)))
467 ivector_push_back (seqnum, seq_getnum(mp, cp));
468 else
469 ivector_push_back (seqnum, -1);
470
471 /* Now count messages in this sequence */
472 ivector_push_back (nSeq, 0);
473 if (mp->nummsg > 0 && ivector_at (seqnum, i) != -1) {
474 for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) {
475 if (in_sequence(mp, ivector_at (seqnum, i), msgnum))
476 (*ivector_atp (nSeq, i))++;
477 }
478 }
479 }
480
481 /* Check if any of the sequence checks were nonzero */
482 nonzero = false;
483 for (i = 0; i < svector_size (sequencesToDo); i++) {
484 if (ivector_at (nSeq, i) > 0) {
485 nonzero = true;
486 break;
487 }
488 }
489
490 if (nonzero || force) {
491 /* save general folder information */
492 AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
493 f = &folders[nFolders++];
494 f->name = mh_xstrdup(name);
495 f->nMsgs = mp->nummsg;
496 f->nSeq = ivector_create (0);
497 f->private = ivector_create (0);
498 f->error = 0;
499 f->priority = AssignPriority(f->name);
500
501 /* record the sequence information */
502 for (i = 0; i < svector_size (sequencesToDo); i++) {
503 *ivector_atp (f->nSeq, i) = ivector_at (nSeq, i);
504 ivector_push_back (f->private,
505 ivector_at (seqnum, i) != -1
506 ? is_seq_private(mp, ivector_at (seqnum, i))
507 : 0);
508 }
509 }
510
511 ivector_free (nSeq);
512 ivector_free (seqnum);
513 folder_free (mp); /* free folder/message structure */
514 return 1;
515 }
516
517 /*
518 * Print the folder/sequence information
519 */
520
521 static void
522 PrintFolders(void)
523 {
524 char tmpname[BUFSIZ];
525 unsigned int i, j, len;
526 bool has_private = false;
527 unsigned int maxfolderlen = 0, maxseqlen = 0;
528 int maxnum = 0, maxseq = 0;
529
530 if (!Total) {
531 for (i = 0; i < nFolders; i++)
532 puts(folders[i].name);
533 return;
534 }
535
536 /*
537 * Find the width we need for various fields
538 */
539 for (i = 0; i < nFolders; ++i) {
540 /* find the length of longest folder name */
541 len = strlen(folders[i].name);
542 if (len > maxfolderlen)
543 maxfolderlen = len;
544
545 /* If folder had error, skip the rest */
546 if (folders[i].error)
547 continue;
548
549 /* find the maximum total messages */
550 if (folders[i].nMsgs > maxnum)
551 maxnum = folders[i].nMsgs;
552
553 for (j = 0; j < svector_size (sequencesToDo); j++) {
554 /* find maximum width of sequence name */
555 len = strlen (svector_at (sequencesToDo, j));
556 if ((ivector_at (folders[i].nSeq, j) > 0 || showzero) &&
557 (len > maxseqlen))
558 maxseqlen = len;
559
560 /* find the maximum number of messages in sequence */
561 if (ivector_at (folders[i].nSeq, j) > maxseq)
562 maxseq = ivector_at (folders[i].nSeq, j);
563
564 /* check if this sequence is private in any of the folders */
565 if (ivector_at (folders[i].private, j))
566 has_private = true;
567 }
568 }
569
570 /* Now print all the folder/sequence information */
571 for (i = 0; i < nFolders; i++) {
572 for (j = 0; j < svector_size (sequencesToDo); j++) {
573 if (ivector_at (folders[i].nSeq, j) > 0 || showzero) {
574 /* Add `+' to end of name of current folder */
575 if (strcmp(curfolder, folders[i].name))
576 snprintf(tmpname, sizeof(tmpname), "%s", folders[i].name);
577 else
578 snprintf(tmpname, sizeof(tmpname), "%s+", folders[i].name);
579
580 if (folders[i].error) {
581 printf("%-*s is unreadable\n", maxfolderlen+1, tmpname);
582 continue;
583 }
584
585 printf("%-*s has %*d in sequence %-*s%s; out of %*d\n",
586 maxfolderlen+1, tmpname,
587 num_digits(maxseq), ivector_at (folders[i].nSeq, j),
588 maxseqlen, svector_at (sequencesToDo, j),
589 !has_private ? "" : ivector_at (folders[i].private, j)
590 ? " (private)" : " ",
591 num_digits(maxnum), folders[i].nMsgs);
592 }
593 }
594 }
595 }
596
597 /*
598 * Put them in priority order.
599 */
600
601 static int
602 CompareFolders(struct Folder *f1, struct Folder *f2)
603 {
604 if (!alphaOrder && f1->priority != f2->priority)
605 return f1->priority - f2->priority;
606 return strcmp(f1->name, f2->name);
607 }
608
609 /*
610 * Make sure we have at least n folders allocated.
611 */
612
613 static void
614 AllocFolders(struct Folder **f, int *nfa, int n)
615 {
616 if (n <= *nfa)
617 return;
618 if (*f == NULL) {
619 *nfa = 10;
620 *f = mh_xmalloc (*nfa * (sizeof(struct Folder)));
621 } else {
622 *nfa *= 2;
623 *f = mh_xrealloc (*f, *nfa * (sizeof(struct Folder)));
624 }
625 }
626
627 /*
628 * Return the priority for a name. The highest comes from an exact match.
629 * After that, the longest match (then first) assigns the priority.
630 */
631 static int
632 AssignPriority(char *name)
633 {
634 int i, ol, nl;
635 int best = nOrders;
636 int bestLen = 0;
637 struct Folder *o;
638
639 nl = strlen(name);
640 for (i = 0; i < nOrders; ++i) {
641 o = &orders[i];
642 if (!strcmp(name, o->name))
643 return o->priority;
644 ol = strlen(o->name);
645 if (nl < ol - 1)
646 continue;
647 if (ol < bestLen)
648 continue;
649 if (o->name[0] == '*' && !strcmp(o->name + 1, name + (nl - ol + 1))) {
650 best = o->priority;
651 bestLen = ol;
652 } else if (o->name[ol - 1] == '*' && strncmp(o->name, name, ol - 1) == 0) {
653 best = o->priority;
654 bestLen = ol;
655 }
656 }
657 return best;
658 }
659
660 /*
661 * Do the read only folders
662 */
663
664 static void
665 do_readonly_folders (void)
666 {
667 int atrlen;
668 char atrcur[BUFSIZ];
669 struct node *np;
670
671 snprintf (atrcur, sizeof(atrcur), "atr-%s-", current);
672 atrlen = strlen (atrcur);
673
674 for (np = m_defs; np; np = np->n_next)
675 if (ssequal (atrcur, np->n_name)
676 && !ssequal (nmhdir, np->n_name + atrlen))
677 BuildFolderList (np->n_name + atrlen, 0);
678 }