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, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 #pragma ident   "%Z%%M% %I%     %E% SMI"
  23 
  24 /*
  25  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
  26  * Use is subject to license terms.
  27  */
  28 /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
  29 /* All Rights Reserved */
  30 /*
  31  * University Copyright- Copyright (c) 1982, 1986, 1988
  32  * The Regents of the University of California
  33  * All Rights Reserved
  34  *
  35  * University Acknowledgment- Portions of this document are derived from
  36  * software developed by the University of California, Berkeley, and its
  37  * contributors.
  38  */
  39 
  40 #pragma ident   "%Z%%M% %I%     %E% SMI"
  41 
  42 #include <stdlib.h>
  43 #include <stdio.h>
  44 #include <unistd.h>
  45 #include <sys/types.h>
  46 #include <netconfig.h>
  47 #include <netdir.h>
  48 #include <rpc/rpc.h>
  49 #include <rpcsvc/rusers.h>
  50 #include <string.h>
  51 #include <limits.h>
  52 
  53 #define NMAX    12              /* These are used as field width specifiers */
  54 #define LMAX    8               /* when printing.                           */
  55 #define HMAX    16              /* "Logged in" host name. */
  56 
  57 #define MACHINELEN 16           /* length of machine name printed out */
  58 #define NUMENTRIES 256
  59 #define min(a, b) ((a) < (b) ? (a) : (b))
  60 
  61 struct entry {
  62         int cnt;
  63         int idle;               /* set to INT_MAX if not present */
  64         char *machine;
  65         utmp_array users;
  66 };
  67 
  68 static int curentry;
  69 static int total_entries;
  70 static struct entry *entry;
  71 static int hflag;               /* host: sort by machine name */
  72 static int iflag;               /* idle: sort by idle time */
  73 static int uflag;               /* users: sort by number of users */
  74 static int lflag;               /* print out long form */
  75 static int aflag;               /* all: list all machines */
  76 static int dflag;               /* debug: list only first n machines */
  77 static int sorted;
  78 static int debug;
  79 static int debugcnt;
  80 static char *nettype;
  81 
  82 static int hcompare(const struct entry *, const struct entry *);
  83 static int icompare(const struct entry *, const struct entry *);
  84 static int ucompare(const struct entry *, const struct entry *);
  85 static int print_info(struct utmpidlearr *, const char *);
  86 static int print_info_3(utmp_array *, const char *);
  87 static int collectnames(void *, struct netbuf *, struct netconfig *);
  88 static int collectnames_3(void *, struct netbuf *, struct netconfig *);
  89 static void singlehost(char *);
  90 static void printnames(void);
  91 static void putline_2(char *, struct utmpidle *);
  92 static void putline_3(char *, rusers_utmp *);
  93 static void prttime(uint_t, char *);
  94 static void usage(void);
  95 
  96 /*
  97  * rusers [-ahilu] [host...]
  98  */
  99 int
 100 main(int argc, char *argv[])
 101 {
 102         int c;
 103         uint_t errflag = 0;
 104         uint_t single = 0;
 105         struct utmpidlearr utmpidlearr;
 106         utmp_array      utmp_array_res;
 107 
 108         curentry = 0;
 109         total_entries = NUMENTRIES;
 110         entry = malloc(sizeof (struct entry) * total_entries);
 111 
 112         while ((c = getopt(argc, argv, ":ad:hilun:")) != -1) {
 113                 switch (c) {
 114                 case 'a':
 115                         aflag++;
 116                         break;
 117                 case 'd':
 118                         dflag++;
 119                         debug = atoi(optarg);
 120                         (void) printf("Will collect %d responses.\n", debug);
 121                         break;
 122                 case 'h':
 123                         hflag++;
 124                         sorted++;
 125                         if (iflag || uflag)
 126                                 errflag++;
 127                         break;
 128                 case 'i':
 129                         iflag++;
 130                         sorted++;
 131                         if (hflag || uflag)
 132                                 errflag++;
 133                         break;
 134                 case 'u':
 135                         uflag++;
 136                         sorted++;
 137                         if (hflag || iflag)
 138                                 errflag++;
 139                         break;
 140                 case 'l':
 141                         lflag++;
 142                         break;
 143                 case ':':       /* required operand missing */
 144                         errflag++;
 145                         break;
 146                 case 'n':
 147                         nettype = optarg;
 148                         break;
 149                 default:
 150                 case '?':       /* Unrecognized option */
 151                         errflag++;
 152                         break;
 153                 }
 154         }
 155         if (errflag)
 156                 usage();
 157 
 158         for (; optind < argc; optind++) {
 159                 single++;
 160                 singlehost(argv[optind]);
 161         }
 162         if (single) {
 163                 if (sorted)
 164                         printnames();
 165                 free(entry);
 166                 exit(0);
 167         }
 168 
 169         if (sorted) {
 170                 (void) printf("Collecting responses...\n");
 171                 (void) fflush(stdout);
 172         }
 173         utmp_array_res.utmp_array_val = NULL;
 174         utmp_array_res.utmp_array_len = 0;
 175         (void) printf("Sending broadcast for rusersd protocol version 3...\n");
 176         (void) rpc_broadcast(RUSERSPROG, RUSERSVERS_3,
 177                 RUSERSPROC_NAMES, (xdrproc_t)xdr_void, NULL,
 178                 (xdrproc_t)xdr_utmp_array, (char *)&utmp_array_res,
 179                 (resultproc_t)collectnames_3, nettype);
 180         utmpidlearr.uia_arr = NULL;
 181         (void) printf("Sending broadcast for rusersd protocol version 2...\n");
 182         (void) rpc_broadcast(RUSERSPROG, RUSERSVERS_IDLE,
 183                 RUSERSPROC_NAMES, (xdrproc_t)xdr_void, NULL,
 184                 (xdrproc_t)xdr_utmpidlearr, (char *)&utmpidlearr,
 185                 (resultproc_t)collectnames, nettype);
 186 
 187         if (sorted)
 188                 printnames();
 189 
 190         free(entry);
 191         return (0);
 192 }
 193 
 194 static void
 195 singlehost(char *name)
 196 {
 197         enum clnt_stat err;
 198         struct utmpidlearr utmpidlearr;
 199         utmp_array      utmp_array_res;
 200 
 201         if (curentry >= total_entries) {
 202                 struct entry *tmp;
 203 
 204                 total_entries += NUMENTRIES;
 205                 if ((tmp = realloc(entry, sizeof (struct entry)
 206                                                 * total_entries)) == NULL)
 207                         return;
 208                 entry = tmp;
 209         }
 210         utmp_array_res.utmp_array_val = NULL;
 211         utmp_array_res.utmp_array_len = 0;
 212         err = rpc_call(name, RUSERSPROG, RUSERSVERS_3,
 213                 RUSERSPROC_NAMES, (xdrproc_t)xdr_void, 0,
 214                 (xdrproc_t)xdr_utmp_array, (char *)&utmp_array_res,
 215                 nettype);
 216         if (err == RPC_SUCCESS) {
 217                 (void) print_info_3(&utmp_array_res, name);
 218                 return;
 219         }
 220         if (err == RPC_PROGVERSMISMATCH) {
 221                 utmpidlearr.uia_arr = NULL;
 222                 err = rpc_call(name, RUSERSPROG, RUSERSVERS_IDLE,
 223                                 RUSERSPROC_NAMES, (xdrproc_t)xdr_void, 0,
 224                                 (xdrproc_t)xdr_utmpidlearr,
 225                                 (char *)&utmpidlearr, nettype);
 226         }
 227         if (err != RPC_SUCCESS) {
 228                 (void) fprintf(stderr, "%s: ", name);
 229                 clnt_perrno(err);
 230                 return;
 231         }
 232         (void) print_info(&utmpidlearr, name);
 233 }
 234 
 235 /*
 236  * Collect responses from RUSERSVERS_IDLE broadcast, convert to
 237  * RUSERSVERS_3 format, and store in entry database.
 238  */
 239 static int
 240 collectnames(void *resultsp, struct netbuf *raddrp, struct netconfig *nconf)
 241 {
 242         struct utmpidlearr utmpidlearr;
 243         struct entry *entryp, *lim;
 244         struct nd_hostservlist *hs;
 245         char host[MACHINELEN + 1];
 246 
 247         utmpidlearr = *(struct utmpidlearr *)resultsp;
 248         if (utmpidlearr.uia_cnt < 1 && !aflag)
 249                 return (0);
 250 
 251         if (netdir_getbyaddr(nconf, &hs, raddrp)) {
 252 #ifdef DEBUG
 253                 netdir_perror("netdir_getbyaddr");
 254 #endif
 255                 /* netdir routine couldn't resolve addr;just print out uaddr */
 256                 (void) sprintf(host, "%.*s", MACHINELEN,
 257                                                 taddr2uaddr(nconf, raddrp));
 258         } else {
 259                 (void) sprintf(host, "%.*s", MACHINELEN,
 260                                                 hs->h_hostservs->h_host);
 261                 netdir_free((char *)hs, ND_HOSTSERVLIST);
 262         }
 263         /*
 264          * need to realloc more space if we have more than 256 machines
 265          * that respond to broadcast
 266          */
 267         if (curentry >= total_entries) {
 268                 struct entry *tmp;
 269 
 270                 total_entries += NUMENTRIES;
 271                 if ((tmp = realloc(entry, sizeof (struct entry)
 272                                                 * total_entries)) == NULL)
 273                         return (1);
 274                 entry = tmp;
 275         }
 276 
 277 
 278         /*
 279          * weed out duplicates
 280          */
 281         lim = entry + curentry;
 282         for (entryp = entry; entryp < lim; entryp++) {
 283                 if (strcmp(entryp->machine, host) == 0)
 284                         return (0);
 285         }
 286         return (print_info((struct utmpidlearr *)resultsp, host));
 287 }
 288 
 289 static int
 290 print_info(struct utmpidlearr *utmpidlearrp, const char *name)
 291 {
 292         utmp_array *iconvert;
 293         int i, cnt, minidle;
 294         char host[MACHINELEN + 1];
 295         char username[NMAX + 1];
 296 
 297         cnt = utmpidlearrp->uia_cnt;
 298         (void) sprintf(host, "%.*s", MACHINELEN, name);
 299 
 300         /*
 301          * if raw, print this entry out immediately
 302          * otherwise store for later sorting
 303          */
 304         if (!sorted) {
 305                 if (lflag && (cnt > 0))
 306                         for (i = 0; i < cnt; i++)
 307                                 putline_2(host, utmpidlearrp->uia_arr[i]);
 308                 else {
 309                     (void) printf("%-*.*s", MACHINELEN, MACHINELEN, host);
 310                     for (i = 0; i < cnt; i++) {
 311                         (void) strlcpy(username,
 312                                     utmpidlearrp->uia_arr[i]->ui_utmp.ut_name,
 313                                     NMAX + 1);
 314                         (void) printf(" %.*s", NMAX, username);
 315                     }
 316                     (void) printf("\n");
 317                 }
 318                 /* store just the name */
 319                 entry[curentry].machine = malloc(MACHINELEN + 1);
 320                 if (entry[curentry].machine == NULL) {
 321                         (void) fprintf(stderr, "Ran out of memory - exiting\n");
 322                         exit(1);
 323                 }
 324                 (void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
 325                 entry[curentry++].cnt = 0;
 326                 if (dflag && (++debugcnt >= debug))
 327                         return (1);
 328                 return (0);
 329         }
 330         entry[curentry].machine = malloc(MACHINELEN + 1);
 331         if (entry[curentry].machine == NULL) {
 332                 (void) fprintf(stderr, "Ran out of memory - exiting\n");
 333                 exit(1);
 334         }
 335         (void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
 336         entry[curentry].cnt = cnt;
 337         iconvert = &entry[curentry].users;
 338         iconvert->utmp_array_len = cnt;
 339         iconvert->utmp_array_val = malloc(cnt * sizeof (rusers_utmp));
 340         minidle = INT_MAX;
 341         for (i = 0; i < cnt; i++) {
 342                 iconvert->utmp_array_val[i].ut_user =
 343                         strdup(utmpidlearrp->uia_arr[i]->ui_utmp.ut_name);
 344                 iconvert->utmp_array_val[i].ut_line =
 345                         strdup(utmpidlearrp->uia_arr[i]->ui_utmp.ut_line);
 346                 iconvert->utmp_array_val[i].ut_host =
 347                         strdup(utmpidlearrp->uia_arr[i]->ui_utmp.ut_host);
 348                 iconvert->utmp_array_val[i].ut_time =
 349                         utmpidlearrp->uia_arr[i]->ui_utmp.ut_time;
 350                 iconvert->utmp_array_val[i].ut_idle =
 351                         utmpidlearrp->uia_arr[i]->ui_idle;
 352                 minidle = min(minidle, utmpidlearrp->uia_arr[i]->ui_idle);
 353         }
 354         entry[curentry].idle = minidle;
 355         curentry++;
 356         if (dflag && (++debugcnt >= debug))
 357                 return (1);
 358         return (0);
 359 }
 360 
 361 
 362 /*
 363  * Collect responses from RUSERSVERS_3 broadcast.
 364  */
 365 static int
 366 collectnames_3(void *resultsp, struct netbuf *raddrp, struct netconfig *nconf)
 367 {
 368         utmp_array *uap;
 369         struct entry *entryp, *lim;
 370         struct nd_hostservlist *hs;
 371         char host[MACHINELEN + 1];
 372 
 373         uap = (utmp_array *)resultsp;
 374         if (uap->utmp_array_len < 1 && !aflag)
 375                 return (0);
 376 
 377         if (netdir_getbyaddr(nconf, &hs, raddrp)) {
 378 #ifdef DEBUG
 379         netdir_perror("netdir_getbyaddr");
 380 #endif
 381                 /* netdir routine couldn't resolve addr;just print out uaddr */
 382                 (void) sprintf(host, "%.*s", MACHINELEN,
 383                                                 taddr2uaddr(nconf, raddrp));
 384         } else {
 385                 (void) sprintf(host, "%.*s", MACHINELEN,
 386                                                 hs->h_hostservs->h_host);
 387                 netdir_free((char *)hs, ND_HOSTSERVLIST);
 388         }
 389 
 390         /*
 391          * need to realloc more space if we have more than 256 machines
 392          * that respond to broadcast
 393          */
 394         if (curentry >= total_entries) {
 395                 struct entry *tmp;
 396 
 397                 total_entries += NUMENTRIES;
 398                 if ((tmp = realloc(entry, sizeof (struct entry)
 399                                                 * total_entries)) == NULL)
 400                         return (1);
 401                 entry = tmp;
 402         }
 403 
 404 
 405         /*
 406          * weed out duplicates
 407          */
 408         lim = entry + curentry;
 409         for (entryp = entry; entryp < lim; entryp++) {
 410                 if (strcmp(entryp->machine, host) == 0)
 411                         return (0);
 412         }
 413         return (print_info_3(uap, host));
 414 }
 415 
 416 static int
 417 print_info_3(utmp_array *uap, const char *name)
 418 {
 419         int i, cnt, minidle;
 420         char host[MACHINELEN + 1];
 421 
 422         cnt = uap->utmp_array_len;
 423 
 424         (void) sprintf(host, "%.*s", MACHINELEN, name);
 425 
 426         /*
 427          * if raw, print this entry out immediately
 428          * otherwise store for later sorting
 429          */
 430         if (!sorted) {
 431                 if (lflag && (cnt > 0))
 432                         for (i = 0; i < cnt; i++)
 433                                 putline_3(host, &uap->utmp_array_val[i]);
 434                 else {
 435                         (void) printf("%-*.*s", MACHINELEN, MACHINELEN, host);
 436                         for (i = 0; i < cnt; i++)
 437                                 (void) printf(" %.*s", NMAX,
 438                                     uap->utmp_array_val[i].ut_user);
 439                         (void) printf("\n");
 440                 }
 441                 /* store just the name */
 442                 entry[curentry].machine = malloc(MACHINELEN + 1);
 443                 if (entry[curentry].machine == NULL) {
 444                         (void) fprintf(stderr, "Ran out of memory - exiting\n");
 445                         exit(1);
 446                 }
 447                 (void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
 448                 entry[curentry++].cnt = 0;
 449                 if (dflag && (++debugcnt >= debug))
 450                         return (1);
 451                 return (0);
 452         }
 453 
 454         entry[curentry].machine = malloc(MACHINELEN + 1);
 455         if (entry[curentry].machine == NULL) {
 456                 (void) fprintf(stderr, "Ran out of memory - exiting\n");
 457                 exit(1);
 458         }
 459         (void) strlcpy(entry[curentry].machine, name, MACHINELEN + 1);
 460         entry[curentry].cnt = cnt;
 461         entry[curentry].users.utmp_array_len = cnt;
 462         entry[curentry].users.utmp_array_val = malloc(cnt *
 463                 sizeof (rusers_utmp));
 464         minidle = INT_MAX;
 465         for (i = 0; i < cnt; i++) {
 466                 entry[curentry].users.utmp_array_val[i].ut_user =
 467                         strdup(uap->utmp_array_val[i].ut_user);
 468                 entry[curentry].users.utmp_array_val[i].ut_line =
 469                         strdup(uap->utmp_array_val[i].ut_line);
 470                 entry[curentry].users.utmp_array_val[i].ut_host =
 471                         strdup(uap->utmp_array_val[i].ut_host);
 472                 entry[curentry].users.utmp_array_val[i].ut_time =
 473                         uap->utmp_array_val[i].ut_time;
 474                 entry[curentry].users.utmp_array_val[i].ut_idle =
 475                         uap->utmp_array_val[i].ut_idle;
 476                 minidle = min(minidle, uap->utmp_array_val[i].ut_idle);
 477         }
 478         entry[curentry].idle = minidle;
 479         curentry++;
 480         if (dflag && (++debugcnt >= debug))
 481                 return (1);
 482         return (0);
 483 }
 484 
 485 static void
 486 printnames(void)
 487 {
 488         int i, j;
 489         int (*compare)(const void *, const void *);
 490 
 491         /* the name of the machine should already be in the structure */
 492         if (iflag)
 493                 compare = (int (*)(const void *, const void *))icompare;
 494         else if (hflag)
 495                 compare = (int (*)(const void *, const void *))hcompare;
 496         else
 497                 compare = (int (*)(const void *, const void *))ucompare;
 498         qsort(entry, curentry, sizeof (struct entry), compare);
 499         for (i = 0; i < curentry; i++) {
 500                 if (!lflag || (entry[i].cnt < 1)) {
 501                         (void) printf("%-*.*s", MACHINELEN,
 502                                         MACHINELEN, entry[i].machine);
 503                         for (j = 0; j < entry[i].cnt; j++)
 504                                 (void) printf(" %.*s", NMAX,
 505                                     entry[i].users.utmp_array_val[j].ut_user);
 506                         (void) printf("\n");
 507                 } else {
 508                         for (j = 0; j < entry[i].cnt; j++)
 509                                 putline_3(entry[i].machine,
 510                                         &entry[i].users.utmp_array_val[j]);
 511                 }
 512         }
 513 }
 514 
 515 static int
 516 hcompare(const struct entry *a, const struct entry *b)
 517 {
 518         return (strcmp(a->machine, b->machine));
 519 }
 520 
 521 static int
 522 ucompare(const struct entry *a, const struct entry *b)
 523 {
 524         return (b->cnt - a->cnt);
 525 }
 526 
 527 static int
 528 icompare(const struct entry *a, const struct entry *b)
 529 {
 530         return (a->idle - b->idle);
 531 }
 532 
 533 static void
 534 putline_2(char *host, struct utmpidle *uip)
 535 {
 536         char *cbuf;
 537         struct ru_utmp *up;
 538         char buf[100];
 539 
 540         up = &uip->ui_utmp;
 541 #define NAMEMAX ((sizeof (up->ut_name) < NMAX) ? NMAX : sizeof (up->ut_name))
 542 #define NAMEMIN ((sizeof (up->ut_name) > NMAX) ? NMAX : sizeof (up->ut_name))
 543         /* Try and align this up nicely */
 544 #define LINEMAX sizeof (up->ut_line)
 545 #define HOSTMAX sizeof (up->ut_host)
 546         /*
 547          * We copy the strings into a buffer because they aren't strictly
 548          * speaking strings but byte arrays (and they may not have a
 549          * terminating NULL.
 550          */
 551 
 552         (void) strncpy(buf, up->ut_name, NAMEMAX);
 553         buf[NAMEMIN] = '\0';
 554         (void) printf("%-*.*s ", NAMEMAX, NAMEMAX, buf);
 555 
 556         (void) strcpy(buf, host);
 557         (void) strcat(buf, ":");
 558         (void) strncat(buf, up->ut_line, LINEMAX);
 559         buf[MACHINELEN+LINEMAX] = '\0';
 560         (void) printf("%-*.*s", MACHINELEN+LINEMAX, MACHINELEN+LINEMAX, buf);
 561 
 562         cbuf = (char *)ctime(&up->ut_time);
 563         (void) printf("  %.12s  ", cbuf+4);
 564         if (uip->ui_idle == INT_MAX)
 565                 (void) printf("    ??");
 566         else
 567                 prttime(uip->ui_idle, "");
 568         if (up->ut_host[0]) {
 569                 (void) strncpy(buf, up->ut_host, HOSTMAX);
 570                 buf[HOSTMAX] = '\0';
 571                 (void) printf(" (%.*s)", HOSTMAX, buf);
 572         }
 573         (void) putchar('\n');
 574 }
 575 
 576 static void
 577 putline_3(char *host, rusers_utmp *rup)
 578 {
 579         char *cbuf;
 580         char buf[100];
 581 
 582         (void) printf("%-*.*s ", NMAX, NMAX, rup->ut_user);
 583         (void) strcpy(buf, host);
 584         (void) strcat(buf, ":");
 585         (void) strncat(buf, rup->ut_line, LMAX);
 586         (void) printf("%-*.*s", MACHINELEN+LMAX, MACHINELEN+LMAX, buf);
 587 
 588         cbuf = (char *)ctime((time_t *)&rup->ut_time);
 589         (void) printf("  %.12s  ", cbuf+4);
 590         if (rup->ut_idle == INT_MAX)
 591                 (void) printf("    ??");
 592         else
 593                 prttime(rup->ut_idle, "");
 594         if (rup->ut_host[0])
 595                 (void) printf(" (%.*s)", HMAX, rup->ut_host);
 596         (void) putchar('\n');
 597 }
 598 
 599 /*
 600  * prttime prints a time in hours and minutes.
 601  * The character string tail is printed at the end, obvious
 602  * strings to pass are "", " ", or "am".
 603  */
 604 static void
 605 prttime(uint_t tim, char *tail)
 606 {
 607         int didhrs = 0;
 608 
 609         if (tim >= 60) {
 610                 (void) printf("%3d:", tim/60);
 611                 didhrs++;
 612         } else {
 613                 (void) printf("    ");
 614         }
 615         tim %= 60;
 616         if (tim > 0 || didhrs) {
 617                 (void) printf(didhrs && tim < 10 ? "%02d" : "%2d", tim);
 618         } else {
 619                 (void) printf("  ");
 620         }
 621         (void) printf("%s", tail);
 622 }
 623 
 624 #ifdef DEBUG
 625 /*
 626  * for debugging
 627  */
 628 int
 629 printit(int i)
 630 {
 631         int j, v;
 632 
 633         (void) printf("%12.12s: ", entry[i].machine);
 634         if (entry[i].cnt) {
 635                 putline_3(entry[i].machine, &entry[i].users.utmp_array_val[0]);
 636                 for (j = 1; j < entry[i].cnt; j++) {
 637                         (void) printf("\t");
 638                         putline_3(entry[i].machine,
 639                                 &entry[i].users.utmp_array_val[j]);
 640                 }
 641         } else
 642                 (void) printf("\n");
 643 }
 644 #endif
 645 
 646 static void
 647 usage(void)
 648 {
 649         (void) fprintf(stderr, "Usage: rusers [-ahilu] [host ...]\n");
 650         free(entry);
 651         exit(1);
 652 }