Home | History | Annotate | Download | only in mailx
      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