xref: /netbsd-src/lib/libform/internals.c (revision deb6f0161a9109e7de9b519dc8dfb9478668dcdd)
1 /*	$NetBSD: internals.c,v 1.39 2018/11/08 06:34:40 msaitoh Exp $	*/
2 
3 /*-
4  * Copyright (c) 1998-1999 Brett Lymn
5  *                         (blymn@baea.com.au, brett_lymn@yahoo.com.au)
6  * All rights reserved.
7  *
8  * This code has been donated to The NetBSD Foundation by the Author.
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. The name of the author may not be used to endorse or promote products
16  *    derived from this software without specific prior written permission
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  *
29  *
30  */
31 
32 #include <sys/cdefs.h>
33 __RCSID("$NetBSD: internals.c,v 1.39 2018/11/08 06:34:40 msaitoh Exp $");
34 
35 #include <limits.h>
36 #include <ctype.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <strings.h>
40 #include <assert.h>
41 #include <err.h>
42 #include <stdarg.h>
43 #include "internals.h"
44 #include "form.h"
45 
46 #ifdef DEBUG
47 /*
48  *  file handle to write debug info to, this will be initialised when
49  *  the form is first posted.
50  */
51 
52 /*
53  * map the request numbers to strings for debug
54  */
55 static const char *reqs[] = {
56 	"NEXT_PAGE", "PREV_PAGE", "FIRST_PAGE",	"LAST_PAGE", "NEXT_FIELD",
57 	"PREV_FIELD", "FIRST_FIELD", "LAST_FIELD", "SNEXT_FIELD",
58 	"SPREV_FIELD", "SFIRST_FIELD", "SLAST_FIELD", "LEFT_FIELD",
59 	"RIGHT_FIELD", "UP_FIELD", "DOWN_FIELD", "NEXT_CHAR", "PREV_CHAR",
60 	"NEXT_LINE", "PREV_LINE", "NEXT_WORD", "PREV_WORD", "BEG_FIELD",
61 	"END_FIELD", "BEG_LINE", "END_LINE", "LEFT_CHAR", "RIGHT_CHAR",
62 	"UP_CHAR", "DOWN_CHAR", "NEW_LINE", "INS_CHAR", "INS_LINE",
63 	"DEL_CHAR", "DEL_PREV", "DEL_LINE", "DEL_WORD", "CLR_EOL",
64 	"CLR_EOF", "CLR_FIELD", "OVL_MODE", "INS_MODE", "SCR_FLINE",
65 	"SCR_BLINE", "SCR_FPAGE", "SCR_BPAGE", "SCR_FHPAGE", "SCR_BHPAGE",
66 	"SCR_FCHAR", "SCR_BCHAR", "SCR_HFLINE", "SCR_HBLINE", "SCR_HFHALF",
67 	"SCR_HBHALF", "VALIDATION", "PREV_CHOICE", "NEXT_CHOICE" };
68 #endif
69 
70 /* define our own min function - this is not generic but will do here
71  * (don't believe me?  think about what value you would get
72  * from min(x++, y++)
73  */
74 #define min(a,b) (((a) > (b))? (b) : (a))
75 
76 /* for the line joining function... */
77 #define JOIN_NEXT    1
78 #define JOIN_NEXT_NW 2 /* next join, don't wrap the joined line */
79 #define JOIN_PREV    3
80 #define JOIN_PREV_NW 4 /* previous join, don't wrap the joined line */
81 
82 /* for the bump_lines function... */
83 #define _FORMI_USE_CURRENT -1 /* indicates current cursor pos to be used */
84 
85 /* used in add_char for initial memory allocation for string in row */
86 #define INITIAL_LINE_ALLOC 16
87 
88 unsigned
89 field_skip_blanks(unsigned int start, _FORMI_FIELD_LINES **rowp);
90 static void
91 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val);
92 static void
93 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val);
94 static int
95 _formi_join_line(FIELD *field, _FORMI_FIELD_LINES **rowp, int direction);
96 void
97 _formi_hscroll_back(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt);
98 void
99 _formi_hscroll_fwd(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt);
100 static void
101 _formi_scroll_back(FIELD *field, unsigned int amt);
102 static void
103 _formi_scroll_fwd(FIELD *field, unsigned int amt);
104 static int
105 _formi_set_cursor_xpos(FIELD *field, int no_scroll);
106 static int
107 find_sow(unsigned int offset, _FORMI_FIELD_LINES **rowp);
108 static int
109 split_line(FIELD *field, bool hard_split, unsigned pos,
110 	   _FORMI_FIELD_LINES **rowp);
111 static bool
112 check_field_size(FIELD *field);
113 static int
114 add_tab(FORM *form, _FORMI_FIELD_LINES *row, unsigned int i, char c);
115 static int
116 tab_size(_FORMI_FIELD_LINES *row, unsigned int i);
117 static unsigned int
118 tab_fit_len(_FORMI_FIELD_LINES *row, unsigned int len);
119 static int
120 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window);
121 static void
122 add_to_free(FIELD *field, _FORMI_FIELD_LINES *line);
123 static void
124 adjust_ypos(FIELD *field, _FORMI_FIELD_LINES *line);
125 static _FORMI_FIELD_LINES *
126 copy_row(_FORMI_FIELD_LINES *row);
127 static void
128 destroy_row_list(_FORMI_FIELD_LINES *start);
129 
130 /*
131  * Calculate the cursor y position to make the given row appear on the
132  * field.  This may be as simple as just changing the ypos (if at all) but
133  * may encompass resetting the start_line of the field to place the line
134  * at the bottom of the field.  The field is assumed to be a multi-line one.
135  */
136 static void
137 adjust_ypos(FIELD *field, _FORMI_FIELD_LINES *line)
138 {
139 	unsigned ypos;
140 	_FORMI_FIELD_LINES *rs;
141 
142 	ypos = 0;
143 	rs = field->alines;
144 	while (rs != line) {
145 		rs = rs->next;
146 		ypos++;
147 	}
148 
149 	field->cursor_ypos = ypos;
150 	field->start_line = field->alines;
151 	if (ypos > (field->rows - 1)) {
152 		  /*
153 		   * cur_line off the end of the field,
154 		   * adjust start_line so fix this.
155 		   */
156 		field->cursor_ypos = field->rows - 1;
157 		ypos = ypos - (field->rows - 1);
158 		while (ypos > 0) {
159 			ypos--;
160 			field->start_line = field->start_line->next;
161 		}
162 	}
163 }
164 
165 
166 /*
167  * Delete the given row and add it to the free list of the given field.
168  */
169 static void
170 add_to_free(FIELD *field, _FORMI_FIELD_LINES *line)
171 {
172 	_FORMI_FIELD_LINES *saved;
173 
174 	saved = line;
175 
176 	  /* don't remove if only one line... */
177 	if ((line->prev == NULL) && (line->next == NULL))
178 		return;
179 
180 	if (line->prev == NULL) {
181 		/* handle top of list */
182 		field->alines = line->next;
183 		field->alines->prev = NULL;
184 
185 		if (field->cur_line == saved)
186 			field->cur_line = field->alines;
187 		if (field->start_line == saved)
188 			field->start_line = saved;
189 	} else if (line->next == NULL) {
190 		/* handle bottom of list */
191 		line->prev->next = NULL;
192 		if (field->cur_line == saved)
193 			field->cur_line = saved->prev;
194 		if (field->start_line == saved)
195 			field->cur_line = saved->prev;
196 	} else {
197 		saved->next->prev = saved->prev;
198 		saved->prev->next = saved->next;
199 		if (field->cur_line == saved)
200 			field->cur_line = saved->prev;
201 		if (field->start_line == saved)
202 			field->start_line = saved;
203 	}
204 
205 	saved->next = field->free;
206 	field->free = saved;
207 	saved->prev = NULL;
208 	if (saved->next != NULL)
209 		saved->next->prev = line;
210 }
211 
212 /*
213  * Duplicate the given row, return the pointer to the new copy or
214  * NULL if the copy fails.
215  */
216 static _FORMI_FIELD_LINES *
217 copy_row(_FORMI_FIELD_LINES *row)
218 {
219 	_FORMI_FIELD_LINES *new;
220 	_formi_tab_t *tp, *newt;
221 
222 	if ((new = (_FORMI_FIELD_LINES *) malloc(sizeof(_FORMI_FIELD_LINES)))
223 	    == NULL) {
224 		return NULL;
225 	}
226 
227 	memcpy(new, row, sizeof(_FORMI_FIELD_LINES));
228 
229 	  /* nuke the pointers from the source row so we don't get confused */
230 	new->next = NULL;
231 	new->prev = NULL;
232 	new->tabs = NULL;
233 
234 	if ((new->string = (char *) malloc((size_t)new->allocated)) == NULL) {
235 		free(new);
236 		return NULL;
237 	}
238 
239 	memcpy(new->string, row->string, (size_t) row->length + 1);
240 
241 	if (row->tabs != NULL) {
242 		tp = row->tabs;
243 		if ((new->tabs = (_formi_tab_t *) malloc(sizeof(_formi_tab_t)))
244 		    == NULL) {
245 			free(new->string);
246 			free(new);
247 			return NULL;
248 		}
249 
250 		memcpy(new->tabs, row->tabs, sizeof(_formi_tab_t));
251 		new->tabs->back = NULL;
252 		new->tabs->fwd = NULL;
253 
254 		tp = tp->fwd;
255 		newt = new->tabs;
256 
257 		while (tp != NULL) {
258 			if ((newt->fwd =
259 			     (_formi_tab_t *) malloc(sizeof(_formi_tab_t)))
260 			    == NULL) {
261 				/* error... unwind allocations */
262 				tp = new->tabs;
263 				while (tp != NULL) {
264 					newt = tp->fwd;
265 					free(tp);
266 					tp = newt;
267 				}
268 
269 				free(new->string);
270 				free(new);
271 				return NULL;
272 			}
273 
274 			memcpy(newt->fwd, tp, sizeof(_formi_tab_t));
275 			newt->fwd->back = newt;
276 			newt = newt->fwd;
277 			newt->fwd = NULL;
278 			tp = tp->fwd;
279 		}
280 	}
281 
282 	return new;
283 }
284 
285 /*
286  * Initialise the row offset for a field, depending on the type of
287  * field it is and the type of justification used.  The justification
288  * is only used on static single line fields, everything else will
289  * have the cursor_xpos set to 0.
290  */
291 void
292 _formi_init_field_xpos(FIELD *field)
293 {
294 	  /* not static or is multi-line which are not justified, so 0 it is */
295 	if (((field->opts & O_STATIC) != O_STATIC) ||
296 	    ((field->rows + field->nrows) != 1)) {
297 		field->cursor_xpos = 0;
298 		return;
299 	}
300 
301 	switch (field->justification) {
302 	case JUSTIFY_RIGHT:
303 		field->cursor_xpos = field->cols - 1;
304 		break;
305 
306 	case JUSTIFY_CENTER:
307 		field->cursor_xpos = (field->cols - 1) / 2;
308 		break;
309 
310 	default: /* assume left justify */
311 		field->cursor_xpos = 0;
312 		break;
313 	}
314 }
315 
316 
317 /*
318  * Open the debug file if it is not already open....
319  */
320 #ifdef DEBUG
321 static FILE *dbg;
322 static const char dbg_file[] = "___form_dbg.out";
323 
324 void
325 _formi_dbg_printf(const char *fmt, ...)
326 {
327 	va_list ap;
328 
329 	if (dbg == NULL && (dbg = fopen(dbg_file, "w")) == NULL) {
330 		warn("Cannot open debug file `%s'", dbg_file);
331 		return;
332 	}
333 	va_start(ap, fmt);
334 	vfprintf(dbg, fmt, ap);
335 	va_end(ap);
336 }
337 #endif
338 
339 /*
340  * Check the sizing of the field, if the maximum size is set for a
341  * dynamic field then check that the number of rows or columns does
342  * not exceed the set maximum.  The decision to check the rows or
343  * columns is made on the basis of how many rows are in the field -
344  * one row means the max applies to the number of columns otherwise it
345  * applies to the number of rows.  If the row/column count is less
346  * than the maximum then return TRUE.
347  *
348  */
349 static bool
350 check_field_size(FIELD *field)
351 {
352 	if ((field->opts & O_STATIC) != O_STATIC) {
353 		  /* dynamic field */
354 		if (field->max == 0) /* unlimited */
355 			return TRUE;
356 
357 		if (field->rows == 1) {
358 			return (field->alines->length < field->max);
359 		} else {
360 			return (field->row_count <= field->max);
361 		}
362 	} else {
363 		if ((field->rows + field->nrows) == 1) {
364 			return (field->alines->length <= field->cols);
365 		} else {
366 			return (field->row_count <= (field->rows
367 						     + field->nrows));
368 		}
369 	}
370 }
371 
372 /*
373  * Set the form's current field to the first valid field on the page.
374  * Assume the fields have been sorted and stitched.
375  */
376 int
377 _formi_pos_first_field(FORM *form)
378 {
379 	FIELD *cur;
380 	int old_page;
381 
382 	old_page = form->page;
383 
384 	  /* scan forward for an active page....*/
385 	while (form->page_starts[form->page].in_use == 0) {
386 		form->page++;
387 		if (form->page > form->max_page) {
388 			form->page = old_page;
389 			return E_REQUEST_DENIED;
390 		}
391 	}
392 
393 	  /* then scan for a field we can use */
394 	cur = form->fields[form->page_starts[form->page].first];
395 	while ((cur->opts & (O_VISIBLE | O_ACTIVE))
396 	       != (O_VISIBLE | O_ACTIVE)) {
397 		cur = TAILQ_NEXT(cur, glue);
398 		if (cur == NULL) {
399 			form->page = old_page;
400 			return E_REQUEST_DENIED;
401 		}
402 	}
403 
404 	form->cur_field = cur->index;
405 	return E_OK;
406 }
407 
408 /*
409  * Set the field to the next active and visible field, the fields are
410  * traversed in index order in the direction given.  If the parameter
411  * use_sorted is TRUE then the sorted field list will be traversed instead
412  * of using the field index.
413  */
414 int
415 _formi_pos_new_field(FORM *form, unsigned direction, unsigned use_sorted)
416 {
417 	FIELD *cur;
418 	int i;
419 
420 	i = form->cur_field;
421 	cur = form->fields[i];
422 
423 	do {
424 		if (direction == _FORMI_FORWARD) {
425 			if (use_sorted == TRUE) {
426 				if ((form->wrap == FALSE) &&
427 				    (cur == TAILQ_LAST(&form->sorted_fields,
428 					_formi_sort_head)))
429 					return E_REQUEST_DENIED;
430 				cur = TAILQ_NEXT(cur, glue);
431 				i = cur->index;
432 			} else {
433 				if ((form->wrap == FALSE) &&
434 				    ((i + 1) >= form->field_count))
435 					return E_REQUEST_DENIED;
436 				i++;
437 				if (i >= form->field_count)
438 					i = 0;
439 			}
440 		} else {
441 			if (use_sorted == TRUE) {
442 				if ((form->wrap == FALSE) &&
443 				    (cur == TAILQ_FIRST(&form->sorted_fields)))
444 					return E_REQUEST_DENIED;
445 				cur = TAILQ_PREV(cur, _formi_sort_head, glue);
446 				i = cur->index;
447 			} else {
448 				if ((form->wrap == FALSE) && (i <= 0))
449 					return E_REQUEST_DENIED;
450 				i--;
451 				if (i < 0)
452 					i = form->field_count - 1;
453 			}
454 		}
455 
456 		if ((form->fields[i]->opts & (O_VISIBLE | O_ACTIVE))
457 			== (O_VISIBLE | O_ACTIVE)) {
458 			form->cur_field = i;
459 			return E_OK;
460 		}
461 	}
462 	while (i != form->cur_field);
463 
464 	return E_REQUEST_DENIED;
465 }
466 
467 /*
468  * Destroy the list of line structs passed by freeing all allocated
469  * memory.
470  */
471 static void
472 destroy_row_list(_FORMI_FIELD_LINES *start)
473 {
474 	_FORMI_FIELD_LINES *temp, *row;
475 	_formi_tab_t *tt, *tp;
476 
477 	row = start;
478 	while (row != NULL) {
479 		if (row->tabs != NULL) {
480 			  /* free up the tab linked list... */
481 			tp = row->tabs;
482 			while (tp != NULL) {
483 				tt = tp->fwd;
484 				free(tp);
485 				tp = tt;
486 			}
487 		}
488 
489 		if (row->string != NULL)
490 			free(row->string);
491 
492 		temp = row->next;
493 		free(row);
494 		row = temp;
495 	}
496 }
497 
498 /*
499  * Word wrap the contents of the field's buffer 0 if this is allowed.
500  * If the wrap is successful, that is, the row count nor the buffer
501  * size is exceeded then the function will return E_OK, otherwise it
502  * will return E_REQUEST_DENIED.
503  */
504 int
505 _formi_wrap_field(FIELD *field, _FORMI_FIELD_LINES *loc)
506 {
507 	int width, wrap_err;
508 	unsigned int pos, saved_xpos, saved_ypos, saved_cur_xpos;
509 	unsigned int saved_row_count;
510 	_FORMI_FIELD_LINES *saved_row, *row, *row_backup, *saved_cur_line;
511 	_FORMI_FIELD_LINES *saved_start_line, *temp;
512 
513 	if ((field->opts & O_STATIC) == O_STATIC) {
514 		if ((field->rows + field->nrows) == 1) {
515 			return E_OK; /* cannot wrap a single line */
516 		}
517 		width = field->cols;
518 	} else {
519 		  /* if we are limited to one line then don't try to wrap */
520 		if ((field->drows + field->nrows) == 1) {
521 			return E_OK;
522 		}
523 
524 		  /*
525 		   * hueristic - if a dynamic field has more than one line
526 		   * on the screen then the field grows rows, otherwise
527 		   * it grows columns, effectively a single line field.
528 		   * This is documented AT&T behaviour.
529 		   */
530 		if (field->rows > 1) {
531 			width = field->cols;
532 		} else {
533 			return E_OK;
534 		}
535 	}
536 
537 	row = loc;
538 
539 	  /* if we are not at the top of the field then back up one
540 	   * row because we may be able to merge the current row into
541 	   * the one above.
542 	   */
543 	if (row->prev != NULL)
544 		row = row->prev;
545 
546 	saved_row = row;
547 	saved_xpos = field->row_xpos;
548 	saved_cur_xpos = field->cursor_xpos;
549 	saved_ypos = field->cursor_ypos;
550 	saved_row_count = field->row_count;
551 
552 	  /*
553 	   * Save a copy of the lines affected, just in case things
554 	   * don't work out.
555 	   */
556 	if ((row_backup = copy_row(row)) == NULL)
557 		return E_SYSTEM_ERROR;
558 
559 	temp = row_backup;
560 	row = row->next;
561 
562 	saved_cur_line = temp;
563 	saved_start_line = temp;
564 
565 	while (row != NULL) {
566 		if ((temp->next = copy_row(row)) == NULL) {
567 			  /* a row copy failed... free up allocations */
568 			destroy_row_list(row_backup);
569 			return E_SYSTEM_ERROR;
570 		}
571 
572 		temp->next->prev = temp;
573 		temp = temp->next;
574 
575 		if (row == field->start_line)
576 			saved_start_line = temp;
577 		if (row == field->cur_line)
578 			saved_cur_line = temp;
579 
580 		row = row->next;
581 	}
582 
583 	row = saved_row;
584 	while (row != NULL) {
585 		pos = row->length - 1;
586 		if (row->expanded < width) {
587 			  /* line may be too short, try joining some lines */
588 			if ((row->hard_ret == TRUE) && (row->next != NULL)) {
589 				/*
590 				 * Skip the line if it has a hard return
591 				 * and it is not the last, we cannot join
592 				 * anything to it.
593 				 */
594 				row = row->next;
595 				continue;
596 			}
597 
598 			if (row->next == NULL) {
599 				/*
600 				 * If there are no more lines and this line
601 				 * is too short then our job is over.
602 				 */
603 				break;
604 			}
605 
606 			if (_formi_join_line(field, &row,
607 					     JOIN_NEXT_NW) == E_OK) {
608 				continue;
609 			} else
610 				break;
611 		} else if (row->expanded > width) {
612 			  /* line is too long, split it */
613 
614 			  /*
615 			   * split on first whitespace before current word
616 			   * if the line has tabs we need to work out where
617 			   * the field border lies when the tabs are expanded.
618 			   */
619 			if (row->tabs == NULL) {
620 				pos = width - 1;
621 				if (pos >= row->expanded)
622 					pos = row->expanded - 1;
623 			} else {
624 				pos = tab_fit_len(row, field->cols);
625 			}
626 
627 			if ((!isblank((unsigned char)row->string[pos])) &&
628 			    ((field->opts & O_WRAP) == O_WRAP)) {
629 				if (!isblank((unsigned char)row->string[pos - 1]))
630 					pos = find_sow((unsigned int) pos,
631 						       &row);
632 				/*
633 				 * If we cannot split the line then return
634 				 * NO_ROOM so the driver can tell that it
635 				 * should not autoskip (if that is enabled)
636 				 */
637 				if ((pos == 0)
638 				    || (!isblank((unsigned char)row->string[pos - 1]))) {
639 					wrap_err = E_NO_ROOM;
640 					goto restore_and_exit;
641 				}
642 			}
643 
644 			  /* if we are at the end of the string and it has
645 			   * a trailing blank, don't wrap the blank.
646 			   */
647 			if ((row->next == NULL) && (pos == row->length - 1) &&
648 			    (isblank((unsigned char)row->string[pos])) &&
649 			    row->expanded <= field->cols)
650 				continue;
651 
652 			  /*
653 			   * otherwise, if we are still sitting on a
654 			   * blank but not at the end of the line
655 			   * move forward one char so the blank
656 			   * is on the line boundary.
657 			   */
658 			if ((isblank((unsigned char)row->string[pos])) &&
659 			    (pos != row->length - 1))
660 				pos++;
661 
662 			if (split_line(field, FALSE, pos, &row) != E_OK) {
663 				wrap_err = E_REQUEST_DENIED;
664 				goto restore_and_exit;
665 			}
666 		} else
667 			  /* line is exactly the right length, do next one */
668 			row = row->next;
669 	}
670 
671 	  /* Check if we have not run out of room */
672 	if ((((field->opts & O_STATIC) == O_STATIC) &&
673 	     field->row_count > (field->rows + field->nrows)) ||
674 	    ((field->max != 0) && (field->row_count > field->max))) {
675 
676 		wrap_err = E_REQUEST_DENIED;
677 
678 	  restore_and_exit:
679 		if (saved_row->prev == NULL) {
680 			field->alines = row_backup;
681 		} else {
682 			saved_row->prev->next = row_backup;
683 			row_backup->prev = saved_row->prev;
684 		}
685 
686 		field->row_xpos = saved_xpos;
687 		field->cursor_xpos = saved_cur_xpos;
688 		field->cursor_ypos = saved_ypos;
689 		field->row_count = saved_row_count;
690 		field->start_line = saved_start_line;
691 		field->cur_line = saved_cur_line;
692 
693 		destroy_row_list(saved_row);
694 		return wrap_err;
695 	}
696 
697 	destroy_row_list(row_backup);
698 	return E_OK;
699 }
700 
701 /*
702  * Join the two lines that surround the location pos, the type
703  * variable indicates the direction of the join, JOIN_NEXT will join
704  * the next line to the current line, JOIN_PREV will join the current
705  * line to the previous line, the new lines will be wrapped unless the
706  * _NW versions of the directions are used.  Returns E_OK if the join
707  * was successful or E_REQUEST_DENIED if the join cannot happen.
708  */
709 static int
710 _formi_join_line(FIELD *field, _FORMI_FIELD_LINES **rowp, int direction)
711 {
712 	int old_len, count;
713 	struct _formi_field_lines *saved;
714 	char *newp;
715 	_FORMI_FIELD_LINES *row = *rowp;
716 
717 	_formi_dbg_printf("%s: working on row %p, row_count = %d\n",
718 	    __func__, row, field->row_count);
719 
720 	if ((direction == JOIN_NEXT) || (direction == JOIN_NEXT_NW)) {
721 		  /*
722 		   * See if there is another line following, or if the
723 		   * line contains a hard return then we don't join
724 		   * any lines to it.
725 		   */
726 		if ((row->next == NULL) || (row->hard_ret == TRUE)) {
727 			return E_REQUEST_DENIED;
728 		}
729 
730 		_formi_dbg_printf(
731 		    "%s: join_next before length = %d, expanded = %d",
732 		    __func__, row->length, row->expanded);
733 		_formi_dbg_printf(
734 		    " :: next row length = %d, expanded = %d\n",
735 		    row->length, row->expanded);
736 
737 		if (row->allocated < (row->length + row->next->length + 1)) {
738 			if ((newp = realloc(row->string, (size_t)(row->length +
739 							  row->next->length
740 							  + 1))) == NULL)
741 				return E_REQUEST_DENIED;
742 			row->string = newp;
743 			row->allocated = row->length + row->next->length + 1;
744 		}
745 
746 		strcat(row->string, row->next->string);
747 		old_len = row->length;
748 		row->length += row->next->length;
749 		if (row->length > 0)
750 			row->expanded =
751 				_formi_tab_expanded_length(row->string, 0,
752 							   row->length - 1);
753 		else
754 			row->expanded = 0;
755 
756 		_formi_calculate_tabs(row);
757 		row->hard_ret = row->next->hard_ret;
758 
759 		  /* adjust current line if it is on the row being eaten */
760 		if (field->cur_line == row->next) {
761 			field->cur_line = row;
762 			field->row_xpos += old_len;
763 			field->cursor_xpos =
764 				_formi_tab_expanded_length(row->string, 0,
765 							   field->row_xpos);
766 			if (field->cursor_xpos > 0)
767 				field->cursor_xpos--;
768 
769 			if (field->cursor_ypos > 0)
770 				field->cursor_ypos--;
771 			else {
772 				if (field->start_line->prev != NULL)
773 					field->start_line =
774 						field->start_line->prev;
775 			}
776 		}
777 
778 		  /* remove joined line record from the row list */
779 		add_to_free(field, row->next);
780 
781 		_formi_dbg_printf(
782 		    "%s: exit length = %d, expanded = %d\n",
783 		    __func__, row->length, row->expanded);
784 	} else {
785 		if (row->prev == NULL) {
786 			return E_REQUEST_DENIED;
787 		}
788 
789 		saved = row->prev;
790 
791 		  /*
792 		   * Don't try to join if the line above has a hard
793 		   * return on it.
794 		   */
795 		if (saved->hard_ret == TRUE) {
796 			return E_REQUEST_DENIED;
797 		}
798 
799 		_formi_dbg_printf(
800 		    "%s: join_prev before length = %d, expanded = %d",
801 		    __func__, row->length, row->expanded);
802 		_formi_dbg_printf(
803 		    " :: prev row length = %d, expanded = %d\n",
804 		    saved->length, saved->expanded);
805 
806 		if (saved->allocated < (row->length + saved->length + 1)) {
807 			if ((newp = realloc(saved->string,
808 					    (size_t) (row->length +
809 						      saved->length
810 						      + 1))) == NULL)
811 				return E_REQUEST_DENIED;
812 			saved->string = newp;
813 			saved->allocated = row->length + saved->length + 1;
814 		}
815 
816 		strcat(saved->string, row->string);
817 		old_len = saved->length;
818 		saved->length += row->length;
819 		if (saved->length > 0)
820 			saved->expanded =
821 				_formi_tab_expanded_length(saved->string, 0,
822 							   saved->length - 1);
823 		else
824 			saved->length = 0;
825 
826 		saved->hard_ret = row->hard_ret;
827 
828 		  /* adjust current line if it was on the row being eaten */
829 		if (field->cur_line == row) {
830 			field->cur_line = saved;
831 			field->row_xpos += old_len;
832 			field->cursor_xpos =
833 				_formi_tab_expanded_length(saved->string, 0,
834 							   field->row_xpos);
835 			if (field->cursor_xpos > 0)
836 				field->cursor_xpos--;
837 		}
838 
839 		add_to_free(field, row);
840 
841 		_formi_dbg_printf(
842 		    "%s: exit length = %d, expanded = %d\n", __func__,
843 		    saved->length, saved->expanded);
844 		row = saved;
845 	}
846 
847 
848 	  /*
849 	   * Work out where the line lies in the field in relation to
850 	   * the cursor_ypos.  First count the rows from the start of
851 	   * the field until we hit the row we just worked on.
852 	   */
853 	saved = field->start_line;
854 	count = 0;
855 	while (saved->next != NULL) {
856 		if (saved == row)
857 			break;
858 		count++;
859 		saved = saved->next;
860 	}
861 
862 	  /* now check if we need to adjust cursor_ypos */
863 	if (field->cursor_ypos > count) {
864 		field->cursor_ypos--;
865 	}
866 
867 	field->row_count--;
868 	*rowp = row;
869 
870 	  /* wrap the field if required, if this fails undo the change */
871 	if ((direction == JOIN_NEXT) || (direction == JOIN_PREV)) {
872 		if (_formi_wrap_field(field, row) != E_OK) {
873 			return E_REQUEST_DENIED;
874 		}
875 	}
876 
877 	return E_OK;
878 }
879 
880 /*
881  * Split the line at the given position, if possible.  If hard_split is
882  * TRUE then split the line regardless of the position, otherwise don't
883  * split at the beginning of a line.
884  */
885 static int
886 split_line(FIELD *field, bool hard_split, unsigned pos,
887 	   _FORMI_FIELD_LINES **rowp)
888 {
889 	struct _formi_field_lines *new_line;
890 	char *newp;
891 	_FORMI_FIELD_LINES *row = *rowp;
892 
893 	  /* if asked to split right where the line already starts then
894 	   * just return - nothing to do unless we are appending a line
895 	   * to the buffer.
896 	   */
897 	if ((pos == 0) && (hard_split == FALSE))
898 		return E_OK;
899 
900 	_formi_dbg_printf("%s: splitting line at %d\n", __func__, pos);
901 
902 	  /* Need an extra line struct, check free list first */
903 	if (field->free != NULL) {
904 		new_line = field->free;
905 		field->free = new_line->next;
906 		if (field->free != NULL)
907 			field->free->prev = NULL;
908 	} else {
909 		if ((new_line = malloc(sizeof(*new_line))) == NULL)
910 			return E_SYSTEM_ERROR;
911 		new_line->prev = NULL;
912 		new_line->next = NULL;
913 		new_line->allocated = 0;
914 		new_line->length = 0;
915 		new_line->expanded = 0;
916 		new_line->string = NULL;
917 		new_line->hard_ret = FALSE;
918 		new_line->tabs = NULL;
919 	}
920 
921 	_formi_dbg_printf("%s: enter: length = %d, expanded = %d\n", __func__,
922 	    row->length, row->expanded);
923 
924 	assert((row->length < INT_MAX) && (row->expanded < INT_MAX));
925 
926 
927 	  /* add new line to the row list */
928 	new_line->next = row->next;
929 	new_line->prev = row;
930 	row->next = new_line;
931 	if (new_line->next != NULL)
932 		new_line->next->prev = new_line;
933 
934 	new_line->length = row->length - pos;
935 	if (new_line->length >= new_line->allocated) {
936 		if ((newp = realloc(new_line->string,
937 				    (size_t) new_line->length + 1)) == NULL)
938 			return E_SYSTEM_ERROR;
939 		new_line->string = newp;
940 		new_line->allocated = new_line->length + 1;
941 	}
942 
943 	strcpy(new_line->string, &row->string[pos]);
944 
945 	row->length = pos;
946 	row->string[pos] = '\0';
947 
948 	if (row->length != 0)
949 		row->expanded = _formi_tab_expanded_length(row->string, 0,
950 							   row->length - 1);
951 	else
952 		row->expanded = 0;
953 	_formi_calculate_tabs(row);
954 
955 	if (new_line->length != 0)
956 		new_line->expanded =
957 			_formi_tab_expanded_length(new_line->string, 0,
958 						   new_line->length - 1);
959 	else
960 		new_line->expanded = 0;
961 
962 	_formi_calculate_tabs(new_line);
963 
964 	  /*
965 	   * If the given row was the current line then adjust the
966 	   * current line pointer if necessary
967 	   */
968 	if ((field->cur_line == row) && (field->row_xpos >= pos)) {
969 		field->cur_line = new_line;
970 		field->row_xpos -= pos;
971 		field->cursor_xpos =
972 			_formi_tab_expanded_length(new_line->string, 0,
973 						   field->row_xpos);
974 		if (field->cursor_xpos > 0)
975 			field->cursor_xpos--;
976 
977 		field->cursor_ypos++;
978 		if (field->cursor_ypos >= field->rows) {
979 			if (field->start_line->next != NULL) {
980 				field->start_line = field->start_line->next;
981 				field->cursor_ypos = field->rows - 1;
982 			}
983 			else
984 				assert(field->start_line->next == NULL);
985 		}
986 	}
987 
988 	  /*
989 	   * If the line split had a hard return then replace the
990 	   * current line's hard return with a soft return and carry
991 	   * the hard return onto the line after.
992 	   */
993 	if (row->hard_ret == TRUE) {
994 		new_line->hard_ret = TRUE;
995 		row->hard_ret = FALSE;
996 	}
997 
998 	  /*
999 	   * except where we are doing a hard split then the current
1000 	   * row must have a hard return on it too...
1001 	   */
1002 	if (hard_split == TRUE) {
1003 		row->hard_ret = TRUE;
1004 	}
1005 
1006 	assert(((row->expanded < INT_MAX) &&
1007 		(new_line->expanded < INT_MAX) &&
1008 		(row->length < INT_MAX) &&
1009 		(new_line->length < INT_MAX)));
1010 
1011 	_formi_dbg_printf("%s: exit: ", __func__);
1012 	_formi_dbg_printf("row.length = %d, row.expanded = %d, ",
1013 	    row->length, row->expanded);
1014 	_formi_dbg_printf("next_line.length = %d, next_line.expanded = %d, ",
1015 	    new_line->length, new_line->expanded);
1016 	_formi_dbg_printf("row_count = %d\n", field->row_count + 1);
1017 
1018 	field->row_count++;
1019 	*rowp = new_line;
1020 
1021 	return E_OK;
1022 }
1023 
1024 /*
1025  * skip the blanks in the given string, start at the index start and
1026  * continue forward until either the end of the string or a non-blank
1027  * character is found.  Return the index of either the end of the string or
1028  * the first non-blank character.
1029  */
1030 unsigned
1031 _formi_skip_blanks(char *string, unsigned int start)
1032 {
1033 	unsigned int i;
1034 
1035 	i = start;
1036 
1037 	while ((string[i] != '\0') && isblank((unsigned char)string[i]))
1038 		i++;
1039 
1040 	return i;
1041 }
1042 
1043 /*
1044  * Skip the blanks in the string associated with the given row, pass back
1045  * the row and the offset at which the first non-blank is found.  If no
1046  * non-blank character is found then return the index to the last
1047  * character on the last line.
1048  */
1049 
1050 unsigned
1051 field_skip_blanks(unsigned int start, _FORMI_FIELD_LINES **rowp)
1052 {
1053 	unsigned int i;
1054 	_FORMI_FIELD_LINES *row, *last = NULL;
1055 
1056 	row = *rowp;
1057 	i = start;
1058 
1059 	do {
1060 		i = _formi_skip_blanks(&row->string[i], i);
1061 		if (!isblank((unsigned char)row->string[i])) {
1062 			last = row;
1063 			row = row->next;
1064 			  /*
1065 			   * don't reset if last line otherwise we will
1066 			   * not be at the end of the string.
1067 			   */
1068 			if (row != NULL)
1069 				i = 0;
1070 		} else
1071 			break;
1072 	}
1073 	while (row != NULL);
1074 
1075 	  /*
1076 	   * If we hit the end of the row list then point at the last row
1077 	   * otherwise we return the row we found the blank on.
1078 	   */
1079 	if (row == NULL)
1080 		*rowp = last;
1081 	else
1082 		*rowp = row;
1083 
1084 	return i;
1085 }
1086 
1087 /*
1088  * Return the index of the top left most field of the two given fields.
1089  */
1090 static int
1091 _formi_top_left(FORM *form, int a, int b)
1092 {
1093 	  /* lower row numbers always win here.... */
1094 	if (form->fields[a]->form_row < form->fields[b]->form_row)
1095 		return a;
1096 
1097 	if (form->fields[a]->form_row > form->fields[b]->form_row)
1098 		return b;
1099 
1100 	  /* rows must be equal, check columns */
1101 	if (form->fields[a]->form_col < form->fields[b]->form_col)
1102 		return a;
1103 
1104 	if (form->fields[a]->form_col > form->fields[b]->form_col)
1105 		return b;
1106 
1107 	  /* if we get here fields must be in exactly the same place, punt */
1108 	return a;
1109 }
1110 
1111 /*
1112  * Return the index to the field that is the bottom-right-most of the
1113  * two given fields.
1114  */
1115 static int
1116 _formi_bottom_right(FORM *form, int a, int b)
1117 {
1118 	  /* check the rows first, biggest row wins */
1119 	if (form->fields[a]->form_row > form->fields[b]->form_row)
1120 		return a;
1121 	if (form->fields[a]->form_row < form->fields[b]->form_row)
1122 		return b;
1123 
1124 	  /* rows must be equal, check cols, biggest wins */
1125 	if (form->fields[a]->form_col > form->fields[b]->form_col)
1126 		return a;
1127 	if (form->fields[a]->form_col < form->fields[b]->form_col)
1128 		return b;
1129 
1130 	  /* fields in the same place, punt */
1131 	return a;
1132 }
1133 
1134 /*
1135  * Find the end of the current word in the string str, starting at
1136  * offset - the end includes any trailing whitespace.  If the end of
1137  * the string is found before a new word then just return the offset
1138  * to the end of the string.  If do_join is TRUE then lines will be
1139  * joined (without wrapping) until either the end of the field or the
1140  * end of a word is found (whichever comes first).
1141  */
1142 static int
1143 find_eow(FIELD *cur, unsigned int offset, bool do_join,
1144 	 _FORMI_FIELD_LINES **rowp)
1145 {
1146 	int start;
1147 	_FORMI_FIELD_LINES *row;
1148 
1149 	row = *rowp;
1150 	start = offset;
1151 
1152 	do {
1153 		  /* first skip any non-whitespace */
1154 		while ((row->string[start] != '\0')
1155 		       && !isblank((unsigned char)row->string[start]))
1156 			start++;
1157 
1158 		  /* see if we hit the end of the string */
1159 		if (row->string[start] == '\0') {
1160 			if (do_join == TRUE) {
1161 				if (row->next == NULL)
1162 					return start;
1163 
1164 				if (_formi_join_line(cur, &row, JOIN_NEXT_NW)
1165 				    != E_OK)
1166 					return E_REQUEST_DENIED;
1167 			} else {
1168 				do {
1169 					if (row->next == NULL) {
1170 						*rowp = row;
1171 						return start;
1172 					} else {
1173 						row = row->next;
1174 						start = 0;
1175 					}
1176 				} while (row->length == 0);
1177 			}
1178 		}
1179 	} while (!isblank((unsigned char)row->string[start]));
1180 
1181 	do {
1182 		  /* otherwise skip the whitespace.... */
1183 		while ((row->string[start] != '\0')
1184 		       && isblank((unsigned char)row->string[start]))
1185 			start++;
1186 
1187 		if (row->string[start] == '\0') {
1188 			if (do_join == TRUE) {
1189 				if (row->next == NULL)
1190 					return start;
1191 
1192 				if (_formi_join_line(cur, &row, JOIN_NEXT_NW)
1193 				    != E_OK)
1194 					return E_REQUEST_DENIED;
1195 			} else {
1196 				do {
1197 					if (row->next == NULL) {
1198 						*rowp = row;
1199 						return start;
1200 					} else {
1201 						row = row->next;
1202 						start = 0;
1203 					}
1204 				} while (row->length == 0);
1205 			}
1206 		}
1207 	} while (isblank((unsigned char)row->string[start]));
1208 
1209 	*rowp = row;
1210 	return start;
1211 }
1212 
1213 /*
1214  * Find the beginning of the current word in the string str, starting
1215  * at offset.
1216  */
1217 static int
1218 find_sow(unsigned int offset, _FORMI_FIELD_LINES **rowp)
1219 {
1220 	int start;
1221 	char *str;
1222 	_FORMI_FIELD_LINES *row;
1223 
1224 	row = *rowp;
1225 	str = row->string;
1226 	start = offset;
1227 
1228 	do {
1229 		if (start > 0) {
1230 			if (isblank((unsigned char)str[start]) ||
1231 			    isblank((unsigned char)str[start - 1])) {
1232 				if (isblank((unsigned char)str[start - 1]))
1233 					start--;
1234 				  /* skip the whitespace.... */
1235 				while ((start >= 0) &&
1236 				    isblank((unsigned char)str[start]))
1237 					start--;
1238 			}
1239 		}
1240 
1241 		  /* see if we hit the start of the string */
1242 		if (start < 0) {
1243 			do {
1244 				if (row->prev == NULL) {
1245 					*rowp = row;
1246 					start = 0;
1247 					return start;
1248 				} else {
1249 					row = row->prev;
1250 					str = row->string;
1251 					if (row->length > 0)
1252 						start = row->length - 1;
1253 					else
1254 						start = 0;
1255 				}
1256 			} while (row->length == 0);
1257 		}
1258 	} while (isblank((unsigned char)row->string[start]));
1259 
1260 	  /* see if we hit the start of the string */
1261 	if (start < 0) {
1262 		*rowp = row;
1263 		return 0;
1264 	}
1265 
1266 	  /* now skip any non-whitespace */
1267 	do {
1268 		while ((start >= 0) && !isblank((unsigned char)str[start]))
1269 			start--;
1270 
1271 
1272 		if (start < 0) {
1273 			do {
1274 				if (row->prev == NULL) {
1275 					*rowp = row;
1276 					start = 0;
1277 					return start;
1278 				} else {
1279 					row = row->prev;
1280 					str = row->string;
1281 					if (row->length > 0)
1282 						start = row->length - 1;
1283 					else
1284 						start = 0;
1285 				}
1286 			} while (row->length == 0);
1287 		}
1288 	} while (!isblank((unsigned char)str[start]));
1289 
1290 	if (start > 0) {
1291 		start++; /* last loop has us pointing at a space, adjust */
1292 		if (start >= row->length) {
1293 			if (row->next != NULL) {
1294 				start = 0;
1295 				row = row->next;
1296 			} else {
1297 				start = row->length - 1;
1298 			}
1299 		}
1300 	}
1301 
1302 	if (start < 0)
1303 		start = 0;
1304 
1305 	*rowp = row;
1306 	return start;
1307 }
1308 
1309 /*
1310  * Scroll the field forward the given number of lines.
1311  */
1312 static void
1313 _formi_scroll_fwd(FIELD *field, unsigned int amt)
1314 {
1315 	unsigned int count;
1316 	_FORMI_FIELD_LINES *end_row;
1317 
1318 	end_row = field->start_line;
1319 	  /* walk the line structs forward to find the bottom of the field */
1320 	count = field->rows - 1;
1321 	while ((count > 0) && (end_row->next != NULL))
1322 	{
1323 		count--;
1324 		end_row = end_row->next;
1325 	}
1326 
1327 	  /* check if there are lines to scroll */
1328 	if ((count > 0) && (end_row->next == NULL))
1329 		return;
1330 
1331 	  /*
1332 	   * ok, lines to scroll - do this by walking both the start_line
1333 	   * and the end_row at the same time for amt lines, we stop when
1334 	   * either we have done the number of lines or end_row hits the
1335 	   * last line in the field.
1336 	   */
1337 	count = amt;
1338 	while ((count > 0) && (end_row->next != NULL)) {
1339 		count--;
1340 		field->start_line = field->start_line->next;
1341 		end_row = end_row->next;
1342 	}
1343 }
1344 
1345 /*
1346  * Scroll the field backward the given number of lines.
1347  */
1348 static void
1349 _formi_scroll_back(FIELD *field, unsigned int amt)
1350 {
1351 	unsigned int count;
1352 
1353 	  /* check for lines above */
1354 	if (field->start_line->prev == NULL)
1355 		return;
1356 
1357 	  /*
1358 	   * Backward scroll is easy, follow row struct chain backward until
1359 	   * the number of lines done or we reach the top of the field.
1360 	   */
1361 	count = amt;
1362 	while ((count > 0) && (field->start_line->prev != NULL)) {
1363 		count--;
1364 		field->start_line = field->start_line->prev;
1365 	}
1366 }
1367 
1368 /*
1369  * Scroll the field forward the given number of characters.
1370  */
1371 void
1372 _formi_hscroll_fwd(FIELD *field, _FORMI_FIELD_LINES *row, int unsigned amt)
1373 {
1374 	unsigned int end, scroll_amt, expanded;
1375 	_formi_tab_t *ts;
1376 
1377 
1378 	if ((row->tabs == NULL) || (row->tabs->in_use == FALSE)) {
1379 		  /* if the line has no tabs things are easy... */
1380 		end =  field->start_char + field->cols + amt - 1;
1381 		scroll_amt = amt;
1382 		if (end > row->length) {
1383 			end = row->length;
1384 			scroll_amt = end - field->start_char - field->cols + 1;
1385 		}
1386 	} else {
1387 		  /*
1388 		   * If there are tabs we need to add on the scroll amount,
1389 		   * find the last char position that will fit into
1390 		   * the field and finally fix up the start_char.  This
1391 		   * is a lot of work but handling the case where there
1392 		   * are not enough chars to scroll by amt is difficult.
1393 		   */
1394 		end = field->start_char + field->row_xpos + amt;
1395 		if (end >= row->length)
1396 			end = row->length - 1;
1397 		else {
1398 			expanded = _formi_tab_expanded_length(
1399 				row->string,
1400 				field->start_char + amt,
1401 				field->start_char + field->row_xpos + amt);
1402 			ts = row->tabs;
1403 			  /* skip tabs to the lhs of our starting point */
1404 			while ((ts != NULL) && (ts->in_use == TRUE)
1405 			       && (ts->pos < end))
1406 				ts = ts->fwd;
1407 
1408 			while ((expanded <= field->cols)
1409 			       && (end < row->length)) {
1410 				if (row->string[end] == '\t') {
1411 					assert((ts != NULL)
1412 					       && (ts->in_use == TRUE));
1413 					if (ts->pos == end) {
1414 						if ((expanded + ts->size)
1415 						    > field->cols)
1416 							break;
1417 						expanded += ts->size;
1418 						ts = ts->fwd;
1419 					}
1420 					else
1421 						assert(ts->pos == end);
1422 				} else
1423 					expanded++;
1424 				end++;
1425 			}
1426 		}
1427 
1428 		scroll_amt = tab_fit_window(field, end, field->cols);
1429 		if (scroll_amt < field->start_char)
1430 			scroll_amt = 1;
1431 		else
1432 			scroll_amt -= field->start_char;
1433 
1434 		scroll_amt = min(scroll_amt, amt);
1435 	}
1436 
1437 	field->start_char += scroll_amt;
1438 	field->cursor_xpos =
1439 		_formi_tab_expanded_length(row->string,
1440 					   field->start_char,
1441 					   field->row_xpos
1442 					   + field->start_char) - 1;
1443 
1444 }
1445 
1446 /*
1447  * Scroll the field backward the given number of characters.
1448  */
1449 void
1450 _formi_hscroll_back(FIELD *field, _FORMI_FIELD_LINES *row, unsigned int amt)
1451 {
1452 	field->start_char -= min(field->start_char, amt);
1453 	field->cursor_xpos =
1454 		_formi_tab_expanded_length(row->string, field->start_char,
1455 					   field->row_xpos
1456 					   + field->start_char) - 1;
1457 	if (field->cursor_xpos >= field->cols) {
1458 		field->row_xpos = 0;
1459 		field->cursor_xpos = 0;
1460 	}
1461 }
1462 
1463 /*
1464  * Find the different pages in the form fields and assign the form
1465  * page_starts array with the information to find them.
1466  */
1467 int
1468 _formi_find_pages(FORM *form)
1469 {
1470 	int i, cur_page = 0;
1471 
1472 	if ((form->page_starts = (_FORMI_PAGE_START *)
1473 	     malloc((form->max_page + 1) * sizeof(_FORMI_PAGE_START))) == NULL)
1474 		return E_SYSTEM_ERROR;
1475 
1476 	  /* initialise the page starts array */
1477 	memset(form->page_starts, 0,
1478 	       (form->max_page + 1) * sizeof(_FORMI_PAGE_START));
1479 
1480 	for (i =0; i < form->field_count; i++) {
1481 		if (form->fields[i]->page_break == 1)
1482 			cur_page++;
1483 		if (form->page_starts[cur_page].in_use == 0) {
1484 			form->page_starts[cur_page].in_use = 1;
1485 			form->page_starts[cur_page].first = i;
1486 			form->page_starts[cur_page].last = i;
1487 			form->page_starts[cur_page].top_left = i;
1488 			form->page_starts[cur_page].bottom_right = i;
1489 		} else {
1490 			form->page_starts[cur_page].last = i;
1491 			form->page_starts[cur_page].top_left =
1492 				_formi_top_left(form,
1493 						form->page_starts[cur_page].top_left,
1494 						i);
1495 			form->page_starts[cur_page].bottom_right =
1496 				_formi_bottom_right(form,
1497 						    form->page_starts[cur_page].bottom_right,
1498 						    i);
1499 		}
1500 	}
1501 
1502 	return E_OK;
1503 }
1504 
1505 /*
1506  * Completely redraw the field of the given form.
1507  */
1508 void
1509 _formi_redraw_field(FORM *form, int field)
1510 {
1511 	unsigned int pre, post, flen, slen, i, j, start, line;
1512 	unsigned int tab, cpos, len;
1513 	char *str, c;
1514 	FIELD *cur;
1515 	_FORMI_FIELD_LINES *row;
1516 #ifdef DEBUG
1517 	char buffer[100];
1518 #endif
1519 
1520 	cur = form->fields[field];
1521 	flen = cur->cols;
1522 	slen = 0;
1523 	start = 0;
1524 	line = 0;
1525 
1526 	for (row = cur->start_line; ((row != NULL) && (line < cur->rows));
1527 	     row = row->next, line++) {
1528 		wmove(form->scrwin, (int) (cur->form_row + line),
1529 		      (int) cur->form_col);
1530 		if ((cur->rows + cur->nrows) == 1) {
1531 			if ((cur->cols + cur->start_char) >= row->length)
1532 				len = row->length;
1533 			else
1534 				len = cur->cols + cur->start_char;
1535 			if (row->string != NULL)
1536 				slen = _formi_tab_expanded_length(
1537 					row->string, cur->start_char, len);
1538 			else
1539 				slen = 0;
1540 
1541 			if (slen > cur->cols)
1542 				slen = cur->cols;
1543 			slen += cur->start_char;
1544 		} else
1545 			slen = row->expanded;
1546 
1547 		if ((cur->opts & O_STATIC) == O_STATIC) {
1548 			switch (cur->justification) {
1549 			case JUSTIFY_RIGHT:
1550 				post = 0;
1551 				if (flen < slen)
1552 					pre = 0;
1553 				else
1554 					pre = flen - slen;
1555 				break;
1556 
1557 			case JUSTIFY_CENTER:
1558 				if (flen < slen) {
1559 					pre = 0;
1560 					post = 0;
1561 				} else {
1562 					pre = flen - slen;
1563 					post = pre = pre / 2;
1564 					  /* get padding right if
1565 					     centring is not even */
1566 					if ((post + pre + slen) < flen)
1567 						post++;
1568 				}
1569 				break;
1570 
1571 			case NO_JUSTIFICATION:
1572 			case JUSTIFY_LEFT:
1573 			default:
1574 				pre = 0;
1575 				if (flen <= slen)
1576 					post = 0;
1577 				else {
1578 					post = flen - slen;
1579 					if (post > flen)
1580 						post = flen;
1581 				}
1582 				break;
1583 			}
1584 		} else {
1585 			  /* dynamic fields are not justified */
1586 			pre = 0;
1587 			if (flen <= slen)
1588 				post = 0;
1589 			else {
1590 				post = flen - slen;
1591 				if (post > flen)
1592 					post = flen;
1593 			}
1594 
1595 			  /* but they do scroll.... */
1596 
1597 			if (pre > cur->start_char - start)
1598 				pre = pre - cur->start_char + start;
1599 			else
1600 				pre = 0;
1601 
1602 			if (slen > cur->start_char) {
1603 				slen -= cur->start_char;
1604 				if (slen > flen)
1605 					post = 0;
1606 				else
1607 					post = flen - slen;
1608 
1609 				if (post > flen)
1610 					post = flen;
1611 			} else {
1612 				slen = 0;
1613 				post = flen - pre;
1614 			}
1615 		}
1616 
1617 		if (form->cur_field == field)
1618 			wattrset(form->scrwin, cur->fore);
1619 		else
1620 			wattrset(form->scrwin, cur->back);
1621 
1622 		str = &row->string[cur->start_char];
1623 
1624 #ifdef DEBUG
1625 		_formi_dbg_printf(
1626 		    "%s: start=%d, pre=%d, slen=%d, flen=%d, post=%d, "
1627 		    "start_char=%d\n", __func__,
1628 		    start, pre, slen, flen, post, cur->start_char);
1629 		if (str != NULL) {
1630 			if (row->expanded != 0) {
1631 				strncpy(buffer, str, flen);
1632 			} else {
1633 				strcpy(buffer, "(empty)");
1634 			}
1635 		} else {
1636 			strcpy(buffer, "(null)");
1637 		}
1638 		buffer[flen] = '\0';
1639 		_formi_dbg_printf("%s: %s\n", __func__,  buffer);
1640 #endif
1641 
1642 		for (i = start + cur->start_char; i < pre; i++)
1643 			waddch(form->scrwin, cur->pad);
1644 
1645 		_formi_dbg_printf("%s: will add %d chars\n", __func__,
1646 			min(slen, flen));
1647 		for (i = 0, cpos = cur->start_char; i < min(slen, flen);
1648 		     i++, str++, cpos++)
1649 		{
1650 			c = *str;
1651 			tab = 0; /* just to shut gcc up */
1652 			_formi_dbg_printf("adding char str[%d]=%c\n",
1653 			    cpos + cur->start_char,	c);
1654 			if (((cur->opts & O_PUBLIC) != O_PUBLIC)) {
1655 				if (c == '\t')
1656 					tab = add_tab(form, row, cpos,
1657 						      cur->pad);
1658 				else
1659 					waddch(form->scrwin, cur->pad);
1660 			} else if ((cur->opts & O_VISIBLE) == O_VISIBLE) {
1661 				if (c == '\t')
1662 					tab = add_tab(form, row, cpos, ' ');
1663 				else
1664 					waddch(form->scrwin, c);
1665 			} else {
1666 				if (c == '\t')
1667 					tab = add_tab(form, row, cpos, ' ');
1668 				else
1669 					waddch(form->scrwin, ' ');
1670 			}
1671 
1672 			  /*
1673 			   * If we have had a tab then skip forward
1674 			   * the requisite number of chars to keep
1675 			   * things in sync.
1676 			   */
1677 			if (c == '\t')
1678 				i += tab - 1;
1679 		}
1680 
1681 		for (i = 0; i < post; i++)
1682 			waddch(form->scrwin, cur->pad);
1683 	}
1684 
1685 	for (i = line; i < cur->rows; i++) {
1686 		wmove(form->scrwin, (int) (cur->form_row + i),
1687 		      (int) cur->form_col);
1688 
1689 		if (form->cur_field == field)
1690 			wattrset(form->scrwin, cur->fore);
1691 		else
1692 			wattrset(form->scrwin, cur->back);
1693 
1694 		for (j = 0; j < cur->cols; j++) {
1695 			waddch(form->scrwin, cur->pad);
1696 		}
1697 	}
1698 
1699 	wattrset(form->scrwin, cur->back);
1700 	return;
1701 }
1702 
1703 /*
1704  * Add the correct number of the given character to simulate a tab
1705  * in the field.
1706  */
1707 static int
1708 add_tab(FORM *form, _FORMI_FIELD_LINES *row, unsigned int i, char c)
1709 {
1710 	int j;
1711 	_formi_tab_t *ts = row->tabs;
1712 
1713 	while ((ts != NULL) && (ts->pos != i))
1714 		ts = ts->fwd;
1715 
1716 	assert(ts != NULL);
1717 
1718 	for (j = 0; j < ts->size; j++)
1719 		waddch(form->scrwin, c);
1720 
1721 	return ts->size;
1722 }
1723 
1724 
1725 /*
1726  * Display the fields attached to the form that are on the current page
1727  * on the screen.
1728  *
1729  */
1730 int
1731 _formi_draw_page(FORM *form)
1732 {
1733 	int i;
1734 
1735 	if (form->page_starts[form->page].in_use == 0)
1736 		return E_BAD_ARGUMENT;
1737 
1738 	wclear(form->scrwin);
1739 
1740 	for (i = form->page_starts[form->page].first;
1741 	     i <= form->page_starts[form->page].last; i++)
1742 		_formi_redraw_field(form, i);
1743 
1744 	return E_OK;
1745 }
1746 
1747 /*
1748  * Add the character c at the position pos in buffer 0 of the given field
1749  */
1750 int
1751 _formi_add_char(FIELD *field, unsigned int pos, char c)
1752 {
1753 	char *new, old_c;
1754 	unsigned int new_size;
1755 	int status;
1756 	_FORMI_FIELD_LINES *row, *temp, *next_temp;
1757 
1758 	row = field->cur_line;
1759 
1760 	  /*
1761 	   * If buffer has not had a string before, set it to a blank
1762 	   * string.  Everything should flow from there....
1763 	   */
1764 	if (row->string == NULL) {
1765 		if ((row->string = (char *) malloc((size_t)INITIAL_LINE_ALLOC))
1766 		    == NULL)
1767 			return E_SYSTEM_ERROR;
1768 		row->string[0] = '\0';
1769 		row->allocated = INITIAL_LINE_ALLOC;
1770 		row->length = 0;
1771 		row->expanded = 0;
1772 	}
1773 
1774 	if (_formi_validate_char(field, c) != E_OK) {
1775 		_formi_dbg_printf("%s: char %c failed char validation\n",
1776 		    __func__, c);
1777 		return E_INVALID_FIELD;
1778 	}
1779 
1780 	if ((c == '\t') && (field->cols <= 8)) {
1781 		_formi_dbg_printf("%s: field too small for a tab\n", __func__);
1782 		return E_NO_ROOM;
1783 	}
1784 
1785 	_formi_dbg_printf("%s: pos=%d, char=%c\n", __func__, pos, c);
1786 	_formi_dbg_printf("%s: xpos=%d, row_pos=%d, start=%d\n", __func__,
1787 		field->cursor_xpos, field->row_xpos, field->start_char);
1788 	_formi_dbg_printf("%s: length=%d(%d), allocated=%d\n", __func__,
1789 		row->expanded, row->length, row->allocated);
1790 	_formi_dbg_printf("%s: %s\n", __func__, row->string);
1791 	_formi_dbg_printf("%s: buf0_status=%d\n", __func__, field->buf0_status);
1792 	if (((field->opts & O_BLANK) == O_BLANK) &&
1793 	    (field->buf0_status == FALSE) &&
1794 	    ((field->row_xpos + field->start_char) == 0)) {
1795 		row = field->alines;
1796 		if (row->next != NULL) {
1797 			  /* shift all but one line structs to free list */
1798 			temp = row->next;
1799 			do {
1800 				next_temp = temp->next;
1801 				add_to_free(field, temp);
1802 				temp = next_temp;
1803 			} while (temp != NULL);
1804 		}
1805 
1806 		row->length = 0;
1807 		row->string[0] = '\0';
1808 		pos = 0;
1809 		field->start_char = 0;
1810 		field->start_line = row;
1811 		field->cur_line = row;
1812 		field->row_count = 1;
1813 		field->row_xpos = 0;
1814 		field->cursor_ypos = 0;
1815 		row->expanded = 0;
1816 		row->length = 0;
1817 		_formi_init_field_xpos(field);
1818 	}
1819 
1820 
1821 	if ((field->overlay == 0)
1822 	    || ((field->overlay == 1) && (pos >= row->length))) {
1823 		  /* first check if the field can have more chars...*/
1824 		if (check_field_size(field) == FALSE)
1825 			return E_REQUEST_DENIED;
1826 
1827 		if (row->length + 2
1828 		    >= row->allocated) {
1829 			new_size = row->allocated + 16 - (row->allocated % 16);
1830 			if ((new = (char *) realloc(row->string,
1831 						  (size_t) new_size )) == NULL)
1832 				return E_SYSTEM_ERROR;
1833 			row->allocated = new_size;
1834 			row->string = new;
1835 		}
1836 	}
1837 
1838 	if ((field->overlay == 0) && (row->length > pos)) {
1839 		bcopy(&row->string[pos], &row->string[pos + 1],
1840 		      (size_t) (row->length - pos + 1));
1841 	}
1842 
1843 	old_c = row->string[pos];
1844 	row->string[pos] = c;
1845 	if (pos >= row->length) {
1846 		  /* make sure the string is terminated if we are at the
1847 		   * end of the string, the terminator would be missing
1848 		   * if we are at the end of the field.
1849 		   */
1850 		row->string[pos + 1] = '\0';
1851 	}
1852 
1853 	  /* only increment the length if we are inserting characters
1854 	   * OR if we are at the end of the field in overlay mode.
1855 	   */
1856 	if ((field->overlay == 0)
1857 	    || ((field->overlay == 1) && (pos >= row->length))) {
1858 		row->length++;
1859 	}
1860 
1861 	_formi_calculate_tabs(row);
1862 	row->expanded = _formi_tab_expanded_length(row->string, 0,
1863 						   row->length - 1);
1864 
1865 	  /* wrap the field, if needed */
1866 	status = _formi_wrap_field(field, row);
1867 
1868 	row = field->cur_line;
1869 	pos = field->row_xpos;
1870 
1871 	  /*
1872 	   * check the wrap worked or that we have not exceeded the
1873 	   * max field size - this can happen if the field is re-wrapped
1874 	   * and the row count is increased past the set limit.
1875 	   */
1876 	if ((status != E_OK) || (check_field_size(field) == FALSE)) {
1877 		if ((field->overlay == 0)
1878 		    || ((field->overlay == 1)
1879 			&& (pos >= (row->length - 1) /*XXXX- append check???*/))) {
1880 			  /*
1881 			   * wrap failed for some reason, back out the
1882 			   * char insert
1883 			   */
1884 			bcopy(&row->string[pos + 1], &row->string[pos],
1885 			      (size_t) (row->length - pos));
1886 			row->length--;
1887 			if (pos > 0)
1888 				pos--;
1889 		} else if (field->overlay == 1) {
1890 			  /* back out character overlay */
1891 			row->string[pos] = old_c;
1892 		}
1893 
1894 		_formi_calculate_tabs(row);
1895 
1896 		_formi_wrap_field(field, row);
1897 		  /*
1898 		   * If we are here then either the status is bad or we
1899 		   * simply ran out of room.  If the status is E_OK then
1900 		   * we ran out of room, let the form driver know this.
1901 		   */
1902 		if (status == E_OK)
1903 			status = E_REQUEST_DENIED;
1904 
1905 	} else {
1906 		field->buf0_status = TRUE;
1907 		field->row_xpos++;
1908 		if ((field->rows + field->nrows) == 1) {
1909 			status = _formi_set_cursor_xpos(field, FALSE);
1910 		} else {
1911 			field->cursor_xpos =
1912 				_formi_tab_expanded_length(
1913 					row->string, 0,	field->row_xpos - 1);
1914 
1915 			  /*
1916 			   * Annoying corner case - if we are right in
1917 			   * the bottom right corner of the field we
1918 			   * need to scroll the field one line so the
1919 			   * cursor is positioned correctly in the
1920 			   * field.
1921 			   */
1922 			if ((field->cursor_xpos >= field->cols) &&
1923 			    (field->cursor_ypos == (field->rows - 1))) {
1924 				field->cursor_ypos--;
1925 				field->start_line = field->start_line->next;
1926 			}
1927 		}
1928 	}
1929 
1930 	assert((field->cursor_xpos <= field->cols)
1931 	       && (field->cursor_ypos < 400000));
1932 
1933 	_formi_dbg_printf("%s: xpos=%d, row_pos=%d, start=%d\n", __func__,
1934 		field->cursor_xpos, field->row_xpos, field->start_char);
1935 	_formi_dbg_printf("%s: length=%d(%d), allocated=%d\n", __func__,
1936 		row->expanded, row->length, row->allocated);
1937 	_formi_dbg_printf("%s: ypos=%d, start_line=%p\n", __func__,
1938 		field->cursor_ypos, field->start_line);
1939 	_formi_dbg_printf("%s: %s\n", __func__, row->string);
1940 	_formi_dbg_printf("%s: buf0_status=%d\n", __func__, field->buf0_status);
1941 	_formi_dbg_printf("%s: status = %s\n", __func__,
1942 		(status == E_OK)? "OK" : "FAILED");
1943 	return status;
1944 }
1945 
1946 /*
1947  * Set the position of the cursor on the screen in the row depending on
1948  * where the current position in the string is and the justification
1949  * that is to be applied to the field.  Justification is only applied
1950  * to single row, static fields.
1951  */
1952 static int
1953 _formi_set_cursor_xpos(FIELD *field, int noscroll)
1954 {
1955 	int just, pos;
1956 
1957 	just = field->justification;
1958 	pos = field->start_char + field->row_xpos;
1959 
1960 	_formi_dbg_printf(
1961 	    "%s: pos %d, start_char %d, row_xpos %d, xpos %d\n", __func__,
1962 	    pos, field->start_char, field->row_xpos, field->cursor_xpos);
1963 
1964 	  /*
1965 	   * make sure we apply the correct justification to non-static
1966 	   * fields.
1967 	   */
1968 	if (((field->rows + field->nrows) != 1) ||
1969 	    ((field->opts & O_STATIC) != O_STATIC))
1970 		just = JUSTIFY_LEFT;
1971 
1972 	switch (just) {
1973 	case JUSTIFY_RIGHT:
1974 		field->cursor_xpos = field->cols - 1
1975 			- _formi_tab_expanded_length(
1976 				field->cur_line->string, 0,
1977 				field->cur_line->length - 1)
1978 			+ _formi_tab_expanded_length(
1979 				field->cur_line->string, 0,
1980 				field->row_xpos);
1981 		break;
1982 
1983 	case JUSTIFY_CENTER:
1984 		field->cursor_xpos = ((field->cols - 1)
1985 			- _formi_tab_expanded_length(
1986 				field->cur_line->string, 0,
1987 				field->cur_line->length - 1) + 1) / 2
1988 			+ _formi_tab_expanded_length(field->cur_line->string,
1989 						     0, field->row_xpos);
1990 
1991 		if (field->cursor_xpos > (field->cols - 1))
1992 			field->cursor_xpos = (field->cols - 1);
1993 		break;
1994 
1995 	default:
1996 		field->cursor_xpos = _formi_tab_expanded_length(
1997 			field->cur_line->string,
1998 			field->start_char,
1999 			field->row_xpos	+ field->start_char);
2000 		if ((field->cursor_xpos <= (field->cols - 1)) &&
2001 		    ((field->start_char + field->row_xpos)
2002 		     < field->cur_line->length))
2003 			field->cursor_xpos--;
2004 
2005 		if (field->cursor_xpos > (field->cols - 1)) {
2006 			if ((field->opts & O_STATIC) == O_STATIC) {
2007 				field->start_char = 0;
2008 
2009 				if (field->row_xpos
2010 				    == (field->cur_line->length - 1)) {
2011 					field->cursor_xpos = field->cols - 1;
2012 				} else {
2013 					field->cursor_xpos =
2014 						_formi_tab_expanded_length(
2015 						  field->cur_line->string,
2016 						  field->start_char,
2017 						  field->row_xpos
2018 						    + field->start_char
2019 						  - 1) - 1;
2020 				}
2021 			} else {
2022 				if (noscroll == FALSE) {
2023 					field->start_char =
2024 						tab_fit_window(
2025 							field,
2026 							field->start_char
2027 							+ field->row_xpos,
2028 							field->cols);
2029 					field->row_xpos = pos
2030 						- field->start_char;
2031 					field->cursor_xpos =
2032 						_formi_tab_expanded_length(
2033 						   field->cur_line->string,
2034 						   field->start_char,
2035 						   field->row_xpos
2036 						      + field->start_char - 1);
2037 				} else {
2038 					field->cursor_xpos = (field->cols - 1);
2039 				}
2040 			}
2041 
2042 		}
2043 		break;
2044 	}
2045 
2046 	_formi_dbg_printf(
2047 	    "%s: pos %d, start_char %d, row_xpos %d, xpos %d\n", __func__,
2048 	    pos, field->start_char, field->row_xpos, field->cursor_xpos);
2049 	return E_OK;
2050 }
2051 
2052 /*
2053  * Manipulate the text in a field, this takes the given form and performs
2054  * the passed driver command on the current text field.  Returns 1 if the
2055  * text field was modified.
2056  */
2057 int
2058 _formi_manipulate_field(FORM *form, int c)
2059 {
2060 	FIELD *cur;
2061 	char *str, saved;
2062 	unsigned int start, end, pos, status, old_count, size;
2063 	unsigned int old_xpos, old_row_pos;
2064 	int len, wb;
2065 	bool eat_char;
2066 	_FORMI_FIELD_LINES *row, *rs;
2067 
2068 	cur = form->fields[form->cur_field];
2069 	if (cur->cur_line->string == NULL)
2070 		return E_REQUEST_DENIED;
2071 
2072 	_formi_dbg_printf("%s: request is REQ_%s\n",
2073 	    __func__, reqs[c - REQ_MIN_REQUEST]);
2074 	_formi_dbg_printf(
2075 	    "%s: xpos=%d, row_pos=%d, start_char=%d, length=%d, allocated=%d\n",
2076 	    __func__, cur->cursor_xpos, cur->row_xpos, cur->start_char,
2077 	    cur->cur_line->length, cur->cur_line->allocated);
2078 	_formi_dbg_printf("%s: start_line=%p, ypos=%d\n", __func__,
2079 	    cur->start_line, cur->cursor_ypos);
2080 	if (cur->cur_line->string == NULL)
2081 		_formi_dbg_printf("%s: string=(null)\n", __func__);
2082 	else
2083 		_formi_dbg_printf("%s: string=\"%s\"\n", __func__,
2084 		    cur->cur_line->string);
2085 
2086 	  /* Cannot manipulate a null string! */
2087 	if (cur->cur_line->string == NULL)
2088 		return E_REQUEST_DENIED;
2089 
2090 	saved = '\0';
2091 	row = cur->cur_line;
2092 
2093 	switch (c) {
2094 	case REQ_RIGHT_CHAR:
2095 		  /*
2096 		   * The right_char request performs the same function
2097 		   * as the next_char request except that the cursor is
2098 		   * not wrapped if it is at the end of the line, so
2099 		   * check if the cursor is at the end of the line and
2100 		   * deny the request otherwise just fall through to
2101 		   * the next_char request handler.
2102 		   */
2103 		if (cur->cursor_xpos >= cur->cols - 1)
2104 			return E_REQUEST_DENIED;
2105 
2106 		  /* FALLTHRU */
2107 
2108 	case REQ_NEXT_CHAR:
2109 		  /* for a dynamic field allow an offset of one more
2110 		   * char so we can insert chars after end of string.
2111 		   * Static fields cannot do this so deny request if
2112 		   * cursor is at the end of the field.
2113 		   */
2114 		if (((cur->opts & O_STATIC) == O_STATIC) &&
2115 		    (cur->row_xpos == cur->cols - 1) &&
2116 		    ((cur->rows + cur->nrows) == 1))
2117 			return E_REQUEST_DENIED;
2118 
2119 		if (((cur->rows + cur->nrows) == 1) &&
2120 		    (cur->row_xpos + cur->start_char + 1) > row->length)
2121 			return E_REQUEST_DENIED;
2122 
2123 		if ((cur->rows + cur->nrows) == 1) {
2124 			cur->row_xpos++;
2125 			_formi_set_cursor_xpos(cur, (c == REQ_RIGHT_CHAR));
2126 		} else {
2127 			if (cur->cursor_xpos >= (row->expanded - 1)) {
2128 				if ((row->next == NULL) ||
2129 				    (c == REQ_RIGHT_CHAR))
2130 					return E_REQUEST_DENIED;
2131 
2132 				cur->cursor_xpos = 0;
2133 				cur->row_xpos = 0;
2134 				cur->cur_line = cur->cur_line->next;
2135 				if (cur->cursor_ypos == (cur->rows - 1))
2136 					cur->start_line =
2137 						cur->start_line->next;
2138 				else
2139 					cur->cursor_ypos++;
2140 			} else {
2141 				old_xpos = cur->cursor_xpos;
2142 				old_row_pos = cur->row_xpos;
2143 				if (row->string[cur->row_xpos] == '\t')
2144 					cur->cursor_xpos += tab_size(row,
2145 							cur->row_xpos);
2146 				else
2147 					cur->cursor_xpos++;
2148 				cur->row_xpos++;
2149 				if (cur->cursor_xpos
2150 				    >= row->expanded) {
2151 					if ((row->next == NULL) ||
2152 					    (c == REQ_RIGHT_CHAR)) {
2153 						cur->cursor_xpos = old_xpos;
2154 						cur->row_xpos = old_row_pos;
2155 						return E_REQUEST_DENIED;
2156 					}
2157 
2158 					cur->cursor_xpos = 0;
2159 					cur->row_xpos = 0;
2160 					cur->cur_line = cur->cur_line->next;
2161 					if (cur->cursor_ypos
2162 					    == (cur->rows - 1))
2163 						cur->start_line =
2164 							cur->start_line->next;
2165 					else
2166 						cur->cursor_ypos++;
2167 				}
2168 			}
2169 		}
2170 
2171 		break;
2172 
2173 	case REQ_LEFT_CHAR:
2174 		  /*
2175 		   * The behaviour of left_char is the same as prev_char
2176 		   * except that the cursor will not wrap if it has
2177 		   * reached the LHS of the field, so just check this
2178 		   * and fall through if we are not at the LHS.
2179 		   */
2180 		if (cur->cursor_xpos == 0)
2181 			return E_REQUEST_DENIED;
2182 
2183 		  /* FALLTHRU */
2184 	case REQ_PREV_CHAR:
2185 		if ((cur->rows + cur->nrows) == 1) {
2186 			if (cur->row_xpos == 0) {
2187 				if (cur->start_char > 0)
2188 					cur->start_char--;
2189 				else
2190 					return E_REQUEST_DENIED;
2191 			} else {
2192 				cur->row_xpos--;
2193 				_formi_set_cursor_xpos(cur, FALSE);
2194 			}
2195 		} else {
2196 			if ((cur->cursor_xpos == 0) &&
2197 			    (cur->cursor_ypos == 0) &&
2198 			    (cur->start_line->prev == NULL))
2199 				return E_REQUEST_DENIED;
2200 
2201 			pos = cur->row_xpos;
2202 			if (cur->cursor_xpos > 0) {
2203 				if (row->string[pos] == '\t') {
2204 					size = tab_size(row, pos);
2205 					if (size > cur->cursor_xpos) {
2206 						cur->cursor_xpos = 0;
2207 						cur->row_xpos = 0;
2208 					} else {
2209 						cur->row_xpos--;
2210 						cur->cursor_xpos -= size;
2211 					}
2212 				} else {
2213 					cur->cursor_xpos--;
2214 					cur->row_xpos--;
2215 				}
2216 			} else {
2217 				cur->cur_line = cur->cur_line->prev;
2218 				if (cur->cursor_ypos > 0)
2219 					cur->cursor_ypos--;
2220 				else
2221 					cur->start_line =
2222 						cur->start_line->prev;
2223 				row = cur->cur_line;
2224 				if (row->expanded > 0) {
2225 					cur->cursor_xpos = row->expanded - 1;
2226 				} else {
2227 					cur->cursor_xpos = 0;
2228 				}
2229 
2230 				if (row->length > 0)
2231 					cur->row_xpos = row->length - 1;
2232 				else
2233 					cur->row_xpos = 0;
2234 			}
2235 		}
2236 
2237 		break;
2238 
2239 	case REQ_DOWN_CHAR:
2240 		  /*
2241 		   * The down_char request has the same functionality as
2242 		   * the next_line request excepting that the field is not
2243 		   * scrolled if the cursor is at the bottom of the field.
2244 		   * Check to see if the cursor is at the bottom of the field
2245 		   * and if it is then deny the request otherwise fall
2246 		   * through to the next_line handler.
2247 		   */
2248 		if (cur->cursor_ypos >= cur->rows - 1)
2249 			return E_REQUEST_DENIED;
2250 
2251 		  /* FALLTHRU */
2252 
2253 	case REQ_NEXT_LINE:
2254 		if ((row->next == NULL) || (cur->cur_line->next == NULL))
2255 			return E_REQUEST_DENIED;
2256 
2257 		cur->cur_line = cur->cur_line->next;
2258 		if ((cur->cursor_ypos + 1) >= cur->rows) {
2259 			cur->start_line = cur->start_line->next;
2260 		} else
2261 			cur->cursor_ypos++;
2262 		row = cur->cur_line;
2263 
2264 		if (row->length == 0) {
2265 			cur->row_xpos = 0;
2266 			cur->cursor_xpos = 0;
2267 		} else {
2268 			if (cur->cursor_xpos > (row->expanded - 1))
2269 				cur->cursor_xpos = row->expanded - 1;
2270 
2271 			cur->row_xpos =	tab_fit_len(row, cur->cursor_xpos + 1);
2272 			if (cur->row_xpos == 0)
2273 				cur->cursor_xpos = 0;
2274 			else
2275 				cur->cursor_xpos =
2276 					_formi_tab_expanded_length(
2277 						row->string, 0, cur->row_xpos);
2278 			if (cur->cursor_xpos > 0)
2279 				cur->cursor_xpos--;
2280 		}
2281 		break;
2282 
2283 	case REQ_UP_CHAR:
2284 		  /*
2285 		   * The up_char request has the same functionality as
2286 		   * the prev_line request excepting the field is not
2287 		   * scrolled, check if the cursor is at the top of the
2288 		   * field, if it is deny the request otherwise fall
2289 		   * through to the prev_line handler.
2290 		   */
2291 		if (cur->cursor_ypos == 0)
2292 			return E_REQUEST_DENIED;
2293 
2294 		  /* FALLTHRU */
2295 
2296 	case REQ_PREV_LINE:
2297 		if (cur->cur_line->prev == NULL)
2298 			return E_REQUEST_DENIED;
2299 
2300 		if (cur->cursor_ypos == 0) {
2301 			if (cur->start_line->prev == NULL)
2302 				return E_REQUEST_DENIED;
2303 			cur->start_line = cur->start_line->prev;
2304 		} else
2305 			cur->cursor_ypos--;
2306 
2307 		cur->cur_line = cur->cur_line->prev;
2308 		row = cur->cur_line;
2309 
2310 		if (row->length == 0) {
2311 			cur->row_xpos = 0;
2312 			cur->cursor_xpos = 0;
2313 		} else {
2314 			if (cur->cursor_xpos > (row->expanded - 1))
2315 				cur->cursor_xpos = row->expanded - 1;
2316 
2317 			cur->row_xpos =	tab_fit_len(row, cur->cursor_xpos + 1);
2318 			cur->cursor_xpos =
2319 				_formi_tab_expanded_length(row->string,
2320 							   0, cur->row_xpos);
2321 			if (cur->cursor_xpos > 0)
2322 				cur->cursor_xpos--;
2323 		}
2324 		break;
2325 
2326 	case REQ_NEXT_WORD:
2327 		start = cur->row_xpos + cur->start_char;
2328 		str = row->string;
2329 
2330 		wb = find_eow(cur, start, FALSE, &row);
2331 		if (wb < 0)
2332 			return wb;
2333 
2334 		start = wb;
2335 		  /* check if we hit the end */
2336 		if (str[start] == '\0')
2337 			return E_REQUEST_DENIED;
2338 
2339 		  /* otherwise we must have found the start of a word...*/
2340 		if ((cur->rows + cur->nrows) == 1) {
2341 			  /* single line field */
2342 			size = _formi_tab_expanded_length(str,
2343 				cur->start_char, start);
2344 			if (size < cur->cols) {
2345 				cur->row_xpos = start - cur->start_char;
2346 			} else {
2347 				cur->start_char = start;
2348 				cur->row_xpos = 0;
2349 			}
2350 			_formi_set_cursor_xpos(cur, FALSE);
2351 		} else {
2352 			  /* multiline field */
2353 			cur->cur_line = row;
2354 			adjust_ypos(cur, row);
2355 
2356 			cur->row_xpos = start;
2357 			cur->cursor_xpos =
2358 				_formi_tab_expanded_length(
2359 					row->string, 0, cur->row_xpos) - 1;
2360 		}
2361 		break;
2362 
2363 	case REQ_PREV_WORD:
2364 		start = cur->start_char + cur->row_xpos;
2365 		if (cur->start_char > 0)
2366 			start--;
2367 
2368 		if ((start == 0) && (row->prev == NULL))
2369 			return E_REQUEST_DENIED;
2370 
2371 		if (start == 0) {
2372 			row = row->prev;
2373 			if (row->length > 0)
2374 				start = row->length - 1;
2375 			else
2376 				start = 0;
2377 		}
2378 
2379 		str = row->string;
2380 
2381 		start = find_sow(start, &row);
2382 
2383 		if ((cur->rows + cur->nrows) == 1) {
2384 			  /* single line field */
2385 			size = _formi_tab_expanded_length(str,
2386 				cur->start_char, start);
2387 
2388 			if (start > cur->start_char) {
2389 				cur->row_xpos = start - cur->start_char;
2390 			} else {
2391 				cur->start_char = start;
2392 				cur->row_xpos = 0;
2393 			}
2394 			_formi_set_cursor_xpos(cur, FALSE);
2395 		} else {
2396 			  /* multiline field */
2397 			cur->cur_line = row;
2398 			adjust_ypos(cur, row);
2399 			cur->row_xpos = start;
2400 			cur->cursor_xpos =
2401 				_formi_tab_expanded_length(
2402 					row->string, 0,
2403 					cur->row_xpos) - 1;
2404 		}
2405 
2406 		break;
2407 
2408 	case REQ_BEG_FIELD:
2409 		cur->start_char = 0;
2410 		while (cur->start_line->prev != NULL)
2411 			cur->start_line = cur->start_line->prev;
2412 		cur->cur_line = cur->start_line;
2413 		cur->row_xpos = 0;
2414 		_formi_init_field_xpos(cur);
2415 		cur->cursor_ypos = 0;
2416 		break;
2417 
2418 	case REQ_BEG_LINE:
2419 		cur->row_xpos = 0;
2420 		_formi_init_field_xpos(cur);
2421 		cur->start_char = 0;
2422 		break;
2423 
2424 	case REQ_END_FIELD:
2425 		while (cur->cur_line->next != NULL)
2426 			cur->cur_line = cur->cur_line->next;
2427 
2428 		if (cur->row_count > cur->rows) {
2429 			cur->start_line = cur->cur_line;
2430 			pos = cur->rows - 1;
2431 			while (pos > 0) {
2432 				cur->start_line = cur->start_line->prev;
2433 				pos--;
2434 			}
2435 			cur->cursor_ypos = cur->rows - 1;
2436 		} else {
2437 			cur->cursor_ypos = cur->row_count - 1;
2438 		}
2439 
2440 		  /* we fall through here deliberately, we are on the
2441 		   * correct row, now we need to get to the end of the
2442 		   * line.
2443 		   */
2444 		  /* FALLTHRU */
2445 
2446 	case REQ_END_LINE:
2447 		row = cur->cur_line;
2448 
2449 		if ((cur->rows + cur->nrows) == 1) {
2450 			if (row->expanded > cur->cols - 1) {
2451 				if ((cur->opts & O_STATIC) != O_STATIC) {
2452 					cur->start_char = tab_fit_window(
2453 						cur, row->length,
2454 						cur->cols) + 1;
2455 					cur->row_xpos = row->length
2456 						- cur->start_char;
2457 				} else {
2458 					cur->start_char = 0;
2459 					cur->row_xpos = cur->cols - 1;
2460 				}
2461 			} else {
2462 				cur->row_xpos = row->length + 1;
2463 				cur->start_char = 0;
2464 			}
2465 			_formi_set_cursor_xpos(cur, FALSE);
2466 		} else {
2467 			cur->row_xpos = row->length - 1;
2468 			cur->cursor_xpos = row->expanded - 1;
2469 			if (row->next == NULL) {
2470 				cur->row_xpos++;
2471 				cur->cursor_xpos++;
2472 			}
2473 		}
2474 		break;
2475 
2476 	case REQ_NEW_LINE:
2477 		start = cur->start_char	+ cur->row_xpos;
2478 		if ((status = split_line(cur, TRUE, start, &row)) != E_OK)
2479 			return status;
2480 		cur->cur_line->hard_ret = TRUE;
2481 		cur->cursor_xpos = 0;
2482 		cur->row_xpos = 0;
2483 		break;
2484 
2485 	case REQ_INS_CHAR:
2486 		if ((status = _formi_add_char(cur, cur->start_char
2487 					      + cur->row_xpos,
2488 					      cur->pad)) != E_OK)
2489 			return status;
2490 		break;
2491 
2492 	case REQ_INS_LINE:
2493 		if ((status = split_line(cur, TRUE, 0, &row)) != E_OK)
2494 			return status;
2495 		cur->cur_line->hard_ret = TRUE;
2496 		break;
2497 
2498 	case REQ_DEL_CHAR:
2499 		row = cur->cur_line;
2500 		start = cur->start_char + cur->row_xpos;
2501 		end = row->length - 1;
2502 		if ((start >= row->length) && (row->next == NULL))
2503 			return E_REQUEST_DENIED;
2504 
2505 		if ((start == row->length - 1) || (row->length == 0)) {
2506 			if ((cur->rows + cur->nrows) > 1) {
2507 				/*
2508 				 * Firstly, check if the current line has
2509 				 * a hard return.  In this case we just
2510 				 * want to "delete" the hard return and
2511 				 * re-wrap the field.  The hard return
2512 				 * does not occupy a character space in
2513 				 * the buffer but we must make it appear
2514 				 * like it does for a deletion.
2515 				 */
2516 				if (row->hard_ret == TRUE) {
2517 					row->hard_ret = FALSE;
2518 					if (_formi_join_line(cur, &row,
2519 							     JOIN_NEXT)
2520 					    != E_OK) {
2521 						row->hard_ret = TRUE;
2522 						return 0;
2523 					} else {
2524 						return 1;
2525 					}
2526 				}
2527 
2528 				/*
2529 				 * If we have more than one row, join the
2530 				 * next row to make things easier unless
2531 				 * we are at the end of the string, in
2532 				 * that case the join would fail but we
2533 				 * really want to delete the last char
2534 				 * in the field.
2535 				 */
2536 				if (row->next != NULL) {
2537 					if (_formi_join_line(cur, &row,
2538 							     JOIN_NEXT_NW)
2539 					    != E_OK) {
2540 						return E_REQUEST_DENIED;
2541 					}
2542 				}
2543 			}
2544 		}
2545 
2546 		saved = row->string[start];
2547 		bcopy(&row->string[start + 1], &row->string[start],
2548 		      (size_t) (end - start + 1));
2549 		row->string[end] = '\0';
2550 		row->length--;
2551 		if (row->length > 0)
2552 			row->expanded = _formi_tab_expanded_length(
2553 				row->string, 0, row->length - 1);
2554 		else
2555 			row->expanded = 0;
2556 
2557 		  /*
2558 		   * recalculate tabs for a single line field, multiline
2559 		   * fields will do this when the field is wrapped.
2560 		   */
2561 		if ((cur->rows + cur->nrows) == 1)
2562 			_formi_calculate_tabs(row);
2563 		  /*
2564 		   * if we are at the end of the string then back the
2565 		   * cursor pos up one to stick on the end of the line
2566 		   */
2567 		if (start == row->length) {
2568 			if (row->length > 1) {
2569 				if ((cur->rows + cur->nrows) == 1) {
2570 					pos = cur->row_xpos + cur->start_char;
2571 					cur->start_char =
2572 						tab_fit_window(
2573 							cur,
2574 							cur->start_char + cur->row_xpos,
2575 							cur->cols);
2576 					cur->row_xpos = pos - cur->start_char
2577 						- 1;
2578 					_formi_set_cursor_xpos(cur, FALSE);
2579 				} else {
2580 					if (cur->row_xpos == 0) {
2581 						if (row->next != NULL) {
2582 							if (_formi_join_line(
2583 								cur, &row,
2584 								JOIN_PREV_NW)
2585 							    != E_OK) {
2586 								return E_REQUEST_DENIED;
2587 							}
2588 						} else {
2589 							if (cur->row_count > 1)
2590 								cur->row_count--;
2591 						}
2592 
2593 					}
2594 
2595 					cur->row_xpos = start - 1;
2596 					cur->cursor_xpos =
2597 						_formi_tab_expanded_length(
2598 							row->string,
2599 							0, cur->row_xpos - 1);
2600 					if ((cur->cursor_xpos > 0)
2601 					    && (start != (row->expanded - 1)))
2602 						cur->cursor_xpos--;
2603 				}
2604 
2605 				start--;
2606 			} else {
2607 				start = 0;
2608 				cur->row_xpos = 0;
2609 				_formi_init_field_xpos(cur);
2610 			}
2611 		}
2612 
2613 		if ((cur->rows + cur->nrows) > 1) {
2614 			if (_formi_wrap_field(cur, row) != E_OK) {
2615 				bcopy(&row->string[start],
2616 				      &row->string[start + 1],
2617 				      (size_t) (end - start));
2618 				row->length++;
2619 				row->string[start] = saved;
2620 				_formi_wrap_field(cur, row);
2621 				return E_REQUEST_DENIED;
2622 			}
2623 		}
2624 		break;
2625 
2626 	case REQ_DEL_PREV:
2627 		if ((cur->cursor_xpos == 0) && (cur->start_char == 0)
2628 		    && (cur->start_line->prev == NULL)
2629 		    && (cur->cursor_ypos == 0))
2630 			   return E_REQUEST_DENIED;
2631 
2632 		row = cur->cur_line;
2633 		start = cur->row_xpos + cur->start_char;
2634 		end = row->length - 1;
2635 		eat_char = TRUE;
2636 
2637 		if ((cur->start_char + cur->row_xpos) == 0) {
2638 			if (row->prev == NULL)
2639 				return E_REQUEST_DENIED;
2640 
2641 			  /*
2642 			   * If we are a multiline field then check if
2643 			   * the line above has a hard return.  If it does
2644 			   * then just "eat" the hard return and re-wrap
2645 			   * the field.
2646 			   */
2647 			if (row->prev->hard_ret == TRUE) {
2648 				row->prev->hard_ret = FALSE;
2649 				if (_formi_join_line(cur, &row,
2650 						     JOIN_PREV) != E_OK) {
2651 					row->prev->hard_ret = TRUE;
2652 					return 0;
2653 				}
2654 
2655 				eat_char = FALSE;
2656 			} else {
2657 				start = row->prev->length;
2658 				  /*
2659 				   * Join this line to the previous
2660 				   * one.
2661 				   */
2662 				if (_formi_join_line(cur, &row,
2663 						     JOIN_PREV_NW) != E_OK) {
2664 					return 0;
2665 				}
2666 				end = row->length - 1;
2667 			}
2668 		}
2669 
2670 		if (eat_char == TRUE) {
2671 			  /*
2672 			   * eat a char from the buffer.  Normally we do
2673 			   * this unless we have deleted a "hard return"
2674 			   * in which case we just want to join the lines
2675 			   * without losing a char.
2676 			   */
2677 			saved = row->string[start - 1];
2678 			bcopy(&row->string[start], &row->string[start - 1],
2679 			      (size_t) (end - start + 1));
2680 			row->length--;
2681 			row->string[row->length] = '\0';
2682 			row->expanded = _formi_tab_expanded_length(
2683 				row->string, 0, row->length - 1);
2684 		}
2685 
2686 		if ((cur->rows + cur->nrows) == 1) {
2687 			_formi_calculate_tabs(row);
2688 			pos = cur->row_xpos + cur->start_char;
2689 			if (pos > 0)
2690 				pos--;
2691 			cur->start_char =
2692 				tab_fit_window(cur,
2693 					       cur->start_char + cur->row_xpos,
2694 					       cur->cols);
2695 			cur->row_xpos = pos - cur->start_char;
2696 			_formi_set_cursor_xpos(cur, FALSE);
2697 		} else {
2698 			if (eat_char == TRUE) {
2699 				cur->row_xpos--;
2700 				if (cur->row_xpos > 0)
2701 					cur->cursor_xpos =
2702 						_formi_tab_expanded_length(
2703 							row->string, 0,
2704 							cur->row_xpos - 1);
2705 				else
2706 					cur->cursor_xpos = 0;
2707 			}
2708 
2709 			if ((_formi_wrap_field(cur, row) != E_OK)) {
2710 				bcopy(&row->string[start - 1],
2711 				      &row->string[start],
2712 				      (size_t) (end - start));
2713 				row->length++;
2714 				row->string[start - 1] = saved;
2715 				row->string[row->length] = '\0';
2716 				_formi_wrap_field(cur, row);
2717 				return E_REQUEST_DENIED;
2718 			}
2719 		}
2720 		break;
2721 
2722 	case REQ_DEL_LINE:
2723 		if (((cur->rows + cur->nrows) == 1) ||
2724 		    (cur->row_count == 1)) {
2725 			  /* single line case */
2726 			row->length = 0;
2727 			row->expanded = row->length = 0;
2728 			cur->row_xpos = 0;
2729 			_formi_init_field_xpos(cur);
2730 			cur->cursor_ypos = 0;
2731 		} else {
2732 			  /* multiline field */
2733 			old_count = cur->row_count;
2734 			cur->row_count--;
2735 			if (cur->row_count == 0)
2736 				cur->row_count = 1;
2737 
2738 			if (old_count == 1) {
2739 				row->expanded = row->length = 0;
2740 				cur->cursor_xpos = 0;
2741 				cur->row_xpos = 0;
2742 				cur->cursor_ypos = 0;
2743 			} else
2744 				add_to_free(cur, row);
2745 
2746 			if (row->next == NULL) {
2747 				if (cur->cursor_ypos == 0) {
2748 					if (cur->start_line->prev != NULL) {
2749 						cur->start_line =
2750 							cur->start_line->prev;
2751 					}
2752 				} else {
2753 					cur->cursor_ypos--;
2754 				}
2755 			}
2756 
2757 			if (old_count > 1) {
2758 				if (cur->cursor_xpos > row->expanded) {
2759 					cur->cursor_xpos = row->expanded - 1;
2760 					cur->row_xpos = row->length - 1;
2761 				}
2762 
2763 				cur->start_line = cur->alines;
2764 				rs = cur->start_line;
2765 				cur->cursor_ypos = 0;
2766 				while (rs != row) {
2767 					if (cur->cursor_ypos < cur->rows)
2768 						cur->cursor_ypos++;
2769 					else
2770 						cur->start_line =
2771 							cur->start_line->next;
2772 					rs = rs->next;
2773 				}
2774 			}
2775 		}
2776 		break;
2777 
2778 	case REQ_DEL_WORD:
2779 		start = cur->start_char + cur->row_xpos;
2780 		str = row->string;
2781 
2782 		wb = find_eow(cur, start, TRUE, &row);
2783 		if (wb < 0)
2784 			return wb;
2785 
2786 		end = wb;
2787 
2788 		  /*
2789 		   * If not at the start of a word then find the start,
2790 		   * we cannot blindly call find_sow because this will
2791 		   * skip back a word if we are already at the start of
2792 		   * a word.
2793 		   */
2794 		if ((start > 0)
2795 		    && !(isblank((unsigned char)str[start - 1]) &&
2796 			!isblank((unsigned char)str[start])))
2797 			start = find_sow(start, &row);
2798 		str = row->string;
2799 		  /* XXXX hmmmm what if start and end on diff rows? XXXX */
2800 		bcopy(&str[end], &str[start],
2801 		      (size_t) (row->length - end + 1));
2802 		len = end - start;
2803 		row->length -= len;
2804 
2805 		if ((cur->rows + cur->nrows) > 1) {
2806 			row = cur->start_line + cur->cursor_ypos;
2807 			if (row->next != NULL) {
2808 				/*
2809 				 * if not on the last row we need to
2810 				 * join on the next row so the line
2811 				 * will be re-wrapped.
2812 				 */
2813 				_formi_join_line(cur, &row, JOIN_NEXT_NW);
2814 			}
2815 			_formi_wrap_field(cur, row);
2816 			cur->row_xpos = start;
2817 			cur->cursor_xpos = _formi_tab_expanded_length(
2818 				row->string, 0, cur->row_xpos);
2819 			if (cur->cursor_xpos > 0)
2820 				cur->cursor_xpos--;
2821 		} else {
2822 			_formi_calculate_tabs(row);
2823 			cur->row_xpos = start - cur->start_char;
2824 			if (cur->row_xpos > 0)
2825 				cur->row_xpos--;
2826 			_formi_set_cursor_xpos(cur, FALSE);
2827 		}
2828 		break;
2829 
2830 	case REQ_CLR_EOL:
2831 		row->string[cur->row_xpos + 1] = '\0';
2832 		row->length = cur->row_xpos + 1;
2833 		row->expanded = cur->cursor_xpos + 1;
2834 		break;
2835 
2836 	case REQ_CLR_EOF:
2837 		row = cur->cur_line->next;
2838 		while (row != NULL) {
2839 			rs = row->next;
2840 			add_to_free(cur, row);
2841 			row = rs;
2842 			cur->row_count--;
2843 		}
2844 		break;
2845 
2846 	case REQ_CLR_FIELD:
2847 		row = cur->alines->next;
2848 		cur->cur_line = cur->alines;
2849 		cur->start_line = cur->alines;
2850 
2851 		while (row != NULL) {
2852 			rs = row->next;
2853 			add_to_free(cur, row);
2854 			row = rs;
2855 		}
2856 
2857 		cur->alines->string[0] = '\0';
2858 		cur->alines->length = 0;
2859 		cur->alines->expanded = 0;
2860 		cur->row_count = 1;
2861 		cur->cursor_ypos = 0;
2862 		cur->row_xpos = 0;
2863 		_formi_init_field_xpos(cur);
2864 		cur->start_char = 0;
2865 		break;
2866 
2867 	case REQ_OVL_MODE:
2868 		cur->overlay = 1;
2869 		break;
2870 
2871 	case REQ_INS_MODE:
2872 		cur->overlay = 0;
2873 		break;
2874 
2875 	case REQ_SCR_FLINE:
2876 		_formi_scroll_fwd(cur, 1);
2877 		break;
2878 
2879 	case REQ_SCR_BLINE:
2880 		_formi_scroll_back(cur, 1);
2881 		break;
2882 
2883 	case REQ_SCR_FPAGE:
2884 		_formi_scroll_fwd(cur, cur->rows);
2885 		break;
2886 
2887 	case REQ_SCR_BPAGE:
2888 		_formi_scroll_back(cur, cur->rows);
2889 		break;
2890 
2891 	case REQ_SCR_FHPAGE:
2892 		_formi_scroll_fwd(cur, cur->rows / 2);
2893 		break;
2894 
2895 	case REQ_SCR_BHPAGE:
2896 		_formi_scroll_back(cur, cur->rows / 2);
2897 		break;
2898 
2899 	case REQ_SCR_FCHAR:
2900 		_formi_hscroll_fwd(cur, row, 1);
2901 		break;
2902 
2903 	case REQ_SCR_BCHAR:
2904 		_formi_hscroll_back(cur, row, 1);
2905 		break;
2906 
2907 	case REQ_SCR_HFLINE:
2908 		_formi_hscroll_fwd(cur, row, cur->cols);
2909 		break;
2910 
2911 	case REQ_SCR_HBLINE:
2912 		_formi_hscroll_back(cur, row, cur->cols);
2913 		break;
2914 
2915 	case REQ_SCR_HFHALF:
2916 		_formi_hscroll_fwd(cur, row, cur->cols / 2);
2917 		break;
2918 
2919 	case REQ_SCR_HBHALF:
2920 		_formi_hscroll_back(cur, row, cur->cols / 2);
2921 		break;
2922 
2923 	default:
2924 		return 0;
2925 	}
2926 
2927 	_formi_dbg_printf(
2928 	     "%s: cursor_xpos=%d, row_xpos=%d, start_char=%d, length=%d, "
2929 	     "allocated=%d\n", __func__, cur->cursor_xpos, cur->row_xpos,
2930 	     cur->start_char, cur->cur_line->length, cur->cur_line->allocated);
2931 	_formi_dbg_printf("%s: start_line=%p, ypos=%d\n", __func__,
2932 	    cur->start_line, cur->cursor_ypos);
2933 	_formi_dbg_printf("%s: string=\"%s\"\n", __func__,
2934 	    cur->cur_line->string);
2935 	assert ((cur->cursor_xpos < INT_MAX) && (cur->row_xpos < INT_MAX)
2936 		&& (cur->cursor_xpos >= cur->row_xpos));
2937 	return 1;
2938 }
2939 
2940 /*
2941  * Validate the given character by passing it to any type character
2942  * checking routines, if they exist.
2943  */
2944 int
2945 _formi_validate_char(FIELD *field, char c)
2946 {
2947 	int ret_val;
2948 
2949 	if (field->type == NULL)
2950 		return E_OK;
2951 
2952 	ret_val = E_INVALID_FIELD;
2953 	_formi_do_char_validation(field, field->type, c, &ret_val);
2954 
2955 	return ret_val;
2956 }
2957 
2958 
2959 /*
2960  * Perform the validation of the character, invoke all field_type validation
2961  * routines.  If the field is ok then update ret_val to E_OK otherwise
2962  * ret_val is not changed.
2963  */
2964 static void
2965 _formi_do_char_validation(FIELD *field, FIELDTYPE *type, char c, int *ret_val)
2966 {
2967 	if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
2968 		_formi_do_char_validation(field, type->link->next, c, ret_val);
2969 		_formi_do_char_validation(field, type->link->prev, c, ret_val);
2970 	} else {
2971 		if (type->char_check == NULL)
2972 			*ret_val = E_OK;
2973 		else {
2974 			if (type->char_check((int)(unsigned char) c,
2975 					     field->args) == TRUE)
2976 				*ret_val = E_OK;
2977 		}
2978 	}
2979 }
2980 
2981 /*
2982  * Validate the current field.  If the field validation returns success then
2983  * return E_OK otherwise return E_INVALID_FIELD.
2984  *
2985  */
2986 int
2987 _formi_validate_field(FORM *form)
2988 {
2989 	FIELD *cur;
2990 	int ret_val, count;
2991 
2992 
2993 	if ((form == NULL) || (form->fields == NULL) ||
2994 	    (form->fields[0] == NULL))
2995 		return E_INVALID_FIELD;
2996 
2997 	cur = form->fields[form->cur_field];
2998 
2999 	  /*
3000 	   * Sync the buffer if it has been modified so the field
3001 	   * validation routines can use it and because this is
3002 	   * the correct behaviour according to AT&T implementation.
3003 	   */
3004 	if ((cur->buf0_status == TRUE)
3005 	    && ((ret_val = _formi_sync_buffer(cur)) != E_OK))
3006 			return ret_val;
3007 
3008 	  /*
3009 	   * If buffer is untouched then the string pointer may be
3010 	   * NULL, see if this is ok or not.
3011 	   */
3012 	if (cur->buffers[0].string == NULL) {
3013 		if ((cur->opts & O_NULLOK) == O_NULLOK)
3014 			return E_OK;
3015 		else
3016 			return E_INVALID_FIELD;
3017 	}
3018 
3019 	count = _formi_skip_blanks(cur->buffers[0].string, 0);
3020 
3021 	  /* check if we have a null field, depending on the nullok flag
3022 	   * this may be acceptable or not....
3023 	   */
3024 	if (cur->buffers[0].string[count] == '\0') {
3025 		if ((cur->opts & O_NULLOK) == O_NULLOK)
3026 			return E_OK;
3027 		else
3028 			return E_INVALID_FIELD;
3029 	}
3030 
3031 	  /* check if an unmodified field is ok */
3032 	if (cur->buf0_status == 0) {
3033 		if ((cur->opts & O_PASSOK) == O_PASSOK)
3034 			return E_OK;
3035 		else
3036 			return E_INVALID_FIELD;
3037 	}
3038 
3039 	  /* if there is no type then just accept the field */
3040 	if (cur->type == NULL)
3041 		return E_OK;
3042 
3043 	ret_val = E_INVALID_FIELD;
3044 	_formi_do_validation(cur, cur->type, &ret_val);
3045 
3046 	return ret_val;
3047 }
3048 
3049 /*
3050  * Perform the validation of the field, invoke all field_type validation
3051  * routines.  If the field is ok then update ret_val to E_OK otherwise
3052  * ret_val is not changed.
3053  */
3054 static void
3055 _formi_do_validation(FIELD *field, FIELDTYPE *type, int *ret_val)
3056 {
3057 	if ((type->flags & _TYPE_IS_LINKED) == _TYPE_IS_LINKED) {
3058 		_formi_do_validation(field, type->link->next, ret_val);
3059 		_formi_do_validation(field, type->link->prev, ret_val);
3060 	} else {
3061 		if (type->field_check == NULL)
3062 			*ret_val = E_OK;
3063 		else {
3064 			if (type->field_check(field, field_buffer(field, 0))
3065 			    == TRUE)
3066 				*ret_val = E_OK;
3067 		}
3068 	}
3069 }
3070 
3071 /*
3072  * Select the next/previous choice for the field, the driver command
3073  * selecting the direction will be passed in c.  Return 1 if a choice
3074  * selection succeeded, 0 otherwise.
3075  */
3076 int
3077 _formi_field_choice(FORM *form, int c)
3078 {
3079 	FIELDTYPE *type;
3080 	FIELD *field;
3081 
3082 	if ((form == NULL) || (form->fields == NULL) ||
3083 	    (form->fields[0] == NULL) ||
3084 	    (form->fields[form->cur_field]->type == NULL))
3085 		return 0;
3086 
3087 	field = form->fields[form->cur_field];
3088 	type = field->type;
3089 
3090 	switch (c) {
3091 	case REQ_NEXT_CHOICE:
3092 		if (type->next_choice == NULL)
3093 			return 0;
3094 		else
3095 			return type->next_choice(field,
3096 						 field_buffer(field, 0));
3097 
3098 	case REQ_PREV_CHOICE:
3099 		if (type->prev_choice == NULL)
3100 			return 0;
3101 		else
3102 			return type->prev_choice(field,
3103 						 field_buffer(field, 0));
3104 
3105 	default: /* should never happen! */
3106 		return 0;
3107 	}
3108 }
3109 
3110 /*
3111  * Update the fields if they have changed.  The parameter old has the
3112  * previous current field as the current field may have been updated by
3113  * the driver.  Return 1 if the form page needs updating.
3114  *
3115  */
3116 int
3117 _formi_update_field(FORM *form, int old_field)
3118 {
3119 	int cur, i;
3120 
3121 	cur = form->cur_field;
3122 
3123 	if (old_field != cur) {
3124 		if (!((cur >= form->page_starts[form->page].first) &&
3125 		      (cur <= form->page_starts[form->page].last))) {
3126 			  /* not on same page any more */
3127 			for (i = 0; i < form->max_page; i++) {
3128 				if ((form->page_starts[i].in_use == 1) &&
3129 				    (form->page_starts[i].first <= cur) &&
3130 				    (form->page_starts[i].last >= cur)) {
3131 					form->page = i;
3132 					return 1;
3133 				}
3134 			}
3135 		}
3136 	}
3137 
3138 	_formi_redraw_field(form, old_field);
3139 	_formi_redraw_field(form, form->cur_field);
3140 	return 0;
3141 }
3142 
3143 /*
3144  * Compare function for the field sorting
3145  *
3146  */
3147 static int
3148 field_sort_compare(const void *one, const void *two)
3149 {
3150 	const FIELD *a, *b;
3151 	int tl;
3152 
3153 	  /* LINTED const castaway; we don't modify these! */
3154 	a = (const FIELD *) *((const FIELD **) one);
3155 	b = (const FIELD *) *((const FIELD **) two);
3156 
3157 	if (a == NULL)
3158 		return 1;
3159 
3160 	if (b == NULL)
3161 		return -1;
3162 
3163 	  /*
3164 	   * First check the page, we want the fields sorted by page.
3165 	   *
3166 	   */
3167 	if (a->page != b->page)
3168 		return ((a->page > b->page)? 1 : -1);
3169 
3170 	tl = _formi_top_left(a->parent, a->index, b->index);
3171 
3172 	  /*
3173 	   * sort fields left to right, top to bottom so the top left is
3174 	   * the lesser value....
3175 	   */
3176 	return ((tl == a->index)? -1 : 1);
3177 }
3178 
3179 /*
3180  * Sort the fields in a form ready for driver traversal.
3181  */
3182 void
3183 _formi_sort_fields(FORM *form)
3184 {
3185 	FIELD **sort_area;
3186 	int i;
3187 
3188 	TAILQ_INIT(&form->sorted_fields);
3189 
3190 	if ((sort_area = malloc(sizeof(*sort_area) * form->field_count))
3191 	    == NULL)
3192 		return;
3193 
3194 	bcopy(form->fields, sort_area,
3195 	      (size_t) (sizeof(FIELD *) * form->field_count));
3196 	qsort(sort_area, (size_t) form->field_count, sizeof(FIELD *),
3197 	      field_sort_compare);
3198 
3199 	for (i = 0; i < form->field_count; i++)
3200 		TAILQ_INSERT_TAIL(&form->sorted_fields, sort_area[i], glue);
3201 
3202 	free(sort_area);
3203 }
3204 
3205 /*
3206  * Set the neighbours for all the fields in the given form.
3207  */
3208 void
3209 _formi_stitch_fields(FORM *form)
3210 {
3211 	int above_row, below_row, end_above, end_below, cur_row, real_end;
3212 	FIELD *cur, *above, *below;
3213 
3214 	  /*
3215 	   * check if the sorted fields circle queue is empty, just
3216 	   * return if it is.
3217 	   */
3218 	if (TAILQ_EMPTY(&form->sorted_fields))
3219 		return;
3220 
3221 	  /* initially nothing is above..... */
3222 	above_row = -1;
3223 	end_above = TRUE;
3224 	above = NULL;
3225 
3226 	  /* set up the first field as the current... */
3227 	cur = TAILQ_FIRST(&form->sorted_fields);
3228 	cur_row = cur->form_row;
3229 
3230 	  /* find the first field on the next row if any */
3231 	below = TAILQ_NEXT(cur, glue);
3232 	below_row = -1;
3233 	end_below = TRUE;
3234 	real_end = TRUE;
3235 	while (below != NULL) {
3236 		if (below->form_row != cur_row) {
3237 			below_row = below->form_row;
3238 			end_below = FALSE;
3239 			real_end = FALSE;
3240 			break;
3241 		}
3242 		below = TAILQ_NEXT(below, glue);
3243 	}
3244 
3245 	  /* walk the sorted fields, setting the neighbour pointers */
3246 	while (cur != NULL) {
3247 		if (cur == TAILQ_FIRST(&form->sorted_fields))
3248 			cur->left = NULL;
3249 		else
3250 			cur->left = TAILQ_PREV(cur, _formi_sort_head, glue);
3251 
3252 		if (cur == TAILQ_LAST(&form->sorted_fields, _formi_sort_head))
3253 			cur->right = NULL;
3254 		else
3255 			cur->right = TAILQ_NEXT(cur, glue);
3256 
3257 		if (end_above == TRUE)
3258 			cur->up = NULL;
3259 		else {
3260 			cur->up = above;
3261 			above = TAILQ_NEXT(above, glue);
3262 			if (above_row != above->form_row) {
3263 				end_above = TRUE;
3264 				above_row = above->form_row;
3265 			}
3266 		}
3267 
3268 		if (end_below == TRUE)
3269 			cur->down = NULL;
3270 		else {
3271 			cur->down = below;
3272 			below = TAILQ_NEXT(below, glue);
3273 			if (below == NULL) {
3274 				end_below = TRUE;
3275 				real_end = TRUE;
3276 			} else if (below_row != below->form_row) {
3277 				end_below = TRUE;
3278 				below_row = below->form_row;
3279 			}
3280 		}
3281 
3282 		cur = TAILQ_NEXT(cur, glue);
3283 		if ((cur != NULL)
3284 		    && (cur_row != cur->form_row)) {
3285 			cur_row = cur->form_row;
3286 			if (end_above == FALSE) {
3287 				for (; above !=
3288 				    TAILQ_FIRST(&form->sorted_fields);
3289 				    above = TAILQ_NEXT(above, glue)) {
3290 					if (above->form_row != above_row) {
3291 						above_row = above->form_row;
3292 						break;
3293 					}
3294 				}
3295 			} else if (above == NULL) {
3296 				above = TAILQ_FIRST(&form->sorted_fields);
3297 				end_above = FALSE;
3298 				above_row = above->form_row;
3299 			} else
3300 				end_above = FALSE;
3301 
3302 			if (end_below == FALSE) {
3303 				while (below_row == below->form_row) {
3304 					below = TAILQ_NEXT(below, glue);
3305 					if (below == NULL) {
3306 						real_end = TRUE;
3307 						end_below = TRUE;
3308 						break;
3309 					}
3310 				}
3311 
3312 				if (below != NULL)
3313 					below_row = below->form_row;
3314 			} else if (real_end == FALSE)
3315 				end_below = FALSE;
3316 
3317 		}
3318 	}
3319 }
3320 
3321 /*
3322  * Calculate the length of the displayed line allowing for any tab
3323  * characters that need to be expanded.  We assume that the tab stops
3324  * are 8 characters apart.  The parameters start and end are the
3325  * character positions in the string str we want to get the length of,
3326  * the function returns the number of characters from the start
3327  * position to the end position that should be displayed after any
3328  * intervening tabs have been expanded.
3329  */
3330 int
3331 _formi_tab_expanded_length(char *str, unsigned int start, unsigned int end)
3332 {
3333 	int len, start_len, i;
3334 
3335 	  /* if we have a null string then there is no length */
3336 	if (str[0] == '\0')
3337 		return 0;
3338 
3339 	len = 0;
3340 	start_len = 0;
3341 
3342 	  /*
3343 	   * preceding tabs affect the length tabs in the span, so
3344 	   * we need to calculate the length including the stuff before
3345 	   * start and then subtract off the unwanted bit.
3346 	   */
3347 	for (i = 0; i <= end; i++) {
3348 		if (i == start) /* stash preamble length for later */
3349 			start_len = len;
3350 
3351 		if (str[i] == '\0')
3352 			break;
3353 
3354 		if (str[i] == '\t')
3355 			len = len - (len % 8) + 8;
3356 		else
3357 			len++;
3358 	}
3359 
3360 	_formi_dbg_printf(
3361 	    "%s: start=%d, end=%d, expanded=%d (diff=%d)\n", __func__,
3362 	    start, end, (len - start_len), (end - start));
3363 
3364 	return (len - start_len);
3365 }
3366 
3367 /*
3368  * Calculate the tab stops on a given line in the field and set up
3369  * the tabs list with the results.  We do this by scanning the line for tab
3370  * characters and if one is found, noting the position and the number of
3371  * characters to get to the next tab stop.  This information is kept to
3372  * make manipulating the field (scrolling and so on) easier to handle.
3373  */
3374 void
3375 _formi_calculate_tabs(_FORMI_FIELD_LINES *row)
3376 {
3377 	_formi_tab_t *ts = row->tabs, *old_ts = NULL, **tsp;
3378 	int i, j;
3379 
3380 	  /*
3381 	   * If the line already has tabs then invalidate them by
3382 	   * walking the list and killing the in_use flag.
3383 	   */
3384 	for (; ts != NULL; ts = ts->fwd)
3385 		ts->in_use = FALSE;
3386 
3387 
3388 	  /*
3389 	   * Now look for tabs in the row and record the info...
3390 	   */
3391 	tsp = &row->tabs;
3392 	for (i = 0, j = 0; i < row->length; i++, j++) {
3393 		if (row->string[i] == '\t') {
3394 			if (*tsp == NULL) {
3395 				if ((*tsp = (_formi_tab_t *)
3396 				     malloc(sizeof(_formi_tab_t))) == NULL)
3397 					return;
3398 				(*tsp)->back = old_ts;
3399 				(*tsp)->fwd = NULL;
3400 			}
3401 
3402 			(*tsp)->in_use = TRUE;
3403 			(*tsp)->pos = i;
3404 			(*tsp)->size = 8 - (j % 8);
3405 			j += (*tsp)->size - 1;
3406 			old_ts = *tsp;
3407 			tsp = &(*tsp)->fwd;
3408 		}
3409 	}
3410 }
3411 
3412 /*
3413  * Return the size of the tab padding for a tab character at the given
3414  * position.  Return 1 if there is not a tab char entry matching the
3415  * given location.
3416  */
3417 static int
3418 tab_size(_FORMI_FIELD_LINES *row, unsigned int i)
3419 {
3420 	_formi_tab_t *ts;
3421 
3422 	ts = row->tabs;
3423 	while ((ts != NULL) && (ts->pos != i))
3424 		ts = ts->fwd;
3425 
3426 	if (ts == NULL)
3427 		return 1;
3428 	else
3429 		return ts->size;
3430 }
3431 
3432 /*
3433  * Find the character offset that corresponds to longest tab expanded
3434  * string that will fit into the given window.  Walk the string backwards
3435  * evaluating the sizes of any tabs that are in the string.  Note that
3436  * using this function on a multi-line window will produce undefined
3437  * results - it is really only required for a single row field.
3438  */
3439 static int
3440 tab_fit_window(FIELD *field, unsigned int pos, unsigned int window)
3441 {
3442 	int scroll_amt, i;
3443 	_formi_tab_t *ts;
3444 
3445 	  /* first find the last tab */
3446 	ts = field->alines->tabs;
3447 
3448 	  /*
3449 	   * unless there are no tabs - just return the window size,
3450 	   * if there is enough room, otherwise 0.
3451 	   */
3452 	if (ts == NULL) {
3453 		if (field->alines->length < window)
3454 			return 0;
3455 		else
3456 			return field->alines->length - window + 1;
3457 	}
3458 
3459 	while ((ts->fwd != NULL) && (ts->fwd->in_use == TRUE))
3460 		ts = ts->fwd;
3461 
3462 	  /*
3463 	   * now walk backwards finding the first tab that is to the
3464 	   * left of our starting pos.
3465 	   */
3466 	while ((ts != NULL) && (ts->in_use == TRUE) && (ts->pos > pos))
3467 		ts = ts->back;
3468 
3469 	scroll_amt = 0;
3470 	for (i = pos; i >= 0; i--) {
3471 		if (field->alines->string[i] == '\t') {
3472 			assert((ts != NULL) && (ts->in_use == TRUE));
3473 			if (ts->pos == i) {
3474 				if ((scroll_amt + ts->size) > window) {
3475 					break;
3476 				}
3477 				scroll_amt += ts->size;
3478 				ts = ts->back;
3479 			}
3480 			else
3481 				assert(ts->pos == i);
3482 		} else {
3483 			scroll_amt++;
3484 			if (scroll_amt > window)
3485 				break;
3486 		}
3487 	}
3488 
3489 	return ++i;
3490 }
3491 
3492 /*
3493  * Return the position of the last character that will fit into the
3494  * given width after tabs have been expanded for a given row of a given
3495  * field.
3496  */
3497 static unsigned int
3498 tab_fit_len(_FORMI_FIELD_LINES *row, unsigned int width)
3499 {
3500 	unsigned int pos, len, row_pos;
3501 	_formi_tab_t *ts;
3502 
3503 	ts = row->tabs;
3504 	pos = 0;
3505 	len = 0;
3506 	row_pos = 0;
3507 
3508 	if (width == 0)
3509 		return 0;
3510 
3511 	while ((len < width) && (pos < row->length)) {
3512 		if (row->string[pos] == '\t') {
3513 			assert((ts != NULL) && (ts->in_use == TRUE));
3514 			if (ts->pos == row_pos) {
3515 				if ((len + ts->size) > width)
3516 					break;
3517 				len += ts->size;
3518 				ts = ts->fwd;
3519 			}
3520 			else
3521 				assert(ts->pos == row_pos);
3522 		} else
3523 			len++;
3524 		pos++;
3525 		row_pos++;
3526 	}
3527 
3528 	if (pos > 0)
3529 		pos--;
3530 	return pos;
3531 }
3532 
3533 /*
3534  * Sync the field line structures with the contents of buffer 0 for that
3535  * field.  We do this by walking all the line structures and concatenating
3536  * all the strings into one single string in buffer 0.
3537  */
3538 int
3539 _formi_sync_buffer(FIELD *field)
3540 {
3541 	_FORMI_FIELD_LINES *line;
3542 	char *nstr, *tmp;
3543 	unsigned length;
3544 
3545 	if (field->alines == NULL)
3546 		return E_BAD_ARGUMENT;
3547 
3548 	if (field->alines->string == NULL)
3549 		return E_BAD_ARGUMENT;
3550 
3551 	  /*
3552 	   * init nstr up front, just in case there are no line contents,
3553 	   * this could happen if the field just contains hard returns.
3554 	   */
3555 	if ((nstr = malloc(sizeof(char))) == NULL)
3556 		return E_SYSTEM_ERROR;
3557 	nstr[0] = '\0';
3558 
3559 	line = field->alines;
3560 	length = 1; /* allow for terminating null */
3561 
3562 	while (line != NULL) {
3563 		if (line->length != 0) {
3564 			if ((tmp = realloc(nstr,
3565 					   (size_t) (length + line->length)))
3566 			    == NULL) {
3567 				if (nstr != NULL)
3568 					free(nstr);
3569 				return (E_SYSTEM_ERROR);
3570 			}
3571 
3572 			nstr = tmp;
3573 			strcat(nstr, line->string);
3574 			length += line->length;
3575 		}
3576 
3577 		line = line->next;
3578 	}
3579 
3580 	if (field->buffers[0].string != NULL)
3581 		free(field->buffers[0].string);
3582 	field->buffers[0].allocated = length;
3583 	field->buffers[0].length = length - 1;
3584 	field->buffers[0].string = nstr;
3585 	return E_OK;
3586 }
3587 
3588 
3589 
3590