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