#include <signal.h>

#include <string.h>
#include <unistd.h>
#if defined(__QNXNTO__)
	#include <fcntl.h>
	#include <sys/procfs.h>
#else
	#include <sys/vc.h>
	#include <sys/debug.h>
	#include <sys/magic.h>
	#include <sys/kernel.h>
	#include <sys/psinfo.h>
#endif

/* Toolkit headers */
#include <Ph.h>
#include <Pt.h>
#include <Ap.h>

/* Local headers */
#include "phterm.h"
#include "abimport.h"
#include "proto.h"

#if defined(__QNXNTO__)
	struct process_info {
		int fd;
		procfs_info pi;
		};
#else
	struct process_info {
		struct _psinfo psi;
		pid_t proc;
		};
#endif

static int has_children( pid_t );

static pid_t find_fg_process(
	pid_t pid, struct process_info *ps
	);

static void getcommand(
	struct process_info const *ps, char prg[ CMDPATH_LEN+1 ],
	char cmd[ TITLE_LEN+1 ], char args[ TITLE_LEN+1 ]
	);

pid_t getprg(
		pid_t pid, char prg[ CMDPATH_LEN+1 ], char cmd[ TITLE_LEN+1 ], char args[ TITLE_LEN+1 ]
		) {
	struct process_info ps;
	pid = find_fg_process( pid, &ps );
	getcommand( &ps, prg, cmd, args );
	return pid;
	}

int
close_base( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )
	{
	PtDestroyWidget( ABW_base );
	return Pt_CONTINUE;
	}

int
safe_exit( PtWidget_t *widget, ApInfo_t *apinfo, PtCallbackInfo_t *cbinfo )
	{
	PhWindowEvent_t *wev = cbinfo->cbdata;
	if ( wev->event_f & Ph_WM_CLOSE ) {
		pid_t child;
		if	(	xflags & XF_EXIT
			||	devopt == 'R'
			&&	devfd == NULL
			&&	(	( child = getres( ABW_tty, Pt_ARG_TTY_PID ) ) == 0
				||	!( xflags & XF_CMD )
				&&	! has_children( child )
				)	)
			PtDestroyWidget( ABW_base );
		ApCreateModule( ABM_exitdlg, widget, cbinfo );
		}
	return Pt_CONTINUE;
	}

/**** Now system-dependent stuff ****/



#if defined(__QNXNTO__)

static int psopen( pid_t pid, struct process_info *ps ) {
	char path[ 32 ];
	sprintf( path, "/proc/%d", pid );
	if ( ( ps->fd = open( path, O_RDONLY ) ) != -1 ) {
		if ( devctl( ps->fd, DCMD_PROC_INFO, &ps->pi, sizeof(ps->pi), 0 ) ) {
			close( ps->fd );
			ps->fd = -1;
		}	}
	return ps->fd;
	}

static void psclose( struct process_info *ps ) {
	if ( ps->fd != -1 ) {
		close( ps->fd );
		ps->fd = -1;
	}	}

static int has_children( pid_t pid ) {
	int result = 1;
	struct process_info buf;
	if ( psopen( pid, &buf ) != -1 ) {
		if ( buf.pi.child == 0 )
			result = 0;
		psclose( &buf );
		}
	return result;
	}

static int psdup( struct process_info *dst, struct process_info const *src ) {
	int fd;
	if ( ( fd = dup( src->fd ) ) != -1 ) {
		psclose( dst );
		*dst = *src;
		dst->fd = fd;
		}
	return fd;
	}	

#define is_foreground( ps )	( (ps)->pi.num_threads != 0 )
#define is_newer( newer, older )	( (newer)->pi.pid > (older)->pi.pid )

static pid_t find_fg_process( pid_t pid, struct process_info *ps ) {
	ps->pi.pid = 0;
	ps->fd = -1;
	if ( psopen( pid, ps ) != -1 ) {
		int havenew;
		pid = ps->pi.child;
		do	{
			struct process_info pp;
			havenew = 0;
			while ( pid && psopen( pid, &pp ) != -1 ) {
				if	(	is_foreground( &pp )
					&&	(	!havenew
						||	is_newer( &pp, ps )
							)
					&&	psdup( ps, &pp ) != -1
						)
					havenew = 1;
				pid = pp.pi.sibling;
				psclose( &pp );
				}
			}	while ( havenew );
		}
	return ps->pi.pid;
	}

static void getargs( int fd, unsigned long offs, char *cmd, char *args ) {
	enum { N = 32 };
	unsigned long argc, argv[ N ];
	if	(	lseek( fd, offs, SEEK_SET ) != -1
		&&	sizeof(argc) == 4
		&&	read( fd, &argc, sizeof(argc) ) == sizeof(argc)
		&&	( argc <= N ? argc != 0 : ( argc = N, 1 ) )
		&&	read( fd, argv, argc*sizeof(argv[0]) ) == argc*sizeof(argv[0])
			) {
		char *dst = cmd;
		int spcavail = TITLE_LEN, i = 0, r;
		for ( i = 0; i < argc && spcavail > 0; ++i ) {
			if	(	lseek( fd, (long) argv[i], SEEK_SET ) == -1
				||	( r = read( fd, dst, spcavail ) ) < 0
					)
				r = 0;
			else {
				char *end;
				if ( ( end = memchr( dst, '\0', r ) ) != NULL )
					r = end - dst;
				dst += r;
				}
			if ( i == 0 ) {
				*dst = '\0';
				dst = args;
				}
			else
				if	(	( spcavail -= r ) > 0
					&&	i > 1
						) {
					--spcavail;
					*dst++ = ' ';
			}		}
		if ( argc > N && spcavail >= 3 )
			strcpy( dst, "..." );
		else
			*dst = '\0';
	}	}

static void getcommand(
		struct process_info const *ps, char prg[ CMDPATH_LEN+1 ],
		char cmd[ TITLE_LEN+1 ], char args[ TITLE_LEN+1 ]
		) {
	strcpy( prg, "???" );
	if ( cmd ) {
		strcpy( cmd, prg );
		args[0] = '\0';
		}
	if ( ps->fd != -1 ) {
		struct {
			procfs_debuginfo h;
			char tail[ _POSIX_PATH_MAX ];
			}
				mdbg;
		if ( devctl( ps->fd, DCMD_PROC_MAPDEBUG_BASE, &mdbg, sizeof(mdbg), 0 ) == 0 ) {
			strncpy( prg, mdbg.h.path, CMDPATH_LEN );
			prg[ CMDPATH_LEN ] = '\0';
			}
		if ( cmd )
			getargs( ps->fd, ps->pi.initial_stack, cmd, args );
		close( ps->fd );
	}	}

#else

static int has_children( pid_t pid ) {
	/* Assume a local process */
	struct _psinfo psi;
	return
		qnx_psinfo( PROC_PID, pid, &psi, NULL, 0 ) != pid
		||	psi.un.proc.son != 0;
	}

static void getcommand(
		struct process_info const *ps, char prg[ CMDPATH_LEN+1 ],
		char arg0[ TITLE_LEN+1 ], char args[ TITLE_LEN+1 ]
		) {
	/* Get command line of process described in *ps */
	char *dst;
	if ( ps->psi.pid == 0 )
		strcpy( prg, "???" );
	else {
		strncpy( prg, ps->psi.un.proc.name, CMDPATH_LEN );
		prg[ CMDPATH_LEN ] = '\0';
		}
	if ( ( dst = arg0 ) != NULL ) {
		union {
			unsigned long		off32;
			struct {
				unsigned short		off;
				unsigned short		seg;
			}					addr16;
		}
			addr;
		struct {
			struct _proc_spawn head;
			char tail[ 2 * TITLE_LEN + PATH_MAX ];
			}
				sp;
		*args = '\0';
		if	(	ps->psi.pid != 0
			&&	__qnx_debug_xfer( ps->proc, ps->psi.pid, _DEBUG_MEM_RD, &addr, sizeof(addr),
					(unsigned) ps->psi.magic_off
					+	offsetof( struct _magic, sptrs[_SPTRS_CMD] ),
					ps->psi.magic_sel
					)
			!=	-1
			&&	addr.off32 != 0
			&&	__qnx_debug_xfer( ps->proc, ps->psi.pid, _DEBUG_MEM_RD, &sp, sizeof(sp),
					(ps->psi.flags & _PPF_32BIT) ? addr.off32 : addr.addr16.off,
					(ps->psi.flags & _PPF_32BIT) ? ps->psi.magic_sel : addr.addr16.seg
					)
			!=	-1
				) {
			char *arg, *s; int space, index;
			sp.tail[ sizeof(sp.tail) - 1 ] = '\0';
			arg = sp.head.data + strlen(sp.head.data) + 1;
	
			if	(	arg < &sp.tail[ sizeof(sp.tail) - 1 ]
				&&	( s = strrchr( arg, '/' ) ) != NULL
					)
				arg = s + 1;
			index = 0; space = TITLE_LEN;
			for (;;) {
				int len, dl;
				if ( ( len = dl = strlen(arg) ) > space )
					dl = space;
				memcpy( dst, arg, dl );
				dst += dl;
				if ( index == 0 ) {
					*dst = '\0';
					dst = args;
					space = TITLE_LEN;
					}
				else
					if ( ( space -= dl+1 ) <= 0 )
						break;
				if ( --sp.head.argc == 0 )
					break;
				else {
					if ( index )
						*dst++ = ' ';
					arg += len+1;
					}
				++index;
			}	}
		*dst = '\0';
		}
	qnx_vc_detach( ps->proc );
	}

static pid_t makevc( nid_t nid ) {
	return qnx_vc_attach( nid, PROC_PID, 0, _VC_AT_SHARE );
	}

#define BG_IGN ( 1L << SIGINT-1 | 1L << SIGQUIT-1 )

static pid_t find_fg_process( pid_t pid, struct process_info *the_psi ) {
	enum { N = 50 }; /* Max depth of process tree */
	struct {
		pid_t pid;
		pid_t pgrp;
		nid_t nid;
		}
			stack[ N ], *ptr = stack;
	nid_t nid = getnid();
	pid_t proc;

	the_psi->proc = proc = PROC_PID;

	the_psi->psi.pid = 0;
	the_psi->psi.un.proc.start_time = 0;

	stack[0].pid = stack[0].pgrp = pid;
	stack[0].nid = nid;

	do	{
		struct _psinfo psi;
		if ( ptr->nid != nid ) {
			qnx_vc_detach( proc );
			if ( ( proc = makevc( nid = ptr->nid ) ) == -1 )
				break;
			}
		pid = ptr--->pid;
		if ( qnx_psinfo( proc, pid, &psi, 0, 0 ) == pid )
			if ( psi.flags & _PPF_VID ) {
				qnx_vc_detach( proc );
				(++ptr)->pid = psi.un.vproc.remote_pid;
				ptr->nid = psi.un.vproc.remote_nid;
				ptr->pgrp = 0;
				if ( ( proc = makevc( nid ) ) == -1 )
					break;
				}
			else {
			//	printf( "%5d g%-5d s%-2d n%-3d m%X %s\n",
			//		psi.pid, psi.pid_group, psi.sid, psi.sid_nid,
			//		psi.signal_ignore & 0xF, psi.un.proc.name
			//		);
				if ( the_psi->psi.pid == 0 )
					the_psi->psi.pid = -1;
				if	(	(	psi.flags
						&	(	_PPF_MID     | _PPF_VMID   | _PPF_EXECING
							|	_PPF_LOADING | _PPF_TERMING
							)	)
					==	0
					&&	ptr < &stack[N-3]
					&&	(	the_psi->psi.pid == -1
						||	!( psi.flags & _PPF_SLEADER )
						&&	(	ptr[1].pgrp == 0
							||	ptr[1].pgrp == psi.pid_group
						)	)	) {
					if	(	psi.un.proc.start_time >= the_psi->psi.un.proc.start_time
						&&	(	( psi.signal_ignore & BG_IGN ) != BG_IGN
							||	the_psi->psi.pid == -1
							)	) {
						the_psi->proc = makevc( nid ); /* Duplicate the vc */
						the_psi->psi = psi;
						}
					if ( psi.un.proc.brother > 0 ) {
						(++ptr)->pid = psi.un.proc.brother;
						ptr->nid = nid;
						ptr->pgrp = psi.pid_group;
						}
					if ( psi.un.proc.son > 0 ) {
						(++ptr)->pid = psi.un.proc.son;
						ptr->nid = nid;
						ptr->pgrp = psi.pid_group;
						}
					if	(	psi.state == STATE_WAIT_BLOCKED
						&&	psi.blocked_on > 0
						&&	psi.blocked_on != psi.un.proc.son
							) {
						(++ptr)->pid = psi.blocked_on;
						ptr->nid = nid;
						ptr->pgrp = psi.pid_group;
				}	}	}
		}	while ( ptr >= stack );
	qnx_vc_detach( proc );
	return the_psi->psi.pid;
	}

#endif

