Home | History | Annotate | Line # | Download | only in mail
support.c revision 1.21
      1 /*	$NetBSD: support.c,v 1.21 2008/04/24 01:27:07 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 1980, 1993
      5  *	The Regents of the University of California.  All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  * 3. Neither the name of the University nor the names of its contributors
     16  *    may be used to endorse or promote products derived from this software
     17  *    without specific prior written permission.
     18  *
     19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     29  * SUCH DAMAGE.
     30  */
     31 
     32 #include <sys/cdefs.h>
     33 #ifndef lint
     34 #if 0
     35 static char sccsid[] = "@(#)aux.c	8.1 (Berkeley) 6/6/93";
     36 #else
     37 __RCSID("$NetBSD: support.c,v 1.21 2008/04/24 01:27:07 christos Exp $");
     38 #endif
     39 #endif /* not lint */
     40 
     41 #include <stdarg.h>
     42 #include <util.h>
     43 
     44 #include "rcv.h"
     45 #include "extern.h"
     46 #include "mime.h"
     47 #include "thread.h"
     48 
     49 /*
     50  * Mail -- a mail program
     51  *
     52  * Auxiliary functions.
     53  */
     54 
     55 
     56 /*
     57  * Return a pointer to a dynamic copy of the argument.
     58  */
     59 PUBLIC char *
     60 savestr(const char *str)
     61 {
     62 	char *new;
     63 	size_t size = strlen(str) + 1;
     64 
     65 	if ((new = salloc(size)) != NULL)
     66 		(void)memmove(new, str, size);
     67 	return new;
     68 }
     69 
     70 /*
     71  * Make a copy of new argument incorporating old one.
     72  */
     73 static char *
     74 save2str(char *str, char *old)
     75 {
     76 	char *new;
     77 	size_t newsize = strlen(str) + 1;
     78 	size_t oldsize = old ? strlen(old) + 1 : 0;
     79 
     80 	if ((new = salloc(newsize + oldsize)) != NULL) {
     81 		if (oldsize) {
     82 			(void)memmove(new, old, oldsize);
     83 			new[oldsize - 1] = ' ';
     84 		}
     85 		(void)memmove(new + oldsize, str, newsize);
     86 	}
     87 	return new;
     88 }
     89 
     90 /*
     91  * Like asprintf(), but with result salloc-ed rather than malloc-ed.
     92  */
     93 PUBLIC int
     94 sasprintf(char **ret, const char *format, ...)
     95 {
     96 	int rval;
     97 	char *p;
     98 	va_list args;
     99 
    100 	va_start(args, format);
    101 	rval = evasprintf(&p, format, args);
    102 	*ret = savestr(p);
    103 	free(p);
    104 	va_end(args);
    105 	return rval;
    106 }
    107 
    108 struct set_m_flag_args_s {
    109 	int and_bits;
    110 	int xor_bits;
    111 };
    112 static int
    113 set_m_flag_core(struct message *mp, void *v)
    114 {
    115 	struct set_m_flag_args_s *args;
    116 	args = v;
    117 	mp->m_flag &= args->and_bits;
    118 	mp->m_flag ^= args->xor_bits;
    119 	return 0;
    120 }
    121 
    122 PUBLIC struct message *
    123 set_m_flag(int msgnum, int and_bits, int xor_bits)
    124 {
    125 	struct message *mp;
    126 	struct set_m_flag_args_s args;
    127 	mp = get_message(msgnum);
    128 	args.and_bits = and_bits;
    129 	args.xor_bits = xor_bits;
    130 	(void)thread_recursion(mp, set_m_flag_core, &args);
    131 	return mp;
    132 }
    133 
    134 /*
    135  * Touch the named message by setting its MTOUCH flag.
    136  * Touched messages have the effect of not being sent
    137  * back to the system mailbox on exit.
    138  */
    139 PUBLIC void
    140 touch(struct message *mp)
    141 {
    142 
    143 	mp->m_flag |= MTOUCH;
    144 	if ((mp->m_flag & MREAD) == 0)
    145 		mp->m_flag |= MREAD|MSTATUS;
    146 }
    147 
    148 /*
    149  * Test to see if the passed file name is a directory.
    150  * Return true if it is.
    151  */
    152 PUBLIC int
    153 isdir(const char name[])
    154 {
    155 	struct stat sbuf;
    156 
    157 	if (stat(name, &sbuf) < 0)
    158 		return 0;
    159 	return S_ISDIR(sbuf.st_mode);
    160 }
    161 
    162 /*
    163  * Count the number of arguments in the given string raw list.
    164  */
    165 PUBLIC int
    166 argcount(char **argv)
    167 {
    168 	char **ap;
    169 
    170 	for (ap = argv; *ap++ != NULL; /*EMPTY*/)
    171 		continue;
    172 	return ap - argv - 1;
    173 }
    174 
    175 /*
    176  * Check whether the passed line is a header line of
    177  * the desired breed.  Return the field body, or NULL.
    178  */
    179 static char*
    180 ishfield(const char linebuf[], char *colon, const char field[])
    181 {
    182 	char *cp = colon;
    183 
    184 	*cp = 0;
    185 	if (strcasecmp(linebuf, field) != 0) {
    186 		*cp = ':';
    187 		return NULL;
    188 	}
    189 	*cp = ':';
    190 	for (cp++; is_WSP(*cp); cp++)
    191 		continue;
    192 	return cp;
    193 }
    194 
    195 /*
    196  * Return the next header field found in the given message.
    197  * Return >= 0 if something found, < 0 elsewise.
    198  * "colon" is set to point to the colon in the header.
    199  * Must deal with \ continuations & other such fraud.
    200  *
    201  * WARNING - do not call this outside hfield() or decoding will not
    202  *           get done.
    203  */
    204 static int
    205 gethfield(FILE *f, char linebuf[], int rem, char **colon)
    206 {
    207 	char line2[LINESIZE];
    208 	char *cp, *cp2;
    209 	int c;
    210 
    211 	for (;;) {
    212 		if (--rem < 0)
    213 			return -1;
    214 		if ((c = mail_readline(f, linebuf, LINESIZE)) <= 0)
    215 			return -1;
    216 		for (cp = linebuf;
    217 		     isprint((unsigned char)*cp) && *cp != ' ' && *cp != ':';
    218 		     cp++)
    219 			continue;
    220 		if (*cp != ':' || cp == linebuf)
    221 			continue;
    222 		/*
    223 		 * I guess we got a headline.
    224 		 * Handle wraparounding
    225 		 */
    226 		*colon = cp;
    227 		cp = linebuf + c;
    228 		for (;;) {
    229 			while (--cp >= linebuf && is_WSP(*cp))
    230 				continue;
    231 			cp++;
    232 			if (rem <= 0)
    233 				break;
    234 			(void)ungetc(c = getc(f), f);
    235 			if (!is_WSP(c))
    236 				break;
    237 			if ((c = mail_readline(f, line2, LINESIZE)) < 0)
    238 				break;
    239 			rem--;
    240 			cp2 = skip_WSP(line2);
    241 			c -= cp2 - line2;
    242 			if (cp + c >= linebuf + LINESIZE - 2)
    243 				break;
    244 			*cp++ = ' ';
    245 			(void)memmove(cp, cp2, (size_t)c);
    246 			cp += c;
    247 		}
    248 		*cp = 0;
    249 		return rem;
    250 	}
    251 	/* NOTREACHED */
    252 }
    253 
    254 /*
    255  * Return the desired header line from the passed message
    256  * pointer (or NULL if the desired header field is not available).
    257  */
    258 PUBLIC char *
    259 hfield(const char field[], const struct message *mp)
    260 {
    261 	FILE *ibuf;
    262 	char linebuf[LINESIZE];
    263 	int lc;
    264 	char *headerfield;
    265 	char *colon, *oldhfield = NULL;
    266 #ifdef MIME_SUPPORT
    267 	int decode;
    268 
    269 	decode = value(ENAME_MIME_DECODE_MSG) &&
    270 	    value(ENAME_MIME_DECODE_HDR);
    271 #endif
    272 
    273 	ibuf = setinput(mp);
    274 	if ((lc = mp->m_lines - 1) < 0)
    275 		return NULL;
    276 	if (mail_readline(ibuf, linebuf, LINESIZE) < 0)
    277 		return NULL;
    278 	while (lc > 0) {
    279 		if ((lc = gethfield(ibuf, linebuf, lc, &colon)) < 0)
    280 			return oldhfield;
    281 #ifdef MIME_SUPPORT
    282 		if ((headerfield = ishfield(linebuf, colon, field)) != NULL) {
    283 			char linebuf2[LINESIZE];
    284 			if (decode)
    285 				headerfield = mime_decode_hfield(linebuf2,
    286 				    sizeof(linebuf2), linebuf, headerfield);
    287 			oldhfield = save2str(headerfield, oldhfield);
    288 		}
    289 #else
    290 		if ((headerfield = ishfield(linebuf, colon, field)) != NULL)
    291 			oldhfield = save2str(headerfield, oldhfield);
    292 #endif
    293 	}
    294 	return oldhfield;
    295 }
    296 
    297 /*
    298  * Copy a string, lowercasing it as we go.
    299  */
    300 PUBLIC void
    301 istrcpy(char *dest, const char *src)
    302 {
    303 
    304 	do {
    305 		*dest++ = tolower((unsigned char)*src);
    306 	} while (*src++ != 0);
    307 }
    308 
    309 /*
    310  * The following code deals with input stacking to do source
    311  * commands.  All but the current file pointer are saved on
    312  * the stack.
    313  */
    314 
    315 static	int	ssp;			/* Top of file stack */
    316 static struct sstack {
    317 	FILE	*s_file;		/* File we were in. */
    318 	int	s_cond;			/* Saved state of conditionals */
    319 #ifdef NEW_CONDITIONAL
    320 	struct cond_stack_s *s_cond_stack; /* Saved conditional stack */
    321 #endif
    322 	int	s_loading;		/* Loading .mailrc, etc. */
    323 } sstack[NOFILE];
    324 
    325 /*
    326  * Pushdown current input file and switch to a new one.
    327  * Set the global flag "sourcing" so that others will realize
    328  * that they are no longer reading from a tty (in all probability).
    329  */
    330 PUBLIC int
    331 source(void *v)
    332 {
    333 	char **arglist = v;
    334 	FILE *fi;
    335 	const char *cp;
    336 
    337 	if ((cp = expand(*arglist)) == NULL)
    338 		return 1;
    339 	if ((fi = Fopen(cp, "r")) == NULL) {
    340 		warn("%s", cp);
    341 		return 1;
    342 	}
    343 	if (ssp >= NOFILE - 1) {
    344 		(void)printf("Too much \"sourcing\" going on.\n");
    345 		(void)Fclose(fi);
    346 		return 1;
    347 	}
    348 	sstack[ssp].s_file = input;
    349 	sstack[ssp].s_cond = cond;
    350 #ifdef NEW_CONDITIONAL
    351 	sstack[ssp].s_cond_stack = cond_stack;
    352 #endif
    353 	sstack[ssp].s_loading = loading;
    354 	ssp++;
    355 	loading = 0;
    356 	cond = CNONE;
    357 	input = fi;
    358 	sourcing++;
    359 	return 0;
    360 }
    361 
    362 /*
    363  * Pop the current input back to the previous level.
    364  * Update the "sourcing" flag as appropriate.
    365  */
    366 PUBLIC int
    367 unstack(void)
    368 {
    369 	if (ssp <= 0) {
    370 		(void)printf("\"Source\" stack over-pop.\n");
    371 		sourcing = 0;
    372 		return 1;
    373 	}
    374 	(void)Fclose(input);
    375 	if (cond != CNONE || cond_stack != NULL)
    376 		(void)printf("Unmatched \"if\"\n");
    377 	ssp--;
    378 	input = sstack[ssp].s_file;
    379 	cond = sstack[ssp].s_cond;
    380 #ifdef NEW_CONDITIONAL
    381 	cond_stack = sstack[ssp].s_cond_stack;
    382 #endif
    383 	loading = sstack[ssp].s_loading;
    384 	if (ssp == 0)
    385 		sourcing = loading;
    386 	return 0;
    387 }
    388 
    389 /*
    390  * Touch the indicated file.
    391  * This is nifty for the shell.
    392  */
    393 PUBLIC void
    394 alter(char *name)
    395 {
    396 	struct stat sb;
    397 	struct timeval tv[2];
    398 
    399 	if (stat(name, &sb))
    400 		return;
    401 	(void)gettimeofday(&tv[0], NULL);
    402 	tv[0].tv_sec++;
    403 	TIMESPEC_TO_TIMEVAL(&tv[1], &sb.st_mtimespec);
    404 	(void)utimes(name, tv);
    405 }
    406 
    407 /*
    408  * Examine the passed line buffer and
    409  * return true if it is all blanks and tabs.
    410  */
    411 PUBLIC int
    412 blankline(char linebuf[])
    413 {
    414 	char *cp;
    415 
    416 	for (cp = linebuf; *cp; cp++)
    417 		if (!is_WSP(*cp))
    418 			return 0;
    419 	return 1;
    420 }
    421 
    422 /*
    423  * Start of a "comment".
    424  * Ignore it.
    425  */
    426 static char *
    427 skip_comment(char *cp)
    428 {
    429 	int nesting = 1;
    430 
    431 	for (/*EMPTY*/; nesting > 0 && *cp; cp++) {
    432 		switch (*cp) {
    433 		case '\\':
    434 			if (cp[1])
    435 				cp++;
    436 			break;
    437 		case '(':
    438 			nesting++;
    439 			break;
    440 		case ')':
    441 			nesting--;
    442 			break;
    443 		}
    444 	}
    445 	return cp;
    446 }
    447 
    448 /*
    449  * Skin an arpa net address according to the RFC 822 interpretation
    450  * of "host-phrase."
    451  */
    452 PUBLIC char *
    453 skin(char *name)
    454 {
    455 	int c;
    456 	char *cp, *cp2;
    457 	char *bufend;
    458 	int gotlt, lastsp;
    459 	char nbuf[LINESIZE];
    460 
    461 	if (name == NULL)
    462 		return NULL;
    463 	if (strchr(name, '(') == NULL && strchr(name, '<') == NULL
    464 	    && strchr(name, ' ') == NULL)
    465 		return name;
    466 	gotlt = 0;
    467 	lastsp = 0;
    468 	bufend = nbuf;
    469 	for (cp = name, cp2 = bufend; (c = *cp++) != '\0'; /*EMPTY*/) {
    470 		switch (c) {
    471 		case '(':
    472 			cp = skip_comment(cp);
    473 			lastsp = 0;
    474 			break;
    475 
    476 		case '"':
    477 			/*
    478 			 * Start of a "quoted-string".
    479 			 * Copy it in its entirety.
    480 			 */
    481 			while ((c = *cp) != '\0') {
    482 				cp++;
    483 				if (c == '"')
    484 					break;
    485 				if (c != '\\')
    486 					*cp2++ = c;
    487 				else if ((c = *cp) != '\0') {
    488 					*cp2++ = c;
    489 					cp++;
    490 				}
    491 			}
    492 			lastsp = 0;
    493 			break;
    494 
    495 		case ' ':
    496 			if (cp[0] == 'a' && cp[1] == 't' && cp[2] == ' ')
    497 				cp += 3, *cp2++ = '@';
    498 			else
    499 			if (cp[0] == '@' && cp[1] == ' ')
    500 				cp += 2, *cp2++ = '@';
    501 			else
    502 				lastsp = 1;
    503 			break;
    504 
    505 		case '<':
    506 			cp2 = bufend;
    507 			gotlt++;
    508 			lastsp = 0;
    509 			break;
    510 
    511 		case '>':
    512 			if (gotlt) {
    513 				gotlt = 0;
    514 				while ((c = *cp) && c != ',') {
    515 					cp++;
    516 					if (c == '(')
    517 						cp = skip_comment(cp);
    518 					else if (c == '"')
    519 						while ((c = *cp) != '\0') {
    520 							cp++;
    521 							if (c == '"')
    522 								break;
    523 							if (c == '\\' && *cp)
    524 								cp++;
    525 						}
    526 				}
    527 				lastsp = 0;
    528 				break;
    529 			}
    530 			/* FALLTHROUGH */
    531 
    532 		default:
    533 			if (lastsp) {
    534 				lastsp = 0;
    535 				*cp2++ = ' ';
    536 			}
    537 			*cp2++ = c;
    538 			if (c == ',' && !gotlt) {
    539 				*cp2++ = ' ';
    540 				for (/*EMPTY*/; *cp == ' '; cp++)
    541 					continue;
    542 				lastsp = 0;
    543 				bufend = cp2;
    544 			}
    545 		}
    546 	}
    547 	*cp2 = 0;
    548 
    549 	return savestr(nbuf);
    550 }
    551 
    552 /*
    553  * Fetch the sender's name from the passed message.
    554  * Reptype can be
    555  *	0 -- get sender's name for display purposes
    556  *	1 -- get sender's name for reply
    557  *	2 -- get sender's name for Reply
    558  */
    559 static char *
    560 name1(struct message *mp, int reptype)
    561 {
    562 	char namebuf[LINESIZE];
    563 	char linebuf[LINESIZE];
    564 	char *cp, *cp2;
    565 	FILE *ibuf;
    566 	int firstrun = 1;
    567 
    568 	if ((cp = hfield("from", mp)) != NULL)
    569 		return cp;
    570 	if (reptype == 0 && (cp = hfield("sender", mp)) != NULL)
    571 		return cp;
    572 	ibuf = setinput(mp);
    573 	namebuf[0] = '\0';
    574 	if (mail_readline(ibuf, linebuf, LINESIZE) < 0)
    575 		return savestr(namebuf);
    576  newname:
    577 	for (cp = linebuf; *cp && *cp != ' '; cp++)
    578 		continue;
    579 	cp = skip_WSP(cp);
    580 	for (cp2 = &namebuf[strlen(namebuf)];
    581 	     *cp && !is_WSP(*cp) && cp2 < namebuf + LINESIZE - 1;
    582 	     /*EMPTY*/)
    583 		*cp2++ = *cp++;
    584 	*cp2 = '\0';
    585 	if (mail_readline(ibuf, linebuf, LINESIZE) < 0)
    586 		return savestr(namebuf);
    587 	if ((cp = strchr(linebuf, 'F')) == NULL)
    588 		return savestr(namebuf);
    589 	if (strncmp(cp, "From", 4) != 0)
    590 		return savestr(namebuf);
    591 	while ((cp = strchr(cp, 'r')) != NULL) {
    592 		if (strncmp(cp, "remote", 6) == 0) {
    593 			if ((cp = strchr(cp, 'f')) == NULL)
    594 				break;
    595 			if (strncmp(cp, "from", 4) != 0)
    596 				break;
    597 			if ((cp = strchr(cp, ' ')) == NULL)
    598 				break;
    599 			cp++;
    600 			if (firstrun) {
    601 				cp2 = namebuf;
    602 				firstrun = 0;
    603 			} else
    604 				cp2 = strrchr(namebuf, '!') + 1;
    605 			while (*cp && cp2 < namebuf + LINESIZE - 1)
    606 				*cp2++ = *cp++;
    607 			if (cp2 < namebuf + LINESIZE - 1)
    608 				*cp2++ = '!';
    609 			*cp2 = '\0';
    610 			if (cp2 < namebuf + LINESIZE - 1)
    611 				goto newname;
    612 			else
    613 				break;
    614 		}
    615 		cp++;
    616 	}
    617 	return savestr(namebuf);
    618 }
    619 
    620 /*
    621  * Count the occurrences of c in str
    622  */
    623 static int
    624 charcount(char *str, int c)
    625 {
    626 	char *cp;
    627 	int i;
    628 
    629 	for (i = 0, cp = str; *cp; cp++)
    630 		if (*cp == c)
    631 			i++;
    632 	return i;
    633 }
    634 
    635 /*
    636  * Get sender's name from this message.  If the message has
    637  * a bunch of arpanet stuff in it, we may have to skin the name
    638  * before returning it.
    639  */
    640 PUBLIC char *
    641 nameof(struct message *mp, int reptype)
    642 {
    643 	char *cp, *cp2;
    644 
    645 	cp = skin(name1(mp, reptype));
    646 	if (reptype != 0 || charcount(cp, '!') < 2)
    647 		return cp;
    648 	cp2 = strrchr(cp, '!');
    649 	cp2--;
    650 	while (cp2 > cp && *cp2 != '!')
    651 		cp2--;
    652 	if (*cp2 == '!')
    653 		return cp2 + 1;
    654 	return cp;
    655 }
    656 
    657 /*
    658  * Copy s1 to s2, return pointer to null in s2.
    659  */
    660 PUBLIC char *
    661 copy(char *s1, char *s2)
    662 {
    663 
    664 	while ((*s2++ = *s1++) != '\0')
    665 		continue;
    666 	return s2 - 1;
    667 }
    668 
    669 /*
    670  * The core routine to check the ignore table for a field.
    671  * Note: realfield should be lower-cased!
    672  */
    673 PUBLIC int
    674 member(char *realfield, struct ignoretab *table)
    675 {
    676 	struct ignore *igp;
    677 
    678 	for (igp = table->i_head[hash(realfield)]; igp != 0; igp = igp->i_link)
    679 		if (*igp->i_field == *realfield &&
    680 		    equal(igp->i_field, realfield))
    681 			return 1;
    682 	return 0;
    683 }
    684 
    685 /*
    686  * See if the given header field is supposed to be ignored.
    687  */
    688 PUBLIC int
    689 isign(const char *field, struct ignoretab ignoretabs[2])
    690 {
    691 	char realfld[LINESIZE];
    692 
    693 	if (ignoretabs == ignoreall)
    694 		return 1;
    695 	/*
    696 	 * Lower-case the string, so that "Status" and "status"
    697 	 * will hash to the same place.
    698 	 */
    699 	istrcpy(realfld, field);
    700 	if (ignoretabs[1].i_count > 0)
    701 		return !member(realfld, ignoretabs + 1);
    702 	else
    703 		return member(realfld, ignoretabs);
    704 }
    705 
    706 PUBLIC void
    707 add_ignore(const char *name, struct ignoretab *tab)
    708 {
    709 	char field[LINESIZE];
    710 	int h;
    711 	struct ignore *igp;
    712 
    713 	istrcpy(field, name);
    714 	if (member(field, tab))
    715 		return;
    716 	h = hash(field);
    717 	igp = ecalloc(1, sizeof(struct ignore));
    718 	igp->i_field = ecalloc(strlen(field) + 1, sizeof(char));
    719 	(void)strcpy(igp->i_field, field);
    720 	igp->i_link = tab->i_head[h];
    721 	tab->i_head[h] = igp;
    722 	tab->i_count++;
    723 }
    724 
    725 /*
    726  * Write a file to stdout, skipping comment lines.
    727  */
    728 PUBLIC void
    729 cathelp(const char *fname)
    730 {
    731 	char *line;
    732 	FILE *f;
    733 	size_t len;
    734 
    735 	if ((f = Fopen(fname, "r")) == NULL) {
    736 		warn(fname);
    737 		return;
    738 	}
    739 	while ((line = fgetln(f, &len)) != NULL) {
    740 		if (*line == '#')
    741 			continue;
    742 		if (fwrite(line, 1, len, stdout) != len)
    743 			break;
    744 	}
    745 	(void)Fclose(f);
    746 	return;
    747 }
    748