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