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