xref: /freebsd/contrib/tcsh/tc.who.c (revision 77a0943ded95b9e6438f7db70c4a28e4d93946d4)
1 /* $Header: /src/pub/tcsh/tc.who.c,v 3.29 2000/06/09 18:33:25 kim 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.29 2000/06/09 18:33:25 kim 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 	if (!force)
240 	    xprintf(CGETS(26, 1,
241 			  "cannot stat %s.  Please \"unset watch\".\n"),
242 		    _PATH_UTMP);
243 # ifdef BSDSIGS
244 	(void) sigsetmask(omask);
245 # else
246 	(void) sigrelse(SIGINT);
247 # endif
248 	return;
249     }
250     if (stlast == sta.st_mtime) {
251 # ifdef BSDSIGS
252 	(void) sigsetmask(omask);
253 # else
254 	(void) sigrelse(SIGINT);
255 # endif
256 	return;
257     }
258     stlast = sta.st_mtime;
259     if ((utmpfd = open(_PATH_UTMP, O_RDONLY)) < 0) {
260 	if (!force)
261 	    xprintf(CGETS(26, 2,
262 			  "%s cannot be opened.  Please \"unset watch\".\n"),
263 		    _PATH_UTMP);
264 # ifdef BSDSIGS
265 	(void) sigsetmask(omask);
266 # else
267 	(void) sigrelse(SIGINT);
268 # endif
269 	return;
270     }
271 
272     /*
273      * xterm clears the entire utmp entry - mark everyone on the status list
274      * OFFLINE or we won't notice X "logouts"
275      */
276     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
277 	wp->who_status = OFFLINE;
278 	wp->who_time = 0;
279     }
280 
281     /*
282      * Read in the utmp file, sort the entries, and update existing entries or
283      * add new entries to the status list.
284      */
285     while (read(utmpfd, (char *) &utmp, sizeof utmp) == sizeof utmp) {
286 
287 # ifdef DEAD_PROCESS
288 #  ifndef IRIS4D
289 	if (utmp.ut_type != USER_PROCESS)
290 	    continue;
291 #  else
292 	/* Why is that? Cause the utmp file is always corrupted??? */
293 	if (utmp.ut_type != USER_PROCESS && utmp.ut_type != DEAD_PROCESS)
294 	    continue;
295 #  endif /* IRIS4D */
296 # endif /* DEAD_PROCESS */
297 
298 	if (utmp.ut_name[0] == '\0' && utmp.ut_line[0] == '\0')
299 	    continue;	/* completely void entry */
300 # ifdef DEAD_PROCESS
301 	if (utmp.ut_type == DEAD_PROCESS && utmp.ut_line[0] == '\0')
302 	    continue;
303 # endif /* DEAD_PROCESS */
304 	wp = whohead.who_next;
305 	while (wp->who_next && (comp = strncmp(wp->who_tty, utmp.ut_line, UTLINLEN)) < 0)
306 	    wp = wp->who_next;/* find that tty! */
307 
308 	if (wp->who_next && comp == 0) {	/* found the tty... */
309 # ifdef DEAD_PROCESS
310 	    if (utmp.ut_type == DEAD_PROCESS) {
311 		wp->who_time = utmp.ut_time;
312 		wp->who_status = OFFLINE;
313 	    }
314 	    else
315 # endif /* DEAD_PROCESS */
316 	    if (utmp.ut_name[0] == '\0') {
317 		wp->who_time = utmp.ut_time;
318 		wp->who_status = OFFLINE;
319 	    }
320 	    else if (strncmp(utmp.ut_name, wp->who_name, UTNAMLEN) == 0) {
321 		/* someone is logged in */
322 		wp->who_time = utmp.ut_time;
323 		wp->who_status = 0;	/* same guy */
324 	    }
325 	    else {
326 		(void) strncpy(wp->who_new, utmp.ut_name, UTNAMLEN);
327 # ifdef UTHOST
328 #  ifdef _SEQUENT_
329 		host = ut_find_host(wp->who_tty);
330 		if (host)
331 		    (void) strncpy(wp->who_host, host, UTHOSTLEN);
332 		else
333 		    wp->who_host[0] = 0;
334 #  else
335 		(void) strncpy(wp->who_host, utmp.ut_host, UTHOSTLEN);
336 #  endif
337 # endif /* UTHOST */
338 		wp->who_time = utmp.ut_time;
339 		if (wp->who_name[0] == '\0')
340 		    wp->who_status = ONLINE;
341 		else
342 		    wp->who_status = CHANGED;
343 	    }
344 	}
345 	else {		/* new tty in utmp */
346 	    wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
347 	    (void) strncpy(wpnew->who_tty, utmp.ut_line, UTLINLEN);
348 # ifdef UTHOST
349 #  ifdef _SEQUENT_
350 	    host = ut_find_host(wpnew->who_tty);
351 	    if (host)
352 		(void) strncpy(wpnew->who_host, host, UTHOSTLEN);
353 	    else
354 		wpnew->who_host[0] = 0;
355 #  else
356 	    (void) strncpy(wpnew->who_host, utmp.ut_host, UTHOSTLEN);
357 #  endif
358 # endif /* UTHOST */
359 	    wpnew->who_time = utmp.ut_time;
360 # ifdef DEAD_PROCESS
361 	    if (utmp.ut_type == DEAD_PROCESS)
362 		wpnew->who_status = OFFLINE;
363 	    else
364 # endif /* DEAD_PROCESS */
365 	    if (utmp.ut_name[0] == '\0')
366 		wpnew->who_status = OFFLINE;
367 	    else {
368 		(void) strncpy(wpnew->who_new, utmp.ut_name, UTNAMLEN);
369 		wpnew->who_status = ONLINE;
370 	    }
371 # ifdef WHODEBUG
372 	    debugwholist(wpnew, wp);
373 # endif /* WHODEBUG */
374 
375 	    wpnew->who_next = wp;	/* link in a new 'who' */
376 	    wpnew->who_prev = wp->who_prev;
377 	    wpnew->who_prev->who_next = wpnew;
378 	    wp->who_prev = wpnew;	/* linked in now */
379 	}
380     }
381     (void) close(utmpfd);
382 # if defined(UTHOST) && defined(_SEQUENT_)
383     endutent();
384 # endif
385 #endif /* !WINNT */
386 
387     if (force || vp == NULL)
388 	return;
389 
390     /*
391      * The state of all logins is now known, so we can search the user's list
392      * of watchables to print the interesting ones.
393      */
394     for (alldone = 0; !alldone && *vp != NULL && **vp != '\0' &&
395 	 *(vp + 1) != NULL && **(vp + 1) != '\0';
396 	 vp += 2) {		/* args used in pairs... */
397 
398 	if (eq(*vp, STRany) && eq(*(vp + 1), STRany))
399 	    alldone = 1;
400 
401 	for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
402 	    if (wp->who_status & ANNOUNCE ||
403 		(!eq(STRany, vp[0]) &&
404 		 !Gmatch(str2short(wp->who_name), vp[0]) &&
405 		 !Gmatch(str2short(wp->who_new),  vp[0])) ||
406 		(!Gmatch(str2short(wp->who_tty),  vp[1]) &&
407 		 !eq(STRany, vp[1])))
408 		continue;	/* entry doesn't qualify */
409 	    /* already printed or not right one to print */
410 
411 
412 	    if (wp->who_time == 0)/* utmp entry was cleared */
413 		wp->who_time = watch_period;
414 
415 	    if ((wp->who_status & OFFLINE) &&
416 		(wp->who_name[0] != '\0')) {
417 		if (!firsttime)
418 		    print_who(wp);
419 		wp->who_name[0] = '\0';
420 		wp->who_status |= ANNOUNCE;
421 		continue;
422 	    }
423 	    if (wp->who_status & ONLINE) {
424 		if (!firsttime)
425 		    print_who(wp);
426 		(void) strcpy(wp->who_name, wp->who_new);
427 		wp->who_status |= ANNOUNCE;
428 		continue;
429 	    }
430 	    if (wp->who_status & CHANGED) {
431 		if (!firsttime)
432 		    print_who(wp);
433 		(void) strcpy(wp->who_name, wp->who_new);
434 		wp->who_status |= ANNOUNCE;
435 		continue;
436 	    }
437 	}
438     }
439 #ifdef BSDSIGS
440     (void) sigsetmask(omask);
441 #else
442     (void) sigrelse(SIGINT);
443 #endif
444 }
445 
446 #ifdef WHODEBUG
447 static void
448 debugwholist(new, wp)
449     register struct who *new, *wp;
450 {
451     register struct who *a;
452 
453     a = whohead.who_next;
454     while (a->who_next != NULL) {
455 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
456 	a = a->who_next;
457     }
458     xprintf("TAIL\n");
459     if (a != &whotail) {
460 	xprintf(CGETS(26, 3, "BUG! last element is not whotail!\n"));
461 	abort();
462     }
463     a = whotail.who_prev;
464     xprintf(CGETS(26, 4, "backward: "));
465     while (a->who_prev != NULL) {
466 	xprintf("%s/%s -> ", a->who_name, a->who_tty);
467 	a = a->who_prev;
468     }
469     xprintf("HEAD\n");
470     if (a != &whohead) {
471 	xprintf(CGETS(26, 5, "BUG! first element is not whohead!\n"));
472 	abort();
473     }
474     if (new)
475 	xprintf(CGETS(26, 6, "new: %s/%s\n"), new->who_name, new->who_tty);
476     if (wp)
477 	xprintf("wp: %s/%s\n", wp->who_name, wp->who_tty);
478 }
479 #endif /* WHODEBUG */
480 
481 
482 static void
483 print_who(wp)
484     struct who *wp;
485 {
486 #ifdef UTHOST
487     Char   *cp = str2short(CGETS(26, 7, "%n has %a %l from %m."));
488 #else
489     Char   *cp = str2short(CGETS(26, 8, "%n has %a %l."));
490 #endif /* UTHOST */
491     struct varent *vp = adrof(STRwho);
492     Char buf[BUFSIZE];
493 
494     if (vp && vp->vec[0])
495 	cp = vp->vec[0];
496 
497     tprintf(FMT_WHO, buf, cp, BUFSIZE, NULL, wp->who_time, (ptr_t) wp);
498     for (cp = buf; *cp;)
499 	xputchar(*cp++);
500     xputchar('\n');
501 } /* end print_who */
502 
503 
504 const char *
505 who_info(ptr, c, wbuf, wbufsiz)
506     ptr_t ptr;
507     int c;
508     char *wbuf;
509     size_t wbufsiz;
510 {
511     struct who *wp = (struct who *) ptr;
512 #ifdef UTHOST
513     char *wb = wbuf;
514     int flg;
515     char *pb;
516 #endif /* UTHOST */
517 
518     switch (c) {
519     case 'n':		/* user name */
520 	switch (wp->who_status & STMASK) {
521 	case ONLINE:
522 	case CHANGED:
523 	    return wp->who_new;
524 	case OFFLINE:
525 	    return wp->who_name;
526 	default:
527 	    break;
528 	}
529 	break;
530 
531     case 'a':
532 	switch (wp->who_status & STMASK) {
533 	case ONLINE:
534 	    return CGETS(26, 9, "logged on");
535 	case OFFLINE:
536 	    return CGETS(26, 10, "logged off");
537 	case CHANGED:
538 	    xsnprintf(wbuf, wbufsiz, CGETS(26, 11, "replaced %s on"),
539 		      wp->who_name);
540 	    return wbuf;
541 	default:
542 	    break;
543 	}
544 	break;
545 
546 #ifdef UTHOST
547     case 'm':
548 	if (wp->who_host[0] == '\0')
549 	    return CGETS(26, 12, "local");
550 	else {
551 	    /* the ':' stuff is for <host>:<display>.<screen> */
552 	    for (pb = wp->who_host, flg = Isdigit(*pb) ? '\0' : '.';
553 		 *pb != '\0' &&
554 		 (*pb != flg || ((pb = strchr(pb, ':')) != 0));
555 		 pb++) {
556 		if (*pb == ':')
557 		    flg = '\0';
558 		*wb++ = Isupper(*pb) ? Tolower(*pb) : *pb;
559 	    }
560 	    *wb = '\0';
561 	    return wbuf;
562 	}
563 
564     case 'M':
565 	if (wp->who_host[0] == '\0')
566 	    return CGETS(26, 12, "local");
567 	else {
568 	    for (pb = wp->who_host; *pb != '\0'; pb++)
569 		*wb++ = Isupper(*pb) ? Tolower(*pb) : *pb;
570 	    *wb = '\0';
571 	    return wbuf;
572 	}
573 #endif /* UTHOST */
574 
575     case 'l':
576 	return wp->who_tty;
577 
578     default:
579 	wbuf[0] = '%';
580 	wbuf[1] = (char) c;
581 	wbuf[2] = '\0';
582 	return wbuf;
583     }
584     return NULL;
585 }
586 
587 void
588 /*ARGSUSED*/
589 dolog(v, c)
590 Char **v;
591 struct command *c;
592 {
593     struct who *wp;
594     struct varent *vp;
595 
596     USE(v);
597     USE(c);
598     vp = adrof(STRwatch);	/* lint insists vp isn't used unless we */
599     if (vp == NULL)		/* unless we assign it outside the if */
600 	stderror(ERR_NOWATCH);
601     resetwatch();
602     wp = whohead.who_next;
603     while (wp->who_next != NULL) {
604 	wp->who_name[0] = '\0';
605 	wp = wp->who_next;
606     }
607 }
608 
609 # ifdef UTHOST
610 char *
611 utmphost()
612 {
613     char *tty = short2str(varval(STRtty));
614     struct who *wp;
615     char *host = NULL;
616 
617     watch_login(1);
618 
619     for (wp = whohead.who_next; wp->who_next != NULL; wp = wp->who_next) {
620 	if (strcmp(tty, wp->who_tty) == 0)
621 	    host = wp->who_host;
622 	wp->who_name[0] = '\0';
623     }
624     resetwatch();
625     return host;
626 }
627 # endif /* UTHOST */
628 
629 #ifdef WINNT
630 void add_to_who_list(name, mach_nm)
631     char *name;
632     char *mach_nm;
633 {
634 
635     struct who *wp, *wpnew;
636     int comp = -1;
637 
638     wp = whohead.who_next;
639     while (wp->who_next && (comp = strncmp(wp->who_tty,mach_nm,UTLINLEN)) < 0)
640 	wp = wp->who_next;/* find that tty! */
641 
642     if (wp->who_next && comp == 0) {	/* found the tty... */
643 
644 	if (*name == '\0') {
645 	    wp->who_time = 0;
646 	    wp->who_status = OFFLINE;
647 	}
648 	else if (strncmp(name, wp->who_name, UTNAMLEN) == 0) {
649 	    /* someone is logged in */
650 	    wp->who_time = 0;
651 	    wp->who_status = 0;	/* same guy */
652 	}
653 	else {
654 	    (void) strncpy(wp->who_new, name, UTNAMLEN);
655 	    wp->who_time = 0;
656 	    if (wp->who_name[0] == '\0')
657 		wp->who_status = ONLINE;
658 	    else
659 		wp->who_status = CHANGED;
660 	}
661     }
662     else {
663 	wpnew = (struct who *) xcalloc(1, sizeof *wpnew);
664 	(void) strncpy(wpnew->who_tty, mach_nm, UTLINLEN);
665 	wpnew->who_time = 0;
666 	if (*name == '\0')
667 	    wpnew->who_status = OFFLINE;
668 	else {
669 	    (void) strncpy(wpnew->who_new, name, UTNAMLEN);
670 	    wpnew->who_status = ONLINE;
671 	}
672 #ifdef WHODEBUG
673 	debugwholist(wpnew, wp);
674 #endif /* WHODEBUG */
675 
676 	wpnew->who_next = wp;	/* link in a new 'who' */
677 	wpnew->who_prev = wp->who_prev;
678 	wpnew->who_prev->who_next = wpnew;
679 	wp->who_prev = wpnew;	/* linked in now */
680     }
681 }
682 #endif /* WINNT */
683 #endif /* HAVENOUTMP */
684