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