]> diplodocus.org Git - nmh/blob - uip/flist.c
Apply David Levine's fix from whomfile() to sendfile() as well.
[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 int nSeq[NUMATTRS]; /* number of messages in each sequence */
61 int private[NUMATTRS]; /* 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 char *sequencesToDo[NUMATTRS];
78 static unsigned int numsequences;
79
80 static int all = FALSE; /* scan all folders in top level? */
81 static int alphaOrder = FALSE; /* want alphabetical order only */
82 static int recurse = FALSE; /* show nested folders? */
83 static int showzero = TRUE; /* show folders even if no messages in seq? */
84 static int Total = TRUE; /* display info on number of messages in *
85 * sequence found, and total num messages */
86
87 static char curfolder[BUFSIZ]; /* name of the current folder */
88 static char *nmhdir; /* base nmh mail directory */
89
90 /*
91 * Type for a compare function for qsort. This keeps
92 * the compiler happy.
93 */
94 typedef int (*qsort_comp) (const void *, const void *);
95
96 /*
97 * prototypes
98 */
99 int CompareFolders(struct Folder *, struct Folder *);
100 void GetFolderOrder(void);
101 void ScanFolders(void);
102 int AddFolder(char *, int);
103 void BuildFolderList(char *, int);
104 void BuildFolderListRecurse(char *, struct stat *, int);
105 void PrintFolders(void);
106 void AllocFolders(struct Folder **, int *, int);
107 int AssignPriority(char *);
108 static void do_readonly_folders(void);
109
110
111
112 int
113 main(int argc, char **argv)
114 {
115 char *cp, **argp;
116 char **arguments;
117 char buf[BUFSIZ];
118
119 #ifdef LOCALE
120 setlocale(LC_ALL, "");
121 #endif
122 invo_name = r1bindex(argv[0], '/');
123
124 /* read user profile/context */
125 context_read();
126
127 /*
128 * If program was invoked with name ending
129 * in `s', then add switch `-all'.
130 */
131 if (argv[0][strlen (argv[0]) - 1] == 's')
132 all = TRUE;
133
134 arguments = getarguments (invo_name, argc, argv, 1);
135 argp = arguments;
136
137 /* allocate the initial space to record the folder names */
138 numfolders = 0;
139 maxfolders = MAXFOLDERS;
140 foldersToDo = (char **) mh_xmalloc ((size_t) (maxfolders * sizeof(*foldersToDo)));
141
142 /* no sequences yet */
143 numsequences = 0;
144
145 /* parse arguments */
146 while ((cp = *argp++)) {
147 if (*cp == '-') {
148 switch (smatch(++cp, switches)) {
149 case AMBIGSW:
150 ambigsw(cp, switches);
151 done(1);
152 case UNKWNSW:
153 adios(NULL, "-%s unknown", cp);
154
155 case HELPSW:
156 snprintf(buf, sizeof(buf), "%s [+folder1 [+folder2 ...]][switches]",
157 invo_name);
158 print_help(buf, switches, 1);
159 done(0);
160 case VERSIONSW:
161 print_version(invo_name);
162 done (0);
163
164 case SEQSW:
165 if (!(cp = *argp++) || *cp == '-')
166 adios (NULL, "missing argument to %s", argp[-2]);
167
168 /* check if too many sequences specified */
169 if (numsequences >= NUMATTRS)
170 adios (NULL, "too many sequences (more than %d) specified", NUMATTRS);
171 sequencesToDo[numsequences++] = cp;
172 break;
173
174 case ALLSW:
175 all = TRUE;
176 break;
177 case NOALLSW:
178 all = FALSE;
179 break;
180
181 case SHOWZERO:
182 showzero = TRUE;
183 break;
184 case NOSHOWZERO:
185 showzero = FALSE;
186 break;
187
188 case ALPHASW:
189 alphaOrder = TRUE;
190 break;
191 case NOALPHASW:
192 alphaOrder = FALSE;
193 break;
194
195 case NOFASTSW:
196 case TOTALSW:
197 Total = TRUE;
198 break;
199
200 case FASTSW:
201 case NOTOTALSW:
202 Total = FALSE;
203 break;
204
205 case RECURSE:
206 recurse = TRUE;
207 break;
208 case NORECURSE:
209 recurse = FALSE;
210 break;
211 }
212 } else {
213 /*
214 * Check if we need to allocate more space
215 * for folder names.
216 */
217 if (numfolders >= maxfolders) {
218 maxfolders += MAXFOLDERS;
219 foldersToDo = (char **) mh_xrealloc (foldersToDo,
220 (size_t) (maxfolders * sizeof(*foldersToDo)));
221 }
222 if (*cp == '+' || *cp == '@') {
223 foldersToDo[numfolders++] =
224 pluspath (cp);
225 } else
226 foldersToDo[numfolders++] = cp;
227 }
228 }
229
230 if (!context_find ("path"))
231 free (path ("./", TFOLDER));
232
233 /* get current folder */
234 strncpy (curfolder, getfolder(1), sizeof(curfolder));
235
236 /* get nmh base directory */
237 nmhdir = m_maildir ("");
238
239 /*
240 * If we didn't specify any sequences, we search
241 * for the "Unseen-Sequence" profile entry and use
242 * all the sequences defined there. We check to
243 * make sure that the Unseen-Sequence entry doesn't
244 * contain more than NUMATTRS sequences.
245 */
246 if (numsequences == 0) {
247 if ((cp = context_find(usequence)) && *cp) {
248 char **ap, *dp;
249
250 dp = getcpy(cp);
251 ap = brkstring (dp, " ", "\n");
252 for (; ap && *ap; ap++) {
253 if (numsequences >= NUMATTRS)
254 adios (NULL, "too many sequences (more than %d) in %s profile entry",
255 NUMATTRS, usequence);
256 else
257 sequencesToDo[numsequences++] = *ap;
258 }
259 } else {
260 adios (NULL, "no sequence specified or %s profile entry found", usequence);
261 }
262 }
263
264 GetFolderOrder();
265 ScanFolders();
266 qsort(folders, nFolders, sizeof(struct Folder), (qsort_comp) CompareFolders);
267 PrintFolders();
268 done (0);
269 return 1;
270 }
271
272 /*
273 * Read the Flist-Order profile entry to determine
274 * how to sort folders for output.
275 */
276
277 void
278 GetFolderOrder(void)
279 {
280 char *p, *s;
281 int priority = 1;
282 struct Folder *o;
283
284 if (!(p = context_find("Flist-Order")))
285 return;
286 for (;;) {
287 while (isspace((unsigned char) *p))
288 ++p;
289 s = p;
290 while (*p && !isspace((unsigned char) *p))
291 ++p;
292 if (p != s) {
293 /* Found one. */
294 AllocFolders(&orders, &nOrdersAlloced, nOrders + 1);
295 o = &orders[nOrders++];
296 o->priority = priority++;
297 o->name = (char *) mh_xmalloc(p - s + 1);
298 strncpy(o->name, s, p - s);
299 o->name[p - s] = 0;
300 } else
301 break;
302 }
303 }
304
305 /*
306 * Scan all the necessary folders
307 */
308
309 void
310 ScanFolders(void)
311 {
312 int i;
313
314 /*
315 * change directory to base of nmh directory
316 */
317 if (chdir (nmhdir) == NOTOK)
318 adios (nmhdir, "unable to change directory to");
319
320 if (numfolders > 0) {
321 /* Update context */
322 strncpy (curfolder, foldersToDo[numfolders - 1], sizeof(curfolder));
323 context_replace (pfolder, curfolder);/* update current folder */
324 context_save (); /* save the context file */
325
326 /*
327 * Scan each given folder. If -all is given,
328 * then also scan the 1st level subfolders under
329 * each given folder.
330 */
331 for (i = 0; i < numfolders; ++i)
332 BuildFolderList(foldersToDo[i], all ? 1 : 0);
333 } else {
334 if (all) {
335 /*
336 * Do the readonly folders
337 */
338 do_readonly_folders();
339
340 /*
341 * Now scan the entire nmh directory for folders
342 */
343 BuildFolderList(".", 0);
344 } else {
345 /*
346 * Else scan current folder
347 */
348 BuildFolderList(curfolder, 0);
349 }
350 }
351 }
352
353 /*
354 * Initial building of folder list for
355 * the top of our search tree.
356 */
357
358 void
359 BuildFolderList(char *dirName, int searchdepth)
360 {
361 struct stat st;
362
363 /* Make sure we have a directory */
364 if ((stat(dirName, &st) == -1) || !S_ISDIR(st.st_mode))
365 return;
366
367 /*
368 * If base directory, don't add it to the
369 * folder list. We just recurse into it.
370 */
371 if (!strcmp (dirName, ".")) {
372 BuildFolderListRecurse (".", &st, 0);
373 return;
374 }
375
376 /*
377 * Add this folder to the list.
378 * If recursing and directory has subfolders,
379 * then build folder list for subfolders.
380 */
381 if (AddFolder(dirName, showzero) && (recurse || searchdepth) && st.st_nlink > 2)
382 BuildFolderListRecurse(dirName, &st, searchdepth - 1);
383 }
384
385 /*
386 * Recursive building of folder list
387 */
388
389 void
390 BuildFolderListRecurse(char *dirName, struct stat *s, int searchdepth)
391 {
392 char *base, name[PATH_MAX];
393 char *n;
394 int nlinks;
395 DIR *dir;
396 struct dirent *dp;
397 struct stat st;
398
399 /*
400 * Keep track of number of directories we've seen so we can
401 * stop stat'ing entries in this directory once we've seen
402 * them all. This optimization will fail if you have extra
403 * directories beginning with ".", since we don't bother to
404 * stat them. But that shouldn't generally be a problem.
405 */
406 nlinks = s->st_nlink;
407 if (nlinks == 1) {
408 /* Disable the optimization under conditions where st_nlink
409 is set to 1. That happens on Cygwin, for example:
410 http://cygwin.com/ml/cygwin-apps/2008-08/msg00264.html */
411 nlinks = INT_MAX;
412 }
413
414 if (!(dir = opendir(dirName)))
415 adios(dirName, "can't open directory");
416
417 /*
418 * A hack so that we don't see a
419 * leading "./" in folder names.
420 */
421 base = strcmp (dirName, ".") ? dirName : dirName + 1;
422
423 while (nlinks && (dp = readdir(dir))) {
424 if (!strcmp(dp->d_name, ".") || !strcmp(dp->d_name, "..")) {
425 nlinks--;
426 continue;
427 }
428 if (dp->d_name[0] == '.')
429 continue;
430 /* Check to see if the name of the file is a number
431 * if it is, we assume it's a mail file and skip it
432 */
433 for (n = dp->d_name; *n && isdigit((unsigned char) *n); n++);
434 if (!*n)
435 continue;
436 strncpy (name, base, sizeof(name) - 2);
437 if (*base)
438 strcat(name, "/");
439 strncat(name, dp->d_name, sizeof(name) - strlen(name) - 1);
440 if ((stat(name, &st) != -1) && S_ISDIR(st.st_mode)) {
441 /*
442 * Check if this was really a symbolic link pointing
443 * to a directory. If not, then decrement link count.
444 */
445 if (lstat (name, &st) == -1)
446 nlinks--;
447 /* Add this folder to the list */
448 if (AddFolder(name, showzero) &&
449 (recurse || searchdepth) && st.st_nlink > 2)
450 BuildFolderListRecurse(name, &st, searchdepth - 1);
451 }
452 }
453 closedir(dir);
454 }
455
456 /*
457 * Add this folder to our list, counting the total number of
458 * messages and the number of messages in each sequence.
459 */
460
461 int
462 AddFolder(char *name, int force)
463 {
464 unsigned int i;
465 int msgnum, nonzero;
466 int seqnum[NUMATTRS], nSeq[NUMATTRS];
467 struct Folder *f;
468 struct msgs *mp;
469
470 /* Read folder and create message structure */
471 if (!(mp = folder_read (name))) {
472 /* Oops, error occurred. Record it and continue. */
473 AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
474 f = &folders[nFolders++];
475 f->name = getcpy(name);
476 f->error = 1;
477 f->priority = AssignPriority(f->name);
478 return 0;
479 }
480
481 for (i = 0; i < numsequences; i++) {
482 /* Convert sequences to their sequence numbers */
483 if (sequencesToDo[i])
484 seqnum[i] = seq_getnum(mp, sequencesToDo[i]);
485 else
486 seqnum[i] = -1;
487
488 /* Now count messages in this sequence */
489 nSeq[i] = 0;
490 if (mp->nummsg > 0 && seqnum[i] != -1) {
491 for (msgnum = mp->lowmsg; msgnum <= mp->hghmsg; msgnum++) {
492 if (in_sequence(mp, seqnum[i], msgnum))
493 nSeq[i]++;
494 }
495 }
496 }
497
498 /* Check if any of the sequence checks were nonzero */
499 nonzero = 0;
500 for (i = 0; i < numsequences; i++) {
501 if (nSeq[i] > 0) {
502 nonzero = 1;
503 break;
504 }
505 }
506
507 if (nonzero || force) {
508 /* save general folder information */
509 AllocFolders(&folders, &nFoldersAlloced, nFolders + 1);
510 f = &folders[nFolders++];
511 f->name = getcpy(name);
512 f->nMsgs = mp->nummsg;
513 f->error = 0;
514 f->priority = AssignPriority(f->name);
515
516 /* record the sequence information */
517 for (i = 0; i < numsequences; i++) {
518 f->nSeq[i] = nSeq[i];
519 f->private[i] = (seqnum[i] != -1) ? is_seq_private(mp, seqnum[i]) : 0;
520 }
521 }
522
523 folder_free (mp); /* free folder/message structure */
524 return 1;
525 }
526
527 /*
528 * Print the folder/sequence information
529 */
530
531 void
532 PrintFolders(void)
533 {
534 char tmpname[BUFSIZ];
535 unsigned int i, j, len, has_private = 0;
536 unsigned int maxfolderlen = 0, maxseqlen = 0;
537 int maxnum = 0, maxseq = 0;
538
539 if (!Total) {
540 for (i = 0; i < nFolders; i++)
541 printf("%s\n", folders[i].name);
542 return;
543 }
544
545 /*
546 * Find the width we need for various fields
547 */
548 for (i = 0; i < nFolders; ++i) {
549 /* find the length of longest folder name */
550 len = strlen(folders[i].name);
551 if (len > maxfolderlen)
552 maxfolderlen = len;
553
554 /* If folder had error, skip the rest */
555 if (folders[i].error)
556 continue;
557
558 /* find the maximum total messages */
559 if (folders[i].nMsgs > maxnum)
560 maxnum = folders[i].nMsgs;
561
562 for (j = 0; j < numsequences; j++) {
563 /* find maximum width of sequence name */
564 len = strlen (sequencesToDo[j]);
565 if ((folders[i].nSeq[j] > 0 || showzero) && (len > maxseqlen))
566 maxseqlen = len;
567
568 /* find the maximum number of messages in sequence */
569 if (folders[i].nSeq[j] > maxseq)
570 maxseq = folders[i].nSeq[j];
571
572 /* check if this sequence is private in any of the folders */
573 if (folders[i].private[j])
574 has_private = 1;
575 }
576 }
577
578 /* Now print all the folder/sequence information */
579 for (i = 0; i < nFolders; i++) {
580 for (j = 0; j < numsequences; j++) {
581 if (folders[i].nSeq[j] > 0 || showzero) {
582 /* Add `+' to end of name of current folder */
583 if (strcmp(curfolder, folders[i].name))
584 snprintf(tmpname, sizeof(tmpname), "%s", folders[i].name);
585 else
586 snprintf(tmpname, sizeof(tmpname), "%s+", folders[i].name);
587
588 if (folders[i].error) {
589 printf("%-*s is unreadable\n", maxfolderlen+1, tmpname);
590 continue;
591 }
592
593 printf("%-*s has %*d in sequence %-*s%s; out of %*d\n",
594 maxfolderlen+1, tmpname,
595 num_digits(maxseq), folders[i].nSeq[j],
596 maxseqlen, sequencesToDo[j],
597 !has_private ? "" : folders[i].private[j] ? " (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 }