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