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