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