// aac.c 
//
// - almost ac -
// mimick the ac command
// implimenting login names and alternate login info with -w

#include    "aac.h"
#define	MAXNAMES	512

//  globals
//
struct link gHead;                                // head of linked list

int main(int ac, char **av)
{
    int    i,
           nameCount = 0;
    float  totalTime = 0.0;                       // total log time
                                                  
    char  *fileName = WTMP_FILE,                  // default file name
          *userNames[MAXNAMES];                   // list of user names to tally
    
    // init linked list
    gHead.next = NULL;
    
    // loop through arguments
    for ( i = 1; i < ac; i++ ) {
        
            // mode change?
            if (av[i][0] == '-') {
                    switch (av[i][1]) {
                        case 'w':
                            if (i == ac - 1)
                                    fatal("missing arg for -w");
                            fileName = av[++i];
                            break;
                        default:
                            fatal("unknown option");
                    }
            }
            else {
                    userNames[nameCount] = av[i]; // append to userNames
                    nameCount++;
            }
    }

    if (! nameCount)                         
            fatal("ERROR: No user specified");            // no user name
    
    // loop through each name in userNames list
    for (i = 0; i < nameCount; i++) {
    
            // read file
            dumpfile( fileName, userNames[i] );
    
            // add up log times
            totalTime += tallyList();
     
            // empty list
            emptyList();
    }
    
    // output total time
    printf("\ttotal      %.2f\n", totalTime);
    
    return (0);		// return 0 means OK
}


//
//  Open file and dump records
//
void dumpfile( char *fileName, char *userName )
{
    struct utmp *utp,		                           // ptr to struct
    *utmp_next();                                          // declare its type

    if ( utmp_open( fileName ) == -1 ) {                   // open file
            perror( fileName );
            return ;
    }

    // loop, reading records
    while( ( utp = utmp_next() ) )
            readUtpLine( utp, userName );
    utmp_close();

}


//
//  Read a line of utp
//
void readUtpLine( struct utmp *rp, char *userName )
{
    struct link *linkp;
    
    
    // linkp = any listed entry with same id and no outTime (if any)
    linkp = lookup( rp->ut_id );
    
    
    switch (rp->ut_type) {
        case 1:
            // RUN_LVL
            
            break;
        case 2:
            // BOOT_TIME
                 // time at startup boot
            break;
        case 3:
            // OLD_TIME
                 //  original time
            break;
        case 4:
            // NEW_TIME
                 //  time change
            break;
        case 5:
            // INIT_PROCESS
            
            break;
        case 6:
            // LOGIN_PROCESS
            
            break;
        case 7:
            // USER_PROCESS
        
            // Has user logged in as another user without logging out?
            if ( linkp && strcmp(rp->ut_user, userName) != 0 )
                    linkp->outTime = linkp->inTime;   // yes, 'zero' broken id
            
            // is entry by user userName?
            if (strcmp(rp->ut_user, userName) == 0)
                    if ( ! linkp )                    // entry id not in list?
                            newEntry(rp->ut_id,rp->ut_time);
            break;
        case 8:
            // DEAD_PROCESS
            
            // check linkp (open entry in list with same id)
            if (linkp)
                    linkp->outTime = rp->ut_time;     // close using outTime
            break;
        case 9:
            // ACCOUNTING
            
            break;
        default:
            // EMPTY
            
            break;       
    }
}


//
//  Add new entry to linked list
//
int newEntry(char *id, int myInTime)
{
	char		 *myId ;
	struct link	 *newLink;

	// get memory and allocate values for struct
	myId = malloc( 1 + strlen(id));
	if (myId == NULL)
                fatal("Out of memory");
	strcpy( myId, id );
        
	newLink = malloc(sizeof(struct link));
	if (newLink == NULL)
                fatal("Out of memory");
	
	// store values into newLink
	newLink->id        = myId;
	newLink->inTime    = myInTime;
        newLink->outTime   = 0;
        newLink->next      = gHead.next;       // attach list to link
        
	gHead.next         = newLink;          // make head pt to link
        
	return YES;
}

//
//  Print Linked List,  prints id, inTime and outTimes of items in list
//
void printLList()
{
    struct link *linkp;
    
    for ( linkp = gHead.next ; linkp ; linkp = linkp->next )
            printf("id = %s   logIn = %d   logOut = %d\n", 
                    linkp->id, linkp->inTime, linkp->outTime);
}

//
//  Return pointer to any items with an id the same as str and 
//  are 'open'.  Open meaning they have a zero outTime.
//
struct link *lookup( char *str )
{
    struct link *linkp;

    for( linkp = gHead.next ; linkp ; linkp = linkp->next )
            if ( strcmp( linkp->id, str) == 0 && ! linkp->outTime) // if found
                    return linkp;		                   // ret value
    return NULL;					           // not found
}


//
//  Tally total time for all entries in linked list
//
float tallyList() {
    
    struct link *linkp;
    
    int    myEnd,
           myStart,
           myDiff;
    float  listTime = 0.0;
            
    for( linkp = gHead.next ; linkp ; linkp = linkp->next ) {
            myEnd   = linkp->outTime;
            myStart = linkp->inTime;
            
            // if myEnd is 0 then use current time (still logged on)
            if (! myEnd)
                    myEnd = time(NULL);

            myDiff = (myEnd - myStart);
            
            // convert difference to hours and add to total
            listTime += (myDiff / 3600.0);
    }
    
    // ensure no value > 0 is less than 0.01
    if (listTime > 0.0 && listTime < 0.01)
            listTime = 0.01;
    
    return (listTime);
}

//
//  Clear all entries in linked list
//
void emptyList()
{
    struct link * linkp,				      // link
                * prevLink;                                   // previous link

    // free all links except end
    prevLink = NULL;
    for( linkp = gHead.next ; linkp ; prevLink = linkp, linkp = linkp->next ) {
            if (prevLink)
                    free(prevLink);                           // free memory
    }
    
    // cleanup end and head
    free(linkp);
    gHead.next = NULL;
}


//
//  Display entire record
//
void show_utrec( struct utmp *rp )
{
    char        *typename();

    printf("%-8.8s ", rp->ut_user );
    printf("%-8.8s ", rp->ut_id   );
    printf("%-8.8s ", rp->ut_line );
    printf("%6d ", rp->ut_pid );
    printf("%4d %-14.14s ", rp->ut_type , typename(rp->ut_type) );
    printf("%12d ", rp->ut_time );
    printf("%s", rp->ut_host );
    putchar('\n');
}


//
//  Convert typenum int to string
//
char * typename( int typenum )
{
    char *uttypes[] = { "EMPTY",
                        "RUN_LVL",
                        "BOOT_TIME",
                        "OLD_TIME",
                        "NEW_TIME",
                        "INIT_PROCESS",
                        "LOGIN_PROCESS", 
                        "USER_PROCESS",
                        "DEAD_PROCESS",
                        "ACCOUNTING" };
                        
    return uttypes[typenum];
}
