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