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