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