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