1 /* $NetBSD: thread.c,v 1.14 2021/12/17 15:29:44 kre 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.14 2021/12/17 15:29:44 kre 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 56 struct 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 63 typedef 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 */ 71 static struct thread_s message_array = THREAD_INIT; /* the basic message array */ 72 static struct thread_s current_thread = THREAD_INIT; /* the current thread */ 73 74 static state_t state = S_STATE_INIT; /* the current state */ 75 76 /* 77 * A state hook used by the format module. 78 */ 79 PUBLIC int 80 thread_hidden(void) 81 { 82 return !S_IS_EXPOSE(state); 83 } 84 85 /************************************************************************ 86 * Debugging stuff that should evaporate eventually. 87 */ 88 #ifdef THREAD_DEBUG 89 static void 90 show_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 107 static void 108 show_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 116 PUBLIC int 117 thread_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 */ 136 static int 137 is_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 149 static int 150 is_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 160 static int 161 has_parent(struct message *mp) 162 { 163 return mp->m_plink != NULL && 164 mp->m_plink->m_clink != current_thread.t_head; 165 } 166 167 static struct message * 168 next_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 185 static struct message * 186 prev_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 197 PUBLIC struct message * 198 next_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 209 PUBLIC struct message * 210 prev_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 221 static struct message * 222 first_message(struct message *mp) 223 { 224 if (S_IS_RESTRICT(state) && is_tagged(mp)) 225 mp = next_message(mp); 226 return mp; 227 } 228 229 PUBLIC struct message * 230 get_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 241 PUBLIC int 242 get_msgnum(struct message *mp) 243 { 244 return mp ? mp->m_index : 0; 245 } 246 247 PUBLIC int 248 get_msgCount(void) 249 { 250 return current_thread.t_msgCount; 251 } 252 253 PUBLIC int 254 get_abs_msgCount(void) 255 { 256 return message_array.t_msgCount; 257 } 258 259 PUBLIC struct message * 260 get_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 268 PUBLIC struct message * 269 next_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 */ 285 PUBLIC int 286 do_recursion(void) 287 { 288 return S_IS_EXPOSE(state) == 0 && value(ENAME_RECURSIVE_CMDS) != NULL; 289 } 290 291 static int 292 thread_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 306 PUBLIC int 307 thread_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 */ 327 PUBLIC int 328 thread_depth(void) 329 { 330 return current_thread.t_head ? current_thread.t_head->m_depth : 0; 331 } 332 333 /************************************************************************/ 334 335 static int 336 reindex_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 358 static void 359 reindex(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 392 static void 393 redepth_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 411 static void 412 redepth(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 */ 442 PUBLIC void 443 thread_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 (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 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(¤t_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(¤t_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 578 static state_t 579 set_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(¤t_thread); 586 redepth(¤t_thread); 587 return old_state; 588 } 589 590 static struct message * 591 first_visible_message(struct message *mp) 592 { 593 struct message *oldmp; 594 595 if (mp == NULL) 596 mp = current_thread.t_head; 597 598 if (mp == NULL) 599 return NULL; 600 601 oldmp = mp; 602 if ((S_IS_RESTRICT(state) && is_tagged(mp)) || mp->m_flag & MDELETED) 603 mp = next_message(mp); 604 605 if (mp == NULL) { 606 mp = oldmp; 607 if ((S_IS_RESTRICT(state) && is_tagged(mp)) || mp->m_flag & MDELETED) 608 mp = prev_message(mp); 609 } 610 if (mp == NULL) 611 mp = current_thread.t_head; 612 613 return mp; 614 } 615 616 static void 617 restore_state(state_t new_state) 618 { 619 state = new_state; 620 reindex(¤t_thread); 621 redepth(¤t_thread); 622 dot = first_visible_message(dot); 623 } 624 625 static struct message * 626 thread_top(struct message *mp) 627 { 628 while (mp && mp->m_plink) { 629 if (mp->m_plink->m_clink == current_thread.t_head) 630 break; 631 mp = mp->m_plink; 632 } 633 return mp; 634 } 635 636 /************************************************************************/ 637 /* 638 * Possibly show the message list. 639 */ 640 static void 641 thread_announce(void *v) 642 { 643 int vec[2]; 644 645 if (v == NULL) /* check this here to avoid it before each call */ 646 return; 647 648 if (dot == NULL) { 649 (void)printf("No applicable messages\n"); 650 return; 651 } 652 vec[0] = get_msgnum(dot); 653 vec[1] = 0; 654 if (get_msgCount() > 0 && value(ENAME_NOHEADER) == NULL) 655 (void)headers(vec); 656 sawcom = 0; /* so next will print the first message */ 657 } 658 659 /************************************************************************/ 660 661 /* 662 * Flatten out the portion of the thread starting with the given 663 * message. 664 */ 665 static void 666 flattencmd_core(struct message *mp) 667 { 668 struct message **marray; 669 size_t mcount; 670 struct message *tp; 671 struct message *nextmp; 672 size_t i; 673 674 if (mp == NULL) 675 return; 676 677 mcount = 1; 678 for (tp = next_message(mp); tp && tp->m_depth > mp->m_depth; tp = next_message(tp)) 679 mcount++; 680 681 if (tp && tp->m_depth < mp->m_depth) 682 nextmp = NULL; 683 else 684 nextmp = tp; 685 686 if (mcount == 1) 687 return; 688 689 marray = csalloc(mcount, sizeof(*marray)); 690 tp = mp; 691 for (i = 0; i < mcount; i++) { 692 marray[i] = tp; 693 tp = next_message(tp); 694 } 695 mp->m_clink = NULL; 696 for (i = 1; i < mcount; i++) { 697 marray[i]->m_depth = mp->m_depth; 698 marray[i]->m_plink = mp->m_plink; 699 marray[i]->m_clink = NULL; 700 marray[i]->m_blink = marray[i - 1]; 701 marray[i - 1]->m_flink = marray[i]; 702 } 703 marray[i - 1]->m_flink = nextmp; 704 if (nextmp) 705 nextmp->m_blink = marray[i - 1]; 706 } 707 708 /* 709 * Flatten out all thread parts given in the message list, or the 710 * current thread, if none given. 711 */ 712 PUBLIC int 713 flattencmd(void *v) 714 { 715 int *msgvec; 716 int *ip; 717 718 msgvec = v; 719 720 if (*msgvec) { /* a message was supplied */ 721 for (ip = msgvec; *ip; ip++) { 722 struct message *mp; 723 mp = get_message(*ip); 724 if (mp != NULL) 725 flattencmd_core(mp); 726 } 727 } 728 else { /* no message given - flatten current thread */ 729 struct message *mp; 730 for (mp = first_message(current_thread.t_head); 731 mp; mp = next_message(mp)) 732 flattencmd_core(mp); 733 } 734 redepth(¤t_thread); 735 thread_announce(v); 736 return 0; 737 } 738 739 740 /************************************************************************/ 741 /* 742 * The basic sort structure. For each message the index and key 743 * fields are set. The key field is used for the basic sort and the 744 * index is used to ensure that the order from the current thread is 745 * maintained when the key compare is equal. 746 */ 747 struct key_sort_s { 748 struct message *mp; /* the message the following refer to */ 749 union { 750 char *str; /* string sort key (typically a field or address) */ 751 long lines; /* a long sort key (typically a message line count) */ 752 off_t size; /* a size sort key (typically the message size) */ 753 time_t time; /* a time sort key (typically from date or headline) */ 754 } key; 755 int index; /* index from of the current thread before sorting */ 756 /* XXX - do we really want index? It is always set to mp->m_index */ 757 }; 758 759 /* 760 * This is the compare function obtained from the key_tbl[]. It is 761 * used by thread_array() to identify the end of the thread and by 762 * qsort_cmpfn() to do the basic sort. 763 */ 764 static struct { 765 int inv; 766 int (*fn)(const void *, const void *); 767 } cmp; 768 769 /* 770 * The routine passed to qsort. Note that cmpfn must be set first! 771 */ 772 static int 773 qsort_cmpfn(const void *left, const void *right) 774 { 775 int delta; 776 const struct key_sort_s *lp = left; 777 const struct key_sort_s *rp = right; 778 779 delta = cmp.fn(left, right); 780 return delta ? cmp.inv ? - delta : delta : lp->index - rp->index; 781 } 782 783 static void 784 link_array(struct key_sort_s *marray, size_t mcount) 785 { 786 size_t i; 787 struct message *lastmp; 788 lastmp = NULL; 789 for (i = 0; i < mcount; i++) { 790 marray[i].mp->m_index = (int)i + 1; 791 marray[i].mp->m_blink = lastmp; 792 marray[i].mp->m_flink = NULL; 793 if (lastmp) 794 lastmp->m_flink = marray[i].mp; 795 lastmp = marray[i].mp; 796 } 797 if (current_thread.t_head->m_plink) 798 current_thread.t_head->m_plink->m_clink = marray[0].mp; 799 800 current_thread.t_head = marray[0].mp; 801 } 802 803 static void 804 cut_array(struct key_sort_s *marray, size_t beg, size_t end) 805 { 806 size_t i; 807 808 if (beg + 1 < end) { 809 assert(marray[beg].mp->m_clink == NULL); 810 811 marray[beg].mp->m_clink = marray[beg + 1].mp; 812 marray[beg + 1].mp->m_blink = NULL; 813 814 marray[beg].mp->m_flink = marray[end].mp; 815 if (marray[end].mp) 816 marray[end].mp->m_blink = marray[beg].mp; 817 818 marray[end - 1].mp->m_flink = NULL; 819 820 for (i = beg + 1; i < end; i++) 821 marray[i].mp->m_plink = marray[beg].mp; 822 } 823 } 824 825 static void 826 thread_array(struct key_sort_s *marray, size_t mcount, int cutit) 827 { 828 struct message *parent; 829 830 if (mcount == 0) 831 return; 832 833 parent = marray[0].mp->m_plink; 834 qsort(marray, mcount, sizeof(*marray), qsort_cmpfn); 835 link_array(marray, mcount); 836 837 if (cutit) { 838 size_t i, j; 839 /* 840 * Flatten out the array. 841 */ 842 for (i = 0; i < mcount; i++) { 843 marray[i].mp->m_plink = parent; 844 marray[i].mp->m_clink = NULL; 845 } 846 847 /* 848 * Now chop it up. There is really only one level here. 849 */ 850 i = 0; 851 for (j = 1; j < mcount; j++) { 852 if (cmp.fn(&marray[i], &marray[j]) != 0) { 853 cut_array(marray, i, j); 854 i = j; 855 } 856 } 857 cut_array(marray, i, j); 858 } 859 } 860 861 /************************************************************************/ 862 /* 863 * thread_on_reference() is the core reference threading routine. It 864 * is not a command itself by called by threadcmd(). 865 */ 866 867 static void 868 adopt_child(struct message *parent, struct message *child) 869 { 870 /* 871 * Unhook the child from its current location. 872 */ 873 if (child->m_blink != NULL) { 874 child->m_blink->m_flink = child->m_flink; 875 } 876 if (child->m_flink != NULL) { 877 child->m_flink->m_blink = child->m_blink; 878 } 879 880 /* 881 * Link the child to the parent. 882 */ 883 if (parent->m_clink == NULL) { /* parent has no child */ 884 parent->m_clink = child; 885 child->m_blink = NULL; 886 } 887 else { /* add message to end of parent's child's flist */ 888 struct message *t; 889 for (t = parent->m_clink; t && t->m_flink; t = t->m_flink) 890 continue; 891 t->m_flink = child; 892 child->m_blink = t; 893 } 894 child->m_flink = NULL; 895 child->m_plink = parent; 896 } 897 898 /* 899 * Get the parent ID for a message (if there is one). 900 * 901 * See RFC 2822, sec 3.6.4. 902 * 903 * Many mailers seem to screw up the In-Reply-To: and/or 904 * References: fields, generally by omitting one or both. 905 * 906 * We give preference to the "References" field. If it does 907 * not exist, try the "In-Reply-To" field. If neither exist, 908 * then the message is either not a reply or someone isn't 909 * adding the necessary fields, so skip it. 910 */ 911 static char * 912 get_parent_id(struct message *mp) 913 { 914 struct name *refs; 915 916 if ((refs = extract(hfield("references", mp), 0)) != NULL) { 917 char *id; 918 while (refs->n_flink) 919 refs = refs->n_flink; 920 921 id = skin(refs->n_name); 922 if (*id != '\0') 923 return id; 924 } 925 926 return skin(hfield("in-reply-to", mp)); 927 } 928 929 /* 930 * Thread on the "In-Reply-To" and "Reference" fields. This is the 931 * normal way to thread. 932 */ 933 static void 934 thread_on_reference(struct message *mp) 935 { 936 struct { 937 struct message *mp; 938 char *message_id; 939 char *parent_id; 940 } *marray; 941 struct message *parent; 942 state_t oldstate; 943 size_t mcount, i; 944 945 assert(mp == current_thread.t_head); 946 947 oldstate = set_state(~(S_RESTRICT | S_EXPOSE), S_EXPOSE); /* restrict off, expose on */ 948 949 mcount = get_msgCount(); 950 951 if (mcount < 2) /* it's hard to thread so few messages! */ 952 goto done; 953 954 marray = csalloc(mcount + 1, sizeof(*marray)); 955 956 /* 957 * Load up the array (skin where necessary). 958 * 959 * With a 40K message file, most of the time is spent here, 960 * not in the search loop below. 961 */ 962 for (i = 0; i < mcount; i++) { 963 marray[i].mp = mp; 964 marray[i].message_id = skin(hfield("message-id", mp)); 965 marray[i].parent_id = get_parent_id(mp); 966 mp = next_message(mp); 967 } 968 969 /* 970 * Save the old parent. 971 */ 972 parent = marray[0].mp->m_plink; 973 974 /* 975 * flatten the array. 976 */ 977 marray[0].mp->m_clink = NULL; 978 for (i = 1; i < mcount; i++) { 979 marray[i].mp->m_depth = marray[0].mp->m_depth; 980 marray[i].mp->m_plink = marray[0].mp->m_plink; 981 marray[i].mp->m_clink = NULL; 982 marray[i].mp->m_blink = marray[i - 1].mp; 983 marray[i - 1].mp->m_flink = marray[i].mp; 984 } 985 marray[i - 1].mp->m_flink = NULL; 986 987 /* 988 * Walk the array hooking up the replies with their parents. 989 */ 990 for (i = 0; i < mcount; i++) { 991 struct message *child; 992 char *parent_id; 993 size_t j; 994 995 if ((parent_id = marray[i].parent_id) == NULL) 996 continue; 997 998 child = marray[i].mp; 999 1000 /* 1001 * Look for the parent message and link this one in 1002 * appropriately. 1003 * 1004 * XXX - This will not scale nicely, though it does 1005 * not appear to be the dominant loop even with 40K 1006 * messages. If this becomes a problem, implement a 1007 * binary search. 1008 */ 1009 for (j = 0; j < mcount; j++) { 1010 /* message_id will be NULL on mbox files */ 1011 if (marray[j].message_id == NULL) 1012 continue; 1013 1014 if (equal(marray[j].message_id, parent_id)) { 1015 /* 1016 * The child is at the top level. If 1017 * it is being adopted and it was top 1018 * left (current_thread.t_head), then 1019 * its right sibling is the new top 1020 * left (current_thread.t_head). 1021 */ 1022 if (current_thread.t_head == child) { 1023 current_thread.t_head = child->m_flink; 1024 assert(current_thread.t_head != NULL); 1025 } 1026 adopt_child(marray[j].mp, child); 1027 break; 1028 } 1029 } 1030 } 1031 1032 if (parent) 1033 parent->m_clink = current_thread.t_head; 1034 /* 1035 * If the old state is not exposed, reset the dot to the head 1036 * of the thread it lived in, so it will be in a valid spot 1037 * when things are re-hidden. 1038 */ 1039 if (!S_IS_EXPOSE(oldstate)) 1040 dot = thread_top(dot); 1041 done: 1042 restore_state(oldstate); 1043 } 1044 1045 /************************************************************************/ 1046 /* 1047 * Tagging commands. 1048 */ 1049 static int 1050 tag1(int *msgvec, int and_bits, int xor_bits) 1051 { 1052 int *ip; 1053 1054 for (ip = msgvec; *ip != 0; ip++) 1055 (void)set_m_flag(*ip, and_bits, xor_bits); 1056 1057 reindex(¤t_thread); 1058 /* thread_announce(v); */ 1059 return 0; 1060 } 1061 1062 /* 1063 * Tag the current message dot or a message list. 1064 */ 1065 PUBLIC int 1066 tagcmd(void *v) 1067 { 1068 return tag1(v, ~MTAGGED, MTAGGED); 1069 } 1070 1071 /* 1072 * Untag the current message dot or a message list. 1073 */ 1074 PUBLIC int 1075 untagcmd(void *v) 1076 { 1077 return tag1(v, ~MTAGGED, 0); 1078 } 1079 1080 /* 1081 * Invert all tags in the message list. 1082 */ 1083 PUBLIC int 1084 invtagscmd(void *v) 1085 { 1086 return tag1(v, ~0, MTAGGED); 1087 } 1088 1089 /* 1090 * Tag all messages below the current dot or below a specified 1091 * message. 1092 */ 1093 PUBLIC int 1094 tagbelowcmd(void *v) 1095 { 1096 int *msgvec; 1097 struct message *mp; 1098 state_t oldstate; 1099 int depth; 1100 1101 msgvec = v; 1102 1103 oldstate = set_state(~(S_RESTRICT | S_EXPOSE), S_EXPOSE); /* restrict off, expose on */ 1104 mp = get_message(*msgvec); 1105 if (mp) { 1106 depth = mp->m_depth; 1107 for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp)) 1108 if (mp->m_depth > depth) { 1109 mp->m_flag |= MTAGGED; 1110 touch(mp); 1111 } 1112 } 1113 /* dot is OK */ 1114 restore_state(oldstate); 1115 /* thread_announce(v); */ 1116 return 0; 1117 } 1118 1119 /* 1120 * Do not display the tagged messages. 1121 */ 1122 PUBLIC int 1123 hidetagscmd(void *v) 1124 { 1125 (void)set_state(~S_RESTRICT, S_RESTRICT); /* restrict on */ 1126 dot = first_visible_message(dot); 1127 thread_announce(v); 1128 return 0; 1129 } 1130 1131 /* 1132 * Display the tagged messages. 1133 */ 1134 PUBLIC int 1135 showtagscmd(void *v) 1136 { 1137 (void)set_state(~S_RESTRICT, 0); /* restrict off */ 1138 dot = first_visible_message(dot); 1139 thread_announce(v); 1140 return 0; 1141 } 1142 1143 /************************************************************************/ 1144 /* 1145 * Basic threading commands. 1146 */ 1147 /* 1148 * Show the threads. 1149 */ 1150 PUBLIC int 1151 exposecmd(void *v) 1152 { 1153 (void)set_state(~S_EXPOSE, S_EXPOSE); /* expose on */ 1154 dot = first_visible_message(dot); 1155 thread_announce(v); 1156 return 0; 1157 } 1158 1159 /* 1160 * Hide the threads. 1161 */ 1162 PUBLIC int 1163 hidecmd(void *v) 1164 { 1165 dot = thread_top(dot); 1166 (void)set_state(~S_EXPOSE, 0); /* expose off */ 1167 dot = first_visible_message(dot); 1168 thread_announce(v); 1169 return 0; 1170 } 1171 1172 /* 1173 * Up one level in the thread tree. Go up multiple levels if given an 1174 * argument. 1175 */ 1176 PUBLIC int 1177 upcmd(void *v) 1178 { 1179 char *str; 1180 int upcnt; 1181 int upone; 1182 1183 str = v; 1184 str = skip_WSP(str); 1185 if (*str == '\0') 1186 upcnt = 1; 1187 else 1188 upcnt = atoi(str); 1189 1190 if (upcnt < 1) { 1191 (void)printf("Sorry, argument must be > 0.\n"); 1192 return 0; 1193 } 1194 if (dot == NULL) { 1195 (void)printf("No applicable messages\n"); 1196 return 0; 1197 } 1198 if (dot->m_plink == NULL) { 1199 (void)printf("top thread\n"); 1200 return 0; 1201 } 1202 upone = 0; 1203 while (upcnt-- > 0) { 1204 struct message *parent; 1205 parent = current_thread.t_head->m_plink; 1206 if (parent == NULL) { 1207 (void)printf("top thread\n"); 1208 break; 1209 } 1210 else { 1211 struct message *mp; 1212 assert(current_thread.t_head->m_depth > 0); 1213 for (mp = parent; mp && mp->m_blink; mp = mp->m_blink) 1214 continue; 1215 current_thread.t_head = mp; 1216 dot = parent; 1217 upone = 1; 1218 } 1219 } 1220 if (upone) { 1221 reindex(¤t_thread); 1222 thread_announce(v); 1223 } 1224 return 0; 1225 } 1226 1227 /* 1228 * Go down one level in the thread tree from the current dot or a 1229 * given message number if given. 1230 */ 1231 PUBLIC int 1232 downcmd(void *v) 1233 { 1234 struct message *child; 1235 struct message *mp; 1236 int *msgvec = v; 1237 1238 if ((mp = get_message(*msgvec)) == NULL || 1239 (child = mp->m_clink) == NULL) 1240 (void)printf("no sub-thread\n"); 1241 else { 1242 current_thread.t_head = child; 1243 dot = child; 1244 reindex(¤t_thread); 1245 thread_announce(v); 1246 } 1247 return 0; 1248 } 1249 1250 /* 1251 * Set the current thread level to the current dot or to the message 1252 * if given. 1253 */ 1254 PUBLIC int 1255 tsetcmd(void *v) 1256 { 1257 struct message *mp; 1258 int *msgvec = v; 1259 1260 if ((mp = get_message(*msgvec)) == NULL) 1261 (void)printf("invalid message\n"); 1262 else { 1263 for (/*EMPTY*/; mp->m_blink; mp = mp->m_blink) 1264 continue; 1265 current_thread.t_head = mp; 1266 reindex(¤t_thread); 1267 thread_announce(v); 1268 } 1269 return 0; 1270 } 1271 1272 /* 1273 * Reverse the current thread order. If threaded, it only operates on 1274 * the heads. 1275 */ 1276 static void 1277 reversecmd_core(struct thread_s *tp) 1278 { 1279 struct message *thread_start; 1280 struct message *mp; 1281 struct message *lastmp; 1282 struct message *old_flink; 1283 1284 thread_start = tp->t_head; 1285 1286 assert(thread_start->m_blink == NULL); 1287 1288 lastmp = NULL; 1289 for (mp = thread_start; mp; mp = old_flink) { 1290 old_flink = mp->m_flink; 1291 mp->m_flink = mp->m_blink; 1292 mp->m_blink = old_flink; 1293 lastmp = mp; 1294 } 1295 if (thread_start->m_plink) 1296 thread_start->m_plink->m_clink = lastmp; 1297 1298 current_thread.t_head = lastmp; 1299 reindex(tp); 1300 } 1301 1302 PUBLIC int 1303 reversecmd(void *v) 1304 { 1305 reversecmd_core(¤t_thread); 1306 thread_announce(v); 1307 return 0; 1308 } 1309 1310 1311 /* 1312 * Get threading and sorting modifiers. 1313 */ 1314 #define MF_IGNCASE 1 /* ignore case when sorting */ 1315 #define MF_REVERSE 2 /* reverse sort direction */ 1316 #define MF_SKIN 4 /* "skin" the field to remove comments */ 1317 static int 1318 get_modifiers(char **str) 1319 { 1320 int modflags; 1321 char *p; 1322 1323 modflags = 0; 1324 for (p = *str; p && *p; p++) { 1325 switch (*p) { 1326 case '!': 1327 modflags |= MF_REVERSE; 1328 break; 1329 case '^': 1330 modflags |= MF_IGNCASE; 1331 break; 1332 case '-': 1333 modflags |= MF_SKIN; 1334 break; 1335 case ' ': 1336 case '\t': 1337 break; 1338 default: 1339 goto done; 1340 } 1341 } 1342 done: 1343 *str = p; 1344 return modflags; 1345 } 1346 1347 /************************************************************************/ 1348 /* 1349 * The key_sort_s compare routines. 1350 */ 1351 1352 static int 1353 keystrcmp(const void *left, const void *right) 1354 { 1355 const struct key_sort_s *lp = left; 1356 const struct key_sort_s *rp = right; 1357 1358 lp = left; 1359 rp = right; 1360 1361 if (rp->key.str == NULL && lp->key.str == NULL) 1362 return 0; 1363 else if (rp->key.str == NULL) 1364 return -1; 1365 else if (lp->key.str == NULL) 1366 return 1; 1367 else 1368 return strcmp(lp->key.str, rp->key.str); 1369 } 1370 1371 static int 1372 keystrcasecmp(const void *left, const void *right) 1373 { 1374 const struct key_sort_s *lp = left; 1375 const struct key_sort_s *rp = right; 1376 1377 if (rp->key.str == NULL && lp->key.str == NULL) 1378 return 0; 1379 else if (rp->key.str == NULL) 1380 return -1; 1381 else if (lp->key.str == NULL) 1382 return 1; 1383 else 1384 return strcasecmp(lp->key.str, rp->key.str); 1385 } 1386 1387 static int 1388 keylongcmp(const void *left, const void *right) 1389 { 1390 const struct key_sort_s *lp = left; 1391 const struct key_sort_s *rp = right; 1392 1393 if (lp->key.lines > rp->key.lines) 1394 return 1; 1395 1396 if (lp->key.lines < rp->key.lines) 1397 return -1; 1398 1399 return 0; 1400 } 1401 1402 static int 1403 keyoffcmp(const void *left, const void *right) 1404 { 1405 const struct key_sort_s *lp = left; 1406 const struct key_sort_s *rp = right; 1407 1408 if (lp->key.size > rp->key.size) 1409 return 1; 1410 1411 if (lp->key.size < rp->key.size) 1412 return -1; 1413 1414 return 0; 1415 } 1416 1417 static int 1418 keytimecmp(const void *left, const void *right) 1419 { 1420 double delta; 1421 const struct key_sort_s *lp = left; 1422 const struct key_sort_s *rp = right; 1423 1424 delta = difftime(lp->key.time, rp->key.time); 1425 if (delta > 0) 1426 return 1; 1427 1428 if (delta < 0) 1429 return -1; 1430 1431 return 0; 1432 } 1433 1434 /************************************************************************ 1435 * key_sort_s loading routines. 1436 */ 1437 static void 1438 field_load(struct key_sort_s *marray, size_t mcount, struct message *mp, 1439 const char *key, int skin_it) 1440 { 1441 size_t i; 1442 for (i = 0; i < mcount; i++) { 1443 marray[i].mp = mp; 1444 marray[i].key.str = 1445 skin_it ? skin(hfield(key, mp)) : hfield(key, mp); 1446 marray[i].index = mp->m_index; 1447 mp = next_message(mp); 1448 } 1449 } 1450 1451 static void 1452 subj_load(struct key_sort_s *marray, size_t mcount, struct message *mp, 1453 const char *key __unused, int flags __unused) 1454 { 1455 size_t i; 1456 #ifdef __lint__ 1457 flags = flags; 1458 key = key; 1459 #endif 1460 for (i = 0; i < mcount; i++) { 1461 char *subj = hfield(key, mp); 1462 while (strncasecmp(subj, "Re:", 3) == 0) 1463 subj = skip_WSP(subj + 3); 1464 marray[i].mp = mp; 1465 marray[i].key.str = subj; 1466 marray[i].index = mp->m_index; 1467 mp = next_message(mp); 1468 } 1469 } 1470 1471 1472 static void 1473 lines_load(struct key_sort_s *marray, size_t mcount, struct message *mp, 1474 const char *key __unused, int flags) 1475 { 1476 size_t i; 1477 int use_blines; 1478 int use_hlines; 1479 #ifdef __lint__ 1480 key = key; 1481 #endif 1482 #define HLINES 1 1483 #define BLINES 2 1484 #define TLINES 3 1485 use_hlines = flags == HLINES; 1486 use_blines = flags == BLINES; 1487 1488 for (i = 0; i < mcount; i++) { 1489 marray[i].mp = mp; 1490 marray[i].key.lines = use_hlines ? mp->m_lines - mp->m_blines : 1491 use_blines ? mp->m_blines : mp->m_lines; 1492 marray[i].index = mp->m_index; 1493 mp = next_message(mp); 1494 } 1495 } 1496 1497 static void 1498 size_load(struct key_sort_s *marray, size_t mcount, struct message *mp, 1499 const char *key __unused, int flags __unused) 1500 { 1501 size_t i; 1502 #ifdef __lint__ 1503 flags = flags; 1504 key = key; 1505 #endif 1506 for (i = 0; i < mcount; i++) { 1507 marray[i].mp = mp; 1508 marray[i].key.size = mp->m_size; 1509 marray[i].index = mp->m_index; 1510 mp = next_message(mp); 1511 } 1512 } 1513 1514 static void __unused 1515 date_load(struct key_sort_s *marray, size_t mcount, struct message *mp, 1516 const char *key __unused, int flags) 1517 { 1518 size_t i; 1519 int use_hl_date; 1520 int zero_hour_min_sec; 1521 #ifdef __lint__ 1522 key = key; 1523 #endif 1524 #define RDAY 1 1525 #define SDAY 2 1526 #define RDATE 3 1527 #define SDATE 4 1528 use_hl_date = (flags == RDAY || flags == RDATE); 1529 zero_hour_min_sec = (flags == RDAY || flags == SDAY); 1530 1531 for (i = 0; i < mcount; i++) { 1532 struct tm tm; 1533 (void)dateof(&tm, mp, use_hl_date); 1534 if (zero_hour_min_sec) { 1535 tm.tm_sec = 0; 1536 tm.tm_min = 0; 1537 tm.tm_hour = 0; 1538 } 1539 marray[i].mp = mp; 1540 marray[i].key.time = mktime(&tm); 1541 marray[i].index = mp->m_index; 1542 mp = next_message(mp); 1543 } 1544 } 1545 1546 static void 1547 from_load(struct key_sort_s *marray, size_t mcount, struct message *mp, 1548 const char *key __unused, int flags __unused) 1549 { 1550 size_t i; 1551 #ifdef __lint__ 1552 flags = flags; 1553 key = key; 1554 #endif 1555 for (i = 0; i < mcount; i++) { 1556 marray[i].mp = mp; 1557 marray[i].key.str = nameof(mp, 0); 1558 marray[i].index = mp->m_index; 1559 mp = next_message(mp); 1560 } 1561 } 1562 1563 /************************************************************************ 1564 * The master table that controls all sorting and threading. 1565 */ 1566 static const struct key_tbl_s { 1567 const char *key; 1568 void (*loadfn)(struct key_sort_s *, size_t, struct message *, const char *, int); 1569 int flags; 1570 int (*cmpfn)(const void*, const void*); 1571 int (*casecmpfn)(const void*, const void*); 1572 } key_tbl[] = { 1573 {"blines", lines_load, BLINES, keylongcmp, keylongcmp}, 1574 {"hlines", lines_load, HLINES, keylongcmp, keylongcmp}, 1575 {"tlines", lines_load, TLINES, keylongcmp, keylongcmp}, 1576 {"size", size_load, 0, keyoffcmp, keyoffcmp}, 1577 {"sday", date_load, SDAY, keytimecmp, keytimecmp}, 1578 {"rday", date_load, RDAY, keytimecmp, keytimecmp}, 1579 {"sdate", date_load, SDATE, keytimecmp, keytimecmp}, 1580 {"rdate", date_load, RDATE, keytimecmp, keytimecmp}, 1581 {"from", from_load, 0, keystrcasecmp, keystrcasecmp}, 1582 {"subject", subj_load, 0, keystrcmp, keystrcasecmp}, 1583 {NULL, field_load, 0, keystrcmp, keystrcasecmp}, 1584 }; 1585 1586 #ifdef USE_EDITLINE 1587 /* 1588 * This is for use in complete.c to get the list of threading key 1589 * names without exposing the key_tbl[]. The first name is returned 1590 * if called with a pointer to a NULL pointer. Subsequent calls with 1591 * the same cookie give successive names. A NULL return indicates the 1592 * end of the list. 1593 */ 1594 PUBLIC const char * 1595 thread_next_key_name(const void **cookie) 1596 { 1597 const struct key_tbl_s *kp; 1598 1599 kp = *cookie; 1600 if (kp == NULL) 1601 kp = key_tbl; 1602 1603 *cookie = kp->key ? &kp[1] : NULL; 1604 1605 return kp->key; 1606 } 1607 #endif /* USE_EDITLINE */ 1608 1609 static const struct key_tbl_s * 1610 get_key(const char *key) 1611 { 1612 const struct key_tbl_s *kp; 1613 for (kp = key_tbl; kp->key != NULL; kp++) 1614 if (strcmp(kp->key, key) == 0) 1615 return kp; 1616 return kp; 1617 } 1618 1619 static int (* 1620 get_cmpfn(const struct key_tbl_s *kp, int ignorecase) 1621 )(const void*, const void*) 1622 { 1623 if (ignorecase) 1624 return kp->casecmpfn; 1625 else 1626 return kp->cmpfn; 1627 } 1628 1629 static void 1630 thread_current_on(char *str, int modflags, int cutit) 1631 { 1632 const struct key_tbl_s *kp; 1633 struct key_sort_s *marray; 1634 size_t mcount; 1635 state_t oldstate; 1636 1637 oldstate = set_state(~(S_RESTRICT | S_EXPOSE), cutit ? S_EXPOSE : 0); 1638 1639 kp = get_key(str); 1640 mcount = get_msgCount(); 1641 marray = csalloc(mcount + 1, sizeof(*marray)); 1642 kp->loadfn(marray, mcount, current_thread.t_head, str, 1643 kp->flags ? kp->flags : modflags & MF_SKIN); 1644 cmp.fn = get_cmpfn(kp, modflags & MF_IGNCASE); 1645 cmp.inv = modflags & MF_REVERSE; 1646 thread_array(marray, mcount, cutit); 1647 1648 if (!S_IS_EXPOSE(oldstate)) 1649 dot = thread_top(dot); 1650 restore_state(oldstate); 1651 } 1652 1653 /* 1654 * The thread command. Thread the current thread on its references or 1655 * on a specified field. 1656 */ 1657 PUBLIC int 1658 threadcmd(void *v) 1659 { 1660 char *str; 1661 1662 str = v; 1663 if (*str == '\0') 1664 thread_on_reference(current_thread.t_head); 1665 else { 1666 int modflags; 1667 modflags = get_modifiers(&str); 1668 thread_current_on(str, modflags, 1); 1669 } 1670 thread_announce(v); 1671 return 0; 1672 } 1673 1674 /* 1675 * Remove all threading information, reverting to the startup state. 1676 */ 1677 PUBLIC int 1678 unthreadcmd(void *v) 1679 { 1680 thread_fix_new_links(message_array.t_head, 0, message_array.t_msgCount); 1681 thread_announce(v); 1682 return 0; 1683 } 1684 1685 /* 1686 * The sort command. 1687 */ 1688 PUBLIC int 1689 sortcmd(void *v) 1690 { 1691 int modflags; 1692 char *str; 1693 1694 str = v; 1695 modflags = get_modifiers(&str); 1696 if (*str != '\0') 1697 thread_current_on(str, modflags, 0); 1698 else { 1699 if (modflags & MF_REVERSE) 1700 reversecmd_core(¤t_thread); 1701 else { 1702 (void)printf("sort on what?\n"); 1703 return 0; 1704 } 1705 } 1706 thread_announce(v); 1707 return 0; 1708 } 1709 1710 1711 /* 1712 * Delete duplicate messages (based on their "Message-Id" field). 1713 */ 1714 /*ARGSUSED*/ 1715 PUBLIC int 1716 deldupscmd(void *v __unused) 1717 { 1718 struct message *mp; 1719 int depth; 1720 state_t oldstate; 1721 1722 oldstate = set_state(~(S_RESTRICT | S_EXPOSE), S_EXPOSE); /* restrict off, expose on */ 1723 1724 thread_current_on(__UNCONST("Message-Id"), 0, 1); 1725 reindex(¤t_thread); 1726 redepth(¤t_thread); 1727 depth = current_thread.t_head->m_depth; 1728 for (mp = first_message(current_thread.t_head); mp; mp = next_message(mp)) { 1729 if (mp->m_depth > depth) { 1730 mp->m_flag &= ~(MPRESERVE | MSAVED | MBOX); 1731 mp->m_flag |= MDELETED | MTOUCH; 1732 touch(mp); 1733 } 1734 } 1735 dot = thread_top(dot); /* do this irrespective of the oldstate */ 1736 restore_state(oldstate); 1737 /* thread_announce(v); */ 1738 return 0; 1739 } 1740 1741 #endif /* THREAD_SUPPORT */ 1742