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