deprecate execfuse/autosshfs in favor of autosshfs-mount script
[hband-tools.git] / compiled-tools / lastcsv.d / lastcsv.c
blob9668a94e4d05ec608fece7f353d0fcfa8168c8ca
1 /*
2 * last.c Re-implementation of the 'last' command, this time
3 * for Linux. Yes I know there is BSD last, but I
4 * just felt like writing this. No thanks :-).
5 * Also, this version gives lots more info (especially with -x)
7 * Author: Miquel van Smoorenburg, miquels@cistron.nl
9 * Version: @(#)last 2.85 30-Jul-2004 miquels@cistron.nl
11 * This file is part of the sysvinit suite,
12 * Copyright (C) 1991-2004 Miquel van Smoorenburg.
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 2 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/fcntl.h>
32 #include <time.h>
33 #include <stdio.h>
34 #include <ctype.h>
35 #include <utmp.h>
36 #include <errno.h>
37 #include <malloc.h>
38 #include <stdlib.h>
39 #include <unistd.h>
40 #include <string.h>
41 #include <signal.h>
42 #include <getopt.h>
43 #include <netinet/in.h>
44 #include <netdb.h>
45 #include <arpa/inet.h>
46 #include "oldutmp.h"
48 #ifndef SHUTDOWN_TIME
49 # define SHUTDOWN_TIME 254
50 #endif
52 char *Version = "@(#) last 2.85 31-Apr-2004 miquels";
54 #define CHOP_DOMAIN 0 /* Define to chop off local domainname. */
55 #define NEW_UTMP 1 /* Fancy & fast utmp read code. */
56 #define UCHUNKSIZE 16384 /* How much we read at once. */
58 /* Double linked list of struct utmp's */
59 struct utmplist {
60 struct utmp ut;
61 struct utmplist *next;
62 struct utmplist *prev;
64 struct utmplist *utmplist = NULL;
66 /* Types of listing */
67 #define R_CRASH 1 /* No logout record, system boot in between */
68 #define R_DOWN 2 /* System brought down in decent way */
69 #define R_NORMAL 3 /* Normal */
70 #define R_NOW 4 /* Still logged in */
71 #define R_REBOOT 5 /* Reboot record. */
72 #define R_PHANTOM 6 /* No logout record but session is stale. */
73 #define R_TIMECHANGE 7 /* NEW_TIME or OLD_TIME */
75 /* Global variables */
76 int maxrecs = 0; /* Maximum number of records to list. */
77 int recsdone = 0; /* Number of records listed */
78 int showhost = 1; /* Show hostname too? */
79 int name_len = 8; /* Default print 8 characters of name */
80 int domain_len = 16; /* Default print 16 characters of domain */
81 int oldfmt = 0; /* Use old libc5 format? */
82 char **show = NULL; /* What do they want us to show */
83 char *ufile; /* Filename of this file */
84 time_t lastdate; /* Last date we've seen */
85 char *progname; /* Name of this program */
86 #if CHOP_DOMAIN
87 char hostname[256]; /* For gethostbyname() */
88 char *domainname; /* Our domainname. */
89 #endif
92 * Convert old utmp format to new.
94 void uconv(struct oldutmp *oldut, struct utmp *utn)
96 memset(utn, 0, sizeof(struct utmp));
97 utn->ut_type = oldut->ut_type;
98 utn->ut_pid = oldut->ut_pid;
99 utn->ut_time = oldut->ut_oldtime;
100 utn->ut_addr = oldut->ut_oldaddr;
101 strncpy(utn->ut_line, oldut->ut_line, OLD_LINESIZE);
102 strncpy(utn->ut_user, oldut->ut_user, OLD_NAMESIZE);
103 strncpy(utn->ut_host, oldut->ut_host, OLD_HOSTSIZE);
106 #if NEW_UTMP
108 * Read one utmp entry, return in new format.
109 * Automatically reposition file pointer.
111 int uread(FILE *fp, struct utmp *u, int *quit)
113 static int utsize;
114 static char buf[UCHUNKSIZE];
115 char tmp[1024];
116 static off_t fpos;
117 static int bpos;
118 struct oldutmp uto;
119 int r;
120 off_t o;
122 if (quit == NULL && u != NULL) {
124 * Normal read.
126 if (oldfmt) {
127 r = fread(&uto, sizeof(uto), 1, fp);
128 uconv(&uto, u);
129 } else
130 r = fread(u, sizeof(struct utmp), 1, fp);
131 return r;
134 if (u == NULL) {
136 * Initialize and position.
138 utsize = oldfmt ? sizeof(uto) : sizeof(struct utmp);
139 fseeko(fp, 0, SEEK_END);
140 fpos = ftello(fp);
141 if (fpos == 0)
142 return 0;
143 o = ((fpos - 1) / UCHUNKSIZE) * UCHUNKSIZE;
144 if (fseeko(fp, o, SEEK_SET) < 0) {
145 fprintf(stderr, "%s: seek failed!\n", progname);
146 return 0;
148 bpos = (int)(fpos - o);
149 if (fread(buf, bpos, 1, fp) != 1) {
150 fprintf(stderr, "%s: read failed!\n", progname);
151 return 0;
153 fpos = o;
154 return 1;
158 * Read one struct. From the buffer if possible.
160 bpos -= utsize;
161 if (bpos >= 0) {
162 if (oldfmt)
163 uconv((struct oldutmp *)(buf + bpos), u);
164 else
165 memcpy(u, buf + bpos, sizeof(struct utmp));
166 return 1;
170 * Oops we went "below" the buffer. We should be able to
171 * seek back UCHUNKSIZE bytes.
173 fpos -= UCHUNKSIZE;
174 if (fpos < 0)
175 return 0;
178 * Copy whatever is left in the buffer.
180 memcpy(tmp + (-bpos), buf, utsize + bpos);
181 if (fseeko(fp, fpos, SEEK_SET) < 0) {
182 perror("fseek");
183 return 0;
187 * Read another UCHUNKSIZE bytes.
189 if (fread(buf, UCHUNKSIZE, 1, fp) != 1) {
190 perror("fread");
191 return 0;
195 * The end of the UCHUNKSIZE byte buffer should be the first
196 * few bytes of the current struct utmp.
198 memcpy(tmp, buf + UCHUNKSIZE + bpos, -bpos);
199 bpos += UCHUNKSIZE;
201 if (oldfmt)
202 uconv((struct oldutmp *)tmp, u);
203 else
204 memcpy(u, tmp, sizeof(struct utmp));
206 return 1;
209 #else /* NEW_UTMP */
212 * Read one utmp entry, return in new format.
213 * Automatically reposition file pointer.
215 int uread(FILE *fp, struct utmp *u, int *quit)
217 struct oldutmp uto;
218 off_t r;
220 if (u == NULL) {
221 r = oldfmt ? sizeof(struct oldutmp) : sizeof(struct utmp);
222 fseek(fp, -1 * r, SEEK_END);
223 return 1;
226 if (!oldfmt) {
227 r = fread(u, sizeof(struct utmp), 1, fp);
228 if (r == 1) {
229 if (fseeko(fp, -2 * sizeof(struct utmp), SEEK_CUR) < 0)
230 if (quit) *quit = 1;
232 return r;
234 r = fread(&uto, sizeof(struct oldutmp), 1, fp);
235 if (r == 1) {
236 if (fseeko(fp, -2 * sizeof(struct oldutmp), SEEK_CUR) < 0)
237 if (quit) *quit = 1;
238 uconv(&uto, u);
241 return r;
243 #endif
246 * Try to be smart about the location of the BTMP file
248 #ifndef BTMP_FILE
249 #define BTMP_FILE getbtmp()
250 char *getbtmp()
252 static char btmp[128];
253 char *p;
255 strcpy(btmp, WTMP_FILE);
256 if ((p = strrchr(btmp, '/')) == NULL)
257 p = btmp;
258 else
259 p++;
260 *p = 0;
261 strcat(btmp, "btmp");
262 return btmp;
264 #endif
267 * Print a short date.
269 char *showdate()
271 char *s = ctime(&lastdate);
272 s[16] = 0;
273 return s;
277 * SIGINT handler
279 void int_handler()
281 printf("Interrupted %s\n", showdate());
282 exit(1);
286 * SIGQUIT handler
288 void quit_handler()
290 printf("Interrupted %s\n", showdate());
291 signal(SIGQUIT, quit_handler);
295 * Get the basename of a filename
297 char *mybasename(char *s)
299 char *p;
301 if ((p = strrchr(s, '/')) != NULL)
302 p++;
303 else
304 p = s;
305 return p;
308 char* localtimestamp(time_t time)
310 char* ret;
311 struct tm* loc;
313 ret = malloc(256);
314 loc = localtime(&time);
315 if(loc == NULL)
317 perror("localtime");
318 sprintf(ret, "?");
320 else
322 strftime(ret, 255, "%s", loc);
324 return ret;
325 /* FIXME: free(ret) */
329 * Show one line of information on screen
331 int list(struct utmp *p, time_t t, int what)
333 char aux1[32] = "";
334 char aux2[32] = "";
335 char ipaddress[16] = "";
336 char final[512];
337 char utline[UT_LINESIZE+1];
338 char domain[256];
339 char *s, **walk;
340 int len;
343 * uucp and ftp have special-type entries
345 utline[0] = 0;
346 strncat(utline, p->ut_line, UT_LINESIZE);
347 if (strncmp(utline, "ftp", 3) == 0 && isdigit(utline[3]))
348 utline[3] = 0;
349 if (strncmp(utline, "uucp", 4) == 0 && isdigit(utline[4]))
350 utline[4] = 0;
353 * Is this something we wanna show?
355 if (show) {
356 for (walk = show; *walk; walk++) {
357 if (strncmp(p->ut_name, *walk, UT_NAMESIZE) == 0 ||
358 strcmp(utline, *walk) == 0 ||
359 (strncmp(utline, "tty", 3) == 0 &&
360 strcmp(utline + 3, *walk) == 0)) break;
362 if (*walk == NULL) return 0;
365 switch(what) {
366 case R_CRASH:
367 sprintf(aux2, "crash");
368 break;
369 case R_DOWN:
370 sprintf(aux2, "down");
371 break;
372 case R_NOW:
373 sprintf(aux2, "still logged in");
374 break;
375 case R_PHANTOM:
376 sprintf(aux2, "gone");
377 break;
378 case R_REBOOT:
379 sprintf(aux1, "reboot");
380 p->ut_name[0] = 0;
381 break;
382 case R_TIMECHANGE:
383 break;
384 case R_NORMAL:
385 break;
389 len = UT_HOSTSIZE;
390 if (len >= (int)sizeof(domain)) len = sizeof(domain) - 1;
391 domain[0] = 0;
392 strncat(domain, p->ut_host, len);
395 #if CHOP_DOMAIN
397 * See if this is in our domain.
399 if (s = strchr(p->ut_host, '.') != NULL &&
400 strcmp(s + 1, domainname) == 0) *s = 0;
401 #endif
402 if(p->ut_addr != 0)
403 sprintf(ipaddress, "%d.%d.%d.%d", p->ut_addr&0xFF, p->ut_addr>>8&0xFF, p->ut_addr>>16&0xFF, p->ut_addr>>24&0xFF);
404 if(p->ut_type == RUN_LVL)
406 sprintf(aux1, p->ut_name);
407 p->ut_name[0] = 0;
408 sprintf(aux2, utline);
409 utline[0] = 0;
411 else if(p->ut_type == SHUTDOWN_TIME)
413 sprintf(aux1, p->ut_name);
414 p->ut_name[0] = 0;
416 len = snprintf(final, sizeof(final),
417 "%s;%s;%s;%s;%s;%s;%s;%s;%d\n",
418 p->ut_name, utline, ipaddress, domain, localtimestamp((time_t)p->ut_time), localtimestamp(t), aux1, aux2, p->ut_pid);
420 #if defined(__GLIBC__)
421 # if (__GLIBC__ == 2) && (__GLIBC_MINOR__ == 0)
422 final[sizeof(final)-1] = '\0';
423 # endif
424 #endif
427 * Print out "final" string safely.
429 for (s = final; *s; s++) {
430 if (*s == '\n' || (*s >= 32 && (unsigned char)*s <= 126))
431 putchar(*s);
432 else
433 putchar('*');
436 if (len < 0 || (size_t)len >= sizeof(final))
437 putchar('\n');
439 recsdone++;
440 if (maxrecs && recsdone >= maxrecs)
441 return 1;
443 return 0;
448 * show usage
450 void usage(char *s)
452 fprintf(stderr, "Usage: %s [-num | -n num] [-f file] "
453 "[-t YYYYMMDDHHMMSS] "
454 "[-ow] [username..] [tty..]\n", s);
455 exit(1);
458 time_t parsetm(char *ts)
460 struct tm u, origu;
461 time_t tm;
463 memset(&tm, 0, sizeof(tm));
465 if (sscanf(ts, "%4d%2d%2d%2d%2d%2d", &u.tm_year,
466 &u.tm_mon, &u.tm_mday, &u.tm_hour, &u.tm_min,
467 &u.tm_sec) != 6)
468 return (time_t)-1;
470 u.tm_year -= 1900;
471 u.tm_mon -= 1;
472 u.tm_isdst = -1;
474 origu = u;
476 if ((tm = mktime(&u)) == (time_t)-1)
477 return tm;
480 * Unfortunately mktime() is much more forgiving than
481 * it should be. For example, it'll gladly accept
482 * "30" as a valid month number. This behavior is by
483 * design, but we don't like it, so we want to detect
484 * it and complain.
486 if (u.tm_year != origu.tm_year ||
487 u.tm_mon != origu.tm_mon ||
488 u.tm_mday != origu.tm_mday ||
489 u.tm_hour != origu.tm_hour ||
490 u.tm_min != origu.tm_min ||
491 u.tm_sec != origu.tm_sec)
492 return (time_t)-1;
494 return tm;
497 int main(int argc, char **argv)
499 FILE *fp; /* Filepointer of wtmp file */
501 struct utmp ut; /* Current utmp entry */
502 struct utmp oldut; /* Old utmp entry to check for duplicates */
503 struct utmplist *p; /* Pointer into utmplist */
504 struct utmplist *next;/* Pointer into utmplist */
506 time_t lastboot = 0; /* Last boottime */
507 time_t lastrch = 0; /* Last run level change */
508 time_t lastdown; /* Last downtime */
509 time_t begintime; /* When wtmp begins */
510 int whydown = 0; /* Why we went down: crash or shutdown */
512 int c, x; /* Scratch */
513 struct stat st; /* To stat the [uw]tmp file */
514 int quit = 0; /* Flag */
515 int down = 0; /* Down flag */
516 int lastb = 0; /* Is this 'lastb' ? */
517 char *altufile = NULL;/* Alternate wtmp */
519 time_t until = 0; /* at what time to stop parsing the file */
521 progname = mybasename(argv[0]);
523 /* Process the arguments. */
524 while((c = getopt(argc, argv, "f:n:xot:0123456789w")) != EOF)
525 switch(c) {
526 case 'n':
527 maxrecs = atoi(optarg);
528 break;
529 case 'o':
530 oldfmt = 1;
531 break;
532 case 'f':
533 if((altufile = malloc(strlen(optarg)+1)) == NULL) {
534 fprintf(stderr, "%s: out of memory\n",
535 progname);
536 exit(1);
538 strcpy(altufile, optarg);
539 break;
540 case 't':
541 if ((until = parsetm(optarg)) == (time_t)-1) {
542 fprintf(stderr, "%s: Invalid time value \"%s\"\n",
543 progname, optarg);
544 usage(progname);
546 break;
547 case 'w':
548 if (UT_NAMESIZE > name_len)
549 name_len = UT_NAMESIZE;
550 if (UT_HOSTSIZE > domain_len)
551 domain_len = UT_HOSTSIZE;
552 break;
553 case '0': case '1': case '2': case '3': case '4':
554 case '5': case '6': case '7': case '8': case '9':
555 maxrecs = 10*maxrecs + c - '0';
556 break;
557 default:
558 usage(progname);
559 break;
561 if (optind < argc) show = argv + optind;
564 * Which file do we want to read?
566 if (strncmp(progname, "lastb", 5) == 0) {
567 ufile = BTMP_FILE;
568 lastb = 1;
569 } else
570 ufile = WTMP_FILE;
571 if (altufile)
572 ufile = altufile;
573 time(&lastdown);
574 lastrch = lastdown;
577 * Fill in 'lastdate'
579 lastdate = lastdown;
581 #if CHOP_DOMAIN
583 * Find out domainname.
585 * This doesn't work on modern systems, where only a DNS
586 * lookup of the result from hostname() will get you the domainname.
587 * Remember that domainname() is the NIS domainname, not DNS.
588 * So basically this whole piece of code is bullshit.
590 hostname[0] = 0;
591 (void) gethostname(hostname, sizeof(hostname));
592 if ((domainname = strchr(hostname, '.')) != NULL) domainname++;
593 if (domainname == NULL || domainname[0] == 0) {
594 hostname[0] = 0;
595 (void) getdomainname(hostname, sizeof(hostname));
596 hostname[sizeof(hostname) - 1] = 0;
597 domainname = hostname;
598 if (strcmp(domainname, "(none)") == 0 || domainname[0] == 0)
599 domainname = NULL;
601 #endif
604 * Install signal handlers
606 signal(SIGINT, int_handler);
607 signal(SIGQUIT, quit_handler);
610 * Open the utmp file
612 if ((fp = fopen(ufile, "r")) == NULL) {
613 x = errno;
614 fprintf(stderr, "%s: %s: %s\n", progname, ufile, strerror(errno));
615 if (altufile == NULL && x == ENOENT)
616 fprintf(stderr, "Perhaps this file was removed by the "
617 "operator to prevent logging %s info.\n", progname);
618 exit(1);
622 * Optimize the buffer size.
624 setvbuf(fp, NULL, _IOFBF, UCHUNKSIZE);
627 * Read first structure to capture the time field
629 if (uread(fp, &ut, NULL) == 1)
630 begintime = ut.ut_time;
631 else {
632 fstat(fileno(fp), &st);
633 begintime = st.st_ctime;
634 quit = 1;
637 printf(";;;;%s;;begin;\n", localtimestamp(begintime));
641 * Go to end of file minus one structure
642 * and/or initialize utmp reading code.
644 uread(fp, NULL, NULL);
647 * Read struct after struct backwards from the file.
649 while(!quit) {
651 if (uread(fp, &ut, &quit) != 1)
652 break;
654 if (until && until < ut.ut_time)
655 continue;
657 if (memcmp(&ut, &oldut, sizeof(struct utmp)) == 0) continue;
658 memcpy(&oldut, &ut, sizeof(struct utmp));
659 lastdate = ut.ut_time;
661 if (lastb) {
662 quit = list(&ut, ut.ut_time, R_NORMAL);
663 continue;
667 * Set ut_type to the correct type.
669 if (strncmp(ut.ut_line, "~", 1) == 0) {
670 strcpy(ut.ut_line, "");
671 if (strncmp(ut.ut_user, "shutdown", 8) == 0)
672 ut.ut_type = SHUTDOWN_TIME;
673 else if (strncmp(ut.ut_user, "reboot", 6) == 0)
674 ut.ut_type = BOOT_TIME;
675 else if (strncmp(ut.ut_user, "runlevel", 8) == 0)
676 ut.ut_type = RUN_LVL;
678 #if 1 /*def COMPAT*/
680 * For stupid old applications that don't fill in
681 * ut_type correctly.
683 else {
684 if (ut.ut_type != DEAD_PROCESS &&
685 ut.ut_name[0] && ut.ut_line[0] &&
686 strcmp(ut.ut_name, "LOGIN") != 0)
687 ut.ut_type = USER_PROCESS;
689 * Even worse, applications that write ghost
690 * entries: ut_type set to USER_PROCESS but
691 * empty ut_name...
693 if (ut.ut_name[0] == 0)
694 ut.ut_type = DEAD_PROCESS;
697 * Clock changes.
699 if (strcmp(ut.ut_name, "date") == 0) {
700 if (ut.ut_line[0] == '|') ut.ut_type = OLD_TIME;
701 if (ut.ut_line[0] == '{') ut.ut_type = NEW_TIME; /* } */
704 #endif
706 switch (ut.ut_type) {
707 case SHUTDOWN_TIME:
708 quit = list(&ut, lastboot, R_NORMAL);
709 lastdown = lastrch = ut.ut_time;
710 down = 1;
711 break;
712 case OLD_TIME:
713 case NEW_TIME:
714 strcpy(ut.ut_line, ut.ut_type == NEW_TIME ? "new time" : "old time");
715 quit = list(&ut, lastdown, R_TIMECHANGE);
716 break;
717 case BOOT_TIME:
718 quit = list(&ut, lastdown, R_REBOOT);
719 lastboot = ut.ut_time;
720 down = 1;
721 break;
722 case RUN_LVL:
723 x = ut.ut_pid & 255;
724 sprintf(ut.ut_line, "%c", x);
725 quit = list(&ut, lastrch, R_NORMAL);
726 if (x == '0' || x == '6') {
727 lastdown = ut.ut_time;
728 down = 1;
729 ut.ut_type = SHUTDOWN_TIME;
731 lastrch = ut.ut_time;
732 break;
734 case USER_PROCESS:
736 * This was a login - show the first matching
737 * logout record and delete all records with
738 * the same ut_line.
740 c = 0;
741 for (p = utmplist; p; p = next) {
742 next = p->next;
743 if (strncmp(p->ut.ut_line, ut.ut_line, UT_LINESIZE) == 0) {
744 /* Show it */
745 if (c == 0) {
746 quit = list(&ut, p->ut.ut_time, R_NORMAL);
747 c = 1;
749 if (p->next) p->next->prev = p->prev;
750 if (p->prev)
751 p->prev->next = p->next;
752 else
753 utmplist = p->next;
754 free(p);
758 * Not found? Then crashed, down, still
759 * logged in, or missing logout record.
761 if (c == 0) {
762 if (lastboot == 0) {
763 c = R_NOW;
764 /* Is process still alive? */
765 if (ut.ut_pid > 0 &&
766 kill(ut.ut_pid, 0) != 0 &&
767 errno == ESRCH)
768 c = R_PHANTOM;
769 } else
770 c = whydown;
771 quit = list(&ut, lastboot, c);
773 /* FALLTHRU */
775 case DEAD_PROCESS:
777 * Just store the data if it is
778 * interesting enough.
780 if (ut.ut_line[0] == 0)
781 break;
782 if ((p = malloc(sizeof(struct utmplist))) == NULL) {
783 fprintf(stderr, "%s: out of memory\n", progname);
784 exit(1);
786 memcpy(&p->ut, &ut, sizeof(struct utmp));
787 p->next = utmplist;
788 p->prev = NULL;
789 if (utmplist) utmplist->prev = p;
790 utmplist = p;
791 break;
795 * If we saw a shutdown/reboot record we can remove
796 * the entire current utmplist.
798 if (down) {
799 lastboot = ut.ut_time;
800 whydown = (ut.ut_type == SHUTDOWN_TIME) ? R_DOWN : R_CRASH;
801 for (p = utmplist; p; p = next) {
802 next = p->next;
803 free(p);
805 utmplist = NULL;
806 down = 0;
810 fclose(fp);
813 * Should we free memory here? Nah. This is not NT :)
815 return 0;