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