thread.c revision 1.2 1 /* $NetBSD: thread.c,v 1.2 2006/11/28 20:29:25 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.2 2006/11/28 20:29:25 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 = 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 if (S_IS_RESTRICT(state) == 0 || !is_tagged(mp))
351 mp->m_index = ++i;
352
353 if (mp->m_clink)
354 (void)reindex_core(mp->m_clink);
355 }
356 return i;
357 }
358
359
360 static void
361 reindex(struct thread_s *tp)
362 {
363 struct message *mp;
364 int i;
365
366 assert(tp->t_head->m_blink == NULL);
367
368 if (S_IS_EXPOSE(state) == 0) {
369 /*
370 * We special case this so that all the hidden
371 * sub-threads get indexed, not just the current one.
372 */
373 i = reindex_core(tp->t_head);
374 }
375 else {
376 i = 0;
377 for (mp = first_message(tp->t_head); mp; mp = next_message(mp))
378 mp->m_index = ++i;
379 }
380
381 assert(i <= message_array.t_msgCount);
382
383 tp->t_msgCount = i;
384 i = 0;
385 for (mp = first_message(tp->t_head); mp; mp = next_message(mp))
386 tp->t_msgtbl[i++] = mp;
387 }
388
389 static void
390 redepth_core(struct message *mp, int depth, struct message *parent)
391 {
392 assert(mp->m_blink == NULL);
393 assert((parent == NULL && depth == 0) ||
394 (parent != NULL && depth != 0 && depth == parent->m_depth + 1));
395
396 for (/*EMPTY*/; mp; mp = mp->m_flink) {
397 assert(mp->m_plink == parent);
398 assert(mp->m_flink == NULL || mp == mp->m_flink->m_blink);
399 assert(mp->m_blink == NULL || mp == mp->m_blink->m_flink);
400
401 mp->m_depth = depth;
402 if (mp->m_clink)
403 redepth_core(mp->m_clink, depth + 1, mp);
404 }
405 }
406
407 static void
408 redepth(struct thread_s *thread)
409 {
410 int depth;
411 struct message *mp;
412
413 assert(thread != NULL);
414
415 if ((mp = thread->t_head) == NULL)
416 return;
417
418 depth = mp->m_plink ? mp->m_plink->m_depth + 1 : 0;
419
420 #ifndef NDEBUG /* a sanity check if asserts are active */
421 {
422 struct message *tp;
423 int i;
424 i = 0;
425 for (tp = mp->m_plink; tp; tp = tp->m_plink)
426 i++;
427 assert(i == depth);
428 }
429 #endif
430
431 redepth_core(mp, depth, mp->m_plink);
432 }
433
434 /************************************************************************
435 * To be called after reallocating the main message list. It is here
436 * as it needs access to current_thread.t_head.
437 */
438 PUBLIC void
439 thread_fix_old_links(struct message *nmessage, struct message *message, int omsgCount)
440 {
441 int i;
442 if (nmessage == message)
443 return;
444
445 #ifndef NDEBUG
446 message_array.t_head = nmessage; /* for assert check in thread_fix_new_links */
447 #endif
448
449 # define FIX_LINK(p) do { if (p) p = nmessage + (p - message); } while(/*CONSTCOND*/0)
450 FIX_LINK(current_thread.t_head);
451 for (i = 0; i < omsgCount; i++) {
452 FIX_LINK(nmessage[i].m_blink);
453 FIX_LINK(nmessage[i].m_flink);
454 FIX_LINK(nmessage[i].m_clink);
455 FIX_LINK(nmessage[i].m_plink);
456 }
457 for (i = 0; i < current_thread.t_msgCount; i++ )
458 FIX_LINK(current_thread.t_msgtbl[i]);
459
460 # undef FIX_LINK
461 }
462
463 static void
464 thread_init(struct thread_s *tp, struct message *mp, int msgCount)
465 {
466 int i;
467
468 if (tp->t_msgtbl == NULL || msgCount > tp->t_msgCount) {
469 if (tp->t_msgtbl)
470 free(tp->t_msgtbl);
471 tp->t_msgtbl = ecalloc((size_t)msgCount, sizeof(tp->t_msgtbl[0]));
472 }
473 tp->t_head = mp;
474 tp->t_msgCount = msgCount;
475 for (i = 0; i < msgCount; i++)
476 tp->t_msgtbl[i] = &mp[i];
477 }
478
479 /*
480 * To be called after reading in the new message structures.
481 * It is here as it needs access to current_thread.t_head.
482 */
483 PUBLIC void
484 thread_fix_new_links(struct message *message, int omsgCount, int msgCount)
485 {
486 int i;
487 struct message *lastmp;
488
489 /* This should only be called at the top level if omsgCount != 0! */
490 assert(omsgCount == 0 || message->m_plink == NULL);
491 assert(omsgCount == 0 || message_array.t_msgCount == omsgCount);
492 assert(message_array.t_head == message);
493
494 message_array.t_head = message;
495 message_array.t_msgCount = msgCount;
496 assert(message_array.t_msgtbl == NULL); /* never used */
497
498 lastmp = NULL;
499 if (omsgCount) {
500 /*
501 * Find the end of the toplevel thread.
502 */
503 for (i = 0; i < omsgCount; i++) {
504 if (message_array.t_head[i].m_depth == 0 &&
505 message_array.t_head[i].m_flink == NULL) {
506 lastmp = &message_array.t_head[i];
507 break;
508 }
509 }
510 #ifndef NDEBUG
511 /*
512 * lastmp better be unique!!!
513 */
514 for (i++; i < omsgCount; i++)
515 assert(message_array.t_head[i].m_depth != 0 ||
516 message_array.t_head[i].m_flink != NULL);
517 assert(lastmp != NULL);
518 #endif /* NDEBUG */
519 }
520 /*
521 * Link and index the new messages linearly at depth 0.
522 */
523 for (i = omsgCount; i < msgCount; i++) {
524 message[i].m_index = i + 1;
525 message[i].m_depth = 0;
526 message[i].m_blink = lastmp;
527 message[i].m_flink = NULL;
528 message[i].m_clink = NULL;
529 message[i].m_plink = NULL;
530 if (lastmp)
531 lastmp->m_flink = &message[i];
532 lastmp = &message[i];
533 }
534
535 /*
536 * Make sure the current thread is setup correctly.
537 */
538 if (omsgCount == 0) {
539 thread_init(¤t_thread, message, msgCount);
540 }
541 else {
542 /*
543 * Make sure current_thread.t_msgtbl is always large
544 * enough.
545 */
546 current_thread.t_msgtbl =
547 erealloc(current_thread.t_msgtbl,
548 msgCount * sizeof(*current_thread.t_msgtbl));
549
550 assert(current_thread.t_head != NULL);
551 if (current_thread.t_head->m_depth == 0)
552 reindex(¤t_thread);
553 }
554 }
555
556 /************************************************************************/
557 /*
558 * All state changes should go through here!!!
559 */
560 static state_t
561 set_state(int and_bits, int xor_bits)
562 {
563 state_t old_state;
564 old_state = state;
565 state &= and_bits;
566 state ^= xor_bits;
567 reindex(¤t_thread);
568 redepth(¤t_thread);
569 return old_state;
570 }
571
572 static void
573 restore_state(state_t new_state)
574 {
575 state = new_state;
576 reindex(¤t_thread);
577 redepth(¤t_thread);
578 }
579
580 /************************************************************************/
581 /*
582 * Possibly show the message list.
583 */
584 static void
585 thread_announce(void *v)
586 {
587 int vec[2];
588
589 if (v == NULL) /* check this here to avoid it before each call */
590 return;
591
592 if (dot == NULL) {
593 (void)printf("No applicable messages\n");
594 return;
595 }
596 vec[0] = get_msgnum(dot);
597 vec[1] = 0;
598 if (get_msgCount() > 0 && value(ENAME_NOHEADER) == NULL)
599 (void)headers(vec);
600 sawcom = 0; /* so next will print the first message */
601 }
602
603 /************************************************************************/
604
605 /*
606 * Flatten out the portion of the thread starting with the given
607 * message.
608 */
609 static void
610 flattencmd_core(struct message *mp)
611 {
612 struct message **marray;
613 size_t mcount;
614 struct message *tp;
615 struct message *nextmp;
616 int i;
617
618 if (mp == NULL)
619 return;
620
621 mcount = 1;
622 for (tp = next_message(mp); tp && tp->m_depth > mp->m_depth; tp = next_message(tp))
623 mcount++;
624
625 if (tp && tp->m_depth < mp->m_depth)
626 nextmp = NULL;
627 else
628 nextmp = tp;
629
630 if (mcount == 1)
631 return;
632
633 marray = csalloc(mcount, sizeof(*marray));
634 tp = mp;
635 for (i = 0; i < mcount; i++) {
636 marray[i] = tp;
637 tp = next_message(tp);
638 }
639 mp->m_clink = NULL;
640 for (i = 1; i < mcount; i++) {
641 marray[i]->m_depth = mp->m_depth;
642 marray[i]->m_plink = mp->m_plink;
643 marray[i]->m_clink = NULL;
644 marray[i]->m_blink = marray[i - 1];
645 marray[i - 1]->m_flink = marray[i];
646 }
647 marray[i - 1]->m_flink = nextmp;
648 if (nextmp)
649 nextmp->m_blink = marray[i - 1];
650 }
651
652 /*
653 * Flatten out all thread parts given in the message list, or the
654 * current thread, if none given.
655 */
656 PUBLIC int
657 flattencmd(void *v)
658 {
659 int *msgvec;
660 int *ip;
661
662 msgvec = v;
663
664 if (*msgvec) { /* a message was supplied */
665 for (ip = msgvec; *ip; ip++) {
666 struct message *mp;
667 mp = get_message(*ip);
668 if (mp != NULL)
669 flattencmd_core(mp);
670 }
671 }
672 else { /* no message given - flatten current thread */
673 struct message *mp;
674 for (mp = first_message(current_thread.t_head);
675 mp; mp = next_message(mp))
676 flattencmd_core(mp);
677 }
678 redepth(¤t_thread);
679 thread_announce(v);
680 return 0;
681 }
682
683
684 /************************************************************************/
685 /*
686 * The basic sort structure. For each message the index and key
687 * fields are set. The key field is used for the basic sort and the
688 * index is used to ensure that the order from the current thread is
689 * maintained when the key compare is equal.
690 */
691 struct key_sort_s {
692 struct message *mp; /* the message the following refer to */
693 union {
694 char *str; /* string sort key (typically a field or address) */
695 long lines; /* a long sort key (typically a message line count) */
696 off_t size; /* a size sort key (typically the message size) */
697 time_t time; /* a time sort key (typically from date or headline) */
698 } key;
699 int index; /* index from of the current thread before sorting */
700 /* XXX - do we really want index? It is always set to mp->m_index */
701 };
702
703 /*
704 * This is the compare function obtained from the key_tbl[]. It is
705 * used by thread_array() to identify the end of the thread and by
706 * qsort_cmpfn() to do the basic sort.
707 */
708 static struct {
709 int inv;
710 int (*fn)(const void *, const void *);
711 } cmp;
712
713 /*
714 * The routine passed to qsort. Note that cmpfn must be set first!
715 */
716 static int
717 qsort_cmpfn(const void *left, const void *right)
718 {
719 int delta;
720 const struct key_sort_s *lp = left;
721 const struct key_sort_s *rp = right;
722
723 delta = cmp.fn(left, right);
724 return delta ? cmp.inv ? - delta : delta : lp->index - rp->index;
725 }
726
727 static void
728 link_array(struct key_sort_s *marray, size_t mcount)
729 {
730 int i;
731 struct message *lastmp;
732 lastmp = NULL;
733 for (i = 0; i < mcount; i++) {
734 marray[i].mp->m_index = i + 1;
735 marray[i].mp->m_blink = lastmp;
736 marray[i].mp->m_flink = NULL;
737 if (lastmp)
738 lastmp->m_flink = marray[i].mp;
739 lastmp = marray[i].mp;
740 }
741 if (current_thread.t_head->m_plink)
742 current_thread.t_head->m_plink->m_clink = marray[0].mp;
743
744 current_thread.t_head = marray[0].mp;
745 }
746
747 static void
748 cut_array(struct key_sort_s *marray, int beg, int end)
749 {
750 int i;
751
752 if (beg + 1 < end) {
753 assert(marray[beg].mp->m_clink == NULL);
754
755 marray[beg].mp->m_clink = marray[beg + 1].mp;
756 marray[beg + 1].mp->m_blink = NULL;
757
758 marray[beg].mp->m_flink = marray[end].mp;
759 if (marray[end].mp)
760 marray[end].mp->m_blink = marray[beg].mp;
761
762 marray[end - 1].mp->m_flink = NULL;
763
764 for (i = beg + 1; i < end; i++)
765 marray[i].mp->m_plink = marray[beg].mp;
766 }
767 }
768
769 static void
770 thread_array(struct key_sort_s *marray, size_t mcount, int cutit)
771 {
772 struct message *parent;
773
774 parent = marray[0].mp->m_plink;
775 qsort(marray, mcount, sizeof(*marray), qsort_cmpfn);
776 link_array(marray, mcount);
777
778 if (cutit) {
779 int i, j;
780 /*
781 * Flatten out the array.
782 */
783 for (i = 0; i < mcount; i++) {
784 marray[i].mp->m_plink = parent;
785 marray[i].mp->m_clink = NULL;
786 }
787
788 /*
789 * Now chop it up. There is really only one level here.
790 */
791 i = 0;
792 for (j = 1; j < mcount; j++) {
793 if (cmp.fn(&marray[i], &marray[j]) != 0) {
794 cut_array(marray, i, j);
795 i = j;
796 }
797 }
798 cut_array(marray, i, j);
799 }
800 }
801
802 /************************************************************************/
803 /*
804 * thread_on_reference() is the core reference threading routine. It
805 * is not a command itself by called by threadcmd().
806 */
807
808 static void
809 adopt_child(struct message *parent, struct message *child)
810 {
811 /*
812 * Unhook the child from its current location.
813 */
814 if (child->m_blink != NULL) {
815 child->m_blink->m_flink = child->m_flink;
816 }
817 if (child->m_flink != NULL) {
818 child->m_flink->m_blink = child->m_blink;
819 }
820
821 /*
822 * Link the child to the parent.
823 */
824 if (parent->m_clink == NULL) { /* parent has no child */
825 parent->m_clink = child;
826 child->m_blink = NULL;
827 }
828 else { /* add message to end of parent's child's flist */
829 struct message *t;
830 for (t = parent->m_clink; t && t->m_flink; t = t->m_flink)
831 continue;
832 t->m_flink = child;
833 child->m_blink = t;
834 }
835 child->m_flink = NULL;
836 child->m_plink = parent;
837 }
838
839 /*
840 * Get the parent ID for a message (if there is one).
841 *
842 * See RFC 2822, sec 3.6.4.
843 *
844 * Many mailers seem to screw up the In-Reply-To: and/or
845 * References: fields, generally by omitting one or both.
846 *
847 * We give preference to the "References" field. If it does
848 * not exist, try the "In-Reply-To" field. If neither exist,
849 * then the message is either not a reply or someone isn't
850 * adding the necessary fields, so skip it.
851 */
852 static char *
853 get_parent_id(struct message *mp)
854 {
855 struct name *refs;
856
857 if ((refs = extract(hfield("references", mp), 0)) != NULL) {
858 char *id;
859 while (refs->n_flink)
860 refs = refs->n_flink;
861
862 id = skin(refs->n_name);
863 if (*id != '\0')
864 return id;
865 }
866
867 return skin(hfield("in-reply-to", mp));
868 }
869
870 struct marray_s {
871 struct message *mp;
872 char *message_id;
873 char *parent_id;
874 };
875
876 static struct message *
877 thread_top(struct message *mp)
878 {
879 while (mp && mp->m_plink) {
880 if (mp->m_plink->m_clink == current_thread.t_head)
881 break;
882 mp = mp->m_plink;
883 }
884 return mp;
885 }
886
887
888 /*
889 * Thread on the "In-Reply-To" and "Reference" fields. This is the
890 * normal way to thread.
891 */
892 static void
893 thread_on_reference(struct message *mp)
894 {
895 struct message *parent;
896 state_t oldstate;
897 size_t mcount;
898 struct marray_s *marray;
899 int i;
900
901 assert(mp == current_thread.t_head);
902
903 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), S_EXPOSE); /* restrict off, expose on */
904
905 mcount = get_msgCount();
906
907 if (mcount < 2) /* it's hard to thread so few messages! */
908 goto done;
909
910 marray = csalloc(mcount + 1, sizeof(*marray));
911
912 /*
913 * Load up the array (skin where necessary).
914 *
915 * With a 40K message file, most of the time is spent here,
916 * not in the search loop below.
917 */
918 for (i = 0; i < mcount; i++) {
919 marray[i].mp = mp;
920 marray[i].message_id = skin(hfield("message-id", mp));
921 marray[i].parent_id = get_parent_id(mp);
922 mp = next_message(mp);
923 }
924
925 /*
926 * Save the old parent.
927 */
928 parent = marray[0].mp->m_plink;
929
930 /*
931 * flatten the array.
932 */
933 marray[0].mp->m_clink = NULL;
934 for (i = 1; i < mcount; i++) {
935 marray[i].mp->m_depth = marray[0].mp->m_depth;
936 marray[i].mp->m_plink = marray[0].mp->m_plink;
937 marray[i].mp->m_clink = NULL;
938 marray[i].mp->m_blink = marray[i - 1].mp;
939 marray[i - 1].mp->m_flink = marray[i].mp;
940 }
941 marray[i - 1].mp->m_flink = NULL;
942
943 /*
944 * Walk the array hooking up the replies with their parents.
945 */
946 for (i = 0; i < mcount; i++) {
947 struct message *child;
948 char *parent_id;
949 int j;
950
951 if ((parent_id = marray[i].parent_id) == NULL)
952 continue;
953
954 child = marray[i].mp;
955
956 /*
957 * Look for the parent message and link this one in
958 * appropriately.
959 *
960 * XXX - This will not scale nicely, though it does
961 * not appear to be the dominant loop even with 40K
962 * messages. If this becomes a problem, implement a
963 * binary search.
964 */
965 for (j = 0; j < mcount; j++) {
966 /* message_id will be NULL on mbox files */
967 if (marray[i].message_id == NULL)
968 continue;
969
970 if (equal(marray[j].message_id, parent_id)) {
971 /*
972 * The child is at the top level. If
973 * it is being adopted and it was top
974 * left (current_thread.t_head), then
975 * its right sibling is the new top
976 * left (current_thread.t_head).
977 */
978 if (current_thread.t_head == child) {
979 current_thread.t_head = child->m_flink;
980 assert(current_thread.t_head != NULL);
981 }
982 adopt_child(marray[j].mp, child);
983 break;
984 }
985 }
986 }
987
988 if (parent)
989 parent->m_clink = current_thread.t_head;
990 /*
991 * If the old state is not exposed, reset the dot to the head
992 * of the thread it lived in, so it will be in a valid spot
993 * when things are re-hidden.
994 */
995 if (!S_IS_EXPOSE(oldstate))
996 dot = thread_top(dot);
997 done:
998 restore_state(oldstate);
999 }
1000
1001 /************************************************************************/
1002 /*
1003 * Tagging commands.
1004 */
1005 static int
1006 tag1(int *msgvec, int and_bits, int xor_bits)
1007 {
1008 int *ip;
1009
1010 for (ip = msgvec; *ip != 0; ip++)
1011 (void)set_m_flag(*ip, and_bits, xor_bits);
1012
1013 reindex(¤t_thread);
1014 /* thread_announce(v); */
1015 return 0;
1016 }
1017
1018 /*
1019 * Tag the current message dot or a message list.
1020 */
1021 PUBLIC int
1022 tagcmd(void *v)
1023 {
1024 return tag1(v, ~MTAGGED, MTAGGED);
1025 }
1026
1027 /*
1028 * Untag the current message dot or a message list.
1029 */
1030 PUBLIC int
1031 untagcmd(void *v)
1032 {
1033 return tag1(v, ~MTAGGED, 0);
1034 }
1035
1036 /*
1037 * Invert all tags in the message list.
1038 */
1039 PUBLIC int
1040 invtagscmd(void *v)
1041 {
1042 return tag1(v, ~0, MTAGGED);
1043 }
1044
1045 /*
1046 * Tag all messages below the current dot or below a specified
1047 * message.
1048 */
1049 PUBLIC int
1050 tagbelowcmd(void *v)
1051 {
1052 int *msgvec;
1053 struct message *mp;
1054 state_t oldstate;
1055 int depth;
1056
1057 msgvec = v;
1058
1059 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), S_EXPOSE); /* restrict off, expose on */
1060 mp = get_message(*msgvec);
1061 if (mp) {
1062 depth = mp->m_depth;
1063 for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp))
1064 if (mp->m_depth > depth ) {
1065 mp->m_flag |= MTAGGED;
1066 touch(mp);
1067 }
1068 }
1069 restore_state(oldstate);
1070 /* thread_announce(v); */
1071 return 0;
1072 }
1073
1074 /*
1075 * Do not display the tagged messages.
1076 */
1077 PUBLIC int
1078 hidetagscmd(void *v)
1079 {
1080 (void)set_state(~S_RESTRICT, S_RESTRICT);
1081 thread_announce(v);
1082 return 0;
1083 }
1084
1085 /*
1086 * Display the tagged messages.
1087 */
1088 PUBLIC int
1089 showtagscmd(void *v)
1090 {
1091 (void)set_state(~S_RESTRICT, 0);
1092 thread_announce(v);
1093 return 0;
1094 }
1095
1096 /************************************************************************/
1097 /*
1098 * Basic threading commands.
1099 */
1100 /*
1101 * Show the threads.
1102 */
1103 PUBLIC int
1104 exposecmd(void *v)
1105 {
1106 (void)set_state(~S_EXPOSE, S_EXPOSE); /* expose on */
1107 thread_announce(v);
1108 return 0;
1109 }
1110
1111 /*
1112 * Hide the threads.
1113 */
1114 PUBLIC int
1115 hidecmd(void *v)
1116 {
1117 dot = thread_top(dot);
1118 (void)set_state(~S_EXPOSE, 0); /* expose off */
1119 thread_announce(v);
1120 return 0;
1121 }
1122
1123 /*
1124 * Up one level in the thread tree. Go up multiple levels if given an
1125 * argument.
1126 */
1127 PUBLIC int
1128 upcmd(void *v)
1129 {
1130 char *str;
1131 int upcnt;
1132 int upone;
1133
1134 str = v;
1135 str = skip_blank(str);
1136 if (*str == '\0')
1137 upcnt = 1;
1138 else
1139 upcnt = atoi(str);
1140
1141 if (upcnt < 1) {
1142 (void)printf("Sorry, argument must be > 0.\n");
1143 return 0;
1144 }
1145 if (dot == NULL) {
1146 (void)printf("No applicable messages\n");
1147 return 0;
1148 }
1149 if (dot->m_plink == NULL) {
1150 (void)printf("top thread\n");
1151 return 0;
1152 }
1153 upone = 0;
1154 while (upcnt-- > 0) {
1155 struct message *parent;
1156 parent = current_thread.t_head->m_plink;
1157 if (parent == NULL) {
1158 (void)printf("top thread\n");
1159 break;
1160 }
1161 else {
1162 struct message *mp;
1163 assert(current_thread.t_head->m_depth > 0);
1164 for (mp = parent; mp && mp->m_blink; mp = mp->m_blink)
1165 continue;
1166 current_thread.t_head = mp;
1167 dot = parent;
1168 upone = 1;
1169 }
1170 }
1171 if (upone) {
1172 reindex(¤t_thread);
1173 thread_announce(v);
1174 }
1175 return 0;
1176 }
1177
1178 /*
1179 * Go down one level in the thread tree from the current dot or a
1180 * given message number if given.
1181 */
1182 PUBLIC int
1183 downcmd(void *v)
1184 {
1185 struct message *child;
1186 struct message *mp;
1187 int *msgvec = v;
1188
1189 if ((mp = get_message(*msgvec)) == NULL ||
1190 (child = mp->m_clink) == NULL)
1191 (void)printf("no sub-thread\n");
1192 else {
1193 current_thread.t_head = child;
1194 dot = child;
1195 reindex(¤t_thread);
1196 thread_announce(v);
1197 }
1198 return 0;
1199 }
1200
1201 /*
1202 * Set the current thread level to the current dot or to the message
1203 * if given.
1204 */
1205 PUBLIC int
1206 tsetcmd(void *v)
1207 {
1208 struct message *mp;
1209 int *msgvec = v;
1210
1211 if ((mp = get_message(*msgvec)) == NULL)
1212 (void)printf("invalid message\n");
1213 else {
1214 for (/*EMPTY*/; mp->m_blink; mp = mp->m_blink)
1215 continue;
1216 current_thread.t_head = mp;
1217 reindex(¤t_thread);
1218 thread_announce(v);
1219 }
1220 return 0;
1221 }
1222
1223 /*
1224 * Reverse the current thread order. If threaded, it only operates on
1225 * the heads.
1226 */
1227 static void
1228 reversecmd_core(struct thread_s *tp)
1229 {
1230 struct message *thread_start;
1231 struct message *mp;
1232 struct message *lastmp;
1233 struct message *old_flink;
1234
1235 thread_start = tp->t_head;
1236
1237 assert(thread_start->m_blink == NULL);
1238
1239 lastmp = NULL;
1240 for (mp = thread_start; mp; mp = old_flink) {
1241 old_flink = mp->m_flink;
1242 mp->m_flink = mp->m_blink;
1243 mp->m_blink = old_flink;
1244 lastmp = mp;
1245 }
1246 if (thread_start->m_plink)
1247 thread_start->m_plink->m_clink = lastmp;
1248
1249 current_thread.t_head = lastmp;
1250 reindex(tp);
1251 }
1252
1253 PUBLIC int
1254 reversecmd(void *v)
1255 {
1256 reversecmd_core(¤t_thread);
1257 thread_announce(v);
1258 return 0;
1259 }
1260
1261
1262 /*
1263 * Get threading and sorting modifiers.
1264 */
1265 #define MF_IGNCASE 1 /* ignore case when sorting */
1266 #define MF_REVERSE 2 /* reverse sort direction */
1267 #define MF_SKIN 4 /* "skin" the field to remove comments */
1268 static int
1269 get_modifiers(char **str)
1270 {
1271 int modflags;
1272 char *p;
1273
1274 modflags = 0;
1275 for (p = *str; p && *p; p++) {
1276 switch (*p) {
1277 case '!':
1278 modflags |= MF_REVERSE;
1279 break;
1280 case '^':
1281 modflags |= MF_IGNCASE;
1282 break;
1283 case '-':
1284 modflags |= MF_SKIN;
1285 break;
1286 case ' ':
1287 case '\t':
1288 break;
1289 default:
1290 goto done;
1291 }
1292 }
1293 done:
1294 *str = p;
1295 return modflags;
1296 }
1297
1298 /************************************************************************/
1299 /*
1300 * The key_sort_s compare routines.
1301 */
1302
1303 static int
1304 keystrcmp(const void *left, const void *right)
1305 {
1306 const struct key_sort_s *lp = left;
1307 const struct key_sort_s *rp = right;
1308
1309 lp = left;
1310 rp = right;
1311
1312 if (rp->key.str == NULL && lp->key.str == NULL)
1313 return 0;
1314 else if (rp->key.str == NULL)
1315 return -1;
1316 else if (lp->key.str == NULL)
1317 return 1;
1318 else
1319 return strcmp(lp->key.str, rp->key.str);
1320 }
1321
1322 static int
1323 keystrcasecmp(const void *left, const void *right)
1324 {
1325 const struct key_sort_s *lp = left;
1326 const struct key_sort_s *rp = right;
1327
1328 if (rp->key.str == NULL && lp->key.str == NULL)
1329 return 0;
1330 else if (rp->key.str == NULL)
1331 return -1;
1332 else if (lp->key.str == NULL)
1333 return 1;
1334 else
1335 return strcasecmp(lp->key.str, rp->key.str);
1336 }
1337
1338 static int
1339 keylongcmp(const void *left, const void *right)
1340 {
1341 const struct key_sort_s *lp = left;
1342 const struct key_sort_s *rp = right;
1343
1344 if (lp->key.lines > rp->key.lines)
1345 return 1;
1346
1347 if (lp->key.lines < rp->key.lines)
1348 return -1;
1349
1350 return 0;
1351 }
1352
1353 static int
1354 keyoffcmp(const void *left, const void *right)
1355 {
1356 const struct key_sort_s *lp = left;
1357 const struct key_sort_s *rp = right;
1358
1359 if (lp->key.size > rp->key.size)
1360 return 1;
1361
1362 if (lp->key.size < rp->key.size)
1363 return -1;
1364
1365 return 0;
1366 }
1367
1368 static int
1369 keytimecmp(const void *left, const void *right)
1370 {
1371 double delta;
1372 const struct key_sort_s *lp = left;
1373 const struct key_sort_s *rp = right;
1374
1375 delta = difftime(lp->key.time, rp->key.time);
1376 if (delta > 0)
1377 return 1;
1378
1379 if (delta < 0)
1380 return -1;
1381
1382 return 0;
1383 }
1384
1385 /************************************************************************
1386 * key_sort_s loading routines.
1387 */
1388 static void
1389 field_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1390 const char *key, int skin_it)
1391 {
1392 int i;
1393 for (i = 0; i < mcount; i++) {
1394 marray[i].mp = mp;
1395 marray[i].key.str =
1396 skin_it ? skin(hfield(key, mp)) : hfield(key, mp);
1397 marray[i].index = mp->m_index;
1398 mp = next_message(mp);
1399 }
1400 }
1401
1402 static void
1403 subj_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1404 const char *key __unused, int flags __unused)
1405 {
1406 int i;
1407 #ifdef __lint__
1408 flags = flags;
1409 key = key;
1410 #endif
1411 for (i = 0; i < mcount; i++) {
1412 char *subj = hfield(key, mp);
1413 while( strncasecmp(subj, "Re:", 3) == 0 )
1414 subj = skip_blank(subj + 3);
1415 marray[i].mp = mp;
1416 marray[i].key.str = subj;
1417 marray[i].index = mp->m_index;
1418 mp = next_message(mp);
1419 }
1420 }
1421
1422
1423 static void
1424 lines_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1425 const char *key __unused, int flags)
1426 {
1427 int i;
1428 int use_blines;
1429 int use_hlines;
1430 #ifdef __lint__
1431 key = key;
1432 #endif
1433 #define HLINES 1
1434 #define BLINES 2
1435 #define TLINES 3
1436 use_hlines = flags == HLINES;
1437 use_blines = flags == BLINES;
1438
1439 for (i = 0; i < mcount; i++) {
1440 marray[i].mp = mp;
1441 marray[i].key.lines = use_hlines ? mp->m_lines - mp->m_blines :
1442 use_blines ? mp->m_blines : mp->m_lines;
1443 marray[i].index = mp->m_index;
1444 mp = next_message(mp);
1445 }
1446 }
1447
1448 static void
1449 size_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1450 const char *key __unused, int flags __unused)
1451 {
1452 int i;
1453 #ifdef __lint__
1454 flags = flags;
1455 key = key;
1456 #endif
1457 for (i = 0; i < mcount; i++) {
1458 marray[i].mp = mp;
1459 marray[i].key.size = mp->m_size;
1460 marray[i].index = mp->m_index;
1461 mp = next_message(mp);
1462 }
1463 }
1464
1465 static void __unused
1466 date_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1467 const char *key __unused, int flags)
1468 {
1469 int i;
1470 int use_hl_date;
1471 int zero_hour_min_sec;
1472 #ifdef __lint__
1473 key = key;
1474 #endif
1475 #define RDAY 1
1476 #define SDAY 2
1477 #define RDATE 3
1478 #define SDATE 4
1479 use_hl_date = (flags == RDAY || flags == RDATE);
1480 zero_hour_min_sec = (flags == RDAY || flags == SDAY);
1481
1482 for (i = 0; i < mcount; i++) {
1483 struct tm tm;
1484 (void)dateof(&tm, mp, use_hl_date);
1485 if (zero_hour_min_sec) {
1486 tm.tm_sec = 0;
1487 tm.tm_min = 0;
1488 tm.tm_hour = 0;
1489 }
1490 marray[i].mp = mp;
1491 marray[i].key.time = mktime(&tm);
1492 marray[i].index = mp->m_index;
1493 mp = next_message(mp);
1494 }
1495 }
1496
1497 static void
1498 from_load(struct key_sort_s *marray, size_t mcount, struct message *mp,
1499 const char *key __unused, int flags __unused)
1500 {
1501 int i;
1502 #ifdef __lint__
1503 flags = flags;
1504 key = key;
1505 #endif
1506 for (i = 0; i < mcount; i++) {
1507 marray[i].mp = mp;
1508 marray[i].key.str = nameof(mp, 0);
1509 marray[i].index = mp->m_index;
1510 mp = next_message(mp);
1511 }
1512 }
1513
1514 /************************************************************************
1515 * The master table that controls all sorting and threading.
1516 */
1517 static const struct key_tbl_s {
1518 const char *key;
1519 void (*loadfn)(struct key_sort_s *, size_t, struct message *, const char *, int);
1520 int flags;
1521 int (*cmpfn)(const void*, const void*);
1522 int (*casecmpfn)(const void*, const void*);
1523 } key_tbl[] = {
1524 {"blines", lines_load, BLINES, keylongcmp, keylongcmp},
1525 {"hlines", lines_load, HLINES, keylongcmp, keylongcmp},
1526 {"tlines", lines_load, TLINES, keylongcmp, keylongcmp},
1527 {"size", size_load, 0, keyoffcmp, keyoffcmp},
1528 {"sday", date_load, SDAY, keytimecmp, keytimecmp},
1529 {"rday", date_load, RDAY, keytimecmp, keytimecmp},
1530 {"sdate", date_load, SDATE, keytimecmp, keytimecmp},
1531 {"rdate", date_load, RDATE, keytimecmp, keytimecmp},
1532 {"from", from_load, 0, keystrcasecmp, keystrcasecmp},
1533 {"subject", subj_load, 0, keystrcmp, keystrcasecmp},
1534 {NULL, field_load, 0, keystrcmp, keystrcasecmp},
1535 };
1536
1537 #ifdef USE_EDITLINE
1538 /*
1539 * This is for use in complete.c to get the list of threading key
1540 * names without exposing the key_tbl[]. The first name is returned
1541 * if called with a pointer to a NULL pointer. Subsequent calls with
1542 * the same cookie give successive names. A NULL return indicates the
1543 * end of the list.
1544 */
1545 PUBLIC const char *
1546 thread_next_key_name(const void **cookie)
1547 {
1548 const struct key_tbl_s *kp;
1549
1550 kp = *cookie;
1551 if (kp == NULL)
1552 kp = key_tbl;
1553
1554 *cookie = kp->key ? &kp[1] : NULL;
1555
1556 return kp->key;
1557 }
1558 #endif /* USE_EDITLINE */
1559
1560 static const struct key_tbl_s *
1561 get_key(const char *key)
1562 {
1563 const struct key_tbl_s *kp;
1564 for (kp = key_tbl; kp->key != NULL; kp++)
1565 if (strcmp(kp->key, key) == 0)
1566 return kp;
1567 return kp;
1568 }
1569
1570 static int (*
1571 get_cmpfn(const struct key_tbl_s *kp, int ignorecase)
1572 )(const void*, const void*)
1573 {
1574 if (ignorecase)
1575 return kp->casecmpfn;
1576 else
1577 return kp->cmpfn;
1578 }
1579
1580 static void
1581 thread_current_on(char *str, int modflags, int cutit)
1582 {
1583 const struct key_tbl_s *kp;
1584 struct key_sort_s *marray;
1585 size_t mcount;
1586 state_t oldstate;
1587
1588 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), cutit ? S_EXPOSE : 0);
1589
1590 kp = get_key(str);
1591 mcount = get_msgCount();
1592 marray = csalloc(mcount + 1, sizeof(*marray));
1593 kp->loadfn(marray, mcount, current_thread.t_head, str,
1594 kp->flags ? kp->flags : modflags & MF_SKIN);
1595 cmp.fn = get_cmpfn(kp, modflags & MF_IGNCASE);
1596 cmp.inv = modflags & MF_REVERSE;
1597 thread_array(marray, mcount, cutit);
1598
1599 if (!S_IS_EXPOSE(oldstate))
1600 dot = thread_top(dot);
1601
1602 restore_state(oldstate);
1603 }
1604
1605 /*
1606 * The thread command. Thread the current thread on its references or
1607 * on a specified field.
1608 */
1609 PUBLIC int
1610 threadcmd(void *v)
1611 {
1612 char *str;
1613
1614 str = v;
1615 if (*str == '\0')
1616 thread_on_reference(current_thread.t_head);
1617 else {
1618 int modflags;
1619 modflags = get_modifiers(&str);
1620 thread_current_on(str, modflags, 1);
1621 }
1622 thread_announce(v);
1623 return 0;
1624 }
1625
1626 /*
1627 * Remove all threading information, reverting to the startup state.
1628 */
1629 PUBLIC int
1630 unthreadcmd(void *v)
1631 {
1632 thread_fix_new_links(message_array.t_head, 0, message_array.t_msgCount);
1633 thread_announce(v);
1634 return 0;
1635 }
1636
1637 /*
1638 * The sort command.
1639 */
1640 PUBLIC int
1641 sortcmd(void *v)
1642 {
1643 int modflags;
1644 char *str;
1645
1646 str = v;
1647 modflags = get_modifiers(&str);
1648 if (*str != '\0')
1649 thread_current_on(str, modflags, 0);
1650 else {
1651 if (modflags & MF_REVERSE)
1652 reversecmd_core(¤t_thread);
1653 else {
1654 (void)printf("sort on what?\n");
1655 return 0;
1656 }
1657 }
1658 thread_announce(v);
1659 return 0;
1660 }
1661
1662
1663 /*
1664 * Delete duplicate messages (based on their "Message-Id" field).
1665 *
1666 * XXX - This doesn't completely belong here, but what the hell.
1667 */
1668 /*ARGSUSED*/
1669 PUBLIC int
1670 deldupscmd(void *v __unused)
1671 {
1672 struct message *mp;
1673 int depth;
1674 state_t oldstate;
1675
1676 oldstate = set_state(~(S_RESTRICT|S_EXPOSE), S_EXPOSE);
1677
1678 thread_current_on(__UNCONST("Message-Id"), 0, 1);
1679 reindex(¤t_thread);
1680 redepth(¤t_thread);
1681 depth = current_thread.t_head->m_depth;
1682 for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp))
1683 if (mp->m_depth > depth ) {
1684 mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
1685 mp->m_flag |= MDELETED|MTOUCH;
1686 touch(mp);
1687 }
1688 restore_state(oldstate);
1689 return 0;
1690 }
1691
1692 #endif /* THREAD_SUPPORT */
1693