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