This is the mail archive of the cygwin mailing list for the Cygwin project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Solution For Handling Signals In Non-Cygwin Apps With SetConsoleCtrlHandler


At work, we use the Cygwin distribution primarily for the SSH server it provides.  Almost everyone writes non-cygwin applications (not linked with Cygwin dll), which we spawn remotely through the Cygwin SSH server and bash.  Some of these Windows programs need to clean up things when they are killed, so we use the Windows API function SetConsoleCtrlHandler() to register a callback function that is called when CTRL-C or CTRL-BREAK is hit.

Unfortunately, because of how signals are propagated under Cygwin, these Windows programs are not notified of the CTRL event and are just terminated instead by bash.  This was not acceptable in our environment, so I created a wrapper Cygwin program that will spawn a non-cygwin app, and then notify this child process of a CTRL-BREAK event.  Now our Windows programs can successfully clean up in the callback function that they registered with SetConsoleCtrlHandler().

The code for the Cygwin wrapper program that I wrote, which I have called "ctrlstart", follows below.  Perhaps this functionality could be incorporated into the "cygstart" utility.  Disclaimer:  The code is only as robust as I needed it to be.

Hoping someone else will benefit from this,
Anthony DeRosa


#include <windows.h>
#include <stdio.h>
#include <signal.h>

/* global process win pid, needed by signal handler */
int pid = -1;

static void handle_sig(int);
static int start_proc(char* command);
static void* wait_proc(void* nuthin);

int main(int argc, char** argv)
{
	/* target command along with its arguments */
	char* command = NULL;

	/* register our signal handler */
	signal(SIGINT, handle_sig);

	/* skip over program name */
	argv++;

	/* Retrieve target command and arguments */
	if (argv && *argv) {
        	if ((command = (char *) malloc(strlen(*argv)+1)) == NULL) {
			fprintf(stderr, "%s: memory allocation error\n", argv[0]);
	            	exit(1);
        	}
        
		strcpy(command, *argv);
	        while (argv++ && *argv) {
        	    if ((command = (char *) realloc(command, strlen(command)+strlen(*argv)+2)) == NULL) {
                	fprintf(stderr, "%s: memory allocation error\n", argv[0]);
	   		exit(1);
        	    }

        		strcat(command, " ");
		        strcat(command, *argv);
        	}
    	}
	
	/* check for proper usage */
	if (!command) {
		fprintf(stderr, "usage: ctrlstart <command> [arg1] [arg2] ...\n");
		exit(1);
	}
		
	/* start the target program */
	if ( (pid=start_proc(command)) > 0 )
	{
		pthread_t thread;
		int tret;

		/* start the thread that waits for the spawned process to exit */
		if( !pthread_create(&thread, NULL, wait_proc, NULL) ) {
			/* wait for a signal to occur or 
			   for the spawned process to exit */
			pthread_join(thread, NULL);
		}
		else {
			fprintf(stderr, "Could not start thread\n");
			exit(1);
		}
	}
	
	return 0;
}

/* Start a process given the path to the executable and argumetns 
   Returns the process id, or -1 if it couldn't start the process */
static int start_proc(char* command)
{
	int ret;

	PROCESS_INFORMATION pi;
	STARTUPINFO si;
	ZeroMemory( &si, sizeof(si) );
	si.cb = sizeof(STARTUPINFO);
	ZeroMemory( &pi, sizeof(pi) );
	pi.dwProcessId = -1;

	/* try to create the process */
	if ( !CreateProcess( NULL, // no module name, use command line
			TEXT(command),
			NULL,
			NULL,
			TRUE, 

			/* needed for the later call to 
			   GenerateConsoleCtrlEvent() */
			CREATE_NEW_PROCESS_GROUP,
			NULL,
			NULL,
			&si,
			&pi )
	)
	{
		LPVOID msg;
		FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | 
				FORMAT_MESSAGE_FROM_SYSTEM,
				NULL,
				GetLastError(),
				MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
				(LPTSTR) &msg,
				0, NULL);

		fprintf(stderr, "CreateProcess failed: %s", msg);
		LocalFree(msg);
	}

	return pi.dwProcessId;
}

/* Wait indefinitely for the process to exit
   This had to be a separate thread, because a call to WaitForSingleObject()
   will not respond to signals */
static void* wait_proc(void* nuthin)
{
	HANDLE h = OpenProcess(SYNCHRONIZE, FALSE, pid);
	WaitForSingleObject(h, INFINITE);
}

/* Signal handler, send CTRL_BREAK to spawned process, because CTRL_C_EVENT
   will be disabled in our spawned process -- see MS documentation) */
static void handle_sig(int sig)
{
	GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pid);
	exit(1);
}


--
Unsubscribe info:      http://cygwin.com/ml/#unsubscribe-simple
Problem reports:       http://cygwin.com/problems.html
Documentation:         http://cygwin.com/docs.html
FAQ:                   http://cygwin.com/faq/


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]