newlib/winsup/testsuite/cygload/cygload.cc
Christopher Faylor bd3b6ab4ee * Makefile.in: Test cygload.
* cygload: New directory.
* cygload/README: New file.
* cygload/Makefile: Ditto.
* cygload/cygload.h: Ditto.
* cygload/cygload.cc: Ditto.
* cygload/cygload.exp: Ditto.
2005-06-06 21:13:31 +00:00

619 lines
16 KiB
C++

// cygload.cpp
//
// Copyright 2005, Red Hat, Inc.
//
// Written by Max Kaehn <slothman@electric-cloud.com>
//
// This software is a copyrighted work licensed under the terms of the
// Cygwin license. Please consult the file "CYGWIN_LICENSE" for details.
//
// Note that dynamically linking to cygwin1.dll automatically places your code
// under the GPL unless you purchase a Cygwin Contract with Red Hat, Inc.
// See http://www.redhat.com/software/cygwin/ for more information.
// Options for this program:
// -v Verbose output. Normal operation is entirely silent,
// save for errors.
// -testinterrupts Pauses the program for 30 seconds so you can demonstrate
// that it handles ^C properly.
// -cygwin Name of DLL to load. Defaults to "cygwin1.dll".
#include "cygload.h"
#include <iostream>
#include <sstream>
#include <vector>
#include <errno.h> // for ENOENT
#include <sys/types.h>
#include <sys/stat.h>
using std::cout;
using std::cerr;
using std::endl;
using std::string;
cygwin::padding *cygwin::padding::_main = NULL;
DWORD cygwin::padding::_mainTID = 0;
// A few cygwin constants.
static const int SIGHUP = 1;
static const int SIGINT = 2;
static const int SIGTERM = 15; // Cygwin won't deliver this one to us;
// expect unadorned "kill" to just kill
// your process.
static const int SIGSTOP = 17; // Cygwin insists on delivering SIGSTOP to
// the main thread. If your main thread
// is not interruptible, you'll miss the
// signal and ignore the request to suspend.
static const int SIGTSTP = 18; // ^Z on a tty.
static const int SIGCONT = 19; // Resume a stopped process.
static const int SIGUSR1 = 30;
static const int SIGUSR2 = 31;
// Using *out instead of cout. In verbose mode, out == &cout.
static std::ostream *out = NULL;
cygwin::padding::padding ()
{
_main = this;
_mainTID = GetCurrentThreadId ();
_end = _padding + sizeof (_padding);
char *stackbase;
#ifdef __GNUC__
__asm__ (
"movl %%fs:4, %0"
:"=r"(stackbase)
);
#else
__asm
{
mov eax, fs:[4];
mov stackbase, eax;
}
#endif
_stackbase = stackbase;
// We've gotten as close as we can to the top of the stack. Even
// subverting the entry point, though, still doesn't get us there-- I'm
// getting 64 bytes in use before the entry point. So we back up the data
// there and restore it when the destructor is called:
if ((_stackbase - _end) != 0)
{
size_t delta = (_stackbase - _end);
_backup.resize (delta);
memcpy (&(_backup[0]), _end, delta);
}
}
cygwin::padding::~padding ()
{
_main = NULL;
if (_backup.size ())
{
memcpy (_end, &(_backup[0]), _backup.size ());
}
}
void
cygwin::padding::check ()
{
if (_main == NULL)
throw std::runtime_error ("No padding declared!");
if (_mainTID != GetCurrentThreadId ())
throw std::runtime_error ("You need to initialize cygwin::connector "
"in the same thread in which you declared the "
"padding.");
if (_main->_backup.size ())
*out << "Warning! Stack base is "
<< static_cast<void *>(_main->_stackbase)
<< ". padding ends at " << static_cast<void *>(_main->_end)
<< ". Delta is " << (_main->_stackbase - _main->_end)
<< ". Stack variables could be overwritten!" << endl;
}
cygwin::connector::connector (const char *dll)
{
// This will throw if padding is not in place.
padding::check ();
*out << "Loading " << dll << "..." << endl;
// This should call init.cc:dll_entry() with DLL_PROCESS_ATTACH,
// which calls dll_crt0_0().
if ((_library = LoadLibrary (dll)) == NULL)
throw windows_error ("LoadLibrary", dll);
*out << "Initializing cygwin..." << endl;
// This calls dcrt0.cc:cygwin_dll_init(), which calls dll_crt0_1(),
// which will, among other things:
// * spawn the cygwin signal handling thread from sigproc_init()
// * initialize the thread-local storage for this thread and overwrite
// the first 4K of the stack
void (*cyginit) ();
get_symbol ("cygwin_dll_init", cyginit);
(*cyginit) ();
*out << "Loading symbols..." << endl;
// Pick up the function pointers for the basic infrastructure.
get_symbol ("__errno", _errno);
get_symbol ("strerror", _strerror);
get_symbol ("cygwin_conv_to_full_posix_path", _conv_to_full_posix_path);
get_symbol ("cygwin_conv_to_full_win32_path", _conv_to_full_win32_path);
// Note that you need to be running an interruptible cygwin function if
// you want to receive signals. You can use the standard signal()
// mechanism if you're willing to have your main thread spend all its time
// in interruptible cygwin functions like sleep(). Christopher Faylor
// cautions that this solution "could be slightly racy": if a second
// signal comes in before the first one is done processing, the thread
// won't be back in sigwait() to catch it.
*out << "Spawning signal handling thread..." << endl;
_waiting_for_signals = true;
_signal_thread_done = false;
InitializeCriticalSection (&_thread_mutex);
DWORD tid;
_signal_thread = CreateThread (NULL, // Default security.
32768, // Adjust the stack size as
// appropriate for what your signal
// handler needs in order to run, and
// then add 4K for cygtls.
&signal_thread, // Function to call
this, // Context
0, // Flags
&tid); // Thread ID
if (_signal_thread == NULL)
throw windows_error ("CreateThread", "signal_thread");
}
cygwin::connector::~connector ()
{
try
{
// First, shut down signal handling.
int (*raze) (int);
int (*pthread_join) (void *, void **);
get_symbol ("raise", raze);
get_symbol ("pthread_join", pthread_join);
// Tell the listener to shut down...
_waiting_for_signals = false;
int err = 0;
EnterCriticalSection (&_thread_mutex);
if (!_signal_thread_done)
err = raze (SIGUSR2);
LeaveCriticalSection (&_thread_mutex);
if (err)
cerr << error (this, "raise", "SIGUSR2").what () << endl;
// ...and get the thread to join.
if (!CloseHandle (_signal_thread))
throw windows_error ("CloseHandle", "signal_thread");
// This should call init.cc:dll_entry() with DLL_PROCESS_DETACH.
if (!FreeLibrary (_library))
throw windows_error ("FreeLibrary", "cygwin1.dll");
}
catch (std::exception &x)
{
cerr << x.what () << endl;
}
}
DWORD WINAPI
cygwin::connector::signal_thread (void *param)
{
connector *that = reinterpret_cast < connector * > (param);
try
{
that->await_signal ();
}
catch (std::exception &x)
{
cerr << "signal_thread caught " << x.what () << endl;
return 0;
}
return 0;
}
void
cygwin::connector::await_signal ()
{
// Wait for signals.
unsigned long sigset[32];
int sig;
int (*empty) (void *);
int (*add) (void *, int);
int (*wait) (void *, int *);
get_symbol ("sigemptyset", empty);
get_symbol ("sigaddset", add);
get_symbol ("sigwait", wait);
empty (sigset);
add (sigset, SIGHUP);
add (sigset, SIGINT);
// add (sigset, SIGSTOP);
// add (sigset, SIGTSTP); // I can't get this to suspend properly, so
// I'll leave it up to chance that the main
// thread is interruptible.
add (sigset, SIGUSR1);
add (sigset, SIGUSR2);
while (_waiting_for_signals)
{
int err = wait (sigset, &sig);
if (err)
cerr << error (this, "sigwait").what () << endl;
else
*out << "Received signal " << sig << "." << endl;
switch (sig)
{
case SIGUSR2:
if (!_waiting_for_signals)
{
// SIGUSR2 is how ~connector wakes this thread
goto done;
}
break;
default:
break;
}
handle_signals (sig);
}
done:
EnterCriticalSection (&_thread_mutex);
_signal_thread_done = true;
LeaveCriticalSection (&_thread_mutex);
*out << "await_signal done." << endl;
}
cygwin::connector::signal_handler *
cygwin::connector::set_handler (int signal, signal_handler *handler)
{
signal_handler *retval = _signal_handlers[signal];
if (handler == NULL)
_signal_handlers.erase (signal);
else
_signal_handlers[signal] = handler;
return retval;
}
void
cygwin::connector::handle_signals (int sig)
{
callback_list::iterator h = _signal_handlers.find (sig);
if (h != _signal_handlers.end ())
{
try
{
signal_handler *handler = h->second;
(*handler) (sig);
return;
}
catch (std::exception &x)
{
cerr << "cygwin::connector::handle_signals caught "
<< x.what () << "!" << endl;
return;
}
}
cerr << "No handler for signal " << sig << "!" << endl;
}
int
cygwin::connector::err_no () const
{
int *e = (*_errno) ();
if (e == NULL)
{
return -1;
}
return *e;
}
string
cygwin::connector::str_error (int err_no) const
{
string retval;
const char *s = (*_strerror) (err_no);
if (s != NULL)
{
retval = s;
}
else
{
std::ostringstream o;
o << "Unexpected errno " << err_no;
retval = o.str ();
}
return retval;
}
string
cygwin::connector::unix_path (const string &windows) const
{
char buf[MAX_PATH];
_conv_to_full_posix_path (windows.c_str (), buf);
return string (buf);
}
string
cygwin::connector::win_path (const string &unix) const
{
char buf[MAX_PATH];
_conv_to_full_win32_path (unix.c_str (), buf);
return string (buf);
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
string
cygwin::error::format (cygwin::connector *c,
int err_no, const char *message, const char *detail)
{
std::ostringstream ret;
ret << message;
if (detail)
{
ret << "(" << detail << ")";
}
ret << ": " << c->str_error (err_no);
return ret.str ();
}
string
windows_error::format (DWORD error, const char *message, const char *detail)
{
std::ostringstream ret;
char buf[512];
DWORD bytes;
ret << message;
if (detail)
ret << "(" << detail << ")";
ret << ": ";
bytes = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, 0, error,
MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
buf, sizeof (buf), 0);
if (bytes == 0)
ret << "Unexpected Windows error " << error;
else
{
// Remove trailing whitespace
char *p = buf + bytes - 1;
while (isspace (*p))
*p-- = '\0';
ret << buf << " (" << error << ")";
}
return ret.str ();
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
extern "C" int mainCRTStartup ();
// This just pushes 4K onto the stack, backs up the original stack, and
// jumps into the regular startup code. This avoids having to worry about
// backing up argc and argv.
extern "C" int __stdcall
cygloadCRTStartup ()
{
cygwin::padding padding;
return mainCRTStartup ();
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void
hangup (int sig)
{
cout << "Hangup (" << sig << ")." << endl;
}
static void
interrupt (int sig)
{
cerr << "Interrupt (" << sig << ")!" << endl;
exit (0);
}
static int caught = false;
static void
catch_signal (int)
{
*out << "Signals are working." << endl;
caught = true;
}
int
main (int argc, char *argv[])
{
// If you do not want to use cygloadCRTStartup() as an entry point,
// uncomment this line, but be sure to have *everything* you want
// from the stack below it backed up before you call the
// constructor for cygwin::connector.
//cygwin::padding padding;
std::ostringstream output;
bool verbose = false, testinterrupts = false;
const char *dll = "cygwin1.dll";
out = &output;
for (int i = 1; i < argc; ++i)
{
string arg = string (argv[i]);
if (arg == "-v")
{
verbose = true;
out = &cout;
}
else if (arg == "-testinterrupts")
testinterrupts = true;
else if (arg == "-cygwin")
{
if (i+1 >= argc)
{
cerr << "Need to supply an argument with -cygwin." << endl;
return 255;
}
dll = argv[++i];
}
}
try
{
*out << "Connecting to cygwin..." << endl;
cygwin::connector cygwin (dll);
*out << "Successfully connected." << endl;
string result = cygwin.str_error (ENOENT);
if (result != "No such file or directory")
{
cerr << "strerror(ENOENT) returned \""
<< result
<< "\" instead of \"No such file or directory\"!"
<< endl;
return 1;
}
else if (verbose)
{
*out << "strerror(ENOENT) = " << result << endl;
}
// Path conversion: from cygwin to Windows...
result = cygwin.win_path ("/usr");
struct _stat statbuf;
if (::_stat (result.c_str (), &statbuf) < 0)
{
cerr << "stat(\"" << result << "\") failed!" << endl;
return 2;
}
else if (verbose)
{
*out << "/usr == " << result << endl;
}
// ...and back:
char buf[MAX_PATH], scratch[256];
GetSystemDirectory (buf, sizeof(buf));
int (*cygstat) (const char *, void *);
cygwin.get_symbol ("stat", cygstat);
if (cygstat (buf, scratch) < 0)
{
cerr << "cygwin stat(\"" << buf << "\") failed!" << endl;
return 3;
}
else if (verbose)
{
*out << buf << " == " << cygwin.unix_path(buf) << endl;
}
// Test error handling. This should output
// "open(/potrzebie/furshlugginer): No such file or directory"
{
int (*cygopen) (const char *, int);
cygwin.get_symbol ("open", cygopen);
if (cygopen ("/potrzebie/furshlugginer", 0 /* O_RDONLY */ ) < 0)
{
int err = cygwin.err_no ();
if (err != ENOENT)
{
cerr << "cygwin open(\"/potrzebie/furshlugginer\", "
"O_RDONLY): expected to fail with ENOENT, got "
<< err << "!" << endl;
return 4;
}
if (verbose)
*out << cygwin::error (&cygwin, "open",
"/potrzebie/furshlugginer").what ()
<< endl;
}
else
{
cerr << "/potrzebie/furshlugginer should not exist!"
<< endl;
return 5;
}
}
// And signal handling:
std::pointer_to_unary_function < int , void > h1 (&hangup);
std::pointer_to_unary_function < int , void > h2 (&interrupt);
std::pointer_to_unary_function < int , void > h3 (&catch_signal);
cygwin.set_handler (SIGHUP, &h1);
cygwin.set_handler (SIGINT, &h2);
cygwin.set_handler (SIGUSR1, &h3);
// Make sure the signal handler thread has had time to start...
Sleep (100);
// Send a test signal to set "caught" to true...
int (*raze) (int);
cygwin.get_symbol ("raise", raze);
raze (SIGUSR1);
// And give the thread time to wait for the shutdown signal.
Sleep (100);
if (testinterrupts)
{
// This is a worst case scenario for testing interrupts: the
// main thread is in a long-duration Windows API call. This
// makes the main thread uninterruptible; cygwin will retry
// 20 times, with a low_priority_sleep(0) between each try.
cout << "Sleeping for 30 seconds, waiting for a signal..." << endl;
Sleep (30000);
cout << "Done waiting." << endl;
}
}
catch (std::exception &x)
{
cerr << x.what () << endl;
return 2;
}
if (caught)
return 0;
else
{
cerr << "Never received SIGUSR1." << endl;
return 1;
}
}