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