xref: /freebsd/contrib/tcsh/tc.who.c (revision 23f282aa31e9b6fceacd449020e936e98d6f2298)
1 /* $Header: /src/pub/tcsh/tc.who.c,v 3.28 1998/04/08 13:59:13 christos Exp $ */
2 /*
3  * tc.who.c: Watch logins and logouts...
4  */
5 /*-
6  * Copyright (c) 1980, 1991 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. All advertising materials mentioning features or use of this software
18  *    must display the following acknowledgement:
19  *	This product includes software developed by the University of
20  *	California, Berkeley and its contributors.
21  * 4. Neither the name of the University nor the names of its contributors
22  *    may be used to endorse or promote products derived from this software
23  *    without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35  * SUCH DAMAGE.
36  */
37 #include "sh.h"
38 
39 RCSID("$Id: tc.who.c,v 3.28 1998/04/08 13:59:13 christos Exp $")
40 
41 #include "tc.h"
42 
43 #ifndef HAVENOUTMP
44 /*
45  * kfk 26 Jan 1984 - for login watch functions.
46  */
47 #include <ctype.h>
48 
49 #ifdef HAVEUTMPX
50 # include <utmpx.h>
51 /* I just redefine a few words here.  Changing every occurrence below
52  * seems like too much of work.  All UTMP functions have equivalent
53  * UTMPX counterparts, so they can be added all here when needed.
54  * Kimmo Suominen, Oct 14 1991
55  */
56 # ifndef _PATH_UTMP
57 #  define _PATH_UTMP UTMPX_FILE
58 # endif /* _PATH_UTMP */
59 # define utmp utmpx
60 # define ut_time ut_xtime
61 #else /* !HAVEUTMPX */
62 # ifndef WINNT
63 #  include <utmp.h>
64 # endif /* WINNT */
65 #endif /* HAVEUTMPX */
66 
67 #ifndef BROKEN_CC
68 # define UTNAMLEN	sizeof(((struct utmp *) 0)->ut_name)
69 # define UTLINLEN	sizeof(((struct utmp *) 0)->ut_line)
70 # ifdef UTHOST
71 #  ifdef _SEQUENT_
72 #   define UTHOSTLEN	100
73 #  else
74 #   define UTHOSTLEN	sizeof(((struct utmp *) 0)->ut_host)
75 #  endif
76 # endif	/* UTHOST */
77 #else
78 /* give poor cc a little help if it needs it */
79 struct utmp __ut;
80 
81 # define UTNAMLEN	sizeof(__ut.ut_name)
82 # define UTLINLEN	sizeof(__ut.ut_line)
83 # ifdef UTHOST
84 #  ifdef _SEQUENT_
85 #   define UTHOSTLEN	100
86 #  else
87 #   define UTHOSTLEN	sizeof(__ut.ut_host)
88 #  endif
89 # endif /* UTHOST */
90 #endif /* BROKEN_CC */
91 
92 #ifndef _PATH_UTMP
93 # ifdef	UTMP_FILE
94 #  define _PATH_UTMP UTMP_FILE
95 # else
96 #  define _PATH_UTMP "/etc/utmp"
97 # endif /* UTMP_FILE */
98 #endif /* _PATH_UTMP */
99 
100 
101 struct who {
102     struct who *who_next;
103     struct who *who_prev;
104     char    who_name[UTNAMLEN + 1];
105     char    who_new[UTNAMLEN + 1];
106     char    who_tty[UTLINLEN + 1];
107 #ifdef UTHOST
108     char    who_host[UTHOSTLEN + 1];
109 #endif /* UTHOST */
110     time_t  who_time;
111     int     who_status;
112 };
113 
114 static struct who whohead, whotail;
115 static time_t watch_period = 0;
116 static time_t stlast = 0;
117 #ifdef WHODEBUG
118 static	void	debugwholist	__P((struct who *, struct who *));
119 #endif
120 static	void	print_who	__P((struct who *));
121 
122 
123 #define ONLINE		01
124 #define OFFLINE		02
125 #define CHANGED		04
126 #define STMASK		07
127 #define ANNOUNCE	010
128 
129 /*
130  * Karl Kleinpaste, 26 Jan 1984.
131  * Initialize the dummy tty list for login watch.
132  * This dummy list eliminates boundary conditions
133  * when doing pointer-chase searches.
134  */
135 void
136 initwatch()
137 {
138     whohead.who_next = &whotail;
139     whotail.who_prev = &whohead;
140     stlast = 1;
141 #ifdef WHODEBUG
142     debugwholist(NULL, NULL);
143 #endif /* WHODEBUG */
144 }
145 
146 void
147 resetwatch()
148 {
149     watch_period = 0;
150     stlast = 0;
151 }
152 
153 /*
154  * Karl Kleinpaste, 26 Jan 1984.
155  * Watch /etc/utmp for login/logout changes.
156  */
157 void
158 watch_login(force)
159     int force;
160 {
161     int     utmpfd, comp = -1, alldone;
162     int	    firsttime = stlast == 1;
163 #ifdef BSDSIGS
164     sigmask_t omask;
165 #endif				/* BSDSIGS */
166     struct utmp utmp;
167     struct who *wp, *wpnew;
168     struct varent *v;
169     Char  **vp = NULL;
170     time_t  t, interval = MAILINTVL;
171     struct stat sta;
172 #if defined(UTHOST) && defined(_SEQUENT_)
173     char   *host, *ut_find_host();
174 #endif
175 #ifdef WINNT
176     static int ncbs_posted = 0;
177     USE(utmp);
178     USE(utmpfd);
179     USE(sta);
180     USE(wpnew);
181 #endif /* WINNT */
182 
183     /* stop SIGINT, lest our login list get trashed. */
184 #ifdef BSDSIGS
185     omask = sigblock(sigmask(SIGINT));
186 #else
187     (void) sighold(SIGINT);
188 #endif
189 
190     v = adrof(STRwatch);
191     if (v == NULL && !force) {
192 #ifdef BSDSIGS
193 	(void) sigsetmask(omask);
194 #else
195 	(void) sigrelse(SIGINT);
196 #endif
197 	return;			/* no names to watch */
198     }
199     if (!force) {
200 	trim(vp = v->vec);
201 	if (blklen(vp) % 2)		/* odd # args: 1st == # minutes. */
202 	    interval = (number(*vp)) ? (getn(*vp++) * 60) : MAILINTVL;
203     }
204     else
205 	interval = 0;
206 
207     (void) time(&t);
208 #ifdef WINNT
209 	/*
210 	 * Since NCB_ASTATs take time, start em async at least 90 secs
211 	 * before we are due -amol 6/5/97
212 	 */
213 	if (!ncbs_posted) {
214 	    unsigned long tdiff = t - watch_period;
215 	    if (!watch_period || ((tdiff  > 0) && (tdiff > (interval - 90)))) {
216 		start_ncbs(vp);
217  		ncbs_posted = 1;
218 	    }
219 	}
220 #endif /* WINNT */
221     if (t - watch_period < interval) {
222 #ifdef BSDSIGS
223 	(void) sigsetmask(omask);
224 #else
225 	(void) sigrelse(SIGINT);
226 #endif
227 	return;			/* not long enough yet... */
228     }
229     watch_period = t;
230 #ifdef WINNT
231     ncbs_posted = 0;
232 #else /* !WINNT */
233 
234     /*
235      * From: Michael Schroeder <mlschroe@immd4.informatik.uni-erlangen.de>
236      * Don't open utmp all the time, stat it first...
237      */
238     if (stat(_PATH_UTMP, &sta)) {
239 	xprintf(CGETS(26, 1, "cannot stat %s.  Please \"unset watch\".\n"),
240 		_PATH_UTMP);
241 # ifdef BSDSIGS
242 	(void) sigsetmask(omask);
243 # else
244 	(void) sigrelse(SIGINT);
245 # endif
246 	return;
247     }
248     if (stlast == sta.st_mtime) {
249 # ifdef BSDSIGS
250 	(void) sigsetmask(omask);
251 # else
252 	(void) sigrelse(SIGINT);
253 # endif
254 	return;
255     }
256     stlast = sta.st_mtime;
257     if ((utmpfd = open(_PATH_UTMP, O_RDONLY)) < 0) {
258 	xprintf(CGETS(26, 2, "%s cannot be opened.  Please \"unset watch\".\n"),
259 		_PATH_UTMP);
260 # ifdef BSDSIGS
261 	(void) sigsetmask(omask);
262 # else
263 	(void) sigrelse(SIGINT);
264 # endif
265 	return;
266     }
267 
268     /*
269      * xterm clears the entire utmp entry - mark everyone on the status list
270      * OFFLINE or we won't notice X "logouts"
271      */
272     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
273 	wp->who_status = OFFLINE;
274 	wp->who_time = 0;
275     }
276 
277     /*
278      * Read in the utmp file, sort the entries, and update existing entries or
279      * add new entries to the status list.
280      */
281     while (read(utmpfd, (char *) &utmp, sizeof utmp) == sizeof utmp) {
282 
283 # ifdef DEAD_PROCESS
284 #  ifndef IRIS4D
285 	if (utmp.ut_type != USER_PROCESS)
286 	    continue;
287 #  else
288 	/* Why is that? Cause the utmp file is always corrupted??? */
289 	if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
290 	    continue;
291 #  endif /* IRIS4D */
292 # endif /* DEAD_PROCESS */
293 
294 	if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
295 	    continue;	/* completely void entry */
296 # ifdef DEAD_PROCESS
297 	if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
298 	    continue;
299 # endif /* DEAD_PROCESS */
300 	wp = whohead.who_next;
301 	while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0)
302 	    wp = wp->who_next;/* find that tty! */
303 
304 	if (wp->who_next && comp == 0) {	/* found the tty... */
305 # ifdef DEAD_PROCESS
306 	    if (utmp.ut_type == DEAD_PROCESS) {
307 		wp->who_time = utmp.ut_time;
308 		wp->who_status = OFFLINE;
309 	    }
310 	    else
311 # endif /* DEAD_PROCESS */
312 	    if (utmp.ut_name[0] == '\0') {
313 		wp->who_time = utmp.ut_time;
314 		wp->who_status = OFFLINE;
315 	    }
316 	    else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) {
317 		/* someone is logged in */
318 		wp->who_time = utmp.ut_time;
319 		wp->who_status = 0;	/* same guy */
320 	    }
321 	    else {
322 		(void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN);
323 # ifdef UTHOST
324 #  ifdef _SEQUENT_
325 		host = ut_find_host(wp->who_tty);
326 		if (host)
327 		    (void) strncpy(wp->who_host, host, UTHOSTLEN);
328 		else
329 		    wp->who_host[0] = 0;
330 #  else
331 		(void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN);
332 #  endif
333 # endif /* UTHOST */
334 		wp->who_time = utmp.ut_time;
335 		if (wp->who_name[0] == '\0')
336 		    wp->who_status = ONLINE;
337 		else
338 		    wp->who_status = CHANGED;
339 	    }
340 	}
341 	else {		/* new tty in utmp */
342 	    wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
343 	    (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN);
344 # ifdef UTHOST
345 #  ifdef _SEQUENT_
346 	    host = ut_find_host(wpnew->who_tty);
347 	    if (host)
348 		(void) strncpy(wpnew->who_host, host, UTHOSTLEN);
349 	    else
350 		wpnew->who_host[0] = 0;
351 #  else
352 	    (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN);
353 #  endif
354 # endif /* UTHOST */
355 	    wpnew->who_time = utmp.ut_time;
356 # ifdef DEAD_PROCESS
357 	    if (utmp.ut_type == DEAD_PROCESS)
358 		wpnew->who_status = OFFLINE;
359 	    else
360 # endif /* DEAD_PROCESS */
361 	    if (utmp.ut_name[0] == '\0')
362 		wpnew->who_status = OFFLINE;
363 	    else {
364 		(void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN);
365 		wpnew->who_status = ONLINE;
366 	    }
367 # ifdef WHODEBUG
368 	    debugwholist(wpnew, wp);
369 # endif /* WHODEBUG */
370 
371 	    wpnew->who_next = wp;	/* link in a new 'who' */
372 	    wpnew->who_prev = wp->who_prev;
373 	    wpnew->who_prev->who_next = wpnew;
374 	    wp->who_prev = wpnew;	/* linked in now */
375 	}
376     }
377     (void) close(utmpfd);
378 # if defined(UTHOST) && defined(_SEQUENT_)
379     endutent();
380 # endif
381 #endif /* !WINNT */
382 
383     if (force || vp == NULL)
384 	return;
385 
386     /*
387      * The state of all logins is now known, so we can search the user's list
388      * of watchables to print the interesting ones.
389      */
390     for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
391 	 *(vp + 1) != NULL && **(vp + 1) != '\0';
392 	 vp += 2) {		/* args used in pairs... */
393 
394 	if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
395 	    alldone = 1;
396 
397 	for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
398 	    if (wp->who_status & ANNOUNCE ||
399 		(!eq(STRany, vp[0]) &&
400 		 !Gmatch(str2short(wp->who_name), vp[0]) &&
401 		 !Gmatch(str2short(wp->who_new),  vp[0])) ||
402 		(!Gmatch(str2short(wp->who_tty),  vp[1]) &&
403 		 !eq(STRany, vp[1])))
404 		continue;	/* entry doesn't qualify */
405 	    /* already printed or not right one to print */
406 
407 
408 	    if (wp->who_time == 0)/* utmp entry was cleared */
409 		wp->who_time = watch_period;
410 
411 	    if ((wp->who_status & OFFLINE) &&
412 		(wp->who_name[0] != '\0')) {
413 		if (!firsttime)
414 		    print_who(wp);
415 		wp->who_name[0] = '\0';
416 		wp->who_status |= ANNOUNCE;
417 		continue;
418 	    }
419 	    if (wp->who_status & ONLINE) {
420 		if (!firsttime)
421 		    print_who(wp);
422 		(void) strcpy(wp->who_name, wp->who_new);
423 		wp->who_status |= ANNOUNCE;
424 		continue;
425 	    }
426 	    if (wp->who_status & CHANGED) {
427 		if (!firsttime)
428 		    print_who(wp);
429 		(void) strcpy(wp->who_name, wp->who_new);
430 		wp->who_status |= ANNOUNCE;
431 		continue;
432 	    }
433 	}
434     }
435 #ifdef BSDSIGS
436     (void) sigsetmask(omask);
437 #else
438     (void) sigrelse(SIGINT);
439 #endif
440 }
441 
442 #ifdef WHODEBUG
443 static void
444 debugwholist(new, wp)
445     register struct who *new, *wp;
446 {
447     register struct who *a;
448 
449     a = whohead.who_next;
450     while (a->who_next != NULL) {
451 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
452 	a = a->who_next;
453     }
454     xprintf("TAIL\n");
455     if (a != &whotail) {
456 	xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n"));
457 	abort();
458     }
459     a = whotail.who_prev;
460     xprintf(CGETS(26, 4, "backward: "));
461     while (a->who_prev != NULL) {
462 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
463 	a = a->who_prev;
464     }
465     xprintf("HEAD\n");
466     if (a != &whohead) {
467 	xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n"));
468 	abort();
469     }
470     if (new)
471 	xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty);
472     if (wp)
473 	xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty);
474 }
475 #endif /* WHODEBUG */
476 
477 
478 static void
479 print_who(wp)
480     struct who *wp;
481 {
482 #ifdef UTHOST
483     Char   *cp = str2short(CGETS(26, 7, "%n has %a %l from %m."));
484 #else
485     Char   *cp = str2short(CGETS(26, 8, "%n has %a %l."));
486 #endif /* UTHOST */
487     struct varent *vp = adrof(STRwho);
488     Char buf[BUFSIZE];
489 
490     if (vp && vp->vec[0])
491 	cp = vp->vec[0];
492 
493     tprintf(FMT_WHO, buf, cp, BUFSIZE, NULL, wp->who_time, (ptr_t) wp);
494     for (cp = buf; *cp;)
495 	xputchar(*cp++);
496     xputchar('\n');
497 } /* end print_who */
498 
499 
500 const char *
501 who_info(ptr, c, wbuf, wbufsiz)
502     ptr_t ptr;
503     int c;
504     char *wbuf;
505     size_t wbufsiz;
506 {
507     struct who *wp = (struct who *) ptr;
508 #ifdef UTHOST
509     char *wb = wbuf;
510     int flg;
511     char *pb;
512 #endif /* UTHOST */
513 
514     switch (c) {
515     case 'n':		/* user name */
516 	switch (wp->who_status & STMASK) {
517 	case ONLINE:
518 	case CHANGED:
519 	    return wp->who_new;
520 	case OFFLINE:
521 	    return wp->who_name;
522 	default:
523 	    break;
524 	}
525 	break;
526 
527     case 'a':
528 	switch (wp->who_status & STMASK) {
529 	case ONLINE:
530 	    return CGETS(26, 9, "logged on");
531 	case OFFLINE:
532 	    return CGETS(26, 10, "logged off");
533 	case CHANGED:
534 	    xsnprintf(wbuf, wbufsiz, CGETS(26, 11, "replaced %s on"),
535 		      wp->who_name);
536 	    return wbuf;
537 	default:
538 	    break;
539 	}
540 	break;
541 
542 #ifdef UTHOST
543     case 'm':
544 	if (wp->who_host[0] == '\0')
545 	    return CGETS(26, 12, "local");
546 	else {
547 	    /* the ':' stuff is for <host>:<display>.<screen> */
548 	    for (pb = wp->who_host, flg = Isdigit(*pb) ? '\0' : '.';
549 		 *pb != '\0' &&
550 		 (*pb != flg || ((pb = strchr(pb, ':')) != 0));
551 		 pb++) {
552 		if (*pb == ':')
553 		    flg = '\0';
554 		*wb++ = Isupper(*pb) ? Tolower(*pb) : *pb;
555 	    }
556 	    *wb = '\0';
557 	    return wbuf;
558 	}
559 
560     case 'M':
561 	if (wp->who_host[0] == '\0')
562 	    return CGETS(26, 12, "local");
563 	else {
564 	    for (pb = wp->who_host; *pb != '\0'; pb++)
565 		*wb++ = Isupper(*pb) ? Tolower(*pb) : *pb;
566 	    *wb = '\0';
567 	    return wbuf;
568 	}
569 #endif /* UTHOST */
570 
571     case 'l':
572 	return wp->who_tty;
573 
574     default:
575 	wbuf[0] = '%';
576 	wbuf[1] = (char) c;
577 	wbuf[2] = '\0';
578 	return wbuf;
579     }
580     return NULL;
581 }
582 
583 void
584 /*ARGSUSED*/
585 dolog(v, c)
586 Char **v;
587 struct command *c;
588 {
589     struct who *wp;
590     struct varent *vp;
591 
592     USE(v);
593     USE(c);
594     vp = adrof(STRwatch);	/* lint insists vp isn't used unless we */
595     if (vp == NULL)		/* unless we assign it outside the if */
596 	stderror(ERR_NOWATCH);
597     resetwatch();
598     wp = whohead.who_next;
599     while (wp->who_next != NULL) {
600 	wp->who_name[0] = '\0';
601 	wp = wp->who_next;
602     }
603 }
604 
605 # ifdef UTHOST
606 char *
607 utmphost()
608 {
609     char *tty = short2str(varval(STRtty));
610     struct who *wp;
611     char *host = NULL;
612 
613     watch_login(1);
614 
615     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
616 	if (strcmp(tty, wp->who_tty) == 0)
617 	    host = wp->who_host;
618 	wp->who_name[0] = '\0';
619     }
620     resetwatch();
621     return host;
622 }
623 # endif /* UTHOST */
624 
625 #ifdef WINNT
626 void add_to_who_list(name, mach_nm)
627     char *name;
628     char *mach_nm;
629 {
630 
631     struct who *wp, *wpnew;
632     int comp = -1;
633 
634     wp = whohead.who_next;
635     while (wp->who_next && (comp = strncmp(wp->who_tty,mach_nm,UTLINLEN)) < 0)
636 	wp = wp->who_next;/* find that tty! */
637 
638     if (wp->who_next && comp == 0) {	/* found the tty... */
639 
640 	if (*name == '\0') {
641 	    wp->who_time = 0;
642 	    wp->who_status = OFFLINE;
643 	}
644 	else if (strncmp(name, wp->who_name, UTNAMLEN) == 0) {
645 	    /* someone is logged in */
646 	    wp->who_time = 0;
647 	    wp->who_status = 0;	/* same guy */
648 	}
649 	else {
650 	    (void) strncpy(wp->who_new, name, UTNAMLEN);
651 	    wp->who_time = 0;
652 	    if (wp->who_name[0] == '\0')
653 		wp->who_status = ONLINE;
654 	    else
655 		wp->who_status = CHANGED;
656 	}
657     }
658     else {
659 	wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
660 	(void) strncpy(wpnew->who_tty, mach_nm, UTLINLEN);
661 	wpnew->who_time = 0;
662 	if (*name == '\0')
663 	    wpnew->who_status = OFFLINE;
664 	else {
665 	    (void) strncpy(wpnew->who_new, name, UTNAMLEN);
666 	    wpnew->who_status = ONLINE;
667 	}
668 #ifdef WHODEBUG
669 	debugwholist(wpnew, wp);
670 #endif /* WHODEBUG */
671 
672 	wpnew->who_next = wp;	/* link in a new 'who' */
673 	wpnew->who_prev = wp->who_prev;
674 	wpnew->who_prev->who_next = wpnew;
675 	wp->who_prev = wpnew;	/* linked in now */
676     }
677 }
678 #endif /* WINNT */
679 #endif /* HAVENOUTMP */
680