Fix some more windowlist glitches
[screen-lua.git] / src / utmp.c
blobc96ef76fe696fefad081cedbebcbbcc5d7ebbda6
1 /* Copyright (c) 1993-2002
2 * Juergen Weigert (jnweiger@immd4.informatik.uni-erlangen.de)
3 * Michael Schroeder (mlschroe@immd4.informatik.uni-erlangen.de)
4 * Copyright (c) 1987 Oliver Laumann
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2, or (at your option)
9 * any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program (see the file COPYING); if not, write to the
18 * Free Software Foundation, Inc.,
19 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
21 ****************************************************************
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <fcntl.h>
28 #include "config.h"
29 #include "screen.h"
30 #include "extern.h"
32 #ifdef HAVE_UTEMPTER
33 #include <utempter.h>
34 #endif
37 extern struct display *display;
38 #ifdef CAREFULUTMP
39 extern struct win *windows;
40 #endif
41 extern struct win *fore;
42 extern char *LoginName;
43 extern int real_uid, eff_uid;
47 * UTNOKEEP: A (ugly) hack for apollo that does two things:
48 * 1) Always close and reopen the utmp file descriptor. (I don't know
49 * for what reason this is done...)
50 * 2) Implement an unsorted utmp file much like GETUTENT.
51 * (split into UT_CLOSE and UT_UNSORTED)
55 #ifdef UTNOKEEP
56 # define UT_CLOSE
57 # define UT_UNSORTED
58 #endif
60 #ifdef UT_CLOSE
61 # undef UT_CLOSE
62 # define UT_CLOSE endutent()
63 #else
64 # define UT_CLOSE
65 #endif
69 * we have a suid-root helper app that changes the utmp for us
70 * (won't work for login-slots)
72 #if (defined(sun) && defined(SVR4) && defined(GETUTENT)) || defined(HAVE_UTEMPTER)
73 # define UTMP_HELPER
74 #endif
78 #ifdef UTMPOK
81 static slot_t TtyNameSlot __P((char *));
82 static void makeuser __P((struct utmp *, char *, char *, int));
83 static void makedead __P((struct utmp *));
84 static int pututslot __P((slot_t, struct utmp *, char *, struct win *));
85 static struct utmp *getutslot __P((slot_t));
86 #ifndef GETUTENT
87 static struct utmp *getutent __P((void));
88 static void endutent __P((void));
89 static int initutmp __P((void));
90 static void setutent __P((void));
91 #endif
92 #if defined(linux) && defined(GETUTENT)
93 static struct utmp *xpututline __P((struct utmp *utmp));
94 # define pututline xpututline
95 #endif
98 static int utmpok;
99 static char UtmpName[] = UTMPFILE;
100 #ifndef UTMP_HELPER
101 static int utmpfd = -1;
102 #endif
105 # if defined(GETUTENT) && (!defined(SVR4) || defined(__hpux))
106 # if defined(hpux) /* cruel hpux release 8.0 */
107 # define pututline _pututline
108 # endif /* hpux */
109 extern struct utmp *getutline(), *pututline();
110 # if defined(_SEQUENT_)
111 extern struct utmp *ut_add_user(), *ut_delete_user();
112 extern char *ut_find_host();
113 # ifndef UTHOST
114 # define UTHOST /* _SEQUENT_ has ut_find_host() */
115 # endif
116 # endif /* _SEQUENT_ */
117 # endif /* GETUTENT && !SVR4 */
119 # if !defined(GETUTENT) && !defined(UT_UNSORTED)
120 # ifdef GETTTYENT
121 # include <ttyent.h>
122 # else
123 struct ttyent { char *ty_name; };
124 static void setttyent __P((void));
125 static struct ttyent *getttyent __P((void));
126 # endif
127 # endif /* !GETUTENT && !UT_UNSORTED */
129 #ifndef _SEQUENT_
130 # undef D_loginhost
131 # define D_loginhost D_utmp_logintty.ut_host
132 #endif
133 #ifndef UTHOST
134 # undef D_loginhost
135 # define D_loginhost ((char *)0)
136 #endif
139 #endif /* UTMPOK */
143 * SlotToggle - modify the utmp slot of the fore window.
145 * how > 0 do try to set a utmp slot.
146 * how = 0 try to withdraw a utmp slot.
148 * w_slot = -1 window not logged in.
149 * w_slot = 0 window not logged in, but should be logged in.
150 * (unable to write utmp, or detached).
153 #ifndef UTMPOK
154 void
155 SlotToggle(how)
156 int how;
158 debug1("SlotToggle (!UTMPOK) %d\n", how);
159 # ifdef UTMPFILE
160 Msg(0, "Unable to modify %s.\n", UTMPFILE);
161 # else
162 Msg(0, "Unable to modify utmp-database.\n");
163 # endif
165 #endif
169 #ifdef UTMPOK
171 void
172 SlotToggle(how)
173 int how;
175 debug1("SlotToggle %d\n", how);
176 if (fore->w_type != W_TYPE_PTY)
178 Msg(0, "Can only work with normal windows.\n");
179 return;
181 if (how)
183 debug(" try to log in\n");
184 if ((fore->w_slot == (slot_t) -1) || (fore->w_slot == (slot_t) 0))
186 #ifdef USRLIMIT
187 if (CountUsers() >= USRLIMIT)
189 Msg(0, "User limit reached.");
190 return;
192 #endif
193 if (SetUtmp(fore) == 0)
194 Msg(0, "This window is now logged in.");
195 else
196 Msg(0, "This window should now be logged in.");
197 WindowChanged(fore, 'f');
199 else
200 Msg(0, "This window is already logged in.");
202 else
204 debug(" try to log out\n");
205 if (fore->w_slot == (slot_t) -1)
206 Msg(0, "This window is already logged out\n");
207 else if (fore->w_slot == (slot_t) 0)
209 debug("What a relief! In fact, it was not logged in\n");
210 Msg(0, "This window is not logged in.");
211 fore->w_slot = (slot_t) -1;
213 else
215 RemoveUtmp(fore);
216 if (fore->w_slot != (slot_t) -1)
217 Msg(0, "What? Cannot remove Utmp slot?");
218 else
219 Msg(0, "This window is no longer logged in.");
220 #ifdef CAREFULUTMP
221 CarefulUtmp();
222 #endif
223 WindowChanged(fore, 'f');
229 #ifdef CAREFULUTMP
231 /* CAREFULUTMP: goodie for paranoid sysadmins: always leave one
232 * window logged in
234 void
235 CarefulUtmp()
237 struct win *p;
239 if (!windows) /* hopeless */
240 return;
241 debug("CarefulUtmp counting slots\n");
242 for (p = windows; p; p = p->w_next)
243 if (p->w_ptyfd >= 0 && p->w_slot != (slot_t)-1)
244 return; /* found one, nothing to do */
246 debug("CarefulUtmp: no slots, log one in again.\n");
247 for (p = windows; p; p = p->w_next)
248 if (p->w_ptyfd >= 0) /* no zombies please */
249 break;
250 if (!p)
251 return; /* really hopeless */
252 SetUtmp(p);
253 Msg(0, "Window %d is now logged in.\n", p->w_number);
255 #endif /* CAREFULUTMP */
258 void
259 InitUtmp()
261 debug1("InitUtmp testing '%s'...\n", UtmpName);
262 #ifndef UTMP_HELPER
263 if ((utmpfd = open(UtmpName, O_RDWR)) == -1)
265 if (errno != EACCES)
266 Msg(errno, UtmpName);
267 debug("InitUtmp failed.\n");
268 utmpok = 0;
269 return;
271 # ifdef GETUTENT
272 close(utmpfd); /* it was just a test */
273 utmpfd = -1;
274 # endif /* GETUTENT */
275 #endif /* UTMP_HELPER */
276 utmpok = 1;
280 #ifdef USRLIMIT
282 CountUsers()
284 struct utmp *ut;
285 int UserCount;
287 debug1("CountUsers() - utmpok=%d\n", utmpok);
288 if (!utmpok)
289 return 0;
290 UserCount = 0;
291 setutent();
292 while (ut = getutent())
293 if (SLOT_USED(ut))
294 UserCount++;
295 UT_CLOSE;
296 return UserCount;
298 #endif /* USRLIMIT */
303 * the utmp entry for tty is located and removed.
304 * it is stored in D_utmp_logintty.
306 void
307 RemoveLoginSlot()
309 struct utmp u, *uu;
311 ASSERT(display);
312 debug("RemoveLoginSlot: removing your logintty\n");
313 D_loginslot = TtyNameSlot(D_usertty);
314 if (D_loginslot == (slot_t)0 || D_loginslot == (slot_t)-1)
315 return;
316 #ifdef UTMP_HELPER
317 if (eff_uid) /* helpers can't do login slots. sigh. */
318 #else
319 if (!utmpok)
320 #endif
322 D_loginslot = 0;
323 debug("RemoveLoginSlot: utmpok == 0\n");
325 else
327 #ifdef _SEQUENT_
329 char *p;
330 if ((p = ut_find_host(D_loginslot)) != 0)
331 strncpy(D_loginhost, p, sizeof(D_loginhost) - 1);
332 D_loginhost[sizeof(D_loginhost) - 1] = 0;
334 #endif /* _SEQUENT_ */
336 if ((uu = getutslot(D_loginslot)) == 0)
338 debug("Utmp slot not found -> not removed");
339 D_loginslot = 0;
341 else
343 D_utmp_logintty = *uu;
344 u = *uu;
345 makedead(&u);
346 if (pututslot(D_loginslot, &u, (char *)0, (struct win *)0) == 0)
347 D_loginslot = 0;
349 UT_CLOSE;
351 debug1(" slot %d zapped\n", (int)D_loginslot);
352 if (D_loginslot == (slot_t)0)
354 /* couldn't remove slot, do a 'mesg n' at least. */
355 struct stat stb;
356 char *tty;
357 debug("couln't zap slot -> do mesg n\n");
358 D_loginttymode = 0;
359 if ((tty = ttyname(D_userfd)) && stat(tty, &stb) == 0 && (int)stb.st_uid == real_uid && ((int)stb.st_mode & 0777) != 0666)
361 D_loginttymode = (int)stb.st_mode & 0777;
362 chmod(D_usertty, stb.st_mode & 0600);
368 * D_utmp_logintty is reinserted into utmp
370 void
371 RestoreLoginSlot()
373 char *tty;
375 debug("RestoreLoginSlot()\n");
376 ASSERT(display);
377 if (utmpok && D_loginslot != (slot_t)0 && D_loginslot != (slot_t)-1)
379 debug1(" logging you in again (slot %#x)\n", (int)D_loginslot);
380 if (pututslot(D_loginslot, &D_utmp_logintty, D_loginhost, (struct win *)0) == 0)
381 Msg(errno,"Could not write %s", UtmpName);
383 UT_CLOSE;
384 D_loginslot = (slot_t)0;
385 if (D_loginttymode && (tty = ttyname(D_userfd)))
386 chmod(tty, D_loginttymode);
392 * Construct a utmp entry for window wi.
393 * the hostname field reflects what we know about the user (display)
394 * location. If d_loginhost is not set, then he is local and we write
395 * down the name of his terminal line; else he is remote and we keep
396 * the hostname here. The letter S and the window id will be appended.
397 * A saved utmp entry in wi->w_savut serves as a template, usually.
401 SetUtmp(wi)
402 struct win *wi;
404 register slot_t slot;
405 struct utmp u;
406 int saved_ut;
407 #ifdef UTHOST
408 char *p;
409 char host[sizeof(D_loginhost) + 15];
410 #else
411 char *host = 0;
412 #endif /* UTHOST */
414 wi->w_slot = (slot_t)0;
415 if (!utmpok || wi->w_type != W_TYPE_PTY)
416 return -1;
417 if ((slot = TtyNameSlot(wi->w_tty)) == (slot_t)0)
419 debug1("SetUtmp failed (tty %s).\n",wi->w_tty);
420 return -1;
422 debug2("SetUtmp %d will get slot %d...\n", wi->w_number, (int)slot);
424 bzero((char *)&u, sizeof(u));
425 if ((saved_ut = bcmp((char *) &wi->w_savut, (char *)&u, sizeof(u))))
426 /* restore original, of which we will adopt all fields but ut_host */
427 bcopy((char *)&wi->w_savut, (char *) &u, sizeof(u));
429 if (!saved_ut)
430 makeuser(&u, stripdev(wi->w_tty), LoginName, wi->w_pid);
432 #ifdef UTHOST
433 host[sizeof(host) - 15] = '\0';
434 if (display)
436 strncpy(host, D_loginhost, sizeof(host) - 15);
437 if (D_loginslot != (slot_t)0 && D_loginslot != (slot_t)-1 && host[0] != '\0')
440 * we want to set our ut_host field to something like
441 * ":ttyhf:s.0" or
442 * "faui45:s.0" or
443 * "132.199.81.4:s.0" (even this may hurt..), but not
444 * "faui45.informati"......:s.0
445 * HPUX uses host:0.0, so chop at "." and ":" (Eric Backus)
447 for (p = host; *p; p++)
448 if ((*p < '0' || *p > '9') && (*p != '.'))
449 break;
450 if (*p)
452 for (p = host; *p; p++)
453 if (*p == '.' || (*p == ':' && p != host))
455 *p = '\0';
456 break;
460 else
462 strncpy(host + 1, stripdev(D_usertty), sizeof(host) - 15 - 1);
463 host[0] = ':';
466 else
467 strncpy(host, "local", sizeof(host) - 15);
469 sprintf(host + strlen(host), ":S.%d", wi->w_number);
470 debug1("rlogin hostname: '%s'\n", host);
472 # if !defined(_SEQUENT_) && !defined(sequent)
473 strncpy(u.ut_host, host, sizeof(u.ut_host));
474 # endif
475 #endif /* UTHOST */
477 if (pututslot(slot, &u, host, wi) == 0)
479 Msg(errno,"Could not write %s", UtmpName);
480 UT_CLOSE;
481 return -1;
483 debug("SetUtmp successful\n");
484 wi->w_slot = slot;
485 bcopy((char *)&u, (char *)&wi->w_savut, sizeof(u));
486 UT_CLOSE;
487 return 0;
491 * if slot could be removed or was 0, wi->w_slot = -1;
492 * else not changed.
496 RemoveUtmp(wi)
497 struct win *wi;
499 struct utmp u, *uu;
500 slot_t slot;
502 slot = wi->w_slot;
503 debug1("RemoveUtmp slot=%#x\n", slot);
504 if (!utmpok)
505 return -1;
506 if (slot == (slot_t)0 || slot == (slot_t)-1)
508 wi->w_slot = (slot_t)-1;
509 return 0;
511 bzero((char *) &u, sizeof(u));
512 #ifdef sgi
513 bcopy((char *)&wi->w_savut, (char *)&u, sizeof(u));
514 uu = &u;
515 #else
516 if ((uu = getutslot(slot)) == 0)
518 Msg(0, "Utmp slot not found -> not removed");
519 return -1;
521 bcopy((char *)uu, (char *)&wi->w_savut, sizeof(wi->w_savut));
522 #endif
523 u = *uu;
524 makedead(&u);
525 if (pututslot(slot, &u, (char *)0, wi) == 0)
527 Msg(errno,"Could not write %s", UtmpName);
528 UT_CLOSE;
529 return -1;
531 debug("RemoveUtmp successfull\n");
532 wi->w_slot = (slot_t)-1;
533 UT_CLOSE;
534 return 0;
539 /*********************************************************************
541 * routines using the getut* api
544 #ifdef GETUTENT
546 #define SLOT_USED(u) (u->ut_type == USER_PROCESS)
548 static struct utmp *
549 getutslot(slot)
550 slot_t slot;
552 struct utmp u;
553 bzero((char *)&u, sizeof(u));
554 strncpy(u.ut_line, slot, sizeof(u.ut_line));
555 setutent();
556 return getutline(&u);
559 static int
560 pututslot(slot, u, host, wi)
561 slot_t slot;
562 struct utmp *u;
563 char *host;
564 struct win *wi;
566 #ifdef _SEQUENT_
567 if (SLOT_USED(u) && host && *host)
568 return ut_add_user(u.ut_name, slot, u.ut_pid, host) != 0;
569 if (!SLOT_USED(u))
570 return ut_delete_user(slot, u.ut_pid, 0, 0) != 0;
571 #endif
572 #ifdef HAVE_UTEMPTER
573 if (eff_uid && wi->w_ptyfd != -1)
575 /* sigh, linux hackers made the helper functions void */
576 if (SLOT_USED(u))
577 addToUtmp(wi->w_tty, host, wi->w_ptyfd);
578 else
579 removeLineFromUtmp(wi->w_tty, wi->w_ptyfd);
580 return 1; /* pray for success */
582 #endif
583 setutent();
584 return pututline(u) != 0;
587 static void
588 makedead(u)
589 struct utmp *u;
591 u->ut_type = DEAD_PROCESS;
592 #if !defined(linux) || defined(EMPTY)
593 u->ut_exit.e_termination = 0;
594 u->ut_exit.e_exit = 0;
595 #endif
596 #if !defined(sun) || !defined(SVR4)
597 u->ut_user[0] = 0; /* for Digital UNIX, kilbi@rad.rwth-aachen.de */
598 #endif
601 static void
602 makeuser(u, line, user, pid)
603 struct utmp *u;
604 char *line, *user;
605 int pid;
607 time_t now;
608 u->ut_type = USER_PROCESS;
609 strncpy(u->ut_user, user, sizeof(u->ut_user));
610 /* Now the tricky part... guess ut_id */
611 #if defined(sgi) || defined(linux)
612 strncpy(u->ut_id, line + 3, sizeof(u->ut_id));
613 #else /* sgi */
614 # ifdef _IBMR2
615 strncpy(u->ut_id, line, sizeof(u->ut_id));
616 # else
617 strncpy(u->ut_id, line + strlen(line) - 2, sizeof(u->ut_id));
618 # endif
619 #endif /* sgi */
620 strncpy(u->ut_line, line, sizeof(u->ut_line));
621 u->ut_pid = pid;
622 /* must use temp variable because of NetBSD/sparc64, where
623 * ut_xtime is long(64) but time_t is int(32) */
624 (void)time(&now);
625 u->ut_time = now;
628 static slot_t
629 TtyNameSlot(nam)
630 char *nam;
632 return stripdev(nam);
636 #else /* GETUTENT */
638 /*********************************************************************
640 * getut emulation for systems lacking the api
643 static struct utmp uent;
645 #define SLOT_USED(u) (u.ut_name[0] != 0)
647 static int
648 initutmp()
650 if (utmpfd >= 0)
651 return 1;
652 return (utmpfd = open(UtmpName, O_RDWR)) >= 0;
655 static void
656 setutent()
658 if (utmpfd >= 0)
659 (void)lseek(utmpfd, (off_t)0, 0);
662 static void
663 endutent()
665 if (utmpfd >= 0)
666 close(utmpfd);
667 utmpfd = -1;
670 static struct utmp *
671 getutent()
673 if (utmpfd < 0 && !initutmp())
674 return 0;
675 if (read(utmpfd, &uent, sizeof(uent)) != sizeof(uent))
676 return 0;
677 return &uent;
680 static struct utmp *
681 getutslot(slot)
682 slot_t slot;
684 if (utmpfd < 0 && !initutmp())
685 return 0;
686 lseek(utmpfd, (off_t)(slot * sizeof(struct utmp)), 0);
687 if (read(utmpfd, &uent, sizeof(uent)) != sizeof(uent))
688 return 0;
689 return &uent;
692 static int
693 pututslot(slot, u, host, wi)
694 slot_t slot;
695 struct utmp *u;
696 char *host;
697 struct win *wi;
699 #ifdef sequent
700 if (SLOT_USED(u))
701 return add_utmp(slot, u) != -1;
702 #endif
703 if (utmpfd < 0 && !initutmp())
704 return 0;
705 lseek(utmpfd, (off_t)(slot * sizeof(*u)), 0);
706 if (write(utmpfd, u, sizeof(*u)) != sizeof(*u))
707 return 0;
708 return 1;
712 static void
713 makedead(u)
714 struct utmp *u;
716 #ifdef UT_UNSORTED
717 bzero(u->ut_name, sizeof(u->ut_name));
718 # ifdef UTHOST
719 bzero(u->ut_host, sizeof(u->ut_host));
720 # endif
721 #else
722 bzero((char *)u, sizeof(*u));
723 #endif
727 static void
728 makeuser(u, line, user, pid)
729 struct utmp *u;
730 char *line, *user;
731 int pid;
733 time_t now;
734 strncpy(u->ut_line, line, sizeof(u->ut_line));
735 strncpy(u->ut_name, user, sizeof(u->ut_name));
736 (void)time(&now);
737 u->ut_time = now;
740 static slot_t
741 TtyNameSlot(nam)
742 char *nam;
744 slot_t slot;
745 char *line;
746 #ifndef UT_UNSORTED
747 struct ttyent *tp;
748 #endif
750 line = stripdev(nam);
751 #ifdef UT_UNSORTED
752 setutent();
753 if (utmpfd < 0)
754 return -1;
755 for (slot = 0; getutent(); slot++)
756 if (strcmp(uent.ut_line, line) == 0)
757 break;
758 UT_CLOSE;
759 #else
760 slot = 1;
761 setttyent();
762 while ((tp = getttyent()) != 0 && strcmp(line, tp->ty_name) != 0)
763 slot++;
764 #endif
765 return slot;
768 #endif /* GETUTENT */
772 /*********************************************************************
774 * Cheap plastic imitation of ttyent routines.
777 #if !defined(GETTTYENT) && !defined(GETUTENT) && !defined(UT_UNSORTED)
780 static char *tt, *ttnext;
781 static char ttys[] = "/etc/ttys";
783 static void
784 setttyent()
786 if (ttnext == 0)
788 struct stat s;
789 register int f;
790 register char *p, *ep;
792 if ((f = open(ttys, O_RDONLY)) == -1 || fstat(f, &s) == -1)
793 Panic(errno, ttys);
794 if ((tt = malloc((unsigned) s.st_size + 1)) == 0)
795 Panic(0, strnomem);
796 if (read(f, tt, s.st_size) != s.st_size)
797 Panic(errno, ttys);
798 close(f);
799 for (p = tt, ep = p + s.st_size; p < ep; p++)
800 if (*p == '\n')
801 *p = '\0';
802 *p = '\0';
804 ttnext = tt;
807 static struct ttyent *
808 getttyent()
810 static struct ttyent t;
812 if (*ttnext == '\0')
813 return NULL;
814 t.ty_name = ttnext + 2;
815 ttnext += strlen(ttnext) + 1;
816 return &t;
819 #endif /* !GETTTYENT && !GETUTENT && !UT_UNSORTED*/
823 #endif /* UTMPOK */
828 /*********************************************************************
830 * getlogin() replacement (for SVR4 machines)
833 # if defined(BUGGYGETLOGIN) && defined(UTMP_FILE)
834 char *
835 getlogin()
837 char *tty = NULL;
838 #ifdef utmp
839 # undef utmp
840 #endif
841 struct utmp u;
842 static char retbuf[sizeof(u.ut_user)+1];
843 int fd;
845 for (fd = 0; fd <= 2 && (tty = ttyname(fd)) == NULL; fd++)
847 if ((tty == NULL) || ((fd = open(UTMP_FILE, O_RDONLY)) < 0))
848 return NULL;
849 tty = stripdev(tty);
850 retbuf[0] = '\0';
851 while (read(fd, (char *)&u, sizeof(struct utmp)) == sizeof(struct utmp))
853 if (!strncmp(tty, u.ut_line, sizeof(u.ut_line)))
855 strncpy(retbuf, u.ut_user, sizeof(u.ut_user));
856 retbuf[sizeof(u.ut_user)] = '\0';
857 if (u.ut_type == USER_PROCESS)
858 break;
861 close(fd);
863 return *retbuf ? retbuf : NULL;
865 # endif /* BUGGYGETLOGIN */
867 #if defined(linux) && defined(GETUTENT)
868 # undef pututline
870 /* aargh, linux' pututline returns void! */
871 struct utmp *
872 xpututline(u)
873 struct utmp *u;
875 struct utmp *u2;
876 pututline(u);
877 setutent();
878 u2 = getutline(u);
879 if (u2 == 0)
880 return u->ut_type == DEAD_PROCESS ? u : 0;
881 return u->ut_type == u2->ut_type ? u : 0;
883 #endif