(main): Declare to be of type int, not void.
[coreutils.git] / src / who-users.c
blob2418e3c3fa0131ce3b57b355c3f19edb4daa2a2c
1 /* GNU's users/who.
2 Copyright (C) 92, 93, 94, 95, 1996 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */
18 /* Written by jla; revised by djm */
20 /* Output format:
21 name [state] line time [idle] host
22 state: -T
23 name, line, time: not -q
24 idle: -u
26 Options:
27 -m Same as 'who am i', for POSIX.
28 -q Only user names and # logged on; overrides all other options.
29 -s Name, line, time (default).
30 -i, -u Idle hours and minutes; '.' means active in last minute;
31 'old' means idle for >24 hours.
32 -H Print column headings at top.
33 -w, -T -s plus mesg (+ or -, or ? if bad line). */
35 #include <config.h>
36 #include <stdio.h>
37 #include <sys/types.h>
39 #ifdef HAVE_UTMPX_H
40 #include <utmpx.h>
41 #define STRUCT_UTMP struct utmpx
42 #else
43 #include <utmp.h>
44 #define STRUCT_UTMP struct utmp
45 #endif
47 #include <time.h>
48 #include <getopt.h>
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
51 #endif
53 #include "system.h"
54 #include "error.h"
56 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
57 #define UTMP_FILE _PATH_UTMP
58 #endif
60 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
61 #undef UTMP_FILE
62 #define UTMP_FILE UTMPX_FILE
63 #endif
65 #ifndef UTMP_FILE
66 #define UTMP_FILE "/etc/utmp"
67 #endif
69 #ifndef MAXHOSTNAMELEN
70 #define MAXHOSTNAMELEN 64
71 #endif
73 #ifndef S_IWGRP
74 #define S_IWGRP 020
75 #endif
77 #ifdef WHO
78 #define COMMAND_NAME "who"
79 #else
80 #ifdef USERS
81 #define COMMAND_NAME "users"
82 #else
83 error You must define one of WHO and USERS.
84 #endif /* USERS */
85 #endif /* WHO */
87 int gethostname ();
88 char *ttyname ();
89 char *xmalloc ();
91 /* The name this program was run with. */
92 char *program_name;
94 /* If nonzero, display usage information and exit. */
95 static int show_help;
97 /* If nonzero, print the version on standard output and exit. */
98 static int show_version;
100 #ifdef WHO
101 /* If nonzero, display only a list of usernames and count of
102 the users logged on.
103 Ignored for `who am i'. */
104 static int short_list;
106 /* If nonzero, display the hours:minutes since each user has touched
107 the keyboard, or "." if within the last minute, or "old" if
108 not within the last day. */
109 static int include_idle;
111 /* If nonzero, display a line at the top describing each field. */
112 static int include_heading;
114 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
115 or a `?' if their tty cannot be statted. */
116 static int include_mesg;
117 #endif /* WHO */
119 static struct option const longopts[] =
121 #ifdef WHO
122 {"count", no_argument, NULL, 'q'},
123 {"idle", no_argument, NULL, 'u'},
124 {"heading", no_argument, NULL, 'H'},
125 {"message", no_argument, NULL, 'T'},
126 {"mesg", no_argument, NULL, 'T'},
127 {"writable", no_argument, NULL, 'T'},
128 #endif /* WHO */
129 {"help", no_argument, &show_help, 1},
130 {"version", no_argument, &show_version, 1},
131 {NULL, 0, NULL, 0}
134 static STRUCT_UTMP *utmp_contents;
136 #if defined (WHO) || defined (USERS)
138 /* Copy UT->ut_name into storage obtained from malloc. Then remove any
139 trailing spaces from the copy, NUL terminate it, and return the copy. */
141 static char *
142 extract_trimmed_name (const STRUCT_UTMP *ut)
144 char *p, *trimmed_name;
146 trimmed_name = xmalloc (sizeof (ut->ut_name) + 1);
147 strncpy (trimmed_name, ut->ut_name, sizeof (ut->ut_name));
148 /* Append a trailing space character. Some systems pad names shorter than
149 the maximum with spaces, others pad with NULs. Remove any spaces. */
150 trimmed_name[sizeof (ut->ut_name)] = ' ';
151 p = strchr (trimmed_name, ' ');
152 if (p != NULL)
153 *p = '\0';
154 return trimmed_name;
157 #endif /* WHO || USERS */
159 #if WHO
161 /* Return a string representing the time between WHEN and the time
162 that this function is first run. */
164 static const char *
165 idle_string (time_t when)
167 static time_t now = 0;
168 static char idle[10];
169 time_t seconds_idle;
171 if (now == 0)
172 time (&now);
174 seconds_idle = now - when;
175 if (seconds_idle < 60) /* One minute. */
176 return " . ";
177 if (seconds_idle < (24 * 60 * 60)) /* One day. */
179 sprintf (idle, "%02d:%02d",
180 (int) (seconds_idle / (60 * 60)),
181 (int) ((seconds_idle % (60 * 60)) / 60));
182 return (const char *) idle;
184 return _(" old ");
187 /* Display a line of information about entry THIS. */
189 static void
190 print_entry (STRUCT_UTMP *this)
192 struct stat stats;
193 time_t last_change;
194 char mesg;
196 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
197 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
199 char line[sizeof (this->ut_line) + DEV_DIR_LEN + 1];
201 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
202 already an absolute pathname. Some system may put the full,
203 absolute pathname in ut_line. */
204 if (this->ut_line[0] == '/')
206 strncpy (line, this->ut_line, sizeof (this->ut_line));
207 line[sizeof (this->ut_line)] = '\0';
209 else
211 strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
212 strncpy (line + DEV_DIR_LEN, this->ut_line, sizeof (this->ut_line));
213 line[DEV_DIR_LEN + sizeof (this->ut_line)] = '\0';
216 if (stat (line, &stats) == 0)
218 mesg = (stats.st_mode & S_IWGRP) ? '+' : '-';
219 last_change = stats.st_atime;
221 else
223 mesg = '?';
224 last_change = 0;
227 printf ("%-8.*s", (int) sizeof (this->ut_name), this->ut_name);
228 if (include_mesg)
229 printf (" %c ", mesg);
230 printf (" %-8.*s", (int) sizeof (this->ut_line), this->ut_line);
232 #ifdef HAVE_UTMPX_H
233 printf (" %-12.12s", ctime (&this->ut_tv.tv_sec) + 4);
234 #else
235 printf (" %-12.12s", ctime (&this->ut_time) + 4);
236 #endif
238 if (include_idle)
240 if (last_change)
241 printf (" %s", idle_string (last_change));
242 else
243 printf (" . ");
245 #ifdef HAVE_UT_HOST
246 if (this->ut_host[0])
248 extern char *canon_host ();
249 char ut_host[sizeof (this->ut_host) + 1];
250 char *host = 0, *display = 0;
252 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
253 strncpy (ut_host, this->ut_host, (int) sizeof (this->ut_host));
254 ut_host[sizeof (this->ut_host)] = '\0';
256 /* Look for an X display. */
257 display = strrchr (ut_host, ':');
258 if (display)
259 *display++ = '\0';
261 if (*ut_host)
262 /* See if we can canonicalize it. */
263 host = canon_host (ut_host);
264 if (! host)
265 host = ut_host;
267 if (display)
268 printf (" (%s:%s)", host, display);
269 else
270 printf (" (%s)", host);
272 #endif
274 putchar ('\n');
277 /* Print the username of each valid entry and the number of valid entries
278 in `utmp_contents', which should have N elements. */
280 static void
281 list_entries_who (int n)
283 register STRUCT_UTMP *this = utmp_contents;
284 int entries;
286 entries = 0;
287 while (n--)
289 if (this->ut_name[0]
290 #ifdef USER_PROCESS
291 && this->ut_type == USER_PROCESS
292 #endif
295 char *trimmed_name;
297 trimmed_name = extract_trimmed_name (this);
299 printf ("%s ", trimmed_name);
300 free (trimmed_name);
301 entries++;
303 this++;
305 printf (_("\n# users=%u\n"), entries);
308 #endif /* WHO */
310 #ifdef USERS
312 static int
313 userid_compare (const void *v_a, const void *v_b)
315 char **a = (char **) v_a;
316 char **b = (char **) v_b;
317 return strcmp (*a, *b);
320 static void
321 list_entries_users (int n)
323 register STRUCT_UTMP *this = utmp_contents;
324 char **u;
325 int i;
326 int n_entries;
328 n_entries = 0;
329 u = (char **) xmalloc (n * sizeof (u[0]));
330 for (i=0; i<n; i++)
332 if (this->ut_name[0]
333 #ifdef USER_PROCESS
334 && this->ut_type == USER_PROCESS
335 #endif
338 char *trimmed_name;
340 trimmed_name = extract_trimmed_name (this);
342 u[n_entries] = trimmed_name;
343 ++n_entries;
345 this++;
348 qsort (u, n_entries, sizeof (u[0]), userid_compare);
350 for (i=0; i<n_entries; i++)
352 int c;
353 fputs (u[i], stdout);
354 c = (i < n_entries-1 ? ' ' : '\n');
355 putchar (c);
358 for (i=0; i<n_entries; i++)
359 free (u[i]);
360 free (u);
363 #endif /* USERS */
365 #ifdef WHO
367 static void
368 print_heading (void)
370 printf ("%-8s ", "USER");
371 if (include_mesg)
372 printf (_("MESG "));
373 printf ("%-8s ", _("LINE"));
374 printf (_("LOGIN-TIME "));
375 if (include_idle)
376 printf (_("IDLE "));
377 printf (_("FROM\n"));
380 /* Display `utmp_contents', which should have N entries. */
382 static void
383 scan_entries (int n)
385 register STRUCT_UTMP *this = utmp_contents;
387 if (include_heading)
388 print_heading ();
390 while (n--)
392 if (this->ut_name[0]
393 #ifdef USER_PROCESS
394 && this->ut_type == USER_PROCESS
395 #endif
397 print_entry (this);
398 this++;
402 #endif /* WHO */
404 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
405 number of entries it contains. */
407 static int
408 read_utmp (char *filename)
410 FILE *utmp;
411 struct stat file_stats;
412 size_t n_read;
413 size_t size;
415 utmp = fopen (filename, "r");
416 if (utmp == NULL)
417 error (1, errno, "%s", filename);
419 fstat (fileno (utmp), &file_stats);
420 size = file_stats.st_size;
421 if (size > 0)
422 utmp_contents = (STRUCT_UTMP *) xmalloc (size);
423 else
425 fclose (utmp);
426 return 0;
429 /* Use < instead of != in case the utmp just grew. */
430 n_read = fread (utmp_contents, 1, size, utmp);
431 if (ferror (utmp) || fclose (utmp) == EOF
432 || n_read < size)
433 error (1, errno, "%s", filename);
435 return size / sizeof (STRUCT_UTMP);
438 /* Display a list of who is on the system, according to utmp file FILENAME. */
440 static void
441 who (char *filename)
443 int users;
445 users = read_utmp (filename);
446 #ifdef WHO
447 if (short_list)
448 list_entries_who (users);
449 else
450 scan_entries (users);
451 #else
452 #ifdef USERS
453 list_entries_users (users);
454 #endif /* USERS */
455 #endif /* WHO */
458 #ifdef WHO
460 /* Search `utmp_contents', which should have N entries, for
461 an entry with a `ut_line' field identical to LINE.
462 Return the first matching entry found, or NULL if there
463 is no matching entry. */
465 static STRUCT_UTMP *
466 search_entries (int n, char *line)
468 register STRUCT_UTMP *this = utmp_contents;
470 while (n--)
472 if (this->ut_name[0]
473 #ifdef USER_PROCESS
474 && this->ut_type == USER_PROCESS
475 #endif
476 && !strncmp (line, this->ut_line, sizeof (this->ut_line)))
477 return this;
478 this++;
480 return NULL;
483 /* Display the entry in utmp file FILENAME for this tty on standard input,
484 or nothing if there is no entry for it. */
486 static void
487 who_am_i (char *filename)
489 register STRUCT_UTMP *utmp_entry;
490 char hostname[MAXHOSTNAMELEN + 1];
491 char *tty;
493 if (gethostname (hostname, MAXHOSTNAMELEN + 1))
494 *hostname = 0;
496 if (include_heading)
498 printf ("%*s ", (int) strlen (hostname), " ");
499 print_heading ();
502 tty = ttyname (0);
503 if (tty == NULL)
504 return;
505 tty += 5; /* Remove "/dev/". */
507 utmp_entry = search_entries (read_utmp (filename), tty);
508 if (utmp_entry == NULL)
509 return;
511 printf ("%s!", hostname);
512 print_entry (utmp_entry);
515 static void
516 usage (int status)
518 if (status != 0)
519 fprintf (stderr, _("Try `%s --help' for more information.\n"),
520 program_name);
521 else
523 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name);
524 printf (_("\
526 -H, --heading print line of column headings\n\
527 -T, -w, --mesg add user's message status as +, - or ?\n\
528 -i, -u, --idle add user idle time as HOURS:MINUTES, . or old\n\
529 -m only hostname and user associated with stdin\n\
530 -q, --count all login names and number of users logged on\n\
531 -s (ignored)\n\
532 --help display this help and exit\n\
533 --message same as -T\n\
534 --version output version information and exit\n\
535 --writeable same as -T\n\
537 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
538 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
539 "));
541 exit (status);
543 #endif /* WHO */
545 #ifdef USERS
546 static void
547 usage (int status)
549 if (status != 0)
550 fprintf (stderr, _("Try `%s --help' for more information.\n"),
551 program_name);
552 else
554 printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name);
555 printf (_("\
556 Output who is currently logged in according to FILE.\n\
557 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
559 --help display this help and exit\n\
560 --version output version information and exit\n"));
562 exit (status);
564 #endif /* USERS */
566 void
567 main (int argc, char **argv)
569 int optc, longind;
570 #ifdef WHO
571 int my_line_only = 0;
572 #endif /* WHO */
574 program_name = argv[0];
575 setlocale (LC_ALL, "");
576 bindtextdomain (PACKAGE, LOCALEDIR);
577 textdomain (PACKAGE);
579 #ifdef WHO
580 while ((optc = getopt_long (argc, argv, "imqsuwHT", longopts, &longind))
581 #else
582 while ((optc = getopt_long (argc, argv, "", longopts, &longind))
583 #endif /* WHO */
584 != EOF)
586 switch (optc)
588 case 0:
589 break;
591 #ifdef WHO
592 case 'm':
593 my_line_only = 1;
594 break;
596 case 'q':
597 short_list = 1;
598 break;
600 case 's':
601 break;
603 case 'i':
604 case 'u':
605 include_idle = 1;
606 break;
608 case 'H':
609 include_heading = 1;
610 break;
612 case 'w':
613 case 'T':
614 include_mesg = 1;
615 break;
616 #endif /* WHO */
618 default:
619 usage (1);
623 if (show_version)
625 printf ("%s - %s\n", COMMAND_NAME, PACKAGE_VERSION);
626 exit (0);
629 if (show_help)
630 usage (0);
632 switch (argc - optind)
634 case 0: /* who */
635 #ifdef WHO
636 if (my_line_only)
637 who_am_i (UTMP_FILE);
638 else
639 #endif /* WHO */
640 who (UTMP_FILE);
641 break;
643 case 1: /* who <utmp file> */
644 #ifdef WHO
645 if (my_line_only)
646 who_am_i (argv[optind]);
647 else
648 #endif /* WHO */
649 who (argv[optind]);
650 break;
652 #ifdef WHO
653 case 2: /* who <blurf> <glop> */
654 who_am_i (UTMP_FILE);
655 break;
656 #endif /* WHO */
658 default: /* lose */
659 error (0, 0, _("too many arguments"));
660 usage (1);
663 exit (0);