/*
    yamm, Yet Another Micro Monitor
    main.c
    Copyright (C) 1992  Andrea Marangoni
    Copyright (C) 1994, 1995  Riccardo Facchetti

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#define _MAIN

#include "kversion.h"

#if defined(NCURSES)
# include "ncurses.h"
#else
# include <curses.h>
#endif /* NCURSES */
#include <pwd.h>
#include <time.h>
#if !defined(linux) || (KERNEL_VERSION < 1001076)
#include <string.h>
#else
#include <linux/string.h>
#endif /* !linux || KERNEL_VERSION < 1001076 */
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/param.h>
#include <sys/user.h>
#if !defined(linux)
# include <sys/proc.h>
#endif /* !linux */
#include <sys/stat.h>
#if !defined(linux)
# include <sys/ptyio.h>
#endif /* !linux */
#include <sys/sysmacros.h>
#if defined(linux)
# include <signal.h>

/*
 * hmmm ... these #undef are needed to remove annoying messages
 * from compiler.
 * Be sure not to use the HZ (curses) macro in this file: you have to use
 * the HZ system clock granularity, defined in <linux/sched.h>
 */
# undef HZ
# undef INT_MAX
# undef UINT_MAX
# undef LONG_MAX
# undef ULONG_MAX
# include <linux/sched.h>
#endif /* linux */

#include "yamm.h"

#if !defined(lint)
static char *_yamm1 =
	"@(#) YAMM ( Yet Another Micro Monitor ) " YAMM_VERSION " " YAMM_DATE
	" by Facchetti Riccardo, from V2.4 by Marangoni Andrea";
static char *_yamm2 =
	"@(#) E-mail: riccardo@cdc8g5.cdc.polimi.it";
#endif /* !lint */


#if !defined(NO_CURSES)
static char *cmd_line = "mN:CVdEDeisI:wtcp:r%Lu:SUW:/:";
#else
static char *cmd_line = "mN:CVdDEeisI:wtcp:r%Lu:SUW:/:";
#endif /* !NO_CURSES */

static char *get_command(int);
static void Look_process(long, int);
static char *trunc_line(char *);

/*
 * Do this to elide the bastard warning messages about _yamm1 and _yamm2 unused
 */
#if defined(__GNUC__)
inline const char * __yamm1__ ( void ) { return _yamm1; }
inline const char * __yamm2__ ( void ) { return _yamm2; }
#else
const char * __yamm1__ ( void ) { return _yamm1; }
const char * __yamm2__ ( void ) { return _yamm2; }
#endif



int main( int ac, char *av[] )
{
	int ret;
	register int count, count2;
	struct pst_dynamic dinfo;

#if defined(SIGWINCH) && defined(OWN_WINCH)
	yammav = av;
#endif /* SIGWINCH && OWN_WINCH */

	if (!init_yamm())
		printf ("yamm: initialization error\n"), exit(1);

/*
 * Thanks to Harald Vogt <harald@cs.ruu.nl> for ideas and suggestions
 */

	order = compare_sched_time; /* HV */
	mpreprocess ( ac, av, cmd_line );

	if ( ( ret = PS_proc ( pbuf, MAX_PROC, 0 )) == -1 )
		( void )perror ( "Pstat: PSTAT_PROC" ), exit ( 1 );

	set_users ();

#if !defined(NO_CURSES)
	( void )fflush ( stdout );
	setup_tty();
	init_curses();
#else
	count_status ( ret );
	printf ( "Number of processes in system: %d\n",ret );
	printf ( "    %d Running, %d Sleeping, %d Stopped, %d Zombie, %d Other.\n",
		status_run(),
		status_sleep(),
		status_stop(),
		status_zombie(),
		status_other());
#endif /* !NO_CURSES */

#if defined(linux)
	if (read_ftab() != 0)
# if !defined(NO_CURSES)
	curses_error("Kernel function table file read error (non existent or invalid table).\n");
# else
	printf("Kernel function table file read error (non existent or invalid table).\n");
# endif /* NO_CURSES */
#endif /* linux */

/*
 * I have used the ret variable to sign the first loop. ret is initialized
 * to 0, but it is 0 only the first loop: then it will be the number of
 * processes, than can not be 0. Good trick :)
 */
	ret = 0;
	do {
		if (ret != 0) {
/*
 * Do not do it in the first loop!
 */

#if !defined(NO_CURSES)
			curses_print_help();
			curses_refresh();
#else
			fflush ( stdout );
#endif /* !NO_CURSES */
#if !defined(NO_CURSES)
			( void )curses_wait ( wait_second );
#else
			( void )sleep ( wait_second <= 0 ? 1 : wait_second );
#endif /* !NO_CURSES */
		}

		if ( ( ret = PS_proc ( pbuf, MAX_PROC, 0 ) ) == -1 )
			( void )perror ( "Pstat: PSTAT_PROC" ), exit ( 1 );

		PS_dynam ( &dinfo );

#if !defined(NO_CURSES)
		update_first_line( ret, &dinfo  );	
#define PF (void)printw
#else
		if ( Num_display_proc == -1 )
			Num_display_proc = ret;
#define PF (void)printf
#endif /* !NO_CURSES */

#if !defined(NO_CURSES)
		if ( load_graph ) {
			ShowLoadGraph( &dinfo );
			continue;
		}
#endif /* !NO_CURSES */

		if ( who ) {
			Print_Who( ret ); 
			continue;
		}

		if ( look_utmp ) {
			examine_utmp();
			continue;
		}

		if ( look_user ) {
			if ( look_uid != -1)
				user_info();
			else {
#if !defined(NO_CURSES)
                curses_error ( "No such user" );
#else
				printf ( "No such user\n" );
#endif /* !NO_CURSES */
				look_uid = -1L;
				look_user = 0;
				reverse = 0;
				reverse_index = -1;
			}
			continue;
		}

		if ( look_process >= 0 ) {
			Look_process ( look_process, ret );
			continue;
		}

		if ( print_configuration ) {
			Print_Conf( ret, &dinfo );
			continue;
		}


		if ( vm_config ) {
			Print_vm();
			continue;
		}
	
		if ( order )
/*
 * Yeah! Casted the qsort 'order' and no more warning 'bout 4th qsort param.
 */
			( void ) qsort ( ( void *)pbuf,
							 ( size_t )ret,
							 ( size_t )sizeof ( struct pst_status ),
							 (int (*)(const void *, const void *))order);

#if !defined(linux)
		if ( look_cols() >= 90 || wcpu )
			PF ( "\nSWN USER       SUID   PID  NI PRI    SIZE     RSS  %%WCPU   %s%cTIME CMD\n",
				look_cols() >= 90 ? "STIME    " : "",
				system_time_include & user_time_include ? ' ':
				user_time_include ? 'u' : 's');
		else
			PF ( "\nSWN USER       SUID   PID  NI PRI    SIZE     RSS   STIME    %cTIME CMD\n",
				system_time_include & user_time_include ? ' ':
				user_time_include ? 'u' : 's');
#else
#if defined(MODULE)
		if ( look_cols() >= 90 || wcpu )
			PF ( "\nSWN USER       SUID   PID  NI PRI    SIZE     RSS  %%WCPU     WCHAN    %s%s%c%s CMD\n",
				look_cols() >= 90 ? "STIME   " : "",
				look_cols() >= 100 ? "   ":"",
				look_cols() >= 100 ? (system_time_include & user_time_include ? ' ':
				user_time_include ? 'u' : 's') : ' ',
				look_cols() >= 100 ? "TIME" : "");
		else
			PF ( "\nSWN USER       SUID   PID  NI PRI    SIZE     RSS   STIME    %cTIME CMD\n",
				system_time_include & user_time_include ? ' ':
				user_time_include ? 'u' : 's');
#else
		if ( look_cols() >= 90 || wcpu )
			PF ( "\nSWN USER        UID   PID  NI PRI    SIZE     RSS  %%WCPU     WCHAN    %s%s%c%s CMD\n",
				look_cols() >= 90 ? "STIME   " : "",
				look_cols() >= 100 ? "   ":"",
				look_cols() >= 100 ? (system_time_include & user_time_include ? ' ':
				user_time_include ? 'u' : 's') : ' ',
				look_cols() >= 100 ? "TIME" : "");
		else
			PF ( "\nSWN USER        UID   PID  NI PRI    SIZE     RSS   STIME    %cTIME CMD\n",
				system_time_include & user_time_include ? ' ':
				user_time_include ? 'u' : 's');
#endif /* MODULE */
#endif /* !linux */


		for ( count = count2 = displayed = 0, reverse_index = -1;
					count < ret; ++count) {
#if !defined(NO_CURSES)
			int bx, by;
#endif /* !NO_CURSES */

			if ( look_uid >= 0L && pbuf [ count ].pst_uid != look_uid )
				continue; /* -U option */

			if ( !allow_root && !pbuf [ count ].pst_uid )
				continue; /* !-R option */

			if ( set_euid  && pbuf [ count ].pst_uid == pbuf [ count ].pst_suid)
				continue; /* -E option */

			if ( sstring && !reg_match ( pbuf [ count ].pst_cmd ))
				continue;

#if !defined(NO_CURSES)
			if ( count2++ < pages*Num_display_proc )
				continue;
#endif /* !NO_CURSES */
			if ( Num_display_proc >= 0 && displayed >= Num_display_proc )
				break;

			displayed++;

#if !defined(NO_CURSES)
			getyx(stdscr, by, bx);

			if ( reverse == displayed  ) {
				reverse_index = count;
				curses_in_reverse();	
			}
#endif /* !NO_CURSES */

			PF ( "%c%c%c %-*s %c%5u %5d %3ld %3ld %s ",
				get_status ( count ),
				get_swstatus ( count ),
				get_nistatus ( count ),
				MAX_LOGIN,
				get_user_info ( count ),
				pbuf [ count ].pst_uid != pbuf [ count ].pst_suid ? '*':' ',
				pbuf [ count ].pst_suid,
				pbuf [ count ].pst_pid,
				pbuf [ count ].pst_nice,
				pbuf [ count ].pst_pri,
#if !defined(linux)
				get_dimension ( 
					pbuf [ count ].pst_dsize +
					pbuf [ count ].pst_tsize +
					pbuf [ count ].pst_ssize )
					);
#else
				get_dimension (  pbuf [ count ].pst_vsize ) );
#endif /* linux */

			PF ( "%s ",
				get_dimension ( pbuf [ count ].pst_rssize ));

			/* printw seems don't well work */
			if ( look_cols () >= 90 || wcpu ) {  /* xterm ? */
				double pcpu = pbuf [ count ].pst_pctcpu * 100.00;
#if defined(linux)
				int count3 = search_func (pbuf [ count ].pst_wchan);
#endif /* linux */

				/*  Bad trick here */
				PF ( "%2d.%.2d%% ", ( int )pcpu,
					( int )(( pcpu - ( int )pcpu) * 100.00 ));
#if defined(linux)
				if ( count3 == -1 )
					PF ( "0x%.10lx ", pbuf [ count ].pst_wchan);
				else
					PF ( "%12.12s ", ftab [ count3 ].name );
#endif /* linux */
			}

			if ( !wcpu )
				PF ( "%s ",
				visual_start_time ( count ));
#if defined(linux)
#define pbuf_pst_swap (pbuf [ count ].pst_size - pbuf [ count ].pst_resident)
			if ( !wcpu && (look_cols() < 90 || look_cols() >= 100) ) {
				PF ( ( (pbuf_pst_swap == 0) ? "%s %s" : "%s (%s)"),
					visual_user_time ( count ),
					long_format ? pbuf [ count ].pst_cmd :
									get_command ( count ));
			}
			else {
				PF ( ( (pbuf_pst_swap == 0) ? "%s" : "(%s)"),
					long_format ? pbuf [ count ].pst_cmd :
									get_command ( count ));
			}
#undef pbuf_pst_swap
#else
			PF ( "%s %s",
				visual_user_time ( count ),
				long_format ? pbuf [ count ].pst_cmd : get_command ( count ));
#endif /* linux */

#if !defined(NO_CURSES)
			move(by+1, 0);
#else
			printf("\n");
#endif /* !NO_CURSES */

#if !defined(NO_CURSES)
			if ( reverse == displayed )
				curses_out_reverse();	
#endif /* !NO_CURSES */
		}

		if ( reverse_index == -1 )
			reverse = 0;

		if ( displayed == 0 && look_uid >= 0 && !pages ) {
#if defined(NO_CURSES)
			PF ( "No such uid: %ld\n", look_uid );
#else
			curses_error ( "No such uid." );
#endif /* NO_CURSES */
			look_uid = -1;
		}

#undef PF
	} while ( wait_second >= 0 );


#if !defined(NO_CURSES)
	quit ( 0 );
#endif /* !NO_CURSES */
	return ( 0 );
}

static char *get_command ( int index )
{
#if !defined(linux)
	extern char *strtok();
	char *first_string = pbuf [ index ].pst_cmd ?
		strtok (  pbuf [ index ].pst_cmd , " \t\n" ) : "???";
#else
/*
 * No need to break the linux pst_cmd because the command line is not
 * complete (only argv[0] is stored in task structure.
 */
	char *first_string = pbuf [ index ].pst_cmd;
#endif /* !linux */


	char *last_cmd = strrchr ( first_string, '/' );

	return (  last_cmd ? last_cmd + 1 : first_string );
}

static void Look_process ( long process, int num )
{
	struct pst_dynamic dinfo;
	register int count;

	for ( count = 0; count < num && process!= pbuf[ count ].pst_pid; ++count );

#define PP (*myps)

#if defined(NO_CURSES)
#define PF (void)printf

#else
#define PF (void)printw
	if ( look_lines() < 24 ) {
		curses_error( "Sorry, you must have at least 80 cols." );
		look_process = -1;
		return;
	}

#endif /* NO_CURSES */

	if ( PS_proc ( myps, 0, process ) == -1 )
#if defined(NO_CURSES)
		( void )perror ( "Pstat: PSTAT_PROC" ),exit ( 1 );
#else
		{
			curses_error( "No such process" );
			look_process = -1;
			return;
		}
#endif /* NO_CURSES */

	PS_dynam ( &dinfo );

#if defined(linux)
	switch (pages) {
		case 0:
#endif /* linux */

#if !defined(linux) || defined(MODULE)
#if defined(MODULE)
			PF ( "\nUID - GID                            : %d  %s - %d  %s\n",
				PP.pst_uid, get_user_info ( count ),
				PP.pst_gid, get_group ( count ));
			PF ( "Process SUID, Filesystem SUID        : %u, %u\n",
			     PP.pst_suid, PP.pst_fsuid );
#else
			PF ( "\nUID                                  : %ld  %s\n",
				PP.pst_uid,
				get_user_info ( count ));
			PF ( "Process SUID                         : %ld\n",
			     PP.pst_suid );
#endif /* MODULE */
#endif /* !linux || MODULE */
#if !defined(linux)
			PF ( "Process ID                           : %ld\n", PP.pst_pid );
			PF ( "Parent process ID                    : %ld\n", PP.pst_ppid );
#else
			PF ( "Process ID, Parent ID, Session ID    : %d, %d, %d\n",
				PP.pst_pid,
				PP.pst_ppid,
				PP.pst_session );
#endif /* !linux */
#if defined(linux)
			PF ( "Memory size, resident, swap, shared  : %s, ",
				get_dimension ( PP.pst_size ));
			PF ( "%s, ",
				get_dimension ( PP.pst_resident ));
			PF ( "%s, ",
				get_dimension ( PP.pst_size - PP.pst_resident ));
			PF ( "%s\n",
				get_dimension ( PP.pst_share ));
			PF ( "t(ext)rs, l(ib)rs, d(ata)rs, dt      : %s, ",
				get_dimension ( PP.pst_trs ));
			PF ( "%s, ",
				get_dimension ( PP.pst_lrs ));
			PF ( "%s, ",
				get_dimension ( PP.pst_drs ));
			PF ( "%s\n",
				get_dimension ( PP.pst_dt ));
#else
			PF ( "Process data, text, stack size       : %s, ",
				get_dimension ( PP.pst_dsize ));
			PF ( "%s, ",
				get_dimension ( PP.pst_tsize ));
			PF ( "%s\n",
				get_dimension ( PP.pst_ssize ));
#endif /* linux */
			PF ( "Resident set size for process        : %-5ld Pages ( %s )\n",
				PP.pst_rssize, get_dimension ( PP.pst_rssize ));
			PF ( "Nice, Priority                       : %ld, %ld\n",
				PP.pst_nice,
				PP.pst_pri );
			PF ( "Process group                        : %d\n", PP.pst_pgrp );
			PF ( "User time spent executing            : %ld Sec.\n", 
				PP.pst_utime );
			PF ( "System time spent executing          : %ld Sec.\n",
				PP.pst_stime );
			PF ( "Start time                           : %s",
				ctime ( &PP.pst_start ));
			PF ( "Current status                       : %c\n",
				get_status ( count ));
#if defined(linux)
#define PP_pst_swap	(PP.pst_size-PP.pst_resident)
			PF ( ( (PP_pst_swap == 0) ?
			    "Command the proc is running          : %s\n" :
				"Command the proc is running          : (%s)\n") ,
				trunc_line(get_cmdline(PP)) );
#undef PP_pst_swap
#else
			PF ( "Command the proc is running          : %s\n",
				PP.pst_cmd );
#endif /* linux */
			PF ( "Terminal associated to the process   : %s\n",
#if !defined(linux)
				get_tty ( PP.pst_term.psd_major, PP.pst_term.psd_minor ));
			PF ( "Resident time for scheduling         : %ld\n",
				PP.pst_time );
#else
				PP.pst_tty);
			PF ( "Timeout time for scheduling          : %ld\n", PP.pst_time );
#endif /* !linux */
			PF ( "Ticks of cpu time                    : %ld\n",
				PP.pst_cpticks);
			PF ( "Total ticks for life of process      : %ld\n",
				PP.pst_cptickstotal );
			PF ( "%%cpu for this process during p_time  : %.2f%%\n",
				PP.pst_pctcpu * 100.00 );
			PF ( "%%mem for used by the process         : %.2f%%\n",
				((float)PP.pst_rssize * 100.00)/(float)dinfo.psd_rm);

#if defined(linux)
#if !defined(NO_CURSES)
			move ( LINES - 2 , 0 );
#endif /* !NO_CURSES */
			PF ( "Type 'f' to next page or <RETURN> to exit." );
			break;

		default:
			pages = 1;

		case 1:
			PF ( "\nItimer real value                    : %ld Sec.\n",
				PP.pst_it_real_value );
			PF ( "Major Page Faults                    : %ld\n",
				PP.pst_maj_flt );
			PF ( "Major Children Page Faults           : %ld\n",
				PP.pst_cmaj_flt );
			PF ( "Minor Page Faults                    : %ld\n",
				PP.pst_min_flt );
			PF ( "Minor Children Page Faults           : %ld\n",
				PP.pst_cmin_flt );
#if (KERNEL_VERSION < 1002000)
			PF ( "Process flags                        : %s%s%s\n",
				(PP.pst_flags & PF_ALIGNWARN) ? "PF_ALIGNWARN " : "",
				(PP.pst_flags & PF_PTRACED) ? "PF_PTRACED " : "",
				(PP.pst_flags & PF_TRACESYS) ? "PF_TRACESYS " : "" );
#else
			PF ( "Process flags                        : %s%s%s%s%s\n",
				(PP.pst_flags & PF_ALIGNWARN) ? "PF_ALIGNWARN " : "",
				(PP.pst_flags & PF_PTRACED) ? "PF_PTRACED " : "",
				(PP.pst_flags & PF_STARTING) ? "PF_STARTING " : "",
				(PP.pst_flags & PF_EXITING) ? "PF_EXITING " : "",
				(PP.pst_flags & PF_TRACESYS) ? "PF_TRACESYS " : "" );
#endif /* KERNEL_VERSION < 1002000 */
			PF ( "WCHAN                                : 0x%lx",
				PP.pst_wchan );
			if ((count = search_func ( PP.pst_wchan )) == -1)
				PF ( "\n" );
			else
				PF ( " (%s)\n", ftab [ count ].name);

#define M0 "0"
#define M1 "1"

			PF ( "\n           SIGNAL MASKS              : 1  ...   SIGNAL NUMBER   ...  %d\n", NSIG );
			PF ( "Pending signals mask                 : " );
			for (count = 0; count < NSIG; count++)
				if ((1 << count) & PP.pst_signal)
					PF ( M1 );
				else
					PF ( M0 );
			PF ( "\n" );
			PF ( "Blocked signals mask                 : " );
			for (count = 0; count < NSIG; count++)
				if ((1 << count) & PP.pst_blocked)
					PF ( M1 );
				else
					PF ( M0 );
			PF ( "\n" );
			PF ( "Ignored signals mask                 : " );
			for (count = 0; count < NSIG; count++)
				if ((1 << count) & PP.pst_sigignore)
					PF ( M1 );
				else
					PF ( M0 );
			PF ( "\n" );
			PF ( "Catched signals mask                 : " );
			for (count = 0; count < NSIG; count++)
				if ((1 << count) & PP.pst_sigcatch)
					PF ( M1 );
				else
					PF ( M0 );
			PF ( "\n" );
#if !defined(NO_CURSES)
			move ( LINES - 2 , 0 );
#endif /* !NO_CURSES */
			PF ( "Type 'b' to previous page or <RETURN> to exit." );
			break;
}

#undef M0
#undef M1
#endif /* linux */

#undef PP
#undef PF
}

/*
 * This function is used in Look_process(), where it is displayed (if available)
 * the complete command line.
 * If the complete command line is too long, truncate it (on the left).
 * If is possible, with something like '.../path/command par1 par2'
 * This code is a bit obscure, but it works :)
 */
static char *trunc_line(char *in)
{
	char *out, *slash;
	int size = strlen(in);
	int allowed = look_cols() - 40;

	if (size <= allowed)
		return in;
	
	out = (char *)&in[size-(allowed-3)];

	if ((slash = strchr(out, '/')) != NULL)
		out = slash;
/*
 * Unroll manually the loop
 */
 	out[-1] = '.';
 	out[-2] = '.';
 	out[-3] = '.';

	return (char *)&out[-3];
}
