
/*
 * CINELERRA
 * Copyright (C) 1997-2014 Adam Williams <broadcast at earthling dot net>
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include "bcsignals.h"
#include "bcwindowbase.h"
#include "bccmodels.h"
#include "bckeyboard.h"
#include "bcresources.h"
#include "cstrdup.h"
#include "filesystem.h"

#include <ctype.h>
#include <dirent.h>
#ifndef NO_BTRACE
#include <execinfo.h>
#endif
#include <fcntl.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#ifndef NO_PRCTL
#include <sys/prctl.h>
#endif
#include <sys/types.h>

BC_Signals* BC_Signals::global_signals = 0;
static int signal_done = 0;

static struct sigaction old_segv = {0, }, old_intr = {0, };
static void handle_dump(int n, siginfo_t * info, void *sc);

const char *BC_Signals::trap_path = 0;
void *BC_Signals::trap_data = 0;
void (*BC_Signals::trap_hook)(FILE *fp, void *data) = 0;
bool BC_Signals::trap_sigsegv = false;
bool BC_Signals::trap_sigintr = false;

static void uncatch_sig(int sig, struct sigaction &old)
{
	struct sigaction act;
	sigaction(sig, &old, &act);
	old.sa_handler = 0;
}

static void catch_sig(int sig, struct sigaction &old)
{
	struct sigaction act;
	memset(&act, 0, sizeof(act));
	act.sa_sigaction = handle_dump;
	act.sa_flags = SA_SIGINFO;
	sigaction(sig, &act, (!old.sa_handler ? &old : 0));
}

static void uncatch_intr() { uncatch_sig(SIGINT, old_intr); }
static void catch_intr() { catch_sig(SIGINT, old_intr); }
static void uncatch_segv() { uncatch_sig(SIGSEGV, old_segv); }
static void catch_segv() { catch_sig(SIGSEGV, old_segv); }

void BC_Signals::set_trap_path(const char *path)
{
	trap_path = path;
}

void BC_Signals::set_trap_hook(void (*hook)(FILE *fp, void *vp), void *data)
{
	trap_data = data;
	trap_hook = hook;
}

void BC_Signals::set_catch_segv(bool v) {
	if( v == trap_sigsegv ) return;
	if( v ) catch_segv();
	else uncatch_segv();
	trap_sigsegv = v;
}

void BC_Signals::set_catch_intr(bool v) {
	if( v == trap_sigintr ) return;
	if( v ) catch_intr();
	else uncatch_intr();
	trap_sigintr = v;
}

static void bc_copy_textfile(int lines, FILE *ofp, const char *fmt,...)
{
	va_list ap;    va_start(ap, fmt);
	char bfr[BCTEXTLEN];  vsnprintf(bfr, sizeof(bfr), fmt, ap);
	va_end(ap);
	FILE *ifp = fopen(bfr,"r");
	if( !ifp ) return;
	while( --lines >= 0 && fgets(bfr,sizeof(bfr),ifp) ) fputs(bfr,ofp);
	fclose(ifp);
}

static void bc_list_openfiles(int lines, FILE *ofp, const char *fmt,...)
{
	va_list ap;    va_start(ap, fmt);
	char bfr[BCTEXTLEN];  vsnprintf(bfr, sizeof(bfr), fmt, ap);
	va_end(ap);
	DIR *dir  = opendir(bfr);
	if( !dir ) return;
	struct dirent64 *dent;
	while( --lines >= 0 && (dent = readdir64(dir)) ) {
		const char *fn = dent->d_name;
		fprintf(ofp, "%s", fn);
		char path[BCTEXTLEN], link[BCTEXTLEN];
		struct stat st;
		snprintf(path, sizeof(path), "%s/%s", bfr, fn);
		if( !stat(path,&st) ) {
			int typ = 0;
			if( S_ISREG(st.st_mode) )       typ = ' ';
			else if( S_ISDIR(st.st_mode) )  typ = 'd';
			else if( S_ISBLK(st.st_mode) )  typ = 'b';
			else if( S_ISCHR(st.st_mode) )  typ = 'c';
			else if( S_ISFIFO(st.st_mode) ) typ = 'f';
			else if( S_ISLNK(st.st_mode) )  typ = 'l';
			else if( S_ISSOCK(st.st_mode) ) typ = 's';
			if( typ ) fprintf(ofp, "\t%c", typ);
			fprintf(ofp, "\tsize %jd", st.st_size);
			int len = readlink(path, link, sizeof(link)-1);
			if( len > 0 ) {
				link[len] = 0;
				fprintf(ofp, "\t-> %s", link);
			}
		}
		snprintf(path, sizeof(path), "%sinfo/%s", bfr, fn);
		FILE *fp = fopen(path,"r");  int64_t pos;
		if( fp ) {
			while( fgets(link, sizeof(link), fp) ) {
				if( sscanf(link, "pos:%jd", &pos) == 1 ) {
					fprintf(ofp, "\tpos: %jd", pos);
					break;
				}
			}
			fclose(fp);
		}
		fprintf(ofp, "\n");
	}
	closedir(dir);
}

// Can't use Mutex because it would be recursive
static pthread_mutex_t *handler_lock = 0;


static const char* signal_titles[] =
{
	"NULL",
	"SIGHUP",
	"SIGINT",
	"SIGQUIT",
	"SIGILL",
	"SIGTRAP",
	"SIGABRT",
	"SIGBUS",
	"SIGFPE",
	"SIGKILL",
	"SIGUSR1",
	"SIGSEGV",
	"SIGUSR2",
	"SIGPIPE",
	"SIGALRM",
	"SIGTERM"
};

void BC_Signals::dump_stack(FILE *fp)
{
#ifndef NO_BTRACE
	void *buffer[256];
	int total = backtrace (buffer, 256);
	char **result = backtrace_symbols (buffer, total);
	fprintf(fp, "BC_Signals::dump_stack\n");
	for(int i = 0; i < total; i++)
	{
		fprintf(fp, "%s\n", result[i]);
	}
#endif
}

// Kill subprocesses
void BC_Signals::kill_subs()
{
// List /proc directory
	struct dirent64 *new_filename;
	struct stat ostat;
	char path[BCTEXTLEN], string[BCTEXTLEN];
	DIR *dirstream = opendir("/proc");
	if( !dirstream ) return;
	pid_t ppid = getpid();

	while( (new_filename = readdir64(dirstream)) != 0 ) {
		char *ptr = new_filename->d_name;
		while( *ptr && *ptr != '.' && !isalpha(*ptr) ) ++ptr;
// All digits are numbers
		if( *ptr ) continue;

// Must be a directory
		sprintf(path, "/proc/%s", new_filename->d_name);
		if( stat(path, &ostat) ) continue;
		if( !S_ISDIR(ostat.st_mode) ) continue;
		strcat(path, "/stat");
		FILE *fd = fopen(path, "r");
		if( !fd ) continue;
		while( !feof(fd) && fgetc(fd)!=')' );
//printf("kill_subs %d %d\n", __LINE__, c);
		for( int sp=2; !feof(fd) && sp>0; )
			if( fgetc(fd) == ' ' ) --sp;
// Read in parent process
		for( ptr=string; !feof(fd) && (*ptr=fgetc(fd))!=' '; ++ptr );
		if( (*ptr=fgetc(fd)) == ' ' ) break;
		*ptr = 0;

// printf("kill_subs %d process=%d getpid=%d parent_process=%d\n",
// __LINE__, atoi(new_filename->d_name), getpid(), atoi(string));
		int parent_process = atoi(string);
// Kill if we're the parent
		if( ppid == parent_process ) {
			int child_process = atoi(new_filename->d_name);
//printf("kill_subs %d: process=%d\n", __LINE__, atoi(new_filename->d_name));
			kill(child_process, SIGKILL);
		}
		fclose(fd);
	}
}

static void signal_entry(int signum)
{
	signal(signum, SIG_DFL);

	pthread_mutex_lock(handler_lock);
	int done = signal_done;
	signal_done = 1;
	pthread_mutex_unlock(handler_lock);
	if( done ) exit(0);

	printf("signal_entry: got %s my pid=%d execution table size=%d:\n",
		signal_titles[signum], getpid(), execution_table.size);

	BC_Signals::kill_subs();
	BC_Trace::dump_traces();
	BC_Trace::dump_locks();
	BC_Trace::dump_buffers();
	BC_Trace::delete_temps();

// Call user defined signal handler
	BC_Signals::global_signals->signal_handler(signum);

	abort();
}

static void signal_entry_recoverable(int signum)
{
	printf("signal_entry_recoverable: got %s my pid=%d\n",
		signal_titles[signum],
		getpid());
}

#ifndef NO_PRCTL
// used to terminate child processes when program terminates
static void handle_exit(int signum)
{
//printf("child %d exit\n", getpid());
	exit(0);
}
#endif

void BC_Signals::set_sighup_exit(int enable)
{
#ifndef NO_PRCTL
	if( enable ) {
// causes SIGHUP to be generated when parent dies
		signal(SIGHUP, handle_exit);
		prctl(PR_SET_PDEATHSIG, SIGHUP, 0,0,0);
// prevents ^C from signalling child when attached to gdb
		setpgid(0, 0);
		if( isatty(0) ) ioctl(0, TIOCNOTTY, 0);
	}
	else {
		signal(SIGHUP, signal_entry);
		prctl(PR_SET_PDEATHSIG, 0,0,0,0);
	}
#endif
}

BC_Signals::BC_Signals()
{
}
BC_Signals::~BC_Signals()
{
  BC_CModels::bcxfer_stop_slicers();
}


int dump_xerror = -1;

int BC_Signals::x_error_handler(Display *display, XErrorEvent *event)
{
	char string[1024];
	XGetErrorText(event->display, event->error_code, string, 1024);
	fprintf(stderr, "BC_Signals::x_error_handler: error_code=%d opcode=%d,%d id=0x%jx %s\n",
		event->error_code, event->request_code, event->minor_code,
		(int64_t)event->resourceid, string);
	if( dump_xerror < 0 ) {
		char *env = getenv("BC_TRACE_XERROR");
		dump_xerror = !env || !atoi(env) ? 0 : 1;
	}
	if( dump_xerror )
		dump_stack();
	return 0;
}


void BC_Signals::initialize(const char *trap_path)
{
	BC_Signals::global_signals = this;
	BC_Trace::global_trace = this;
	set_trap_path(trap_path);
	handler_lock = (pthread_mutex_t*)calloc(1, sizeof(pthread_mutex_t));
	pthread_mutex_init(handler_lock, 0);
	old_err_handler = XSetErrorHandler(x_error_handler);
	initialize2();
}

void BC_Signals::terminate()
{
	BC_Signals::global_signals = 0;
	BC_Trace::global_trace = 0;
	uncatch_segv();  uncatch_intr();
	signal(SIGHUP, SIG_DFL);
	signal(SIGINT, SIG_DFL);
	signal(SIGQUIT, SIG_DFL);
	signal(SIGTERM, SIG_DFL);
	signal(SIGFPE, SIG_DFL);
	signal(SIGPIPE, SIG_DFL);
	signal(SIGUSR2, SIG_DFL);
	XSetErrorHandler(old_err_handler);
}

// kill SIGUSR2
void BC_Signals::signal_dump(int signum)
{
	BC_KeyboardHandler::kill_grabs();
	dump();
	signal(SIGUSR2, signal_dump);
}






void BC_Signals::initialize2()
{
	signal(SIGHUP, signal_entry);
	signal(SIGINT, signal_entry);
	signal(SIGQUIT, signal_entry);
	// SIGKILL cannot be stopped
	// signal(SIGKILL, signal_entry);
	catch_segv();
	signal(SIGTERM, signal_entry);
	signal(SIGFPE, signal_entry);
	signal(SIGPIPE, signal_entry_recoverable);
	signal(SIGUSR2, signal_dump);
}


void BC_Signals::signal_handler(int signum)
{
printf("BC_Signals::signal_handler\n");
//	exit(0);
}

const char* BC_Signals::sig_to_str(int number)
{
	return signal_titles[number];
}


#ifndef NO_CTX
#include <ucontext.h>
#include <sys/wait.h>
#include "thread.h"

#if __i386__
#define IP eip
#define sigregs_t sigcontext

static void reg_dump(FILE *fp,sigregs_t *rp)
{
	fprintf(fp,"REGS:\n");
	fprintf(fp,"  gs: %04x:%04x\n", rp->gs,rp->__gsh);
	fprintf(fp,"  fs: %04x:%04x\n", rp->fs,rp->__fsh);
	fprintf(fp,"  es: %04x:%04x\n", rp->es,rp->__esh);
	fprintf(fp,"  ds: %04x:%04x\n", rp->ds,rp->__dsh);
	fprintf(fp," edi: %14p %d\n", (void*)rp->edi,rp->edi);
	fprintf(fp," esi: %14p %d\n", (void*)rp->esi,rp->esi);
	fprintf(fp," ebp: %14p %d\n", (void*)rp->ebp,rp->ebp);
	fprintf(fp," esp: %14p %d\n", (void*)rp->esp,rp->esp);
	fprintf(fp," ebx: %14p %d\n", (void*)rp->ebx,rp->ebx);
	fprintf(fp," edx: %14p %d\n", (void*)rp->edx,rp->edx);
	fprintf(fp," ecx: %14p %d\n", (void*)rp->ecx,rp->ecx);
	fprintf(fp," eax: %14p %d\n", (void*)rp->eax,rp->eax);
	fprintf(fp," trapno: %14p %d\n", (void*)rp->trapno,rp->trapno);
	fprintf(fp," err: %14p %d\n", (void*)rp->err,rp->err);
	fprintf(fp," eip: %14p %d\n", (void*)rp->eip,rp->eip);
	fprintf(fp," cs: %04xd : %04x\n", rp->cs,rp->__csh);
	fprintf(fp," eflags: %14p %d\n", (void*)rp->eflags,rp->eflags);
	fprintf(fp," esp_at_signal: %p %d\n", (void*)rp->esp_at_signal,rp->esp_at_signal);
	fprintf(fp," ss: %04xd : %04x\n", rp->ss,rp->__ssh);
	fprintf(fp," oldmask: %14p %d\n", (void*)rp->oldmask,rp->oldmask);
	fprintf(fp," cr2: %14p %d\n", (void*)rp->cr2,rp->cr2);
	fprintf(fp,"\n");
}
#endif

#if __x86_64__
#define IP rip
#define sigregs_t sigcontext

static void reg_dump(FILE *fp,sigregs_t *rp)
{
	fprintf(fp,"REGS:\n");
	fprintf(fp,"  r8: %20p %jd\n", (void*)rp->r8,rp->r8);
	fprintf(fp,"  r9: %20p %jd\n", (void*)rp->r9,rp->r9);
	fprintf(fp," r10: %20p %jd\n", (void*)rp->r10,rp->r10);
	fprintf(fp," r11: %20p %jd\n", (void*)rp->r11,rp->r11);
	fprintf(fp," r12: %20p %jd\n", (void*)rp->r12,rp->r12);
	fprintf(fp," r13: %20p %jd\n", (void*)rp->r13,rp->r13);
	fprintf(fp," r14: %20p %jd\n", (void*)rp->r14,rp->r14);
	fprintf(fp," r15: %20p %jd\n", (void*)rp->r15,rp->r15);
	fprintf(fp," rdi: %20p %jd\n", (void*)rp->rdi,rp->rdi);
	fprintf(fp," rsi: %20p %jd\n", (void*)rp->rsi,rp->rsi);
	fprintf(fp," rbp: %20p %jd\n", (void*)rp->rbp,rp->rbp);
	fprintf(fp," rbx: %20p %jd\n", (void*)rp->rbx,rp->rbx);
	fprintf(fp," rdx: %20p %jd\n", (void*)rp->rdx,rp->rdx);
	fprintf(fp," rax: %20p %jd\n", (void*)rp->rax,rp->rax);
	fprintf(fp," rcx: %20p %jd\n", (void*)rp->rcx,rp->rcx);
	fprintf(fp," rsp: %20p %jd\n", (void*)rp->rsp,rp->rsp);
	fprintf(fp," rip: %20p %jd\n", (void*)rp->rip,rp->rip);
	fprintf(fp," eflags: %14p %jd\n", (void*)rp->eflags,rp->eflags);
	fprintf(fp,"  cs: %04x\n", rp->cs);
	fprintf(fp,"  gs: %04x\n", rp->gs);
	fprintf(fp,"  fs: %04x\n", rp->fs);
	fprintf(fp," err: %20p %jd\n", (void*)rp->err,rp->err);
	fprintf(fp," trapno: %20p %jd\n", (void*)rp->trapno,rp->trapno);
	fprintf(fp," oldmask: %20p %jd\n", (void*)rp->oldmask,rp->oldmask);
	fprintf(fp," cr2: %20p %jd\n", (void*)rp->cr2,rp->cr2);
	fprintf(fp,"\n");
}

#endif

#if __powerpc__ || __powerpc64__ || __powerpc64le__
#include <asm/ptrace.h>
#define IP nip
#define sigregs_t pt_regs
static void reg_dump(FILE *fp,sigregs_t *rp) {}
#endif

#ifndef IP
#error gotta have IP
#endif

// HAVE_CTX
#endif

static void handle_dump(int n, siginfo_t * info, void *sc)
{
	uncatch_segv();  uncatch_intr();
	signal(SIGSEGV, SIG_DFL);
	signal(SIGINT, SIG_DFL);
	// gotta be root, or the dump is worthless
	int uid = getuid();
// it is not necessary to be root if ptrace is allowed via:
// echo 0 > /proc/sys/kernel/yama/ptrace_scope (usually set to 1)
//	if( uid != 0 ) return;
	int pid = getpid(), tid = gettid();
	void *ip = 0;
#ifndef NO_CTX
	ucontext_t *uc = (ucontext_t *)sc;
	struct sigregs_t *c = (struct sigregs_t *)&uc->uc_mcontext;
	ip = (void *)c->IP;
#endif
	fprintf(stderr,"** %s at %p in pid %d, tid %d\n",
		n==SIGSEGV? "segv" : n==SIGINT? "intr" : "trap",
		ip, pid, tid);
	FILE *fp = 0;
	char fn[PATH_MAX];
	if( BC_Signals::trap_path ) {
		snprintf(fn, sizeof(fn), BC_Signals::trap_path, pid);
		fp = fopen(fn,"w");
	}
	if( fp ) {
		fprintf(stderr,"writing debug data to %s\n", fn);
		fprintf(fp,"** %s at %p in pid %d, tid %d\n",
			n==SIGSEGV? "segv" : n==SIGINT? "intr" : "trap",
			ip, pid, tid);
	}
	else {
		strcpy(fn, "stdout");
		fp = stdout;
	}
	time_t t;  time(&t);
	fprintf(fp,"created on %s", ctime(&t));
	struct passwd *pw = getpwuid(uid);
	if( pw ) {
		fprintf(fp,"        by %d:%d %s(%s)\n",
			pw->pw_uid, pw->pw_gid, pw->pw_name, pw->pw_gecos);
	}
	fprintf(fp,"\nOS:\n");  bc_copy_textfile(16, fp,"/etc/os-release");
	fprintf(fp,"\nCPUS: %d\n",   BC_Resources::get_machine_cpus());
	fprintf(fp,"\nCPUINFO:\n");  bc_copy_textfile(32, fp,"/proc/cpuinfo");
	fprintf(fp,"\nTHREADS:\n");  BC_Trace::dump_threads(fp);
	fprintf(fp,"\nTRACES:\n");   BC_Trace::dump_traces(fp);
	fprintf(fp,"\nLOCKS:\n");    BC_Trace::dump_locks(fp);
	fprintf(fp,"\nBUFFERS:\n");  BC_Trace::dump_buffers(fp);
	fprintf(fp,"\nSHMMEM:\n");   BC_Trace::dump_shm_stats(fp);
	if( BC_Signals::trap_hook ) {
		fprintf(fp,"\nMAIN HOOK:\n");
		BC_Signals::trap_hook(fp, BC_Signals::trap_data);
	}
	fprintf(fp,"\nVERSION:\n");  bc_copy_textfile(INT_MAX, fp,"/proc/version");
	fprintf(fp,"\nMEMINFO:\n");  bc_copy_textfile(INT_MAX, fp,"/proc/meminfo");
	fprintf(fp,"\nSTATUS:\n");   bc_copy_textfile(INT_MAX, fp,"/proc/%d/status",pid);
	fprintf(fp,"\nFD:\n");	     bc_list_openfiles(INT_MAX, fp,"/proc/%d/fd", pid);
	fprintf(fp,"\nMAPS:\n");     bc_copy_textfile(INT_MAX, fp,"/proc/%d/maps",pid);
#ifndef NO_CTX
	char proc_mem[64];
	if( tid > 0 && tid != pid )
		sprintf(proc_mem,"/proc/%d/task/%d/mem",pid,tid);
	else
		sprintf(proc_mem,"/proc/%d/mem",pid);
	int pfd = open(proc_mem,O_RDONLY);
	if( pfd >= 0 ) {
		fprintf(fp,"\nCODE:\n");
		for( int i=-32; i<32; ) {
			uint8_t v;  void *vp = (void *)((char*)ip + i);
			if( !(i & 7) ) fprintf(fp,"%p:  ", vp);
			if( pread(pfd,&v,sizeof(v),(off_t)vp) != sizeof(v) ) break;
			fprintf(fp,"%c%02x", !i ? '>' : ' ', v);
			if( !(++i & 7) ) fprintf(fp,"\n");
		}
		fprintf(fp,"\n");
		close(pfd);
	}
	else
		fprintf(fp,"err opening: %s, %m\n", proc_mem);
	reg_dump(fp, c);
#endif
	BC_Signals::dump_stack(fp);
	fprintf(fp,"\n\n");
	if( fp != stdout ) fclose(fp);
#ifndef NO_GDB
	char cmd[1024], *cp = cmd;
	cp += sprintf(cp, "exec gdb /proc/%d/exe -p %d --batch --quiet "
		"-ex \"thread apply all info registers\" "
		"-ex \"thread apply all bt full\" "
		"-ex \"quit\"", pid, pid);
	if( fp != stdout )
		cp += sprintf(cp," >> \"%s\"", fn);
	cp += sprintf(cp," 2>&1");
//printf("handle_dump:: pid=%d, cmd='%s'  fn='%s'\n",pid,cmd,fn);
        pid = vfork();
        if( pid < 0 ) {
		fprintf(stderr,"** can't start gdb, dump abondoned\n");
		return;
	}
	if( pid > 0 ) {
		waitpid(pid,0,0);
		fprintf(stderr,"** dump complete\n");
		return;
	}
        char *const argv[4] = { (char*) "/bin/sh", (char*) "-c", cmd, 0 };
        execvp(argv[0], &argv[0]);
#endif
}

