Use `n_users' in place of `argc - optind' in two more places.
[platform/upstream/coreutils.git] / src / pinky.c
1 /* GNU's pinky.
2    Copyright (C) 1992-1997, 1999, 2000, 2001 Free Software Foundation, Inc.
3
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.
8
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.
13
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.  */
17
18 /* Created by hacking who.c by Kaveh Ghazi ghazi@caip.rutgers.edu */
19
20 #include <config.h>
21 #include <getopt.h>
22 #include <pwd.h>
23 #include <stdio.h>
24
25 #include <sys/types.h>
26 #include "system.h"
27
28 #include "error.h"
29 #include "readutmp.h"
30 #include "closeout.h"
31
32 /* The official name of this program (e.g., no `g' prefix).  */
33 #define PROGRAM_NAME "pinky"
34
35 #define AUTHORS N_ ("Joseph Arceneaux, David MacKenzie, and Kaveh Ghazi")
36
37 #ifndef MAXHOSTNAMELEN
38 # define MAXHOSTNAMELEN 64
39 #endif
40
41 #ifndef S_IWGRP
42 # define S_IWGRP 020
43 #endif
44
45 int gethostname ();
46 char *ttyname ();
47
48 /* The name this program was run with. */
49 const char *program_name;
50
51 /* If nonzero, display the hours:minutes since each user has touched
52    the keyboard, or blank if within the last minute, or days followed
53    by a 'd' if not within the last day. */
54 static int include_idle = 1;
55
56 /* If nonzero, display a line at the top describing each field. */
57 static int include_heading = 1;
58
59 /* if nonzero, display the user's full name from pw_gecos. */
60 static int include_fullname = 1;
61
62 /* if nonzero, display the user's ~/.project file when doing long format. */
63 static int include_project = 1;
64
65 /* if nonzero, display the user's ~/.plan file when doing long format. */
66 static int include_plan = 1;
67
68 /* if nonzero, display the user's home directory and shell
69    when doing long format. */
70 static int include_home_and_shell = 1;
71
72 /* if nonzero, use the "short" output format. */
73 static int do_short_format = 1;
74
75 /* if nonzero, display the ut_host field. */
76 #ifdef HAVE_UT_HOST
77 static int include_where = 1;
78 #endif
79
80 static struct option const longopts[] =
81 {
82   {GETOPT_HELP_OPTION_DECL},
83   {GETOPT_VERSION_OPTION_DECL},
84   {NULL, 0, NULL, 0}
85 };
86
87 /* Count and return the number of ampersands in STR.  */
88
89 static int
90 count_ampersands (const char *str)
91 {
92   int count = 0;
93   do
94     {
95       if (*str == '&')
96         count++;
97     } while (*str++);
98   return count;
99 }
100
101 /* Create a string (via xmalloc) which contains a full name by substituting
102    for each ampersand in GECOS_NAME the USER_NAME string with its first
103    character capitalized.  The caller must ensure that GECOS_NAME contains
104    no `,'s.  The caller also is responsible for free'ing the return value of
105    this function.  */
106
107 static char *
108 create_fullname (const char *gecos_name, const char *user_name)
109 {
110   const int result_len = strlen (gecos_name)
111     + count_ampersands (gecos_name) * (strlen (user_name) - 1) + 1;
112   char *const result = xmalloc (result_len);
113   char *r = result;
114
115   while (*gecos_name)
116     {
117       if (*gecos_name == '&')
118         {
119           const char *uname = user_name;
120           if (ISLOWER (*uname))
121             *r++ = TOUPPER (*uname++);
122           while (*uname)
123             *r++ = *uname++;
124         }
125       else
126         {
127           *r++ = *gecos_name;
128         }
129
130       gecos_name++;
131     }
132   *r = 0;
133
134   return result;
135 }
136
137 /* Return a string representing the time between WHEN and the time
138    that this function is first run. */
139
140 static const char *
141 idle_string (time_t when)
142 {
143   static time_t now = 0;
144   static char idle_hhmm[10];
145   time_t seconds_idle;
146
147   if (now == 0)
148     time (&now);
149
150   seconds_idle = now - when;
151   if (seconds_idle < 60)        /* One minute. */
152     return "     ";
153   if (seconds_idle < (24 * 60 * 60))    /* One day. */
154     {
155       sprintf (idle_hhmm, "%02d:%02d",
156                (int) (seconds_idle / (60 * 60)),
157                (int) ((seconds_idle % (60 * 60)) / 60));
158       return (const char *) idle_hhmm;
159     }
160   sprintf (idle_hhmm, "%dd", (int) (seconds_idle / (24 * 60 * 60)));
161   return (const char *) idle_hhmm;
162 }
163
164 /* Display a line of information about UTMP_ENT. */
165
166 static void
167 print_entry (const STRUCT_UTMP *utmp_ent)
168 {
169   struct stat stats;
170   time_t last_change;
171   char mesg;
172
173 #define DEV_DIR_WITH_TRAILING_SLASH "/dev/"
174 #define DEV_DIR_LEN (sizeof (DEV_DIR_WITH_TRAILING_SLASH) - 1)
175
176   char line[sizeof (utmp_ent->ut_line) + DEV_DIR_LEN + 1];
177   time_t tm;
178
179   /* Copy ut_line into LINE, prepending `/dev/' if ut_line is not
180      already an absolute pathname.  Some system may put the full,
181      absolute pathname in ut_line.  */
182   if (utmp_ent->ut_line[0] == '/')
183     {
184       strncpy (line, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
185       line[sizeof (utmp_ent->ut_line)] = '\0';
186     }
187   else
188     {
189       strcpy (line, DEV_DIR_WITH_TRAILING_SLASH);
190       strncpy (line + DEV_DIR_LEN, utmp_ent->ut_line, sizeof (utmp_ent->ut_line));
191       line[DEV_DIR_LEN + sizeof (utmp_ent->ut_line)] = '\0';
192     }
193
194   if (stat (line, &stats) == 0)
195     {
196       mesg = (stats.st_mode & S_IWGRP) ? ' ' : '*';
197       last_change = stats.st_atime;
198     }
199   else
200     {
201       mesg = '?';
202       last_change = 0;
203     }
204
205   printf ("%-8.*s", (int) sizeof (UT_USER (utmp_ent)), UT_USER (utmp_ent));
206
207   if (include_fullname)
208     {
209       struct passwd *pw;
210       char name[sizeof (UT_USER (utmp_ent)) + 1];
211
212       strncpy (name, UT_USER (utmp_ent), sizeof (UT_USER (utmp_ent)));
213       name[sizeof (UT_USER (utmp_ent))] = '\0';
214       pw = getpwnam (name);
215       if (pw == NULL)
216         printf (" %19s", "        ???");
217       else
218         {
219           char *const comma = strchr (pw->pw_gecos, ',');
220           char *result;
221
222           if (comma)
223             *comma = '\0';
224
225           result = create_fullname (pw->pw_gecos, pw->pw_name);
226           printf (" %-19.19s", result);
227           free (result);
228         }
229     }
230
231   printf (" %c%-8.*s",
232           mesg, (int) sizeof (utmp_ent->ut_line), utmp_ent->ut_line);
233
234   if (include_idle)
235     {
236       if (last_change)
237         printf (" %-6s", idle_string (last_change));
238       else
239         printf (" %-6s", "???");
240     }
241
242   /* Don't take the address of UT_TIME_MEMBER directly.
243      Ulrich Drepper wrote:
244      ``... GNU libc (and perhaps other libcs as well) have extended
245      utmp file formats which do not use a simple time_t ut_time field.
246      In glibc, ut_time is a macro which selects for backward compatibility
247      the tv_sec member of a struct timeval value.''  */
248   tm = UT_TIME_MEMBER (utmp_ent);
249   printf (" %-12.12s", ctime (&tm) + 4);
250
251 #ifdef HAVE_UT_HOST
252   if (include_where && utmp_ent->ut_host[0])
253     {
254       extern char *canon_host ();
255       char ut_host[sizeof (utmp_ent->ut_host) + 1];
256       char *host = 0, *display = 0;
257
258       /* Copy the host name into UT_HOST, and ensure it's nul terminated. */
259       strncpy (ut_host, utmp_ent->ut_host, (int) sizeof (utmp_ent->ut_host));
260       ut_host[sizeof (utmp_ent->ut_host)] = '\0';
261
262       /* Look for an X display.  */
263       display = strrchr (ut_host, ':');
264       if (display)
265         *display++ = '\0';
266
267       if (*ut_host)
268         /* See if we can canonicalize it.  */
269         host = canon_host (ut_host);
270       if ( ! host)
271         host = ut_host;
272
273       if (display)
274         printf (" %s:%s", host, display);
275       else
276         printf (" %s", host);
277     }
278 #endif
279
280   putchar ('\n');
281 }
282
283 /* Display a verbose line of information about UTMP_ENT. */
284
285 static void
286 print_long_entry (const char name[])
287 {
288   struct passwd *pw;
289
290   pw = getpwnam (name);
291
292   printf (_("Login name: "));
293   printf ("%-28s", name);
294
295   printf (_("In real life: "));
296   if (pw == NULL)
297     {
298       printf (" %s", _("???\n"));
299       return;
300     }
301   else
302     {
303       char *const comma = strchr (pw->pw_gecos, ',');
304       char *result;
305
306       if (comma)
307         *comma = '\0';
308
309       result = create_fullname (pw->pw_gecos, pw->pw_name);
310       printf (" %s", result);
311       free (result);
312     }
313
314   putchar ('\n');
315
316   if (include_home_and_shell)
317     {
318       printf (_("Directory: "));
319       printf ("%-29s", pw->pw_dir);
320       printf (_("Shell: "));
321       printf (" %s", pw->pw_shell);
322       putchar ('\n');
323     }
324
325   if (include_project)
326     {
327       FILE *stream;
328       char buf[1024];
329       const char *const baseproject = "/.project";
330       char *const project =
331       xmalloc (strlen (pw->pw_dir) + strlen (baseproject) + 1);
332
333       strcpy (project, pw->pw_dir);
334       strcat (project, baseproject);
335
336       stream = fopen (project, "r");
337       if (stream)
338         {
339           int bytes;
340
341           printf (_("Project: "));
342
343           while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0)
344             fwrite (buf, 1, bytes, stdout);
345           fclose (stream);
346         }
347
348       free (project);
349     }
350
351   if (include_plan)
352     {
353       FILE *stream;
354       char buf[1024];
355       const char *const baseplan = "/.plan";
356       char *const plan =
357       xmalloc (strlen (pw->pw_dir) + strlen (baseplan) + 1);
358
359       strcpy (plan, pw->pw_dir);
360       strcat (plan, baseplan);
361
362       stream = fopen (plan, "r");
363       if (stream)
364         {
365           int bytes;
366
367           printf (_("Plan:\n"));
368
369           while ((bytes = fread (buf, 1, sizeof (buf), stream)) > 0)
370             fwrite (buf, 1, bytes, stdout);
371           fclose (stream);
372         }
373
374       free (plan);
375     }
376
377   putchar ('\n');
378 }
379
380 /* Print the username of each valid entry and the number of valid entries
381    in UTMP_BUF, which should have N elements. */
382
383 static void
384 print_heading (void)
385 {
386   printf ("%-8s", _("Login"));
387   if (include_fullname)
388     printf (" %-19s", _("   Name"));
389   printf (" %-9s", _("TTY"));
390   if (include_idle)
391     printf (" %-6s", _("Idle"));
392   printf (" %-12s", _("When"));
393 #ifdef HAVE_UT_HOST
394   if (include_where)
395     printf (" %s", _("Where"));
396 #endif
397   putchar ('\n');
398 }
399
400 /* Display UTMP_BUF, which should have N entries. */
401
402 static void
403 scan_entries (int n, const STRUCT_UTMP *utmp_buf,
404               const int argc_names, char *const argv_names[])
405 {
406   if (include_heading)
407     print_heading ();
408
409   while (n--)
410     {
411       if (UT_USER (utmp_buf)[0]
412 #ifdef USER_PROCESS
413           && utmp_buf->ut_type == USER_PROCESS
414 #endif
415         )
416         {
417           if (argc_names)
418             {
419               int i;
420
421               for (i = 0; i < argc_names; i++)
422                 if (strncmp (UT_USER (utmp_buf), argv_names[i],
423                              sizeof (UT_USER (utmp_buf))) == 0)
424                   {
425                     print_entry (utmp_buf);
426                     break;
427                   }
428             }
429           else
430             print_entry (utmp_buf);
431         }
432       utmp_buf++;
433     }
434 }
435
436 /* Display a list of who is on the system, according to utmp file FILENAME. */
437
438 static void
439 short_pinky (const char *filename,
440              const int argc_names, char *const argv_names[])
441 {
442   int n_users;
443   STRUCT_UTMP *utmp_buf;
444   int fail = read_utmp (filename, &n_users, &utmp_buf);
445
446   if (fail)
447     error (1, errno, "%s", filename);
448
449   scan_entries (n_users, utmp_buf, argc_names, argv_names);
450 }
451
452 static void
453 long_pinky (const int argc_names, char *const argv_names[])
454 {
455   int i;
456
457   for (i = 0; i < argc_names; i++)
458     print_long_entry (argv_names[i]);
459 }
460
461 void
462 usage (int status)
463 {
464   if (status != 0)
465     fprintf (stderr, _("Try `%s --help' for more information.\n"),
466              program_name);
467   else
468     {
469       printf (_("Usage: %s [OPTION]... [USER]...\n"), program_name);
470       printf (_("\
471 \n\
472   -l              produce long format output for the specified USERs\n\
473   -b              omit the user's home directory and shell in long format\n\
474   -h              omit the user's project file in long format\n\
475   -p              omit the user's plan file in long format\n\
476   -s              do short format output, this is the default\n\
477   -f              omit the line of column headings in short format\n\
478   -w              omit the user's full name in short format\n\
479   -i              omit the user's full name and remote host in short format\n\
480   -q              omit the user's full name, remote host and idle time\n\
481                   in short format\n\
482       --help      display this help and exit\n\
483       --version   output version information and exit\n\
484 \n\
485 A lightweight `finger' program;  print user information.\n\
486 The utmp file will be %s.\n\
487 "), UTMP_FILE);
488       puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
489     }
490   exit (status);
491 }
492
493 int
494 main (int argc, char **argv)
495 {
496   int optc, longind;
497   int n_users;
498
499   program_name = argv[0];
500   setlocale (LC_ALL, "");
501   bindtextdomain (PACKAGE, LOCALEDIR);
502   textdomain (PACKAGE);
503
504   atexit (close_stdout);
505
506   while ((optc = getopt_long (argc, argv, "sfwiqbhlp", longopts, &longind))
507          != -1)
508     {
509       switch (optc)
510         {
511         case 0:
512           break;
513
514         case 's':
515           do_short_format = 1;
516           break;
517
518         case 'l':
519           do_short_format = 0;
520           break;
521
522         case 'f':
523           include_heading = 0;
524           break;
525
526         case 'w':
527           include_fullname = 0;
528           break;
529
530         case 'i':
531           include_fullname = 0;
532 #ifdef HAVE_UT_HOST
533           include_where = 0;
534 #endif
535           break;
536
537         case 'q':
538           include_fullname = 0;
539 #ifdef HAVE_UT_HOST
540           include_where = 0;
541 #endif
542           include_idle = 0;
543           break;
544
545         case 'h':
546           include_project = 0;
547           break;
548
549         case 'p':
550           include_plan = 0;
551           break;
552
553         case 'b':
554           include_home_and_shell = 0;
555           break;
556
557         case_GETOPT_HELP_CHAR;
558
559         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
560
561         default:
562           usage (1);
563         }
564     }
565
566   n_users = argc - optind;
567
568   if (do_short_format == 0 && n_users == 0)
569     {
570       error (0, 0, _("no username specified; at least one must be\
571  specified when using -l"));
572       usage (1);
573     }
574
575   if (do_short_format)
576     short_pinky (UTMP_FILE, n_users, argv + optind);
577   else
578     long_pinky (n_users, argv + optind);
579
580   exit (0);
581 }