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