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