1 /* $NetBSD: field.c,v 1.21 2002/08/09 14:15:12 blymn Exp $ */ 2 /*- 3 * Copyright (c) 1998-1999 Brett Lymn 4 * (blymn@baea.com.au, brett_lymn@yahoo.com.au) 5 * All rights reserved. 6 * 7 * This code has been donated to The NetBSD Foundation by the Author. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 * 28 * 29 */ 30 31 #include <stdlib.h> 32 #include <strings.h> 33 #include <stdarg.h> 34 #include <form.h> 35 #include "internals.h" 36 37 extern FORM _formi_default_form; 38 39 FIELD _formi_default_field = { 40 0, /* rows in the field */ 41 0, /* columns in the field */ 42 0, /* dynamic rows */ 43 0, /* dynamic columns */ 44 0, /* maximum growth */ 45 0, /* starting row in the form subwindow */ 46 0, /* starting column in the form subwindow */ 47 0, /* number of off screen rows */ 48 0, /* index of this field in form fields array. */ 49 0, /* number of buffers associated with this field */ 50 FALSE, /* set to true if buffer 0 has changed. */ 51 NO_JUSTIFICATION, /* justification style of the field */ 52 FALSE, /* set to true if field is in overlay mode */ 53 0, /* starting char in string (horiz scroll) */ 54 0, /* starting line in field (vert scroll) */ 55 0, /* number of rows actually used in field */ 56 0, /* actual pos of cursor in row, not same as x pos due to tabs */ 57 0, /* x pos of cursor in field */ 58 0, /* y pos of cursor in field */ 59 0, /* start of a new page on the form if 1 */ 60 0, /* number of the page this field is on */ 61 A_NORMAL, /* character attributes for the foreground */ 62 A_NORMAL, /* character attributes for the background */ 63 ' ', /* padding character */ 64 DEFAULT_FORM_OPTS, /* options for the field */ 65 NULL, /* the form this field is bound to, if any */ 66 NULL, /* field above this one */ 67 NULL, /* field below this one */ 68 NULL, /* field to the left of this one */ 69 NULL, /* field to the right of this one */ 70 NULL, /* user defined pointer. */ 71 NULL, /* used if fields are linked */ 72 NULL, /* type struct for the field */ 73 {NULL, NULL}, /* circle queue glue for sorting fields */ 74 NULL, /* args for field type. */ 75 0, /* number of allocated slots in lines array */ 76 NULL, /* pointer to the array of lines structures. */ 77 NULL, /* array of buffers for the field */ 78 }; 79 80 /* internal function prototypes */ 81 static int 82 field_buffer_init(FIELD *field, int buffer, size_t len); 83 static FIELD * 84 _formi_create_field(FIELD *, int, int, int, int, int, int); 85 86 87 /* 88 * Set the userptr for the field 89 */ 90 int 91 set_field_userptr(FIELD *field, void *ptr) 92 { 93 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 94 95 fp->userptr = ptr; 96 97 return E_OK; 98 } 99 100 /* 101 * Return the userptr for the field. 102 */ 103 104 void * 105 field_userptr(FIELD *field) 106 { 107 if (field == NULL) 108 return _formi_default_field.userptr; 109 else 110 return field->userptr; 111 } 112 113 /* 114 * Set the options for the designated field. 115 */ 116 int 117 set_field_opts(FIELD *field, Form_Options options) 118 { 119 int i; 120 121 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 122 123 /* not allowed to set opts if the field is the current one */ 124 if ((field != NULL) && (field->parent != NULL) && 125 (field->parent->cur_field == field->index)) 126 return E_CURRENT; 127 128 if ((options & O_STATIC) == O_STATIC) { 129 for (i = 0; i < fp->nbuf; i++) { 130 if (fp->buffers[i].length > fp->cols) 131 fp->buffers[i].string[fp->cols] = '\0'; 132 } 133 } 134 135 fp->opts = options; 136 137 /* if appropriate, redraw the field */ 138 if ((field != NULL) && (field->parent != NULL) 139 && (field->parent->posted == 1)) { 140 _formi_redraw_field(field->parent, field->index); 141 pos_form_cursor(field->parent); 142 wrefresh(field->parent->scrwin); 143 } 144 145 return E_OK; 146 } 147 148 /* 149 * Turn on the passed field options. 150 */ 151 int 152 field_opts_on(FIELD *field, Form_Options options) 153 { 154 int i; 155 156 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 157 158 /* not allowed to set opts if the field is the current one */ 159 if ((field != NULL) && (field->parent != NULL) && 160 (field->parent->cur_field == field->index)) 161 return E_CURRENT; 162 163 if ((options & O_STATIC) == O_STATIC) { 164 for (i = 0; i < fp->nbuf; i++) { 165 if (fp->buffers[i].length > fp->cols) 166 fp->buffers[i].string[fp->cols] = '\0'; 167 } 168 } 169 170 fp->opts |= options; 171 172 /* if appropriate, redraw the field */ 173 if ((field != NULL) && (field->parent != NULL) 174 && (field->parent->posted == 1)) { 175 _formi_redraw_field(field->parent, field->index); 176 pos_form_cursor(field->parent); 177 wrefresh(field->parent->scrwin); 178 } 179 180 return E_OK; 181 } 182 183 /* 184 * Turn off the passed field options. 185 */ 186 int 187 field_opts_off(FIELD *field, Form_Options options) 188 { 189 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 190 191 /* not allowed to set opts if the field is the current one */ 192 if ((field != NULL) && (field->parent != NULL) && 193 (field->parent->cur_field == field->index)) 194 return E_CURRENT; 195 196 fp->opts &= ~options; 197 198 /* if appropriate, redraw the field */ 199 if ((field != NULL) && (field->parent != NULL) 200 && (field->parent->posted == 1)) { 201 _formi_redraw_field(field->parent, field->index); 202 pos_form_cursor(field->parent); 203 wrefresh(field->parent->scrwin); 204 } 205 206 return E_OK; 207 } 208 209 /* 210 * Return the field options associated with the passed field. 211 */ 212 Form_Options 213 field_opts(FIELD *field) 214 { 215 if (field == NULL) 216 return _formi_default_field.opts; 217 else 218 return field->opts; 219 } 220 221 /* 222 * Set the justification for the passed field. 223 */ 224 int 225 set_field_just(FIELD *field, int justification) 226 { 227 FIELD *fp = (field == NULL) ? &_formi_default_field : field; 228 229 /* 230 * not allowed to set justification if the field is 231 * the current one 232 */ 233 if ((field != NULL) && (field->parent != NULL) && 234 (field->parent->cur_field == field->index)) 235 return E_CURRENT; 236 237 if ((justification < MIN_JUST_STYLE) /* check justification valid */ 238 || (justification > MAX_JUST_STYLE)) 239 return E_BAD_ARGUMENT; 240 241 /* only allow justification on static, single row fields */ 242 if (((fp->opts & O_STATIC) != O_STATIC) || 243 ((fp->rows + fp->nrows) > 1)) 244 return E_BAD_ARGUMENT; 245 246 fp->justification = justification; 247 248 _formi_init_field_xpos(fp); 249 250 return E_OK; 251 } 252 253 /* 254 * Return the justification style of the field passed. 255 */ 256 int 257 field_just(FIELD *field) 258 { 259 if (field == NULL) 260 return _formi_default_field.justification; 261 else 262 return field->justification; 263 } 264 265 /* 266 * Return information about the field passed. 267 */ 268 int 269 field_info(FIELD *field, int *rows, int *cols, int *frow, int *fcol, 270 int *nrow, int *nbuf) 271 { 272 if (field == NULL) 273 return E_BAD_ARGUMENT; 274 275 *rows = field->rows; 276 *cols = field->cols; 277 *frow = field->form_row; 278 *fcol = field->form_col; 279 *nrow = field->nrows; 280 *nbuf = field->nbuf; 281 282 return E_OK; 283 } 284 285 /* 286 * Report the dynamic field information. 287 */ 288 int 289 dynamic_field_info(FIELD *field, int *drows, int *dcols, int *max) 290 { 291 if (field == NULL) 292 return E_BAD_ARGUMENT; 293 294 if ((field->opts & O_STATIC) == O_STATIC) { 295 *drows = field->rows; 296 *dcols = field->cols; 297 } else { 298 *drows = field->drows; 299 *dcols = field->dcols; 300 } 301 302 *max = field->max; 303 304 return E_OK; 305 } 306 307 /* 308 * Init all the field variables, perform wrapping and other tasks 309 * after the field buffer is set. 310 */ 311 static int 312 field_buffer_init(FIELD *field, int buffer, size_t len) 313 { 314 int status; 315 316 if (buffer == 0) { 317 field->start_char = 0; 318 field->start_line = 0; 319 field->row_xpos = 0; 320 field->cursor_xpos = 0; 321 field->cursor_ypos = 0; 322 field->row_count = 1; /* must be at least one row */ 323 field->lines[0].start = 0; 324 field->lines[0].end = (len > 0)? (len - 1) : 0; 325 field->lines[0].length = 326 _formi_tab_expanded_length(field->buffers[0].string, 327 0, field->lines[0].end); 328 329 /* we have to hope the wrap works - if it does not then the 330 buffer is pretty much borked */ 331 status = _formi_wrap_field(field, 0); 332 if (status != E_OK) 333 return status; 334 335 /* 336 * calculate the tabs for a single row field, the 337 * multiline case is handled when the wrap is done. 338 */ 339 if (field->row_count == 1) 340 _formi_calculate_tabs(field, 0); 341 342 /* redraw the field to reflect the new contents. If the field 343 * is attached.... 344 */ 345 if ((field->parent != NULL) && (field->parent->posted == 1)) { 346 _formi_redraw_field(field->parent, field->index); 347 /* make sure cursor goes back to current field */ 348 pos_form_cursor(field->parent); 349 } 350 } 351 352 return E_OK; 353 } 354 355 356 /* 357 * Set the field buffer to the string that results from processing 358 * the given format (fmt) using sprintf. 359 */ 360 int 361 set_field_printf(FIELD *field, int buffer, char *fmt, ...) 362 { 363 int len; 364 va_list args; 365 366 if (field == NULL) 367 return E_BAD_ARGUMENT; 368 369 if (buffer >= field->nbuf) 370 return E_BAD_ARGUMENT; 371 372 va_start(args, fmt); 373 /* check for buffer already existing, free the storage */ 374 if (field->buffers[buffer].allocated != 0) 375 free(field->buffers[buffer].string); 376 377 len = vasprintf(&field->buffers[buffer].string, fmt, args); 378 va_end(args); 379 if (len < 0) 380 return E_SYSTEM_ERROR; 381 382 field->buffers[buffer].length = len; 383 field->buffers[buffer].allocated = len + 1; 384 if (((field->opts & O_STATIC) == O_STATIC) && (len > field->cols) 385 && ((field->rows + field->nrows) == 1)) 386 len = field->cols; 387 388 field->buffers[buffer].string[len] = '\0'; 389 return field_buffer_init(field, buffer, (unsigned int) len); 390 } 391 392 /* 393 * Set the value of the field buffer to the value given. 394 */ 395 396 int 397 set_field_buffer(FIELD *field, int buffer, char *value) 398 { 399 size_t len; 400 int status; 401 402 if (field == NULL) 403 return E_BAD_ARGUMENT; 404 405 if (buffer >= field->nbuf) /* make sure buffer is valid */ 406 return E_BAD_ARGUMENT; 407 408 len = strlen(value); 409 if (((field->opts & O_STATIC) == O_STATIC) && (len > field->cols) 410 && ((field->rows + field->nrows) == 1)) 411 len = field->cols; 412 413 #ifdef DEBUG 414 if (_formi_create_dbg_file() != E_OK) 415 return E_SYSTEM_ERROR; 416 417 fprintf(dbg, 418 "set_field_buffer: entry: len = %d, value = %s, buffer=%d\n", 419 len, value, buffer); 420 fprintf(dbg, "set_field_buffer: entry: string = "); 421 if (field->buffers[buffer].string != NULL) 422 fprintf(dbg, "%s, len = %d\n", field->buffers[buffer].string, 423 field->buffers[buffer].length); 424 else 425 fprintf(dbg, "(null), len = 0\n"); 426 fprintf(dbg, "set_field_buffer: entry: lines.len = %d\n", 427 field->lines[0].length); 428 #endif 429 430 if ((field->buffers[buffer].string = 431 (char *) realloc(field->buffers[buffer].string, len + 1)) == NULL) 432 return E_SYSTEM_ERROR; 433 434 strlcpy(field->buffers[buffer].string, value, len + 1); 435 field->buffers[buffer].length = len; 436 field->buffers[buffer].allocated = len + 1; 437 status = field_buffer_init(field, buffer, len); 438 439 #ifdef DEBUG 440 fprintf(dbg, "set_field_buffer: exit: len = %d, value = %s\n", 441 len, value); 442 fprintf(dbg, "set_field_buffer: exit: string = %s, len = %d\n", 443 field->buffers[buffer].string, field->buffers[buffer].length); 444 fprintf(dbg, "set_field_buffer: exit: lines.len = %d\n", 445 field->lines[0].length); 446 #endif 447 448 return status; 449 } 450 451 /* 452 * Return the requested field buffer to the caller. 453 */ 454 char * 455 field_buffer(FIELD *field, int buffer) 456 { 457 458 if (field == NULL) 459 return NULL; 460 461 if (buffer >= field->nbuf) 462 return NULL; 463 464 return field->buffers[buffer].string; 465 } 466 467 /* 468 * Set the buffer 0 field status. 469 */ 470 int 471 set_field_status(FIELD *field, int status) 472 { 473 474 if (field == NULL) 475 return E_BAD_ARGUMENT; 476 477 if (status != FALSE) 478 field->buf0_status = TRUE; 479 else 480 field->buf0_status = FALSE; 481 482 return E_OK; 483 } 484 485 /* 486 * Return the buffer 0 status flag for the given field. 487 */ 488 int 489 field_status(FIELD *field) 490 { 491 492 if (field == NULL) /* the default buffer 0 never changes :-) */ 493 return FALSE; 494 495 return field->buf0_status; 496 } 497 498 /* 499 * Set the maximum growth for a dynamic field. 500 */ 501 int 502 set_max_field(FIELD *fptr, int max) 503 { 504 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr; 505 506 if ((field->opts & O_STATIC) == O_STATIC) /* check if field dynamic */ 507 return E_BAD_ARGUMENT; 508 509 if (max < 0) /* negative numbers are bad.... */ 510 return E_BAD_ARGUMENT; 511 512 field->max = max; 513 return E_OK; 514 } 515 516 /* 517 * Set the field foreground character attributes. 518 */ 519 int 520 set_field_fore(FIELD *fptr, chtype attribute) 521 { 522 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr; 523 524 field->fore = attribute; 525 return E_OK; 526 } 527 528 /* 529 * Return the foreground character attribute for the given field. 530 */ 531 chtype 532 field_fore(FIELD *field) 533 { 534 if (field == NULL) 535 return _formi_default_field.fore; 536 else 537 return field->fore; 538 } 539 540 /* 541 * Set the background character attribute for the given field. 542 */ 543 int 544 set_field_back(FIELD *field, chtype attribute) 545 { 546 if (field == NULL) 547 _formi_default_field.back = attribute; 548 else 549 field->back = attribute; 550 551 return E_OK; 552 } 553 554 /* 555 * Get the background character attribute for the given field. 556 */ 557 chtype 558 field_back(FIELD *field) 559 { 560 if (field == NULL) 561 return _formi_default_field.back; 562 else 563 return field->back; 564 } 565 566 /* 567 * Set the pad character for the given field. 568 */ 569 int 570 set_field_pad(FIELD *field, int pad) 571 { 572 if (field == NULL) 573 _formi_default_field.pad = pad; 574 else 575 field->pad = pad; 576 577 return E_OK; 578 } 579 580 /* 581 * Return the padding character for the given field. 582 */ 583 int 584 field_pad(FIELD *field) 585 { 586 if (field == NULL) 587 return _formi_default_field.pad; 588 else 589 return field->pad; 590 } 591 592 /* 593 * Set the field initialisation function hook. 594 */ 595 int 596 set_field_init(FORM *form, Form_Hook function) 597 { 598 if (form == NULL) 599 _formi_default_form.field_init = function; 600 else 601 form->field_init = function; 602 603 return E_OK; 604 } 605 606 /* 607 * Return the function hook for the field initialisation. 608 */ 609 Form_Hook 610 field_init(FORM *form) 611 { 612 if (form == NULL) 613 return _formi_default_form.field_init; 614 else 615 return form->field_init; 616 } 617 618 /* 619 * Set the field termination function hook. 620 */ 621 int 622 set_field_term(FORM *form, Form_Hook function) 623 { 624 if (form == NULL) 625 _formi_default_form.field_term = function; 626 else 627 form->field_term = function; 628 629 return E_OK; 630 } 631 632 /* 633 * Return the function hook defined for the field termination. 634 */ 635 Form_Hook 636 field_term(FORM *form) 637 { 638 if (form == NULL) 639 return _formi_default_form.field_term; 640 else 641 return form->field_term; 642 } 643 644 /* 645 * Set the page flag on the given field to indicate it is the start of a 646 * new page. 647 */ 648 int 649 set_new_page(FIELD *fptr, int page) 650 { 651 FIELD *field = (fptr == NULL)? &_formi_default_field : fptr; 652 653 if (field->parent != NULL) /* check if field is connected to a form */ 654 return E_CONNECTED; 655 656 field->page_break = (page != FALSE); 657 return E_OK; 658 } 659 660 /* 661 * Return the page status for the given field. TRUE is returned if the 662 * field is the start of a new page. 663 */ 664 int 665 new_page(FIELD *field) 666 { 667 if (field == NULL) 668 return _formi_default_field.page_break; 669 else 670 return field->page_break; 671 } 672 673 /* 674 * Return the index of the field in the form fields array. 675 */ 676 int 677 field_index(FIELD *field) 678 { 679 if (field == NULL) 680 return E_BAD_ARGUMENT; 681 682 if (field->parent == NULL) 683 return E_NOT_CONNECTED; 684 685 return field->index; 686 } 687 688 /* 689 * Internal function that does most of the work to create a new field. 690 * The new field is initialised from the information in the prototype 691 * field passed. 692 * Returns NULL on error. 693 */ 694 static FIELD * 695 _formi_create_field(FIELD *prototype, int rows, int cols, int frow, 696 int fcol, int nrows, int nbuf) 697 { 698 FIELD *new; 699 700 if ((rows <= 0) || (cols <= 0) || (frow < 0) || (fcol < 0) || 701 (nrows < 0) || (nbuf < 0)) 702 return NULL; 703 704 if ((new = (FIELD *)malloc(sizeof(FIELD))) == NULL) { 705 return NULL; 706 } 707 708 /* copy in the default field info */ 709 bcopy(prototype, new, sizeof(FIELD)); 710 711 new->nbuf = nbuf + 1; 712 new->rows = rows; 713 new->cols = cols; 714 new->form_row = frow; 715 new->form_col = fcol; 716 new->nrows = nrows; 717 new->link = new; 718 return new; 719 } 720 721 /* 722 * Create a new field structure. 723 */ 724 FIELD * 725 new_field(int rows, int cols, int frow, int fcol, int nrows, int nbuf) 726 { 727 FIELD *new; 728 size_t buf_len; 729 int i; 730 731 732 if ((new = _formi_create_field(&_formi_default_field, rows, cols, 733 frow, fcol, nrows, nbuf)) == NULL) 734 return NULL; 735 736 buf_len = (nbuf + 1) * sizeof(FORM_STR); 737 738 if ((new->buffers = (FORM_STR *)malloc(buf_len)) == NULL) { 739 free(new); 740 return NULL; 741 } 742 743 /* Initialise the strings to a zero length string */ 744 for (i = 0; i < nbuf + 1; i++) { 745 if ((new->buffers[i].string = 746 (char *) malloc(sizeof(char))) == NULL) { 747 free(new->buffers); 748 free(new); 749 return NULL; 750 } 751 new->buffers[i].string[0] = '\0'; 752 new->buffers[i].length = 0; 753 new->buffers[i].allocated = 1; 754 } 755 756 if ((new->lines = (_FORMI_FIELD_LINES *) 757 malloc(sizeof(struct _formi_field_lines))) == NULL) { 758 free(new->buffers); 759 free(new); 760 return NULL; 761 } 762 763 new->lines_alloced = 1; 764 new->lines[0].length = 0; 765 new->lines[0].start = 0; 766 new->lines[0].end = 0; 767 new->lines[0].tabs = NULL; 768 769 return new; 770 } 771 772 /* 773 * Duplicate the given field, including it's buffers. 774 */ 775 FIELD * 776 dup_field(FIELD *field, int frow, int fcol) 777 { 778 FIELD *new; 779 size_t row_len, buf_len; 780 781 if (field == NULL) 782 return NULL; 783 784 /* XXXX this right???? */ 785 if ((new = _formi_create_field(field, (int) field->rows, 786 (int ) field->cols, 787 frow, fcol, (int) field->nrows, 788 field->nbuf - 1)) == NULL) 789 return NULL; 790 791 row_len = (field->rows + field->nrows + 1) * field->cols; 792 buf_len = (field->nbuf + 1) * row_len * sizeof(FORM_STR); 793 794 if ((new->buffers = (FORM_STR *)malloc(buf_len)) == NULL) { 795 free(new); 796 return NULL; 797 } 798 799 /* copy the buffers from the source field into the new copy */ 800 bcopy(field->buffers, new->buffers, buf_len); 801 802 return new; 803 } 804 805 /* 806 * Create a new field at the specified location by duplicating the given 807 * field. The buffers are shared with the parent field. 808 */ 809 FIELD * 810 link_field(FIELD *field, int frow, int fcol) 811 { 812 FIELD *new; 813 814 if (field == NULL) 815 return NULL; 816 817 if ((new = _formi_create_field(field, (int) field->rows, 818 (int) field->cols, 819 frow, fcol, (int) field->nrows, 820 field->nbuf - 1)) == NULL) 821 return NULL; 822 823 new->link = field->link; 824 field->link = new; 825 826 /* we are done. The buffer pointer was copied during the field 827 creation. */ 828 return new; 829 } 830 831 /* 832 * Release all storage allocated to the field 833 */ 834 int 835 free_field(FIELD *field) 836 { 837 FIELD *flink; 838 int i; 839 _formi_tab_t *ts, *nts; 840 841 if (field == NULL) 842 return E_BAD_ARGUMENT; 843 844 if (field->parent != NULL) 845 return E_CONNECTED; 846 847 if (field->link == field) { /* check if field linked */ 848 /* no it is not - release the buffers */ 849 free(field->buffers); 850 /* free the tab structures */ 851 for (i = 0; i < field->row_count - 1; i++) { 852 if (field->lines[i].tabs != NULL) { 853 ts = field->lines[i].tabs; 854 while (ts != NULL) { 855 nts = ts->fwd; 856 free(ts); 857 ts = nts; 858 } 859 } 860 } 861 } else { 862 /* is linked, traverse the links to find the field referring 863 * to the one to be freed. 864 */ 865 for (flink = field->link; flink != field; flink = flink->link); 866 flink->link = field->link; 867 } 868 869 free(field); 870 return E_OK; 871 } 872 873 874