Home | History | Annotate | Download | only in gen
      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) 1988 AT&T	*/
     28 /*	  All Rights Reserved  	*/
     29 
     30 /*
     31  * Compatibility routines to read and write alternate
     32  * utmp-like files.  These routines are only used in
     33  * the case where utmpname() is used to change to a file
     34  * other than /var/adm/utmp or /var/adm/wtmp.  In this case,
     35  * we assume that someone really wants to read old utmp-format
     36  * files.  Otherwise, the getutent, setutent, getutid, setutline,
     37  * and pututline functions are actually wrappers around the
     38  * equivalent function operating on utmpx-like files.
     39  */
     40 
     41 #include "lint.h"
     42 #include <stdio.h>
     43 #include <sys/param.h>
     44 #include <sys/types.h>
     45 #include <sys/stat.h>
     46 #include <utmpx.h>
     47 #include <errno.h>
     48 #include <fcntl.h>
     49 #include <string.h>
     50 #include <strings.h>
     51 #include <stdlib.h>
     52 #include <unistd.h>
     53 #include <ctype.h>
     54 #include <utime.h>
     55 #include <sys/wait.h>
     56 
     57 #define	IDLEN	4	/* length of id field in utmp */
     58 #define	SC_WILDC	0xff	/* wild char for utmp ids */
     59 #define	MAXVAL	255	/* max value for an id 'character' */
     60 
     61 #ifdef ut_time
     62 #undef ut_time
     63 #endif
     64 
     65 static void	utmp_frec2api(const struct futmp *, struct utmp *);
     66 static void	utmp_api2frec(const struct utmp *, struct futmp *);
     67 struct utmp 	*_compat_getutent(void);
     68 struct utmp	*_compat_getutid(const struct utmp *);
     69 struct utmp	*_compat_getutline(const struct utmp *);
     70 struct utmp	*_compat_pututline(const struct utmp *);
     71 void		_compat_setutent(void);
     72 void		_compat_endutent(void);
     73 void		_compat_updwtmp(const char *, struct utmp *);
     74 struct utmp	*_compat_makeut(struct utmp *);
     75 struct utmp	*_compat_modut(struct utmp *);
     76 
     77 static void	unlockut(void);
     78 static int	idcmp(const char *, const char *);
     79 static int	allocid(char *, unsigned char *);
     80 static int	lockut(void);
     81 
     82 
     83 static int fd = -1;	/* File descriptor for the utmp file. */
     84 /*
     85  * name of the current utmp-like file - set by utmpname (getutx.c)
     86  * only if running in backward compatibility mode
     87  * We don't modify this, but we can't declare it const or lint will freak.
     88  */
     89 extern char _compat_utmpfile[];
     90 
     91 #ifdef ERRDEBUG
     92 static long loc_utmp;	/* Where in "utmp" the current "ubuf" was found. */
     93 #endif
     94 
     95 static struct futmp fubuf;	/* Copy of last entry read in. */
     96 static struct utmp ubuf;	/* Last entry returned to client */
     97 
     98 /*
     99  * In the 64-bit world, the utmp data structure grows because of
    100  * the ut_time field (a time_t) at the end of it.
    101  */
    102 static void
    103 utmp_frec2api(const struct futmp *src, struct utmp *dst)
    104 {
    105 	if (src == NULL)
    106 		return;
    107 
    108 	(void) strncpy(dst->ut_user, src->ut_user, sizeof (dst->ut_user));
    109 	(void) strncpy(dst->ut_line, src->ut_line, sizeof (dst->ut_line));
    110 	(void) memcpy(dst->ut_id, src->ut_id, sizeof (dst->ut_id));
    111 	dst->ut_pid = src->ut_pid;
    112 	dst->ut_type = src->ut_type;
    113 	dst->ut_exit.e_termination = src->ut_exit.e_termination;
    114 	dst->ut_exit.e_exit = src->ut_exit.e_exit;
    115 	dst->ut_time = (time_t)src->ut_time;
    116 }
    117 
    118 static void
    119 utmp_api2frec(const struct utmp *src, struct futmp *dst)
    120 {
    121 	if (src == NULL)
    122 		return;
    123 
    124 	(void) strncpy(dst->ut_user, src->ut_user, sizeof (dst->ut_user));
    125 	(void) strncpy(dst->ut_line, src->ut_line, sizeof (dst->ut_line));
    126 	(void) memcpy(dst->ut_id, src->ut_id, sizeof (dst->ut_id));
    127 	dst->ut_pid = src->ut_pid;
    128 	dst->ut_type = src->ut_type;
    129 	dst->ut_exit.e_termination = src->ut_exit.e_termination;
    130 	dst->ut_exit.e_exit = src->ut_exit.e_exit;
    131 	dst->ut_time = (time32_t)src->ut_time;
    132 }
    133 
    134 /*
    135  * "getutent_frec" gets the raw version of the next entry in the utmp file.
    136  */
    137 static struct futmp *
    138 getutent_frec(void)
    139 {
    140 	/*
    141 	 * If the "utmp" file is not open, attempt to open it for
    142 	 * reading.  If there is no file, attempt to create one.  If
    143 	 * both attempts fail, return NULL.  If the file exists, but
    144 	 * isn't readable and writeable, do not attempt to create.
    145 	 */
    146 	if (fd < 0) {
    147 		if ((fd = open(_compat_utmpfile, O_RDWR|O_CREAT, 0644)) < 0) {
    148 
    149 			/*
    150 			 * If the open failed for permissions, try opening
    151 			 * it only for reading.  All "pututline()" later
    152 			 * will fail the writes.
    153 			 */
    154 			if ((fd = open(_compat_utmpfile, O_RDONLY)) < 0)
    155 				return (NULL);
    156 		}
    157 	}
    158 
    159 	/* Try to read in the next entry from the utmp file.  */
    160 
    161 	if (read(fd, &fubuf, sizeof (fubuf)) != sizeof (fubuf)) {
    162 		bzero(&fubuf, sizeof (fubuf));
    163 		return (NULL);
    164 	}
    165 
    166 	/* Save the location in the file where this entry was found. */
    167 
    168 	(void) lseek(fd, 0L, 1);
    169 	return (&fubuf);
    170 }
    171 
    172 /*
    173  * "_compat_getutent" gets the next entry in the utmp file.
    174  */
    175 struct utmp *
    176 _compat_getutent(void)
    177 {
    178 	struct futmp *futp;
    179 
    180 	futp = getutent_frec();
    181 	utmp_frec2api(&fubuf, &ubuf);
    182 	if (futp == NULL)
    183 		return (NULL);
    184 	return (&ubuf);
    185 }
    186 
    187 /*
    188  * "_compat_getutid" finds the specified entry in the utmp file.  If
    189  * it can't find it, it returns NULL.
    190  */
    191 struct utmp *
    192 _compat_getutid(const struct utmp *entry)
    193 {
    194 	short type;
    195 
    196 	utmp_api2frec(&ubuf, &fubuf);
    197 
    198 	/*
    199 	 * Start looking for entry.  Look in our current buffer before
    200 	 * reading in new entries.
    201 	 */
    202 	do {
    203 		/*
    204 		 * If there is no entry in "ubuf", skip to the read.
    205 		 */
    206 		if (fubuf.ut_type != EMPTY) {
    207 			switch (entry->ut_type) {
    208 
    209 			/*
    210 			 * Do not look for an entry if the user sent
    211 			 * us an EMPTY entry.
    212 			 */
    213 			case EMPTY:
    214 				return (NULL);
    215 
    216 			/*
    217 			 * For RUN_LVL, BOOT_TIME, DOWN_TIME,
    218 			 * OLD_TIME, and NEW_TIME entries, only the
    219 			 * types have to match.  If they do, return
    220 			 * the address of internal buffer.
    221 			 */
    222 			case RUN_LVL:
    223 			case BOOT_TIME:
    224 			case DOWN_TIME:
    225 			case OLD_TIME:
    226 			case NEW_TIME:
    227 				if (entry->ut_type == fubuf.ut_type) {
    228 					utmp_frec2api(&fubuf, &ubuf);
    229 					return (&ubuf);
    230 				}
    231 				break;
    232 
    233 			/*
    234 			 * For INIT_PROCESS, LOGIN_PROCESS, USER_PROCESS,
    235 			 * and DEAD_PROCESS the type of the entry in "fubuf",
    236 			 * must be one of the above and id's must match.
    237 			 */
    238 			case INIT_PROCESS:
    239 			case LOGIN_PROCESS:
    240 			case USER_PROCESS:
    241 			case DEAD_PROCESS:
    242 				if (((type = fubuf.ut_type) == INIT_PROCESS ||
    243 				    type == LOGIN_PROCESS ||
    244 				    type == USER_PROCESS ||
    245 				    type == DEAD_PROCESS) &&
    246 				    fubuf.ut_id[0] == entry->ut_id[0] &&
    247 				    fubuf.ut_id[1] == entry->ut_id[1] &&
    248 				    fubuf.ut_id[2] == entry->ut_id[2] &&
    249 				    fubuf.ut_id[3] == entry->ut_id[3]) {
    250 					utmp_frec2api(&fubuf, &ubuf);
    251 					return (&ubuf);
    252 				}
    253 				break;
    254 
    255 			/* Do not search for illegal types of entry. */
    256 			default:
    257 				return (NULL);
    258 			}
    259 		}
    260 	} while (getutent_frec() != NULL);
    261 
    262 	/* the proper entry wasn't found. */
    263 
    264 	utmp_frec2api(&fubuf, &ubuf);
    265 	return (NULL);
    266 }
    267 
    268 /*
    269  * "_compat_getutline" searches the "utmp" file for a LOGIN_PROCESS or
    270  * USER_PROCESS with the same "line" as the specified "entry".
    271  */
    272 struct utmp *
    273 _compat_getutline(const struct utmp *entry)
    274 {
    275 	utmp_api2frec(&ubuf, &fubuf);
    276 
    277 	do {
    278 		/*
    279 		 * If the current entry is the one we are interested in,
    280 		 * return a pointer to it.
    281 		 */
    282 		if (fubuf.ut_type != EMPTY &&
    283 		    (fubuf.ut_type == LOGIN_PROCESS ||
    284 		    fubuf.ut_type == USER_PROCESS) &&
    285 		    strncmp(&entry->ut_line[0], &fubuf.ut_line[0],
    286 		    sizeof (fubuf.ut_line)) == 0) {
    287 			utmp_frec2api(&fubuf, &ubuf);
    288 			return (&ubuf);
    289 		}
    290 	} while (getutent_frec() != NULL);
    291 
    292 	utmp_frec2api(&fubuf, &ubuf);
    293 	return (NULL);
    294 }
    295 
    296 /*
    297  * "_compat_pututline" writes the structure sent into the utmp file
    298  * If there is already an entry with the same id, then it is
    299  * overwritten, otherwise a new entry is made at the end of the
    300  * utmp file.
    301  */
    302 struct utmp *
    303 _compat_pututline(const struct utmp *entry)
    304 {
    305 	int fc;
    306 	struct utmp *answer;
    307 	struct utmp tmpbuf;
    308 	struct futmp ftmpbuf;
    309 
    310 	/*
    311 	 * Copy the user supplied entry into our temporary buffer to
    312 	 * avoid the possibility that the user is actually passing us
    313 	 * the address of "ubuf".
    314 	 */
    315 	tmpbuf = *entry;
    316 	utmp_api2frec(entry, &ftmpbuf);
    317 
    318 	(void) getutent_frec();
    319 	if (fd < 0) {
    320 #ifdef	ERRDEBUG
    321 		gdebug("pututline: Unable to create utmp file.\n");
    322 #endif
    323 		return (NULL);
    324 	}
    325 
    326 	/* Make sure file is writable */
    327 
    328 	if ((fc = fcntl(fd, F_GETFL, NULL)) == -1 || (fc & O_RDWR) != O_RDWR)
    329 		return (NULL);
    330 
    331 	/*
    332 	 * Find the proper entry in the utmp file.  Start at the current
    333 	 * location.  If it isn't found from here to the end of the
    334 	 * file, then reset to the beginning of the file and try again.
    335 	 * If it still isn't found, then write a new entry at the end of
    336 	 * the file.  (Making sure the location is an integral number of
    337 	 * utmp structures into the file incase the file is scribbled.)
    338 	 */
    339 
    340 	if (_compat_getutid(&tmpbuf) == NULL) {
    341 #ifdef	ERRDEBUG
    342 		gdebug("1st getutid() failed. fd: %d", fd);
    343 #endif
    344 		_compat_setutent();
    345 		if (_compat_getutid(&tmpbuf) == NULL) {
    346 #ifdef	ERRDEBUG
    347 			loc_utmp = lseek(fd, 0L, 1);
    348 			gdebug("2nd getutid() failed. fd: %d loc_utmp: %ld\n",
    349 			    fd, loc_utmp);
    350 #endif
    351 			(void) fcntl(fd, F_SETFL, fc | O_APPEND);
    352 		} else
    353 			(void) lseek(fd, -(long)sizeof (struct futmp), 1);
    354 	} else
    355 		(void) lseek(fd, -(long)sizeof (struct futmp), 1);
    356 
    357 	/*
    358 	 * Write out the user supplied structure.  If the write fails,
    359 	 * then the user probably doesn't have permission to write the
    360 	 * utmp file.
    361 	 */
    362 	if (write(fd, &ftmpbuf, sizeof (ftmpbuf)) != sizeof (ftmpbuf)) {
    363 #ifdef	ERRDEBUG
    364 		gdebug("pututline failed: write-%d\n", errno);
    365 #endif
    366 		answer = NULL;
    367 	} else {
    368 		/*
    369 		 * Copy the new user structure into ubuf so that it will
    370 		 * be up to date in the future.
    371 		 */
    372 		fubuf = ftmpbuf;
    373 		utmp_frec2api(&fubuf, &ubuf);
    374 		answer = &ubuf;
    375 
    376 #ifdef	ERRDEBUG
    377 		gdebug("id: %c%c loc: %ld\n", fubuf.ut_id[0],
    378 		    fubuf.ut_id[1], fubuf.ut_id[2], fubuf.ut_id[3],
    379 		    loc_utmp);
    380 #endif
    381 	}
    382 
    383 	(void) fcntl(fd, F_SETFL, fc);
    384 
    385 	return (answer);
    386 }
    387 
    388 /*
    389  * "_compat_setutent" just resets the utmp file back to the beginning.
    390  */
    391 void
    392 _compat_setutent(void)
    393 {
    394 	if (fd != -1)
    395 		(void) lseek(fd, 0L, 0);
    396 
    397 	/*
    398 	 * Zero the stored copy of the last entry read, since we are
    399 	 * resetting to the beginning of the file.
    400 	 */
    401 	bzero(&ubuf, sizeof (ubuf));
    402 	bzero(&fubuf, sizeof (fubuf));
    403 }
    404 
    405 /*
    406  * "_compat_endutent" closes the utmp file.
    407  */
    408 void
    409 _compat_endutent(void)
    410 {
    411 	if (fd != -1)
    412 		(void) close(fd);
    413 	fd = -1;
    414 	bzero(&ubuf, sizeof (ubuf));
    415 	bzero(&fubuf, sizeof (fubuf));
    416 }
    417 
    418 
    419 /*
    420  * If one of wtmp and wtmpx files exist, create the other, and the record.
    421  * If they both exist add the record.
    422  */
    423 void
    424 _compat_updwtmp(const char *file, struct utmp *ut)
    425 {
    426 	struct futmp fut;
    427 	int fd;
    428 
    429 
    430 	fd = open(file, O_WRONLY | O_APPEND);
    431 
    432 	if (fd < 0) {
    433 		if ((fd = open(file, O_WRONLY|O_CREAT, 0644)) < 0)
    434 			return;
    435 	}
    436 
    437 	(void) lseek(fd, 0, 2);
    438 
    439 	utmp_api2frec(ut, &fut);
    440 	(void) write(fd, &fut, sizeof (fut));
    441 
    442 	(void) close(fd);
    443 }
    444 
    445 
    446 
    447 /*
    448  * makeut - create a utmp entry, recycling an id if a wild card is
    449  *	specified.
    450  *
    451  *	args:	utmp - point to utmp structure to be created
    452  */
    453 struct utmp *
    454 _compat_makeut(struct utmp *utmp)
    455 {
    456 	int i;
    457 	struct utmp *utp;	/* "current" utmp entry being examined */
    458 	int wild;		/* flag, true iff wild card char seen */
    459 
    460 	/* the last id we matched that was NOT a dead proc */
    461 	unsigned char saveid[IDLEN];
    462 
    463 	wild = 0;
    464 	for (i = 0; i < IDLEN; i++)
    465 		if ((unsigned char)utmp->ut_id[i] == SC_WILDC) {
    466 			wild = 1;
    467 			break;
    468 		}
    469 
    470 	if (wild) {
    471 
    472 		/*
    473 		 * try to lock the utmp file, only needed if we're
    474 		 * doing wildcard matching
    475 		 */
    476 
    477 		if (lockut())
    478 			return (0);
    479 		_compat_setutent();
    480 
    481 		/* find the first alphanumeric character */
    482 		for (i = 0; i < MAXVAL; ++i)
    483 			if (isalnum(i))
    484 				break;
    485 
    486 		(void) memset(saveid, i, IDLEN);
    487 
    488 		while ((utp = _compat_getutent()) != 0) {
    489 			if (idcmp(utmp->ut_id, utp->ut_id))
    490 				continue;
    491 			if (utp->ut_type == DEAD_PROCESS)
    492 				break;
    493 			(void) memcpy(saveid, utp->ut_id, IDLEN);
    494 		}
    495 
    496 		if (utp) {
    497 			/*
    498 			 * found an unused entry, reuse it
    499 			 */
    500 			(void) memcpy(utmp->ut_id, utp->ut_id, IDLEN);
    501 			utp = _compat_pututline(utmp);
    502 			if (utp)
    503 				_compat_updwtmp(WTMP_FILE, utp);
    504 			_compat_endutent();
    505 			unlockut();
    506 			return (utp);
    507 
    508 		} else {
    509 			/*
    510 			 * nothing available, try to allocate an id
    511 			 */
    512 			if (allocid(utmp->ut_id, saveid)) {
    513 				_compat_endutent();
    514 				unlockut();
    515 				return (NULL);
    516 			} else {
    517 				utp = _compat_pututline(utmp);
    518 				if (utp)
    519 					_compat_updwtmp(WTMP_FILE, utp);
    520 				_compat_endutent();
    521 				unlockut();
    522 				return (utp);
    523 			}
    524 		}
    525 	} else {
    526 		utp = _compat_pututline(utmp);
    527 		if (utp)
    528 			_compat_updwtmp(WTMP_FILE, utp);
    529 		_compat_endutent();
    530 		return (utp);
    531 	}
    532 }
    533 
    534 
    535 /*
    536  * _compat_modut - modify a utmp entry.
    537  *
    538  *	args:	utmp - point to utmp structure to be created
    539  */
    540 struct utmp *
    541 _compat_modut(struct utmp *utp)
    542 {
    543 	int i;					/* scratch variable */
    544 	struct utmp utmp;			/* holding area */
    545 	struct utmp *ucp = &utmp;		/* and a pointer to it */
    546 	struct utmp *up;	/* "current" utmp entry being examined */
    547 	struct futmp *fup;
    548 
    549 	for (i = 0; i < IDLEN; ++i)
    550 		if ((unsigned char)utp->ut_id[i] == SC_WILDC)
    551 			return (0);
    552 
    553 	/* copy the supplied utmp structure someplace safe */
    554 	utmp = *utp;
    555 	_compat_setutent();
    556 	while (fup = getutent_frec()) {
    557 		if (idcmp(ucp->ut_id, fup->ut_id))
    558 			continue;
    559 		break;
    560 	}
    561 	up = _compat_pututline(ucp);
    562 	if (up)
    563 		_compat_updwtmp(WTMP_FILE, up);
    564 	_compat_endutent();
    565 	return (up);
    566 }
    567 
    568 
    569 
    570 /*
    571  * idcmp - compare two id strings, return 0 if same, non-zero if not *
    572  *	args:	s1 - first id string
    573  *		s2 - second id string
    574  */
    575 static int
    576 idcmp(const char *s1, const char *s2)
    577 {
    578 	int i;
    579 
    580 	for (i = 0; i < IDLEN; ++i)
    581 		if ((unsigned char)*s1 != SC_WILDC && (*s1++ != *s2++))
    582 			return (-1);
    583 	return (0);
    584 }
    585 
    586 
    587 /*
    588  * allocid - allocate an unused id for utmp, either by recycling a
    589  *	DEAD_PROCESS entry or creating a new one.  This routine only
    590  *	gets called if a wild card character was specified.
    591  *
    592  *	args:	srcid - pattern for new id
    593  *		saveid - last id matching pattern for a non-dead process
    594  */
    595 static int
    596 allocid(char *srcid, unsigned char *saveid)
    597 {
    598 	int i;		/* scratch variable */
    599 	int changed;	/* flag to indicate that a new id has been generated */
    600 	char copyid[IDLEN];	/* work area */
    601 
    602 	(void) memcpy(copyid, srcid, IDLEN);
    603 	changed = 0;
    604 	for (i = 0; i < IDLEN; ++i) {
    605 		/*
    606 		 * if this character isn't wild, it'll
    607 		 * be part of the generated id
    608 		 */
    609 		if ((unsigned char) copyid[i] != SC_WILDC)
    610 			continue;
    611 		/*
    612 		 * it's a wild character, retrieve the
    613 		 * character from the saved id
    614 		 */
    615 		copyid[i] = saveid[i];
    616 		/*
    617 		 * if we haven't changed anything yet,
    618 		 * try to find a new char to use
    619 		 */
    620 		if (!changed && (saveid[i] < MAXVAL)) {
    621 
    622 /*
    623  * Note: this algorithm is taking the "last matched" id and trying to make
    624  * a 1 character change to it to create a new one.  Rather than special-case
    625  * the first time (when no perturbation is really necessary), just don't
    626  * allocate the first valid id.
    627  */
    628 
    629 			while (++saveid[i] < MAXVAL) {
    630 				/* make sure new char is alphanumeric */
    631 				if (isalnum(saveid[i])) {
    632 					copyid[i] = saveid[i];
    633 					changed = 1;
    634 					break;
    635 				}
    636 			}
    637 
    638 			if (!changed) {
    639 				/*
    640 				 * Then 'reset' the current count at
    641 				 * this position to it's lowest valid
    642 				 * value, and propagate the carry to
    643 				 * the next wild-card slot
    644 				 *
    645 				 * See 1113208.
    646 				 */
    647 				saveid[i] = 0;
    648 				while (!isalnum(saveid[i]))
    649 					saveid[i]++;
    650 				copyid[i] = ++saveid[i];
    651 			}
    652 		}
    653 	}
    654 	/* changed is true if we were successful in allocating an id */
    655 	if (changed) {
    656 		(void) memcpy(srcid, copyid, IDLEN);
    657 		return (0);
    658 	} else
    659 		return (-1);
    660 }
    661 
    662 
    663 /*
    664  * lockut - lock utmp file
    665  */
    666 static int
    667 lockut(void)
    668 {
    669 	if ((fd = open(_compat_utmpfile, O_RDWR|O_CREAT, 0644)) < 0)
    670 		return (-1);
    671 
    672 	if (lockf(fd, F_LOCK, 0) < 0) {
    673 		(void) close(fd);
    674 		fd = -1;
    675 		return (-1);
    676 	}
    677 	return (0);
    678 }
    679 
    680 
    681 /*
    682  * unlockut - unlock utmp file
    683  */
    684 static void
    685 unlockut(void)
    686 {
    687 	(void) lockf(fd, F_ULOCK, 0);
    688 	(void) close(fd);
    689 	fd = -1;
    690 }
    691 
    692 
    693 
    694 #ifdef  ERRDEBUG
    695 
    696 #include <stdarg.h>
    697 #include <stdio.h>
    698 
    699 static void
    700 gdebug(const char *fmt, ...)
    701 {
    702 	FILE *fp;
    703 	int errnum;
    704 	va_list ap;
    705 
    706 	if ((fp = fopen("/etc/dbg.getut", "a+F")) == NULL)
    707 		return;
    708 	va_start(ap, fmt);
    709 	(void) vfprintf(fp, fmt, ap);
    710 	va_end(ap);
    711 	(void) fclose(fp);
    712 }
    713 #endif
    714