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