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
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, 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
44 #define STRUCT_UTMP struct utmp
49 #ifdef HAVE_SYS_PARAM_H
50 #include <sys/param.h>
56 #if !defined (UTMP_FILE) && defined (_PATH_UTMP) /* 4.4BSD. */
57 #define UTMP_FILE _PATH_UTMP
60 #if defined (UTMPX_FILE) /* Solaris, SysVr4 */
62 #define UTMP_FILE UTMPX_FILE
66 #define UTMP_FILE "/etc/utmp"
69 #ifndef MAXHOSTNAMELEN
70 #define MAXHOSTNAMELEN 64
78 #define COMMAND_NAME "who"
81 #define COMMAND_NAME "users"
83 error You must define one of WHO
and USERS
.
91 /* The name this program was run with. */
94 /* If nonzero, display usage information and exit. */
97 /* If nonzero, print the version on standard output and exit. */
98 static int show_version
;
101 /* If nonzero, display only a list of usernames and count of
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
;
119 static struct option
const longopts
[] =
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'},
129 {"help", no_argument
, &show_help
, 1},
130 {"version", no_argument
, &show_version
, 1},
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. */
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
, ' ');
157 #endif /* WHO || USERS */
161 /* Return a string representing the time between WHEN and the time
162 that this function is first run. */
165 idle_string (time_t when
)
167 static time_t now
= 0;
168 static char idle
[10];
174 seconds_idle
= now
- when
;
175 if (seconds_idle
< 60) /* One minute. */
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
;
187 /* Display a line of information about entry THIS. */
190 print_entry (STRUCT_UTMP
*this)
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';
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
;
227 printf ("%-8.*s", (int) sizeof (this->ut_name
), this->ut_name
);
229 printf (" %c ", mesg
);
230 printf (" %-8.*s", (int) sizeof (this->ut_line
), this->ut_line
);
233 printf (" %-12.12s", ctime (&this->ut_tv
.tv_sec
) + 4);
235 printf (" %-12.12s", ctime (&this->ut_time
) + 4);
241 printf (" %s", idle_string (last_change
));
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
, ':');
262 /* See if we can canonicalize it. */
263 host
= canon_host (ut_host
);
268 printf (" (%s:%s)", host
, display
);
270 printf (" (%s)", host
);
277 /* Print the username of each valid entry and the number of valid entries
278 in `utmp_contents', which should have N elements. */
281 list_entries_who (int n
)
283 register STRUCT_UTMP
*this = utmp_contents
;
291 && this->ut_type
== USER_PROCESS
297 trimmed_name
= extract_trimmed_name (this);
299 printf ("%s ", trimmed_name
);
305 printf (_("\n# users=%u\n"), entries
);
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
);
321 list_entries_users (int n
)
323 register STRUCT_UTMP
*this = utmp_contents
;
329 u
= (char **) xmalloc (n
* sizeof (u
[0]));
334 && this->ut_type
== USER_PROCESS
340 trimmed_name
= extract_trimmed_name (this);
342 u
[n_entries
] = trimmed_name
;
348 qsort (u
, n_entries
, sizeof (u
[0]), userid_compare
);
350 for (i
=0; i
<n_entries
; i
++)
353 fputs (u
[i
], stdout
);
354 c
= (i
< n_entries
-1 ? ' ' : '\n');
358 for (i
=0; i
<n_entries
; i
++)
370 printf ("%-8s ", "USER");
373 printf ("%-8s ", _("LINE"));
374 printf (_("LOGIN-TIME "));
377 printf (_("FROM\n"));
380 /* Display `utmp_contents', which should have N entries. */
385 register STRUCT_UTMP
*this = utmp_contents
;
394 && this->ut_type
== USER_PROCESS
404 /* Read the utmp file FILENAME into UTMP_CONTENTS and return the
405 number of entries it contains. */
408 read_utmp (char *filename
)
411 struct stat file_stats
;
415 utmp
= fopen (filename
, "r");
417 error (1, errno
, "%s", filename
);
419 fstat (fileno (utmp
), &file_stats
);
420 size
= file_stats
.st_size
;
422 utmp_contents
= (STRUCT_UTMP
*) xmalloc (size
);
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
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. */
445 users
= read_utmp (filename
);
448 list_entries_who (users
);
450 scan_entries (users
);
453 list_entries_users (users
);
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. */
466 search_entries (int n
, char *line
)
468 register STRUCT_UTMP
*this = utmp_contents
;
474 && this->ut_type
== USER_PROCESS
476 && !strncmp (line
, this->ut_line
, sizeof (this->ut_line
)))
483 /* Display the entry in utmp file FILENAME for this tty on standard input,
484 or nothing if there is no entry for it. */
487 who_am_i (char *filename
)
489 register STRUCT_UTMP
*utmp_entry
;
490 char hostname
[MAXHOSTNAMELEN
+ 1];
493 if (gethostname (hostname
, MAXHOSTNAMELEN
+ 1))
498 printf ("%*s ", (int) strlen (hostname
), " ");
505 tty
+= 5; /* Remove "/dev/". */
507 utmp_entry
= search_entries (read_utmp (filename
), tty
);
508 if (utmp_entry
== NULL
)
511 printf ("%s!", hostname
);
512 print_entry (utmp_entry
);
519 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
523 printf (_("Usage: %s [OPTION]... [ FILE | ARG1 ARG2 ]\n"), program_name
);
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\
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\
550 fprintf (stderr
, _("Try `%s --help' for more information.\n"),
554 printf (_("Usage: %s [OPTION]... [ FILE ]\n"), program_name
);
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"));
567 main (int argc
, char **argv
)
571 int my_line_only
= 0;
574 program_name
= argv
[0];
575 setlocale (LC_ALL
, "");
576 bindtextdomain (PACKAGE
, LOCALEDIR
);
577 textdomain (PACKAGE
);
580 while ((optc
= getopt_long (argc
, argv
, "imqsuwHT", longopts
, &longind
))
582 while ((optc
= getopt_long (argc
, argv
, "", longopts
, &longind
))
625 printf ("%s - %s\n", COMMAND_NAME
, PACKAGE_VERSION
);
632 switch (argc
- optind
)
637 who_am_i (UTMP_FILE
);
643 case 1: /* who <utmp file> */
646 who_am_i (argv
[optind
]);
653 case 2: /* who <blurf> <glop> */
654 who_am_i (UTMP_FILE
);
659 error (0, 0, _("too many arguments"));