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