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