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