1 /* GNU's uptime/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)
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 Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Written by jla; revised by djm */
21 name [state] line time [idle] host
23 name, line, time: not -q
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). */
37 #include <sys/types.h>
41 # define STRUCT_UTMP struct utmpx
42 # define UT_TIME_MEMBER(UT_PTR) ((UT_PTR)->ut_tv.tv_sec)
45 # define STRUCT_UTMP struct utmp
46 # define UT_TIME_MEMBER(UT_PTR) ((UT_PTR)->ut_time)
51 #ifdef HAVE_SYS_PARAM_H
52 #include <sys/param.h>
58 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
59 #define UTMP_FILE _PATH_UTMP
62 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
64 #define UTMP_FILE UTMPX_FILE
68 #define UTMP_FILE "/etc/utmp"
71 #ifndef MAXHOSTNAMELEN
72 #define MAXHOSTNAMELEN 64
80 #define COMMAND_NAME "who"
83 #define COMMAND_NAME "users"
86 #define COMMAND_NAME "uptime"
88 error You must define one of WHO
, UPTIME
or USERS
.
97 /* The name this program was run with. */
100 /* If nonzero, display usage information and exit. */
101 static int show_help
;
103 /* If nonzero, print the version on standard output and exit. */
104 static int show_version
;
107 /* If nonzero, display only a list of usernames and count of
109 Ignored for `who am i'. */
110 static int short_list
;
112 /* If nonzero, display the hours:minutes since each user has touched
113 the keyboard, or "." if within the last minute, or "old" if
114 not within the last day. */
115 static int include_idle
;
117 /* If nonzero, display a line at the top describing each field. */
118 static int include_heading
;
120 /* If nonzero, display a `+' for each user if mesg y, a `-' if mesg n,
121 or a `?' if their tty cannot be statted. */
122 static int include_mesg
;
125 static struct option
const longopts
[] =
128 {"count", no_argument
, NULL
, 'q'},
129 {"idle", no_argument
, NULL
, 'u'},
130 {"heading", no_argument
, NULL
, 'H'},
131 {"message", no_argument
, NULL
, 'T'},
132 {"mesg", no_argument
, NULL
, 'T'},
133 {"writable", no_argument
, NULL
, 'T'},
135 {"help", no_argument
, &show_help
, 1},
136 {"version", no_argument
, &show_version
, 1},
140 static STRUCT_UTMP
*utmp_contents
;
146 register STRUCT_UTMP
*this = utmp_contents
;
147 register int entries
= 0;
148 time_t boot_time
= 0;
158 /* Loop through all the utmp entries we just read and count up the valid
159 ones, also in the process possibly gleaning boottime. */
164 && this->ut_type
== USER_PROCESS
170 /* If BOOT_MSG is defined, we can get boottime from utmp. This avoids
171 possibly needing special privs to read /dev/kmem. */
173 if (!strcmp (this->ut_line
, BOOT_MSG
))
174 boot_time
= UT_TIME_MEMBER (this);
175 #endif /* BOOT_MSG */
179 error (1, errno
, _("couldn't get boot time"));
181 uptime
= time_now
- boot_time
;
182 updays
= uptime
/ 86400;
183 uphours
= (uptime
- (updays
* 86400)) / 3600;
184 upmins
= (uptime
- (updays
* 86400) - (uphours
* 3600)) / 60;
185 tmn
= localtime (&time_now
);
186 printf (_(" %2d:%02d%s up "), ((tmn
->tm_hour
% 12) == 0
187 ? 12 : tmn
->tm_hour
% 12),
188 tmn
->tm_min
, (tmn
->tm_hour
< 12 ? _("am") : _("pm")));
190 printf ("%d %s,", updays
, (updays
== 1 ? _("days") : _("day")));
191 printf (" %2d:%02d, %d %s", uphours
, upmins
, entries
,
192 (entries
== 1) ? _("user") : _("users"));
194 #ifdef HAVE_GETLOADAVG
195 loads
= getloadavg (avg
, 3);
205 printf (_(", load average: %.2f"), avg
[0]);
207 printf (", %.2f", avg
[1]);
209 printf (", %.2f", avg
[2]);
218 #if defined (WHO) || defined (USERS)
220 /* Copy UT->ut_name into storage obtained from malloc. Then remove any
221 trailing spaces from the copy, NUL terminate it, and return the copy. */
224 extract_trimmed_name (const STRUCT_UTMP
*ut
)
226 char *p
, *trimmed_name
;
228 trimmed_name
= xmalloc (sizeof (ut
->ut_name
) + 1);
229 strncpy (trimmed_name
, ut
->ut_name
, sizeof (ut
->ut_name
));
230 /* Append a trailing space character. Some systems pad names shorter than
231 the maximum with spaces, others pad with NULs. Remove any spaces. */
232 trimmed_name
[sizeof (ut
->ut_name
)] = ' ';
233 p
= strchr (trimmed_name
, ' ');
239 #endif /* WHO || USERS */
243 /* Return a string representing the time between WHEN and the time
244 that this function is first run. */
247 idle_string (time_t when
)
249 static time_t now
= 0;
250 static char idle
[10];
256 seconds_idle
= now
- when
;
257 if (seconds_idle
< 60) /* One minute. */
259 if (seconds_idle
< (24 * 60 * 60)) /* One day. */
261 sprintf (idle
, "%02d:%02d",
262 (int) (seconds_idle
/ (60 * 60)),
263 (int) ((seconds_idle
% (60 * 60)) / 60));
264 return (const char *) idle
;
269 /* Display a line of information about entry THIS. */
272 print_entry (STRUCT_UTMP
*this)
278 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
279 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
281 char line
[sizeof (this->ut_line
) + DEV_DIR_LEN
+ 1];
283 /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
284 already an absolute pathname. Some system may put the full,
285 absolute pathname in ut_line. */
286 if (this->ut_line
[0] == '/')
288 strncpy (line
, this->ut_line
, sizeof (this->ut_line
));
289 line
[sizeof (this->ut_line
)] = '\0';
293 strcpy (line
, DEV_DIR_WITH_TRAILING_SLASH
);
294 strncpy (line
+ DEV_DIR_LEN
, this->ut_line
, sizeof (this->ut_line
));
295 line
[DEV_DIR_LEN
+ sizeof (this->ut_line
)] = '\0';
298 if (stat (line
, &stats
) == 0)
300 mesg
= (stats
.st_mode
& S_IWGRP
) ? '+' : '-';
301 last_change
= stats
.st_atime
;
309 printf ("%-8.*s", (int) sizeof (this->ut_name
), this->ut_name
);
311 printf (" %c ", mesg
);
312 printf (" %-8.*s", (int) sizeof (this->ut_line
), this->ut_line
);
313 printf (" %-12.12s", ctime (&UT_TIME_MEMBER (this)) + 4);
318 printf (" %s", idle_string (last_change
));
323 if (this->ut_host
[0])
325 extern char *canon_host ();
326 char ut_host
[sizeof (this->ut_host
) + 1];
327 char *host
= 0, *display
= 0;
329 /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
330 strncpy (ut_host
, this->ut_host
, (int) sizeof (this->ut_host
));
331 ut_host
[sizeof (this->ut_host
)] = '\0';
333 /* Look for an X display. */
334 display
= strrchr (ut_host
, ':');
339 /* See if we can canonicalize it. */
340 host
= canon_host (ut_host
);
345 printf (" (%s:%s)", host
, display
);
347 printf (" (%s)", host
);
354 /* Print the username of each valid entry and the number of valid entries
355 in `utmp_contents', which should have N elements. */
358 list_entries_who (int n
)
360 register STRUCT_UTMP
*this = utmp_contents
;
368 && this->ut_type
== USER_PROCESS
374 trimmed_name
= extract_trimmed_name (this);
376 printf ("%s ", trimmed_name
);
382 printf (_("\n# users=%u\n"), entries
);
390 userid_compare (const void *v_a
, const void *v_b
)
392 char **a
= (char **) v_a
;
393 char **b
= (char **) v_b
;
394 return strcmp (*a
, *b
);
398 list_entries_users (int n
)
400 register STRUCT_UTMP
*this = utmp_contents
;
406 u
= (char **) xmalloc (n
* sizeof (u
[0]));
407 for (i
= 0; i
< n
; i
++)
411 && this->ut_type
== USER_PROCESS
417 trimmed_name
= extract_trimmed_name (this);
419 u
[n_entries
] = trimmed_name
;
425 qsort (u
, n_entries
, sizeof (u
[0]), userid_compare
);
427 for (i
= 0; i
< n_entries
; i
++)
430 fputs (u
[i
], stdout
);
431 c
= (i
< n_entries
-1 ? ' ' : '\n');
435 for (i
= 0; i
< n_entries
; i
++)
447 printf ("%-8s ", _("USER"));
450 printf ("%-8s ", _("LINE"));
451 printf (_("LOGIN-TIME "));
454 printf (_("FROM\n"));
457 /* Display `utmp_contents', which should have N entries. */
462 register STRUCT_UTMP
*this = utmp_contents
;
471 && this->ut_type
== USER_PROCESS
481 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
482 number of entries it contains. */
485 read_utmp (char *filename
)
488 struct stat file_stats
;
492 utmp
= fopen (filename
, "r");
494 error (1, errno
, "%s", filename
);
496 fstat (fileno (utmp
), &file_stats
);
497 size
= file_stats
.st_size
;
499 utmp_contents
= (STRUCT_UTMP
*) xmalloc (size
);
506 /* Use < instead of != in case the utmp just grew. */
507 n_read
= fread (utmp_contents
, 1, size
, utmp
);
508 if (ferror (utmp
) || fclose (utmp
) == EOF
510 error (1, errno
, "%s", filename
);
512 return size
/ sizeof (STRUCT_UTMP
);
515 /* Display a list of who is on the system, according to utmp file FILENAME. */
522 users
= read_utmp (filename
);
525 list_entries_who (users
);
527 scan_entries (users
);
530 list_entries_users (users
);
533 print_uptime (users
);
541 /* Search `utmp_contents', which should have N entries, for
542 an entry with a `ut_line' field identical to LINE.
543 Return the first matching entry found, or NULL if there
544 is no matching entry. */
547 search_entries (int n
, char *line
)
549 register STRUCT_UTMP
*this = utmp_contents
;
555 && this->ut_type
== USER_PROCESS
557 && !strncmp (line
, this->ut_line
, sizeof (this->ut_line
)))
564 /* Display the entry in utmp file FILENAME for this tty on standard input,
565 or nothing if there is no entry for it. */
568 who_am_i (char *filename
)
570 register STRUCT_UTMP
*utmp_entry
;
571 char hostname
[MAXHOSTNAMELEN
+ 1];
574 if (gethostname (hostname
, MAXHOSTNAMELEN
+ 1))
579 printf ("%*s ", (int) strlen (hostname
), " ");
586 tty
+= 5; /* Remove "/dev/". */
588 utmp_entry
= search_entries (read_utmp (filename
), tty
);
589 if (utmp_entry
== NULL
)
592 printf ("%s!", hostname
);
593 print_entry (utmp_entry
);
600 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
604 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name
);
607 -H, --heading print line of column headings\n\
608 -T, -w, --mesg add user's message status as +, - or ?\n\
609 -i, -u, --idle add user idle time as HOURS:MINUTES, . or old\n\
610 -m only hostname and user associated with stdin\n\
611 -q, --count all login names and number of users logged on\n\
613 --help display this help and exit\n\
614 --message same as -T\n\
615 --version output version information and exit\n\
616 --writeable same as -T\n\
618 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
619 If ARG1 ARG2 given, -m presumed: `am i' or `mom likes' are usual.\n\
626 #if defined(USERS) || defined(UPTIME)
631 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
635 printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name
);
637 Output who is currently logged in according to FILE.\n\
638 If FILE not given, uses /etc/utmp. /etc/wtmp as FILE is common.\n\
640 --help display this help and exit\n\
641 --version output version information and exit\n"));
645 #endif /* USERS || UPTIME */
648 main (int argc
, char **argv
)
652 int my_line_only
= 0;
655 program_name
= argv
[0];
656 setlocale (LC_ALL
, "");
657 bindtextdomain (PACKAGE
, LOCALEDIR
);
658 textdomain (PACKAGE
);
661 while ((optc
= getopt_long (argc
, argv
, "imqsuwHT", longopts
, &longind
))
663 while ((optc
= getopt_long (argc
, argv
, "", longopts
, &longind
))
706 printf ("%s - %s\n", COMMAND_NAME
, PACKAGE_VERSION
);
713 switch (argc
- optind
)
718 who_am_i (UTMP_FILE
);
724 case 1: /* who <utmp file> */
727 who_am_i (argv
[optind
]);
734 case 2: /* who <blurf> <glop> */
735 who_am_i (UTMP_FILE
);
740 error (0, 0, _("too many arguments"));