thread.c revision 1.3 1 /* $NetBSD: thread.c,v 1.3 2006/12/05 03:47:41 christos Exp $ */
2
3 /*-
4 * Copyright (c) 2006 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Anon Ymous.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the NetBSD
21 * Foundation, Inc. and its contributors.
22 * 4. Neither the name of The NetBSD Foundation nor the names of its
23 * contributors may be used to endorse or promote products derived
24 * from this software without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
27 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
28 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
29 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
30 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
34 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 * POSSIBILITY OF SUCH DAMAGE.
37 */
38
39 /*
40 * This module contains the threading and sorting routines.
41 */
42
43 #ifdef THREAD_SUPPORT
44
45 #include <sys/cdefs.h>
46 #ifndef __lint__
47 __RCSID("$NetBSD: thread.c,v 1.3 2006/12/05 03:47:41 christos Exp $");
48 #endif /* not __lint__ */
49
50 #include <assert.h>
51 #include <ctype.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54 #include <util.h>
55
56 #include "def.h"
57 #include "glob.h"
58 #include "extern.h"
59 #include "format.h"
60 #include "thread.h"
61
62
63 struct thread_s {
64 struct message *t_head; /* head of the thread */
65 struct message **t_msgtbl; /* message array indexed by msgnum */
66 int t_msgCount; /* count of messages in thread */
67 };
68 #define THREAD_INIT {NULL, NULL, 0}
69
70 typedef int state_t;
71 #define S_STATE_INIT 0
72 #define S_EXPOSE 1 /* flag to expose the thread */
73 #define S_RESTRICT 2 /* flag to restrict to tagged messages */
74 #define S_IS_EXPOSE(a) ((a) & S_EXPOSE)
75 #define S_IS_RESTRICT(a) ((a) & S_RESTRICT)
76
77 /* XXX - this isn't really a thread */
78 static struct thread_s message_array = THREAD_INIT; /* the basic message array */
79 static struct thread_s current_thread = THREAD_INIT; /* the current thread */
80
81 static state_t state = S_STATE_INIT; /* the current state */
82
83 /*
84 * A state hook used by the format module.
85 */
86 PUBLIC int
87 thread_hidden(void)
88 {
89 return !S_IS_EXPOSE(state);
90 }
91
92 /************************************************************************
93 * Debugging stuff that should evaporate eventually.
94 */
95 #ifdef THREAD_DEBUG
96 static void
97 show_msg(struct message *mp)
98 {
99 if (mp == NULL)
100 return;
101 /*
102 * Arg! '%p' doesn't like the '0' modifier.
103 */
104 (void)printf("%3d (%p):"
105 " flink=%p blink=%p clink=%p plink=%p"
106 " depth=%d flags=0x%03x\n",
107 mp->m_index, mp,
108 mp->m_flink, mp->m_blink, mp->m_clink, mp->m_plink,
109 mp->m_depth, mp->m_flag);
110 }
111
112 #ifndef __lint__
113 __unused
114 static void
115 show_thread(struct message *mp)
116 {
117 (void)printf("current_thread.t_head=%p\n", current_thread.t_head);
118 for (/*EMPTY*/; mp; mp = next_message(mp))
119 show_msg(mp);
120 }
121 #endif
122
123 PUBLIC int
124 thread_showcmd(void *v)
125 {
126 int *ip;
127
128 (void)printf("current_thread.t_head=%p\n", current_thread.t_head);
129 for (ip = v; *ip; ip++)
130 show_msg(get_message(*ip));
131
132 return 0;
133 }
134 #endif /* THREAD_DEBUG */
135
136 /*************************************************************************
137 * tag/restrict routines
138 */
139
140 /*
141 * Return TRUE iff all messages forward or below this one are tagged.
142 */
143 static int
144 is_tagged_core(struct message *mp)
145 {
146 for (/*EMPTY*/; mp; mp = mp->m_flink)
147 if ((mp->m_flag & MTAGGED) == 0 ||
148 is_tagged_core(mp->m_clink) == 0)
149 return 0;
150 return 1;
151 }
152
153 static int
154 is_tagged(struct message *mp)
155 {
156 return (mp->m_flag & MTAGGED) && is_tagged_core(mp->m_clink);
157 }
158
159 /************************************************************************
160 * These are the core routines to access messages via the links used
161 * everywhere outside this module and fio.c.
162 */
163
164 static int
165 has_parent(struct message *mp)
166 {
167 return mp->m_plink != NULL &&
168 mp->m_plink->m_clink != current_thread.t_head;
169 }
170
171 static struct message *
172 next_message1(struct message *mp)
173 {
174 if (mp == NULL)
175 return NULL;
176
177 if (S_IS_EXPOSE(state) == 0)
178 return mp->m_flink;
179
180 if (mp->m_clink)
181 return mp->m_clink;
182
183 while (mp->m_flink == NULL && has_parent(mp))
184 mp = mp->m_plink;
185
186 return mp->m_flink;
187 }
188
189 static struct message *
190 prev_message1(struct message *mp)
191 {
192 if (mp == NULL)
193 return NULL;
194
195 if (S_IS_EXPOSE(state) && mp->m_blink == NULL && has_parent(mp))
196 return mp->m_plink;
197
198 return mp->m_blink;
199 }
200
201 PUBLIC struct message *
202 next_message(struct message *mp)
203 {
204 if (S_IS_RESTRICT(state) == 0)
205 return next_message1(mp);
206
207 while ((mp = next_message1(mp)) != NULL && is_tagged(mp))
208 continue;
209
210 return mp;
211 }
212
213 PUBLIC struct message *
214 prev_message(struct message *mp)
215 {
216 if (S_IS_RESTRICT(state) == 0)
217 return prev_message1(mp);
218
219 while ((mp = prev_message1(mp)) != NULL && is_tagged(mp))
220 continue;
221
222 return mp;
223 }
224
225 static struct message *
226 first_message(struct message *mp)
227 {
228 if (S_IS_RESTRICT(state) && is_tagged(mp))
229 mp = next_message(mp);
230 return mp;
231 }
232
233 PUBLIC struct message *
234 get_message(int msgnum)
235 {
236 struct message *mp;
237
238 if (msgnum < 1 || msgnum > current_thread.t_msgCount)
239 return NULL;
240 mp = current_thread.t_msgtbl[msgnum - 1];
241 assert(mp->m_index == msgnum);
242 return mp;
243 }
244
245 PUBLIC int
246 get_msgnum(struct message *mp)
247 {
248 return mp ? mp->m_index : 0;
249 }
250
251 PUBLIC int
252 get_msgCount(void)
253 {
254 return current_thread.t_msgCount;
255 }
256
257 PUBLIC int
258 get_abs_msgCount(void)
259 {
260 return message_array.t_msgCount;
261 }
262
263 PUBLIC struct message *
264 get_abs_message(int msgnum)
265 {
266 if (msgnum < 1 || msgnum > message_array.t_msgCount)
267 return NULL;
268
269 return &message_array.t_head[msgnum - 1];
270 }
271
272 PUBLIC struct message *
273 next_abs_message(struct message *mp)
274 {
275 int i;
276
277 i = mp - message_array.t_head;
278
279 if (i < 0 || i + 1 >= message_array.t_msgCount)
280 return NULL;
281
282 return &message_array.t_head[i + 1];
283 }
284
285 /************************************************************************/
286 /*
287 * routines to handle the recursion of commands.
288 */
289 PUBLIC int
290 do_recursion(void)
291 {
292 return S_IS_EXPOSE(state) == 0 && value(ENAME_RECURSIVE_CMDS) != NULL;
293 }
294
295 static int
296 thread_recursion_flist(struct message *mp, int (*fn)(struct message *, void *), void *args)
297 {
298 int retval;
299 for (/*EMPTY*/; mp; mp = mp->m_flink) {
300 if (S_IS_RESTRICT(state) && is_tagged(mp))
301 continue;
302 if ((retval = fn(mp, args)) != 0 ||
303 (retval = thread_recursion_flist(mp->m_clink, fn, args)) != 0)
304 return retval;
305 }
306
307 return 0;
308 }
309
310 PUBLIC int
311 thread_recursion(struct message *mp, int (*fn)(struct message *, void *), void *args)
312 {
313 int retval;
314
315 assert(mp != NULL);
316
317 if ((retval = fn(mp, args)) != 0)
318 return retval;
319
320 if (do_recursion() &&
321 (retval = thread_recursion_flist(mp->m_clink, fn, args)) != 0)
322 return retval;
323
324 return 0;
325 }
326
327 /************************************************************************
328 * A hook for sfmtfield() in format.c. It is the only place outside
329 * this module that the m_depth is known.
330 */
331 PUBLIC int
332 thread_depth(void)
333 {
334 return current_thread.t_head ? current_thread.t_head->m_depth : 0;
335 }
336
337 /************************************************************************/
338
339 static int
340 reindex_core(struct message *mp)
341 {
342 int i;
343 assert(mp->m_blink == NULL);
344
345 i = 0;
346 for (mp = first_message(mp); mp; mp = mp->m_flink) {
347 assert(mp->m_flink == NULL || mp == mp->m_flink->m_blink);
348 assert(mp->m_blink == NULL || mp == mp->m_blink->m_flink);
349
350 assert(mp->m_size != 0);
351
352 if (S_IS_RESTRICT(state) == 0 || !is_tagged(mp))
353 mp->m_index = ++i;
354
355 if (mp->m_clink)
356 (void)reindex_core(mp->m_clink);
357 }
358 return i;
359 }
360
361
362 static void
363 reindex(struct thread_s *tp)
364 {
365 struct message *mp;
366 int i;
367
368 assert(tp != NULL);
369
370 if ((mp = tp->t_head) == NULL || mp->m_size == 0)
371 return;
372
373 assert(mp->m_blink == NULL);
374
375 if (S_IS_EXPOSE(state) == 0) {
376 /*
377 * We special case this so that all the hidden
378 * sub-threads get indexed, not just the current one.
379 */
380 i = reindex_core(tp->t_head);
381 }
382 else {
383 i = 0;
384 for (mp = first_message(tp->t_head); mp; mp = next_message(mp))
385 mp->m_index = ++i;
386 }
387
388 assert(i <= message_array.t_msgCount);
389
390 tp->t_msgCount = i;
391 i = 0;
392 for (mp = first_message(tp->t_head); mp; mp = next_message(mp))
393 tp->t_msgtbl[i++] = mp;
394 }
395
396 static void
397 redepth_core(struct message *mp, int depth, struct message *parent)
398 {
399 assert(mp->m_blink == NULL);
400 assert((parent == NULL && depth == 0) ||
401 (parent != NULL && depth != 0 && depth == parent->m_depth + 1));
402
403 for (/*EMPTY*/; mp; mp = mp->m_flink) {
404 assert(mp->m_plink == parent);
405 assert(mp->m_flink == NULL || mp == mp->m_flink->m_blink);
406 assert(mp->m_blink == NULL || mp == mp->m_blink->m_flink);
407 assert(mp->m_size != 0);
408
409 mp->m_depth = depth;
410 if (mp->m_clink)
411 redepth_core(mp->m_clink, depth + 1, mp);
412 }
413 }
414
415 static void
416 redepth(struct thread_s *thread)
417 {
418 int depth;
419 struct message *mp;
420
421 assert(thread != NULL);
422
423 if ((mp = thread->t_head) == NULL || mp->m_size == 0)
424 return;
425
426 depth = mp->m_plink ? mp->m_plink->m_depth + 1 : 0;
427
428 #ifndef NDEBUG /* a sanity check if asserts are active */
429 {
430 struct message *tp;
431 int i;
432 i = 0;
433 for (tp = mp->m_plink; tp; tp = tp->m_plink)
434 i++;
435 assert(i == depth);
436 }
437 #endif
438
439 redepth_core(mp, depth, mp->m_plink);
440 }
441
442 /************************************************************************
443 * To be called after reallocating the main message list. It is here
444 * as it needs access to current_thread.t_head.
445 */
446 PUBLIC void
447 thread_fix_old_links(struct message *nmessage, struct message *message, int omsgCount)
448 {
449 int i;
450 if (nmessage == message)
451 return;
452
453 #ifndef NDEBUG
454 message_array.t_head = nmessage; /* for assert check in thread_fix_new_links */
455 #endif
456
457 # define FIX_LINK(p) do { if (p) p = nmessage + (p - message); } while(/*CONSTCOND*/0)
458 FIX_LINK(current_thread.t_head);
459 for (i = 0; i < omsgCount; i++) {
460 FIX_LINK(nmessage[i].m_blink);
461 FIX_LINK(nmessage[i].m_flink);
462 FIX_LINK(nmessage[i].m_clink);
463 FIX_LINK(nmessage[i].m_plink);
464 }
465 for (i = 0; i < current_thread.t_msgCount; i++ )
466 FIX_LINK(current_thread.t_msgtbl[i]);
467
468 # undef FIX_LINK
469 }
470
471 static void
472 thread_init(struct thread_s *tp, struct message *mp, int msgCount)
473 {
474 int i;
475
476 if (tp->t_msgtbl == NULL || msgCount > tp->t_msgCount) {
477 if (tp->t_msgtbl)
478 free(tp->t_msgtbl);
479 tp->t_msgtbl = ecalloc((size_t)msgCount, sizeof(tp->t_msgtbl[0]));
480 }
481 tp->t_head = mp;
482 tp->t_msgCount = msgCount;
483 for (i = 0; i < msgCount; i++)
484 tp->t_msgtbl[i] = &mp[i];
485 }
486
487 /*
488 * To be called after reading in the new message structures.
489 * It is here as it needs access to current_thread.t_head.
490 */
491 PUBLIC void
492 thread_fix_new_links(struct message *message, int omsgCount, int msgCount)
493 {
494 int i;
495 struct message *lastmp;
496
497 /* This should only be called at the top level if omsgCount != 0! */
498 assert(omsgCount == 0 || message->m_plink == NULL);
499 assert(omsgCount == 0 || message_array.t_msgCount == omsgCount);
500 assert(message_array.t_head == message);
501
502 message_array.t_head = message;
503 message_array.t_msgCount = msgCount;
504 assert(message_array.t_msgtbl == NULL); /* never used */
505
506 lastmp = NULL;
507 if (omsgCount) {
508 /*
509 * Find the end of the toplevel thread.
510 */
511 for (i = 0; i < omsgCount; i++) {
512 if (message_array.t_head[i].m_depth == 0 &&
513 message_array.t_head[i].m_flink == NULL) {
514 lastmp = &message_array.t_head[i];
515 break;
516 }
517 }
518 #ifndef NDEBUG
519 /*
520 * lastmp better be unique!!!
521 */
522 for (i++; i < omsgCount; i++)
523 assert(message_array.t_head[i].m_depth != 0 ||
524 message_array.t_head[i].m_flink != NULL);
525 assert(lastmp != NULL);
526 #endif /* NDEBUG */
527 }
528 /*
529 * Link and index the new messages linearly at depth 0.
530 */
531 for (i = omsgCount; i < msgCount; i++) {
532 message[i].m_index = i + 1;
533 message[i].m_depth = 0;
534 message[i].m_blink = lastmp;
535 message[i].m_flink = NULL;
536 message[i].m_clink = NULL;
537 message[i].m_plink = NULL;
538 if (lastmp)
539 lastmp->m_flink = &message[i];
540 lastmp = &message[i];
541 }
542
543 /*
544 * Make sure the current thread is setup correctly.
545 */
546 if (omsgCount == 0) {
547 thread_init(¤t_thread, message, msgCount);
548 }
549 else {
550 /*
551 * Make sure current_thread.t_msgtbl is always large
552 * enough.
553 */
554 current_thread.t_msgtbl =
555 erealloc(current_thread.t_msgtbl,
556 msgCount * sizeof(*current_thread.t_msgtbl));
557
558 assert(current_thread.t_head != NULL);
559 if (current_thread.t_head->m_depth == 0)
560 reindex(¤t_thread);
561 }
562 }
563
564 /************************************************************************/
565 /*
566 * All state changes should go through here!!!
567 */
568 static state_t
569 set_state(int and_bits, int xor_bits)
570 {
571 state_t old_state;
572 old_state = state;
573 state &= and_bits;
574 state ^= xor_bits;
575 reindex(¤t_thread);
576 redepth(¤t_thread);
577 return old_state;
578 }
579
580 static void
581 restore_state(state_t new_state)
582 {
583 state = new_state;
584 reindex(¤t_thread);
585 redepth(¤t_thread);
586 }
587
588 /************************************************************************/
589 /*
590 * Possibly show the message list.
591 */
592 static void
593 thread_announce(void *v)
594 {
595 int vec[2];
596
597 if (v == NULL) /* check this here to avoid it before each call */
598 return;
599
600 if (dot == NULL) {
601 (void)printf("No applicable messages\n");
602 return;
603 }
604 vec[0] = get_msgnum(dot);
605 vec[1] = 0;
606 if (get_msgCount() > 0 && value(ENAME_NOHEADER) == NULL)
607 (void)headers(vec);
608 sawcom = 0; /* so next will print the first message */
609 }
610
611 /************************************************************************/
612
613 /*
614 * Flatten out the portion of the thread starting with the given
615 * message.
616 */
617 static void
618 flattencmd_core(struct message *mp)
619 {
620 struct message **marray;
621 size_t mcount;
622 struct message *tp;
623 struct message *nextmp;
624 int i;
625
626 if (mp == NULL)
627 return;
628
629 mcount = 1;
630 for (tp = next_message(mp); tp && tp->m_depth > mp->m_depth; tp = next_message(tp))
631 mcount++;
632
633 if (tp && tp->m_depth < mp->m_depth)
634 nextmp = NULL;
635 else
636 nextmp = tp;
637
638 if (mcount == 1)
639 return;
640
641 marray = csalloc(mcount, sizeof(*marray));
642 tp = mp;
643 for (i = 0; i < mcount; i++) {
644 marray[i] = tp;
645 tp = next_message(tp);
646 }
647 mp->m_clink = NULL;
648 for (i = 1; i < mcount; i++) {
649 marray[i]->m_depth = mp->m_depth;
650 marray[i]->m_plink = mp->m_plink;
651 marray[i]->m_clink = NULL;
652 marray[i]->m_blink = marray[i - 1];
653 marray[i - 1]->m_flink = marray[i];
654 }
655 marray[i - 1]->m_flink = nextmp;
656 if (nextmp)
657 nextmp->m_blink = marray[i - 1];
658 }
659
660 /*
661 * Flatten out all thread parts given in the message list, or the
662 * current thread, if none given.
663 */
664 PUBLIC int
665 flattencmd(void *v)
666 {
667 int *msgvec;
668 int *ip;
669
670 msgvec = v;
671
672 if (*msgvec) { /* a message was supplied */
673 for (ip = msgvec; *ip; ip++) {
674 struct message *mp;
675 mp = get_message(*ip);
676 if (mp != NULL)
677 flattencmd_core(mp);
678 }
679 }
680 else { /* no message given - flatten current thread */
681 struct message *mp;
682 for (mp = first_message(current_thread.t_head);
683 mp; mp = next_message(mp))
684 flattencmd_core(mp);
685 }
686 redepth(¤t_thread);
687 thread_announce(v);
688 return 0;
689 }
690
691
692 /************************************************************************/
693 /*
694 * The basic sort structure. For each message the index and key
695 * fields are set. The key field is used for the basic sort and the
696 * index is used to ensure that the order from the current thread is
697 * maintained when the key compare is equal.
698 */
699 struct key_sort_s {
700 struct message *mp; /* the message the following refer to */
701 union {
702 char *str; /* string sort key (typically a field or address) */
703 long lines; /* a long sort key (typically a message line count) */
704 off_t size; /* a size sort key (typically the message size) */
705 time_t time; /* a time sort key (typically from date or headline) */
706 } key;
707 int index; /* index from of the current thread before sorting */
708 /* XXX - do we really want index? It is always set to mp->m_index */
709 };
710
711 /*
712 * This is the compare function obtained from the key_tbl[]. It is
713 * used by thread_array() to identify the end of the thread and by
714 * qsort_cmpfn() to do the basic sort.
715 */
716 static struct {
717 int inv;
718 int (*fn)(const void *, const void *);
719 } cmp;
720
721 /*
722 * The routine passed to qsort. Note that cmpfn must be set first!
723 */
724 static int
725 qsort_cmpfn(const void *left, const void *right)
726 {
727 int delta;
728 const struct key_sort_s *lp = left;
729 const struct key_sort_s *rp = right;
730
731 delta = cmp.fn(left, right);
732 return delta ? cmp.inv ? - delta : delta : lp->index - rp->index;
733 }
734
735 static void
736 link_array(struct key_sort_s *marray, size_t mcount)
737 {
738 int i;
739 struct message *lastmp;
740 lastmp = NULL;
741 for (i = 0; i < mcount; i++) {
742 marray[i].mp->m_index = i + 1;
743 marray[i].mp->m_blink = lastmp;
744 marray[i].mp->m_flink = NULL;
745 if (lastmp)
746 lastmp->m_flink = marray[i].mp;
747 lastmp = marray[i].mp;
748 }
749 if (current_thread.t_head->m_plink)
750 current_thread.t_head->m_plink->m_clink = marray[0].mp;
751
752 current_thread.t_head = marray[0].mp;
753 }
754
755 static void
756 cut_array(struct key_sort_s *marray, int beg, int end)
757 {
758 int i;
759
760 if (beg + 1 < end) {
761 assert(marray[beg].mp->m_clink == NULL);
762
763 marray[beg].mp->m_clink = marray[beg + 1].mp;
764 marray[beg + 1].mp->m_blink = NULL;
765
766 marray[beg].mp->m_flink = marray[end].mp;
767 if (marray[end].mp)
768 marray[end].mp->m_blink = marray[beg].mp;
769
770 marray[end - 1].mp->m_flink = NULL;
771
772 for (i = beg + 1; i < end; i++)
773 marray[i].mp->m_plink = marray[beg].mp;
774 }
775 }
776
777 static void
778 thread_array(struct key_sort_s *marray, size_t mcount, int cutit)
779 {
780 struct message *parent;
781
782 parent = marray[0].mp->m_plink;
783 qsort(marray, mcount, sizeof(*marray), qsort_cmpfn);
784 link_array(marray, mcount);
785
786 if (cutit) {
787 int i, j;
788 /*
789 * Flatten out the array.
790 */
791 for (i = 0; i < mcount; i++) {
792 marray[i].mp->m_plink = parent;
793 marray[i].mp->m_clink = NULL;
794 }
795
796 /*
797 * Now chop it up. There is really only one level here.
798 */
799 i = 0;
800 for (j = 1; j < mcount; j++) {
801 if (cmp.fn(&marray[i], &marray[j]) != 0) {
802 cut_array(marray, i, j);
803 i = j;
804 }
805 }
806 cut_array(marray, i, j);
807 }
808 }
809
810 /************************************************************************/
811 /*
812 * thread_on_reference() is the core reference threading routine. It
813 * is not a command itself by called by threadcmd().
814 */
815
816 static void
817 adopt_child(struct message *parent, struct message *child)
818 {
819 /*
820 * Unhook the child from its current location.
821 */
822 if (child->m_blink != NULL) {
823 child->m_blink->m_flink = child->m_flink;
824 }
825 if (child->m_flink != NULL) {
826 child->m_flink->m_blink = child->m_blink;
827 }
828
829 /*
830 * Link the child to the parent.
831 */
832 if (parent->m_clink == NULL) { /* parent has no child */
833 parent->m_clink = child;
834 child->m_blink = NULL;
835 }
836 else { /* add message to end of parent's child's flist */
837 struct message *t;
838 for (t = parent->m_clink; t && t->m_flink; t = t->m_flink)
839 continue;
840 t->m_flink = child;
841 child->m_blink = t;
842 }
843 child->m_flink = NULL;
844 child->m_plink = parent;
845 }
846
847 /*
848 * Get the parent ID for a message (if there is one).
849 *
850 * See RFC 2822, sec 3.6.4.
851 *
852 * Many mailers seem to screw up the In-Reply-To: and/or
853 * References: fields, generally by omitting one or both.
854 *
855 * We give preference to the "References" field. If it does
856 * not exist, try the "In-Reply-To" field. If neither exist,
857 * then the message is either not a reply or someone isn't
858 * adding the necessary fields, so skip it.
859 */
860 static char *
861 get_parent_id(struct message *mp)
862 {
863 struct name *refs;
864
865 if ((refs = extract(hfield("references", mp), 0)) != NULL) {
866 char *id;
867 while (refs->n_flink)
868 refs = refs->n_flink;
869
870 id = skin(refs->n_name);
871 if (*id != '\0')
872 return id;
873 }
874
875 return skin(hfield("in-reply-to", mp));
876 }
877
878 struct marray_s {
879 struct message *mp;
880 char *message_id;
881 char *parent_id;
882 };
883
884 static struct message *
885 thread_top(struct message *mp)
886 {
887 while (mp && mp->m_plink) {
888 if (mp->m_plink->m_clink == current_thread.t_head)
889 break;
890 mp = mp->m_plink;
891 }
892 return mp;
893 }
894
895
896 /*
897 * Thread on the "In-Reply-To" and "Reference" fields. This is the
898 * normal way to thread.
899 */
900 static void
901 thread_on_reference(struct message *mp)
902 {
903 struct message *parent;
904 state_t oldstate;
905 size_t mcount;
906 struct marray_s *marray;
907 int i;
908
909 assert(mp == current_thread.t_head);
910
911 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), S_EXPOSE); /* restrict off, expose on */
912
913 mcount = get_msgCount();
914
915 if (mcount < 2) /* it's hard to thread so few messages! */
916 goto done;
917
918 marray = csalloc(mcount + 1, sizeof(*marray));
919
920 /*
921 * Load up the array (skin where necessary).
922 *
923 * With a 40K message file, most of the time is spent here,
924 * not in the search loop below.
925 */
926 for (i = 0; i < mcount; i++) {
927 marray[i].mp = mp;
928 marray[i].message_id = skin(hfield("message-id", mp));
929 marray[i].parent_id = get_parent_id(mp);
930 mp = next_message(mp);
931 }
932
933 /*
934 * Save the old parent.
935 */
936 parent = marray[0].mp->m_plink;
937
938 /*
939 * flatten the array.
940 */
941 marray[0].mp->m_clink = NULL;
942 for (i = 1; i < mcount; i++) {
943 marray[i].mp->m_depth = marray[0].mp->m_depth;
944 marray[i].mp->m_plink = marray[0].mp->m_plink;
945 marray[i].mp->m_clink = NULL;
946 marray[i].mp->m_blink = marray[i - 1].mp;
947 marray[i - 1].mp->m_flink = marray[i].mp;
948 }
949 marray[i - 1].mp->m_flink = NULL;
950
951 /*
952 * Walk the array hooking up the replies with their parents.
953 */
954 for (i = 0; i < mcount; i++) {
955 struct message *child;
956 char *parent_id;
957 int j;
958
959 if ((parent_id = marray[i].parent_id) == NULL)
960 continue;
961
962 child = marray[i].mp;
963
964 /*
965 * Look for the parent message and link this one in
966 * appropriately.
967 *
968 * XXX - This will not scale nicely, though it does
969 * not appear to be the dominant loop even with 40K
970 * messages. If this becomes a problem, implement a
971 * binary search.
972 */
973 for (j = 0; j < mcount; j++) {
974 /* message_id will be NULL on mbox files */
975 if (marray[i].message_id == NULL)
976 continue;
977
978 if (equal(marray[j].message_id, parent_id)) {
979 /*
980 * The child is at the top level. If
981 * it is being adopted and it was top
982 * left (current_thread.t_head), then
983 * its right sibling is the new top
984 * left (current_thread.t_head).
985 */
986 if (current_thread.t_head == child) {
987 current_thread.t_head = child->m_flink;
988 assert(current_thread.t_head != NULL);
989 }
990 adopt_child(marray[j].mp, child);
991 break;
992 }
993 }
994 }
995
996 if (parent)
997 parent->m_clink = current_thread.t_head;
998 /*
999 * If the old state is not exposed, reset the dot to the head
1000 * of the thread it lived in, so it will be in a valid spot
1001 * when things are re-hidden.
1002 */
1003 if (!S_IS_EXPOSE(oldstate))
1004 dot = thread_top(dot);
1005 done:
1006 restore_state(oldstate);
1007 }
1008
1009 /************************************************************************/
1010 /*
1011 * Tagging commands.
1012 */
1013 static int
1014 tag1(int *msgvec, int and_bits, int xor_bits)
1015 {
1016 int *ip;
1017
1018 for (ip = msgvec; *ip != 0; ip++)
1019 (void)set_m_flag(*ip, and_bits, xor_bits);
1020
1021 reindex(¤t_thread);
1022 /* thread_announce(v); */
1023 return 0;
1024 }
1025
1026 /*
1027 * Tag the current message dot or a message list.
1028 */
1029 PUBLIC int
1030 tagcmd(void *v)
1031 {
1032 return tag1(v, ~MTAGGED, MTAGGED);
1033 }
1034
1035 /*
1036 * Untag the current message dot or a message list.
1037 */
1038 PUBLIC int
1039 untagcmd(void *v)
1040 {
1041 return tag1(v, ~MTAGGED, 0);
1042 }
1043
1044 /*
1045 * Invert all tags in the message list.
1046 */
1047 PUBLIC int
1048 invtagscmd(void *v)
1049 {
1050 return tag1(v, ~0, MTAGGED);
1051 }
1052
1053 /*
1054 * Tag all messages below the current dot or below a specified
1055 * message.
1056 */
1057 PUBLIC int
1058 tagbelowcmd(void *v)
1059 {
1060 int *msgvec;
1061 struct message *mp;
1062 state_t oldstate;
1063 int depth;
1064
1065 msgvec = v;
1066
1067 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), S_EXPOSE); /* restrict off, expose on */
1068 mp = get_message(*msgvec);
1069 if (mp) {
1070 depth = mp->m_depth;
1071 for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp))
1072 if (mp->m_depth > depth ) {
1073 mp->m_flag |= MTAGGED;
1074 touch(mp);
1075 }
1076 }
1077 restore_state(oldstate);
1078 /* thread_announce(v); */
1079 return 0;
1080 }
1081
1082 /*
1083 * Do not display the tagged messages.
1084 */
1085 PUBLIC int
1086 hidetagscmd(void *v)
1087 {
1088 (void)set_state(~S_RESTRICT, S_RESTRICT);
1089 thread_announce(v);
1090 return 0;
1091 }
1092
1093 /*
1094 * Display the tagged messages.
1095 */
1096 PUBLIC int
1097 showtagscmd(void *v)
1098 {
1099 (void)set_state(~S_RESTRICT, 0);
1100 thread_announce(v);
1101 return 0;
1102 }
1103
1104 /************************************************************************/
1105 /*
1106 * Basic threading commands.
1107 */
1108 /*
1109 * Show the threads.
1110 */
1111 PUBLIC int
1112 exposecmd(void *v)
1113 {
1114 (void)set_state(~S_EXPOSE, S_EXPOSE); /* expose on */
1115 thread_announce(v);
1116 return 0;
1117 }
1118
1119 /*
1120 * Hide the threads.
1121 */
1122 PUBLIC int
1123 hidecmd(void *v)
1124 {
1125 dot = thread_top(dot);
1126 (void)set_state(~S_EXPOSE, 0); /* expose off */
1127 thread_announce(v);
1128 return 0;
1129 }
1130
1131 /*
1132 * Up one level in the thread tree. Go up multiple levels if given an
1133 * argument.
1134 */
1135 PUBLIC int
1136 upcmd(void *v)
1137 {
1138 char *str;
1139 int upcnt;
1140 int upone;
1141
1142 str = v;
1143 str = skip_blank(str);
1144 if (*str == '\0')
1145 upcnt = 1;
1146 else
1147 upcnt = atoi(str);
1148
1149 if (upcnt < 1) {
1150 (void)printf("Sorry, argument must be > 0.\n");
1151 return 0;
1152 }
1153 if (dot == NULL) {
1154 (void)printf("No applicable messages\n");
1155 return 0;
1156 }
1157 if (dot->m_plink == NULL) {
1158 (void)printf("top thread\n");
1159 return 0;
1160 }
1161 upone = 0;
1162 while (upcnt-- > 0) {
1163 struct message *parent;
1164 parent = current_thread.t_head->m_plink;
1165 if (parent == NULL) {
1166 (void)printf("top thread\n");
1167 break;
1168 }
1169 else {
1170 struct message *mp;
1171 assert(current_thread.t_head->m_depth > 0);
1172 for (mp = parent; mp && mp->m_blink; mp = mp->m_blink)
1173 continue;
1174 current_thread.t_head = mp;
1175 dot = parent;
1176 upone = 1;
1177 }
1178 }
1179 if (upone) {
1180 reindex(¤t_thread);
1181 thread_announce(v);
1182 }
1183 return 0;
1184 }
1185
1186 /*
1187 * Go down one level in the thread tree from the current dot or a
1188 * given message number if given.
1189 */
1190 PUBLIC int
1191 downcmd(void *v)
1192 {
1193 struct message *child;
1194 struct message *mp;
1195 int *msgvec = v;
1196
1197 if ((mp = get_message(*msgvec)) == NULL ||
1198 (child = mp->m_clink) == NULL)
1199 (void)printf("no sub-thread\n");
1200 else {
1201 current_thread.t_head = child;
1202 dot = child;
1203 reindex(¤t_thread);
1204 thread_announce(v);
1205 }
1206 return 0;
1207 }
1208
1209 /*
1210 * Set the current thread level to the current dot or to the message
1211 * if given.
1212 */
1213 PUBLIC int
1214 tsetcmd(void *v)
1215 {
1216 struct message *mp;
1217 int *msgvec = v;
1218
1219 if ((mp = get_message(*msgvec)) == NULL)
1220 (void)printf("invalid message\n");
1221 else {
1222 for (/*EMPTY*/; mp->m_blink; mp = mp->m_blink)
1223 continue;
1224 current_thread.t_head = mp;
1225 reindex(¤t_thread);
1226 thread_announce(v);
1227 }
1228 return 0;
1229 }
1230
1231 /*
1232 * Reverse the current thread order. If threaded, it only operates on
1233 * the heads.
1234 */
1235 static void
1236 reversecmd_core(struct thread_s *tp)
1237 {
1238 struct message *thread_start;
1239 struct message *mp;
1240 struct message *lastmp;
1241 struct message *old_flink;
1242
1243 thread_start = tp->t_head;
1244
1245 assert(thread_start->m_blink == NULL);
1246
1247 lastmp = NULL;
1248 for (mp = thread_start; mp; mp = old_flink) {
1249 old_flink = mp->m_flink;
1250 mp->m_flink = mp->m_blink;
1251 mp->m_blink = old_flink;
1252 lastmp = mp;
1253 }
1254 if (thread_start->m_plink)
1255 thread_start->m_plink->m_clink = lastmp;
1256
1257 current_thread.t_head = lastmp;
1258 reindex(tp);
1259 }
1260
1261 PUBLIC int
1262 reversecmd(void *v)
1263 {
1264 reversecmd_core(¤t_thread);
1265 thread_announce(v);
1266 return 0;
1267 }
1268
1269
1270 /*
1271 * Get threading and sorting modifiers.
1272 */
1273 #define MF_IGNCASE 1 /* ignore case when sorting */
1274 #define MF_REVERSE 2 /* reverse sort direction */
1275 #define MF_SKIN 4 /* "skin" the field to remove comments */
1276 static int
1277 get_modifiers(char **str)
1278 {
1279 int modflags;
1280 char *p;
1281
1282 modflags = 0;
1283 for (p = *str; p && *p; p++) {
1284 switch (*p) {
1285 case '!':
1286 modflags |= MF_REVERSE;
1287 break;
1288 case '^':
1289 modflags |= MF_IGNCASE;
1290 break;
1291 case '-':
1292 modflags |= MF_SKIN;
1293 break;
1294 case ' ':
1295 case '\t':
1296 break;
1297 default:
1298 goto done;
1299 }
1300 }
1301 done:
1302 *str = p;
1303 return modflags;
1304 }
1305
1306 /************************************************************************/
1307 /*
1308 * The key_sort_s compare routines.
1309 */
1310
1311 static int
1312 keystrcmp(const void *left, const void *right)
1313 {
1314 const struct key_sort_s *lp = left;
1315 const struct key_sort_s *rp = right;
1316
1317 lp = left;
1318 rp = right;
1319
1320 if (rp->key.str == NULL && lp->key.str == NULL)
1321 return 0;
1322 else if (rp->key.str == NULL)
1323 return -1;
1324 else if (lp->key.str == NULL)
1325 return 1;
1326 else
1327 return strcmp(lp->key.str, rp->key.str);
1328 }
1329
1330 static int
1331 keystrcasecmp(const void *left, const void *right)
1332 {
1333 const struct key_sort_s *lp = left;
1334 const struct key_sort_s *rp = right;
1335
1336 if (rp->key.str == NULL && lp->key.str == NULL)
1337 return 0;
1338 else if (rp->key.str == NULL)
1339 return -1;
1340 else if (lp->key.str == NULL)
1341 return 1;
1342 else
1343 return strcasecmp(lp->key.str, rp->key.str);
1344 }
1345
1346 static int
1347 keylongcmp(const void *left, const void *right)
1348 {
1349 const struct key_sort_s *lp = left;
1350 const struct key_sort_s *rp = right;
1351
1352 if (lp->key.lines > rp->key.lines)
1353 return 1;
1354
1355 if (lp->key.lines < rp->key.lines)
1356 return -1;
1357
1358 return 0;
1359 }
1360
1361 static int
1362 keyoffcmp(const void *left, const void *right)
1363 {
1364 const struct key_sort_s *lp = left;
1365 const struct key_sort_s *rp = right;
1366
1367 if (lp->key.size > rp->key.size)
1368 return 1;
1369
1370 if (lp->key.size < rp->key.size)
1371 return -1;
1372
1373 return 0;
1374 }
1375
1376 static int
1377 keytimecmp(const void *left, const void *right)
1378 {
1379 double delta;
1380 const struct key_sort_s *lp = left;
1381 const struct key_sort_s *rp = right;
1382
1383 delta = difftime(lp->key.time, rp->key.time);
1384 if (delta > 0)
1385 return 1;
1386
1387 if (delta < 0)
1388 return -1;
1389
1390 return 0;
1391 }
1392
1393 /************************************************************************
1394 * key_sort_s loading routines.
1395 */
1396 static void
1397 field_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1398 const char *key, int skin_it)
1399 {
1400 int i;
1401 for (i = 0; i < mcount; i++) {
1402 marray[i].mp = mp;
1403 marray[i].key.str =
1404 skin_it ? skin(hfield(key, mp)) : hfield(key, mp);
1405 marray[i].index = mp->m_index;
1406 mp = next_message(mp);
1407 }
1408 }
1409
1410 static void
1411 subj_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1412 const char *key __unused, int flags __unused)
1413 {
1414 int i;
1415 #ifdef __lint__
1416 flags = flags;
1417 key = key;
1418 #endif
1419 for (i = 0; i < mcount; i++) {
1420 char *subj = hfield(key, mp);
1421 while( strncasecmp(subj, "Re:", 3) == 0 )
1422 subj = skip_blank(subj + 3);
1423 marray[i].mp = mp;
1424 marray[i].key.str = subj;
1425 marray[i].index = mp->m_index;
1426 mp = next_message(mp);
1427 }
1428 }
1429
1430
1431 static void
1432 lines_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1433 const char *key __unused, int flags)
1434 {
1435 int i;
1436 int use_blines;
1437 int use_hlines;
1438 #ifdef __lint__
1439 key = key;
1440 #endif
1441 #define HLINES 1
1442 #define BLINES 2
1443 #define TLINES 3
1444 use_hlines = flags == HLINES;
1445 use_blines = flags == BLINES;
1446
1447 for (i = 0; i < mcount; i++) {
1448 marray[i].mp = mp;
1449 marray[i].key.lines = use_hlines ? mp->m_lines - mp->m_blines :
1450 use_blines ? mp->m_blines : mp->m_lines;
1451 marray[i].index = mp->m_index;
1452 mp = next_message(mp);
1453 }
1454 }
1455
1456 static void
1457 size_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1458 const char *key __unused, int flags __unused)
1459 {
1460 int i;
1461 #ifdef __lint__
1462 flags = flags;
1463 key = key;
1464 #endif
1465 for (i = 0; i < mcount; i++) {
1466 marray[i].mp = mp;
1467 marray[i].key.size = mp->m_size;
1468 marray[i].index = mp->m_index;
1469 mp = next_message(mp);
1470 }
1471 }
1472
1473 static void __unused
1474 date_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1475 const char *key __unused, int flags)
1476 {
1477 int i;
1478 int use_hl_date;
1479 int zero_hour_min_sec;
1480 #ifdef __lint__
1481 key = key;
1482 #endif
1483 #define RDAY 1
1484 #define SDAY 2
1485 #define RDATE 3
1486 #define SDATE 4
1487 use_hl_date = (flags == RDAY || flags == RDATE);
1488 zero_hour_min_sec = (flags == RDAY || flags == SDAY);
1489
1490 for (i = 0; i < mcount; i++) {
1491 struct tm tm;
1492 (void)dateof(&tm, mp, use_hl_date);
1493 if (zero_hour_min_sec) {
1494 tm.tm_sec = 0;
1495 tm.tm_min = 0;
1496 tm.tm_hour = 0;
1497 }
1498 marray[i].mp = mp;
1499 marray[i].key.time = mktime(&tm);
1500 marray[i].index = mp->m_index;
1501 mp = next_message(mp);
1502 }
1503 }
1504
1505 static void
1506 from_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1507 const char *key __unused, int flags __unused)
1508 {
1509 int i;
1510 #ifdef __lint__
1511 flags = flags;
1512 key = key;
1513 #endif
1514 for (i = 0; i < mcount; i++) {
1515 marray[i].mp = mp;
1516 marray[i].key.str = nameof(mp, 0);
1517 marray[i].index = mp->m_index;
1518 mp = next_message(mp);
1519 }
1520 }
1521
1522 /************************************************************************
1523 * The master table that controls all sorting and threading.
1524 */
1525 static const struct key_tbl_s {
1526 const char *key;
1527 void (*loadfn)(struct key_sort_s *, size_t, struct message *, const char *, int);
1528 int flags;
1529 int (*cmpfn)(const void*, const void*);
1530 int (*casecmpfn)(const void*, const void*);
1531 } key_tbl[] = {
1532 {"blines", lines_load, BLINES, keylongcmp, keylongcmp},
1533 {"hlines", lines_load, HLINES, keylongcmp, keylongcmp},
1534 {"tlines", lines_load, TLINES, keylongcmp, keylongcmp},
1535 {"size", size_load, 0, keyoffcmp, keyoffcmp},
1536 {"sday", date_load, SDAY, keytimecmp, keytimecmp},
1537 {"rday", date_load, RDAY, keytimecmp, keytimecmp},
1538 {"sdate", date_load, SDATE, keytimecmp, keytimecmp},
1539 {"rdate", date_load, RDATE, keytimecmp, keytimecmp},
1540 {"from", from_load, 0, keystrcasecmp, keystrcasecmp},
1541 {"subject", subj_load, 0, keystrcmp, keystrcasecmp},
1542 {NULL, field_load, 0, keystrcmp, keystrcasecmp},
1543 };
1544
1545 #ifdef USE_EDITLINE
1546 /*
1547 * This is for use in complete.c to get the list of threading key
1548 * names without exposing the key_tbl[]. The first name is returned
1549 * if called with a pointer to a NULL pointer. Subsequent calls with
1550 * the same cookie give successive names. A NULL return indicates the
1551 * end of the list.
1552 */
1553 PUBLIC const char *
1554 thread_next_key_name(const void **cookie)
1555 {
1556 const struct key_tbl_s *kp;
1557
1558 kp = *cookie;
1559 if (kp == NULL)
1560 kp = key_tbl;
1561
1562 *cookie = kp->key ? &kp[1] : NULL;
1563
1564 return kp->key;
1565 }
1566 #endif /* USE_EDITLINE */
1567
1568 static const struct key_tbl_s *
1569 get_key(const char *key)
1570 {
1571 const struct key_tbl_s *kp;
1572 for (kp = key_tbl; kp->key != NULL; kp++)
1573 if (strcmp(kp->key, key) == 0)
1574 return kp;
1575 return kp;
1576 }
1577
1578 static int (*
1579 get_cmpfn(const struct key_tbl_s *kp, int ignorecase)
1580 )(const void*, const void*)
1581 {
1582 if (ignorecase)
1583 return kp->casecmpfn;
1584 else
1585 return kp->cmpfn;
1586 }
1587
1588 static void
1589 thread_current_on(char *str, int modflags, int cutit)
1590 {
1591 const struct key_tbl_s *kp;
1592 struct key_sort_s *marray;
1593 size_t mcount;
1594 state_t oldstate;
1595
1596 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), cutit ? S_EXPOSE : 0);
1597
1598 kp = get_key(str);
1599 mcount = get_msgCount();
1600 marray = csalloc(mcount + 1, sizeof(*marray));
1601 kp->loadfn(marray, mcount, current_thread.t_head, str,
1602 kp->flags ? kp->flags : modflags & MF_SKIN);
1603 cmp.fn = get_cmpfn(kp, modflags & MF_IGNCASE);
1604 cmp.inv = modflags & MF_REVERSE;
1605 thread_array(marray, mcount, cutit);
1606
1607 if (!S_IS_EXPOSE(oldstate))
1608 dot = thread_top(dot);
1609
1610 restore_state(oldstate);
1611 }
1612
1613 /*
1614 * The thread command. Thread the current thread on its references or
1615 * on a specified field.
1616 */
1617 PUBLIC int
1618 threadcmd(void *v)
1619 {
1620 char *str;
1621
1622 str = v;
1623 if (*str == '\0')
1624 thread_on_reference(current_thread.t_head);
1625 else {
1626 int modflags;
1627 modflags = get_modifiers(&str);
1628 thread_current_on(str, modflags, 1);
1629 }
1630 thread_announce(v);
1631 return 0;
1632 }
1633
1634 /*
1635 * Remove all threading information, reverting to the startup state.
1636 */
1637 PUBLIC int
1638 unthreadcmd(void *v)
1639 {
1640 thread_fix_new_links(message_array.t_head, 0, message_array.t_msgCount);
1641 thread_announce(v);
1642 return 0;
1643 }
1644
1645 /*
1646 * The sort command.
1647 */
1648 PUBLIC int
1649 sortcmd(void *v)
1650 {
1651 int modflags;
1652 char *str;
1653
1654 str = v;
1655 modflags = get_modifiers(&str);
1656 if (*str != '\0')
1657 thread_current_on(str, modflags, 0);
1658 else {
1659 if (modflags & MF_REVERSE)
1660 reversecmd_core(¤t_thread);
1661 else {
1662 (void)printf("sort on what?\n");
1663 return 0;
1664 }
1665 }
1666 thread_announce(v);
1667 return 0;
1668 }
1669
1670
1671 /*
1672 * Delete duplicate messages (based on their "Message-Id" field).
1673 *
1674 * XXX - This doesn't completely belong here, but what the hell.
1675 */
1676 /*ARGSUSED*/
1677 PUBLIC int
1678 deldupscmd(void *v __unused)
1679 {
1680 struct message *mp;
1681 int depth;
1682 state_t oldstate;
1683
1684 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), S_EXPOSE);
1685
1686 thread_current_on(__UNCONST("Message-Id"), 0, 1);
1687 reindex(¤t_thread);
1688 redepth(¤t_thread);
1689 depth = current_thread.t_head->m_depth;
1690 for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp))
1691 if (mp->m_depth > depth ) {
1692 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
1693 mp->m_flag |= MDELETED|MTOUCH;
1694 touch(mp);
1695 }
1696 restore_state(oldstate);
1697 return 0;
1698 }
1699
1700 #endif /* THREAD_SUPPORT */
1701