xref: /netbsd-src/usr.bin/mail/thread.c (revision 8b0f9554ff8762542c4defc4f70e1eb76fb508fa)
1 /*	$NetBSD: thread.c,v 1.4 2007/10/23 14:58:45 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.4 2007/10/23 14:58:45 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(&current_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(&current_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(&current_thread);
576 	redepth(&current_thread);
577 	return old_state;
578 }
579 
580 static void
581 restore_state(state_t new_state)
582 {
583 	state = new_state;
584 	reindex(&current_thread);
585 	redepth(&current_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(&current_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(&current_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_WSP(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(&current_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(&current_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(&current_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(&current_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_WSP(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(&current_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(&current_thread);
1688 	redepth(&current_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