xref: /illumos-gate/usr/src/cmd/mailx/cmd2.c (revision f3041bfa)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*	Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T	*/
28 /*	  All Rights Reserved  	*/
29 
30 /*
31  * University Copyright- Copyright (c) 1982, 1986, 1988
32  * The Regents of the University of California
33  * All Rights Reserved
34  *
35  * University Acknowledgment- Portions of this document are derived from
36  * software developed by the University of California, Berkeley, and its
37  * contributors.
38  */
39 
40 #include "rcv.h"
41 #include <locale.h>
42 
43 /*
44  * mailx -- a modified version of a University of California at Berkeley
45  *	mail program
46  *
47  * More user commands.
48  */
49 
50 static int	igshow(void);
51 static int	igcomp(const void *l, const void *r);
52 static int	save1(char str[], int mark);
53 static int	Save1(int *msgvec, int mark);
54 static void	savemsglist(char *file, int *msgvec, int flag);
55 static int	put1(char str[], int doign);
56 static int	svputs(const char *line, FILE *obuf);
57 static int	wrputs(const char *line, FILE *obuf);
58 static int	retshow(void);
59 
60 /* flags for savemsglist() */
61 #define	S_MARK		1		/* mark the message as saved */
62 #define	S_NOHEADER	2		/* don't write out the header */
63 #define	S_SAVING	4		/* doing save/copy */
64 #define	S_NOIGNORE	8		/* don't do ignore processing */
65 
66 /*
67  * If any arguments were given, print the first message
68  * identified by the first argument. If no arguments are given,
69  * print the next applicable message after dot.
70  */
71 
72 int
73 next(int *msgvec)
74 {
75 	register struct message *mp;
76 	int list[2];
77 
78 	if (*msgvec != NULL) {
79 		if (*msgvec < 0) {
80 			printf((gettext("Negative message given\n")));
81 			return (1);
82 		}
83 		mp = &message[*msgvec - 1];
84 		if ((mp->m_flag & MDELETED) == 0) {
85 			dot = mp;
86 			goto hitit;
87 		}
88 		printf(gettext("No applicable message\n"));
89 		return (1);
90 	}
91 
92 	/*
93 	 * If this is the first command, select message 1.
94 	 * Note that this must exist for us to get here at all.
95 	 */
96 	if (!sawcom)
97 		goto hitit;
98 
99 	/*
100 	 * Just find the next good message after dot, no
101 	 * wraparound.
102 	 */
103 	for (mp = dot+1; mp < &message[msgCount]; mp++)
104 		if ((mp->m_flag & (MDELETED|MSAVED)) == 0)
105 			break;
106 	if (mp >= &message[msgCount]) {
107 		printf(gettext("At EOF\n"));
108 		return (0);
109 	}
110 	dot = mp;
111 hitit:
112 	/*
113 	 * Print dot.
114 	 */
115 	list[0] = dot - &message[0] + 1;
116 	list[1] = NULL;
117 	return (type(list));
118 }
119 
120 /*
121  * Save a message in a file.  Mark the message as saved
122  * so we can discard when the user quits.
123  */
124 int
125 save(char str[])
126 {
127 	return (save1(str, S_MARK));
128 }
129 
130 /*
131  * Copy a message to a file without affected its saved-ness
132  */
133 int
134 copycmd(char str[])
135 {
136 	return (save1(str, 0));
137 }
138 
139 /*
140  * Save/copy the indicated messages at the end of the passed file name.
141  * If mark is true, mark the message "saved."
142  */
143 static int
144 save1(char str[], int mark)
145 {
146 	char *file, *cmd;
147 	int f, *msgvec;
148 
149 	cmd = mark ? "save" : "copy";
150 	msgvec = (int *)salloc((msgCount + 2) * sizeof (*msgvec));
151 	if ((file = snarf(str, &f, 0)) == NOSTR)
152 		file = Getf("MBOX");
153 	if (f == -1)
154 		return (1);
155 	if (!f) {
156 		*msgvec = first(0, MMNORM);
157 		if (*msgvec == NULL) {
158 			printf(gettext("No messages to %s.\n"), cmd);
159 			return (1);
160 		}
161 		msgvec[1] = NULL;
162 	}
163 	if (f && getmsglist(str, msgvec, 0) < 0)
164 		return (1);
165 	if ((file = expand(file)) == NOSTR)
166 		return (1);
167 	savemsglist(file, msgvec, mark | S_SAVING);
168 	return (0);
169 }
170 
171 int
172 Save(int *msgvec)
173 {
174 	return (Save1(msgvec, S_MARK));
175 }
176 
177 int
178 Copy(int *msgvec)
179 {
180 	return (Save1(msgvec, 0));
181 }
182 
183 /*
184  * save/copy the indicated messages at the end of a file named
185  * by the sender of the first message in the msglist.
186  */
187 static int
188 Save1(int *msgvec, int mark)
189 {
190 	register char *from;
191 	char recfile[BUFSIZ];
192 
193 #ifdef notdef
194 	from = striphosts(nameof(&message[*msgvec-1], 0));
195 #else
196 	from = nameof(&message[*msgvec-1]);
197 #endif
198 	getrecf(from, recfile, 1, sizeof (recfile));
199 	if (*recfile != '\0')
200 		savemsglist(safeexpand(recfile), msgvec, mark | S_SAVING);
201 	return (0);
202 }
203 
204 int
205 sput(char str[])
206 {
207 	return (put1(str, 0));
208 }
209 
210 int
211 Sput(char str[])
212 {
213 	return (put1(str, S_NOIGNORE));
214 }
215 
216 /*
217  * Put the indicated messages at the end of the passed file name.
218  */
219 static int
220 put1(char str[], int doign)
221 {
222 	char *file;
223 	int f, *msgvec;
224 
225 	msgvec = (int *)salloc((msgCount + 2) * sizeof (*msgvec));
226 	if ((file = snarf(str, &f, 0)) == NOSTR)
227 		file = Getf("MBOX");
228 	if (f == -1)
229 		return (1);
230 	if (!f) {
231 		*msgvec = first(0, MMNORM);
232 		if (*msgvec == NULL) {
233 			printf(gettext("No messages to put.\n"));
234 			return (1);
235 		}
236 		msgvec[1] = NULL;
237 	}
238 	if (f && getmsglist(str, msgvec, 0) < 0)
239 		return (1);
240 	if ((file = expand(file)) == NOSTR)
241 		return (1);
242 	savemsglist(file, msgvec, doign);
243 	return (0);
244 }
245 
246 /*
247  * save a message list in a file.
248  * if wr set, doing "write" instead
249  * of "save" or "copy" so don't put
250  * out header.
251  */
252 
253 static	int wr_linecount;		/* count of lines written */
254 static	int wr_charcount;		/* char count of lines written */
255 static	int wr_inlines;			/* count of lines read */
256 static	long wr_maxlines;		/* total lines in message */
257 static	int wr_inhead;			/* in header of message */
258 
259 static void
260 savemsglist(char *file, int *msgvec, int flag)
261 {
262 	register int *ip, mesg;
263 	register struct message *mp;
264 	char *disp;
265 	FILE *obuf;
266 	struct stat statb;
267 	long lc, cc, t;
268 	int bnry, mflag;
269 
270 	printf("\"%s\" ", file);
271 	flush();
272 	if (stat(file, &statb) >= 0)
273 		disp = "[Appended]";
274 	else
275 		disp = "[New file]";
276 	if ((obuf = fopen(file, "a")) == NULL) {
277 		perror("");
278 		return;
279 	}
280 	lc = cc = 0;
281 	bnry = 0;
282 	if (flag & S_SAVING)
283 		mflag = (int)value("alwaysignore")?(M_IGNORE|M_SAVING):M_SAVING;
284 	else if (flag & S_NOIGNORE)
285 		mflag = 0;
286 	else
287 		mflag = M_IGNORE;
288 	for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
289 		mesg = *ip;
290 		mp = &message[mesg-1];
291 		if (!mp->m_text) {
292 			bnry = 1;
293 		}
294 		wr_linecount = 0;
295 		wr_charcount = 0;
296 		if (flag & S_NOHEADER) {
297 			wr_inhead = 1;
298 			wr_maxlines = mp->m_lines;
299 			wr_inlines = 0;
300 			t = msend(mp, obuf, 0, wrputs);
301 		} else {
302 			t = msend(mp, obuf, mflag, svputs);
303 		}
304 		if (t < 0) {
305 			perror(file);
306 			fclose(obuf);
307 			return;
308 		}
309 		touch(mesg);
310 		dot = mp;
311 		lc += wr_linecount;
312 		cc += wr_charcount;
313 		if (flag & S_MARK)
314 			mp->m_flag |= MSAVED;
315 	}
316 	fflush(obuf);
317 	if (fferror(obuf))
318 		perror(file);
319 	fclose(obuf);
320 	if (!bnry) {
321 		printf("%s %ld/%ld\n", disp, lc, cc);
322 	} else {
323 		printf("%s binary/%ld\n", disp, cc);
324 	}
325 }
326 
327 static int
328 svputs(const char *line, FILE *obuf)
329 {
330 	wr_linecount++;
331 	wr_charcount += strlen(line);
332 	return (fputs(line, obuf));
333 }
334 
335 static int
336 wrputs(const char *line, FILE *obuf)
337 {
338 	/*
339 	 * If this is a header line or
340 	 * the last line, don't write it out.  Since we may add a
341 	 * "Status" line the line count may be off by one so insist
342 	 * that the last line is blank before we skip it.
343 	 */
344 	wr_inlines++;
345 	if (wr_inhead) {
346 		if (strcmp(line, "\n") == 0)
347 			wr_inhead = 0;
348 		return (0);
349 	}
350 	if (wr_inlines >= wr_maxlines && strcmp(line, "\n") == 0)
351 		return (0);
352 	wr_linecount++;
353 	wr_charcount += strlen(line);
354 	return (fputs(line, obuf));
355 }
356 
357 /*
358  * Write the indicated messages at the end of the passed
359  * file name, minus header and trailing blank line.
360  */
361 
362 int
363 swrite(char str[])
364 {
365 	register char *file;
366 	int f, *msgvec;
367 
368 	msgvec = (int *)salloc((msgCount + 2) * sizeof (*msgvec));
369 	if ((file = snarf(str, &f, 1)) == NOSTR)
370 		return (1);
371 	if (f == -1)
372 		return (1);
373 	if ((file = expand(file)) == NOSTR)
374 		return (1);
375 	if (!f) {
376 		*msgvec = first(0, MMNORM);
377 		if (*msgvec == NULL) {
378 			printf(gettext("No messages to write.\n"));
379 			return (1);
380 		}
381 		msgvec[1] = NULL;
382 	}
383 	if (f && getmsglist(str, msgvec, 0) < 0)
384 		return (1);
385 	savemsglist(file, msgvec, S_MARK|S_NOHEADER);
386 	return (0);
387 }
388 
389 /*
390  * Snarf the file from the end of the command line and
391  * return a pointer to it.  If there is no file attached,
392  * just return NOSTR.  Put a null in front of the file
393  * name so that the message list processing won't see it,
394  * unless the file name is the only thing on the line, in
395  * which case, return 0 in the reference flag variable.
396  */
397 
398 /*
399  * The following definitions are used to characterize the syntactic
400  * category of the preceding character in the following parse procedure.
401  * The variable pc_type assumes these values.
402  */
403 
404 #define	SN_DELIM	1	/* Delimiter (<blank> or line beginning) */
405 #define	SN_TOKEN	2	/* A part of a token */
406 #define	SN_QUOTE	4	/* An entire quoted string (ie, "...") */
407 
408 char *
409 snarf(char linebuf[], int *flag, int erf)
410 {
411 	register char *p;		/* utility pointer */
412 	register char qc;		/* quotation character to match */
413 	register unsigned int  pc_type;	/* preceding character type */
414 	register char *tok_beg;		/* beginning of last token */
415 	register char *tok_end;		/* end of last token */
416 	char *line_beg;			/* beginning of line, after */
417 					/* leading whitespace */
418 
419 	/*
420 	 * Skip leading whitespace.
421 	 */
422 	for (line_beg = linebuf;
423 	    *line_beg && any(*line_beg, " \t");
424 	    line_beg++) {
425 		/* empty body */
426 	}
427 	if (!*line_beg) {
428 		if (erf) {
429 			printf(gettext("No file specified.\n"));
430 		}
431 		*flag = 0;
432 		return (NOSTR);
433 	}
434 	/*
435 	 * Process line from left-to-right, 1 char at a time.
436 	 */
437 	pc_type = SN_DELIM;
438 	tok_beg = tok_end = NOSTR;
439 	p = line_beg;
440 	while (*p != '\0') {
441 		if (any(*p, " \t")) {
442 			/* This character is a DELIMITER */
443 			if (pc_type & (SN_TOKEN|SN_QUOTE)) {
444 				tok_end = p - 1;
445 			}
446 			pc_type = SN_DELIM;
447 			p++;
448 		} else if ((qc = *p) == '"' || qc == '\'') {
449 			/* This character is a QUOTE character */
450 			if (pc_type == SN_TOKEN) {
451 				/* embedded quotation symbols are simply */
452 				/* token characters. */
453 				p++;
454 				continue;
455 			}
456 			/* Search for the matching QUOTE character */
457 			for (tok_beg = p, tok_end = NOSTR, p++;
458 			    *p != '\0' && *p != qc;
459 			    p++) {
460 				if (*p == '\\' && *(p+1) == qc) {
461 					p++;
462 				}
463 			}
464 			if (*p == '\0') {
465 				printf(gettext("Syntax error: missing "
466 				    "%c.\n"), qc);
467 				*flag = -1;
468 				return (NOSTR);
469 			}
470 			tok_end = p;
471 			pc_type = SN_QUOTE;
472 			p++;
473 		} else {
474 			/* This character should be a TOKEN character */
475 			if (pc_type & (SN_DELIM|SN_TOKEN)) {
476 				if (pc_type & SN_DELIM) {
477 					tok_beg = p;
478 					tok_end = NOSTR;
479 				}
480 			} else {
481 				printf(gettext("improper quotes"
482 				    " at \"%s\".\n"), p);
483 				*flag = -1;
484 				return (NOSTR);
485 			}
486 			if (*p == '\\' && *++p == '\0') {
487 				printf(gettext("\'\\\' at "
488 				    "end of line.\n"));
489 				*flag = -1;
490 				return (NOSTR);
491 			}
492 			pc_type = SN_TOKEN;
493 			p++;
494 		}
495 	}
496 	if (pc_type == SN_TOKEN) {
497 		tok_end = p - 1;
498 	}
499 	if (tok_beg != NOSTR && tok_end != NOSTR) {
500 		if (tok_beg == line_beg) {
501 			*flag = 0;
502 		} else {
503 			tok_beg[-1] = '\0';
504 			*flag = 1;
505 		}
506 		tok_end[1] = '\0';
507 		return (tok_beg);
508 	} else {
509 		if (erf) {
510 			printf(gettext("No file specified.\n"));
511 		}
512 		*flag = 0;
513 		return (NOSTR);
514 	}
515 }
516 
517 /*
518  * Delete messages, then type the new dot.
519  */
520 
521 int
522 deltype(int msgvec[])
523 {
524 	int list[2];
525 	int lastdot;
526 
527 	lastdot = dot - &message[0] + 1;
528 	if (delm(msgvec) >= 0) {
529 		list[0] = dot - &message[0];
530 		list[0]++;
531 		if (list[0] > lastdot) {
532 			touch(list[0]);
533 			list[1] = NULL;
534 			return (type(list));
535 		}
536 		printf(gettext("At EOF\n"));
537 		return (0);
538 	} else {
539 		printf(gettext("No more messages\n"));
540 		return (0);
541 	}
542 }
543 
544 /*
545  * Delete the indicated messages.
546  * Set dot to some nice place afterwards.
547  */
548 int
549 delm(int *msgvec)
550 {
551 	register struct message *mp;
552 	int *ip, mesg;
553 	int last;
554 
555 	last = NULL;
556 	for (ip = msgvec; *ip != NULL; ip++) {
557 		mesg = *ip;
558 		touch(mesg);
559 		mp = &message[mesg-1];
560 		mp->m_flag |= MDELETED|MTOUCH;
561 		mp->m_flag &= ~(MPRESERVE|MSAVED|MBOX);
562 		last = mesg;
563 	}
564 	if (last != NULL) {
565 		dot = &message[last-1];
566 		last = first(0, MDELETED);
567 		if (last != NULL) {
568 			dot = &message[last-1];
569 			return (0);
570 		} else {
571 			dot = &message[0];
572 			return (-1);
573 		}
574 	}
575 
576 	/*
577 	 * Following can't happen -- it keeps lint happy
578 	 */
579 
580 	return (-1);
581 }
582 
583 /*
584  * Undelete the indicated messages.
585  */
586 int
587 undelete(int *msgvec)
588 {
589 	register struct message *mp;
590 	int *ip, mesg;
591 
592 	for (ip = msgvec; ip-msgvec < msgCount; ip++) {
593 		mesg = *ip;
594 		if (mesg == 0)
595 			return (0);
596 		touch(mesg);
597 		mp = &message[mesg-1];
598 		dot = mp;
599 		mp->m_flag &= ~MDELETED;
600 	}
601 	return (0);
602 }
603 
604 /*
605  * Add the given header fields to the retained list.
606  * If no arguments, print the current list of retained fields.
607  */
608 int
609 retfield(char *list[])
610 {
611 	char field[BUFSIZ];
612 	register int h;
613 	register struct ignore *igp;
614 	char **ap;
615 
616 	if (argcount(list) == 0)
617 		return (retshow());
618 	for (ap = list; *ap != 0; ap++) {
619 		istrcpy(field, sizeof (field), *ap);
620 
621 		if (member(field, retain))
622 			continue;
623 
624 		h = hash(field);
625 		if ((igp = (struct ignore *)
626 		    calloc(1, sizeof (struct ignore))) == NULL) {
627 			panic("Couldn't allocate memory");
628 		}
629 		if ((igp->i_field = (char *)
630 		    calloc(strlen(field) + 1, sizeof (char))) == NULL) {
631 			panic("Couldn't allocate memory");
632 		}
633 		strcpy(igp->i_field, field);
634 		igp->i_link = retain[h];
635 		retain[h] = igp;
636 		nretained++;
637 	}
638 	return (0);
639 }
640 
641 /*
642  * Print out all currently retained fields.
643  */
644 static int
645 retshow(void)
646 {
647 	register int h, count;
648 	struct ignore *igp;
649 	char **ap, **ring;
650 
651 	count = 0;
652 	for (h = 0; h < HSHSIZE; h++)
653 		for (igp = retain[h]; igp != 0; igp = igp->i_link)
654 			count++;
655 	if (count == 0) {
656 		printf(gettext("No fields currently being retained.\n"));
657 		return (0);
658 	}
659 	ring = (char **)salloc((count + 1) * sizeof (char *));
660 	ap = ring;
661 	for (h = 0; h < HSHSIZE; h++)
662 		for (igp = retain[h]; igp != 0; igp = igp->i_link)
663 			*ap++ = igp->i_field;
664 	*ap = 0;
665 	qsort(ring, count, sizeof (char *), igcomp);
666 	for (ap = ring; *ap != 0; ap++)
667 		printf("%s\n", *ap);
668 	return (0);
669 }
670 
671 /*
672  * Remove a list of fields from the retain list.
673  */
674 int
675 unretfield(char *list[])
676 {
677 	char **ap, field[BUFSIZ];
678 	register int h, count = 0;
679 	register struct ignore *ig1, *ig2;
680 
681 	if (argcount(list) == 0) {
682 		for (h = 0; h < HSHSIZE; h++) {
683 			ig1 = retain[h];
684 			while (ig1) {
685 				free(ig1->i_field);
686 				ig2 = ig1->i_link;
687 				free((char *)ig1);
688 				ig1 = ig2;
689 				count++;
690 			}
691 			retain[h] = NULL;
692 		}
693 		if (count == 0)
694 			printf(gettext(
695 			    "No fields currently being retained.\n"));
696 		nretained = 0;
697 		return (0);
698 	}
699 	for (ap = list; *ap; ap++) {
700 		istrcpy(field, sizeof (field), *ap);
701 		h = hash(field);
702 		for (ig1 = retain[h]; ig1; ig2 = ig1, ig1 = ig1->i_link)
703 			if (strcmp(ig1->i_field, field) == 0) {
704 				if (ig1 == retain[h])
705 					retain[h] = ig1->i_link;
706 				else
707 					ig2->i_link = ig1->i_link;
708 				free(ig1->i_field);
709 				free((char *)ig1);
710 				nretained--;
711 				break;
712 			}
713 	}
714 	return (0);
715 }
716 
717 /*
718  * Add the given header fields to the ignored list.
719  * If no arguments, print the current list of ignored fields.
720  */
721 int
722 igfield(char *list[])
723 {
724 	char field[BUFSIZ];
725 	register int h;
726 	register struct ignore *igp;
727 	char **ap;
728 
729 	if (argcount(list) == 0)
730 		return (igshow());
731 	for (ap = list; *ap != 0; ap++) {
732 		if (isign(*ap, 0))
733 			continue;
734 		istrcpy(field, sizeof (field), *ap);
735 		h = hash(field);
736 		if ((igp = (struct ignore *)
737 		    calloc(1, sizeof (struct ignore))) == NULL) {
738 			panic("Couldn't allocate memory");
739 		}
740 		if ((igp->i_field = (char *)
741 		    calloc((unsigned)strlen(field) + 1,
742 		    sizeof (char))) == NULL) {
743 			panic("Couldn't allocate memory");
744 		}
745 		strcpy(igp->i_field, field);
746 		igp->i_link = ignore[h];
747 		ignore[h] = igp;
748 	}
749 	return (0);
750 }
751 
752 /*
753  * Print out all currently ignored fields.
754  */
755 static int
756 igshow(void)
757 {
758 	register int h, count;
759 	struct ignore *igp;
760 	char **ap, **ring;
761 
762 	count = 0;
763 	for (h = 0; h < HSHSIZE; h++)
764 		for (igp = ignore[h]; igp != 0; igp = igp->i_link)
765 			count++;
766 	if (count == 0) {
767 		printf(gettext("No fields currently being ignored.\n"));
768 		return (0);
769 	}
770 	ring = (char **)salloc((count + 1) * sizeof (char *));
771 	ap = ring;
772 	for (h = 0; h < HSHSIZE; h++)
773 		for (igp = ignore[h]; igp != 0; igp = igp->i_link)
774 			*ap++ = igp->i_field;
775 	*ap = 0;
776 	qsort((char *)ring, (unsigned)count, sizeof (char *), igcomp);
777 	for (ap = ring; *ap != 0; ap++)
778 		printf("%s\n", *ap);
779 	return (0);
780 }
781 
782 /*
783  * Compare two names for sorting ignored field list.
784  */
785 static int
786 igcomp(const void *l, const void *r)
787 {
788 	return (strcmp(*(char **)l, *(char **)r));
789 }
790 
791 /*
792  * Remove a list of fields from the ignore list.
793  */
794 int
795 unigfield(char *list[])
796 {
797 	char **ap, field[BUFSIZ];
798 	register int h, count = 0;
799 	register struct ignore *ig1, *ig2;
800 
801 	if (argcount(list) == 0) {
802 		for (h = 0; h < HSHSIZE; h++) {
803 			ig1 = ignore[h];
804 			while (ig1) {
805 				free(ig1->i_field);
806 				ig2 = ig1->i_link;
807 				free((char *)ig1);
808 				ig1 = ig2;
809 				count++;
810 			}
811 			ignore[h] = NULL;
812 		}
813 		if (count == 0)
814 			printf(gettext("No fields currently being ignored.\n"));
815 		return (0);
816 	}
817 	for (ap = list; *ap; ap++) {
818 		istrcpy(field, sizeof (field), *ap);
819 		h = hash(field);
820 		for (ig1 = ignore[h]; ig1; ig2 = ig1, ig1 = ig1->i_link)
821 			if (strcmp(ig1->i_field, field) == 0) {
822 				if (ig1 == ignore[h])
823 					ignore[h] = ig1->i_link;
824 				else
825 					ig2->i_link = ig1->i_link;
826 				free(ig1->i_field);
827 				free((char *)ig1);
828 				break;
829 			}
830 	}
831 	return (0);
832 }
833