diff options
Diffstat (limited to 'ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp')
-rw-r--r-- | ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp | 614 |
1 files changed, 614 insertions, 0 deletions
diff --git a/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp b/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp new file mode 100644 index 00000000000..e8fc7c80550 --- /dev/null +++ b/ACE/bin/LabVIEW_RT/labview_test_controller/labview_test_controller.cpp @@ -0,0 +1,614 @@ +// $Id$ +// +// Defines the entry point for the LabVIEW RT test controller DLL application. +// This DLL is loaded at system boot by LabVIEW RT. The controller waits for +// TCP connections from the ACE+TAO test scripts. The test scripts will direct +// operation of the tests via commands sent over TCP. In order to be ready for +// connections without intervention via VI, the initial load will spawn a +// thread that sets up the listening socket. + +#include "stdafx.h" +#include <errno.h> +#include <fcntl.h> +#include <io.h> +#include <memory.h> +#include <process.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include <Winsock2.h> + +// NULL is the documented way to check DLL handles, and this is plain +// Windows code, not ACE, so we stick to the Microsoft way... +// FUZZ: disable check_for_NULL + +// This is plain Windows code, not ACE. Therefore we disable +// the check for ACE_OS +// FUZZ: disable check_for_lack_ACE_OS + +// TEST_FUNC is the prototype for the called test's main entrypoint. It's +// the normal C main. +typedef int (*TEST_FUNC) (int argc, char *argv[]); + +// Thread entrypoints +static unsigned int __stdcall test_control (void *param); +static unsigned int __stdcall peer_svc (void *peer_p); +static unsigned int __stdcall run_test (void *test_p); + +static const char *format_errmsg (unsigned int errcode, const char *prefix); + +// Logging information +static const char *LogName = "acetao.log"; +static HANDLE logf = INVALID_HANDLE_VALUE; + +BOOL APIENTRY DllMain( HANDLE hModule, + DWORD ul_reason_for_call, + LPVOID lpReserved + ) +{ + if (ul_reason_for_call == DLL_PROCESS_ATTACH) + { + return (0 != _beginthreadex (0, // security + 8 * 1024, // stack size + test_control, // entrypoint + 0, // param + 0, // creation flags + 0)); // ptr to thread id + } + + return TRUE; +} + +class Test +{ +public: + Test () : dll_handle_ (NULL), + thr_handle_ (0), + entry_ (0), + running_ (false), + status_ (-1) + {} + ~Test (); + + HANDLE handle (void) { return this->thr_handle_; } + int run (void); + const char *start (const char *name); + bool status (int *exit_status); + int wait (void); + void kill (void); + + // Clean up remnants of a test run. + void cleanup (void); + +private: + HMODULE dll_handle_; + HANDLE thr_handle_; + TEST_FUNC entry_; + bool running_; + int status_; + enum { CMDLINE_LEN = 1024, ARGV_SIZE = 100 }; + char name_[CMDLINE_LEN]; + char cmdline_[CMDLINE_LEN]; + int argc_; + char *argv_[ARGV_SIZE]; +}; + +class Peer +{ +public: + Peer (SOCKET h) : handle_ (h) {} + + // Run the Peer's session; intended to be called from a new thread devoted + // to this peer's session. + int svc (void); + +private: + Peer () {}; + + // Process command input from socket. + int command (void); + + // Send a reply string to the peer. + int reply (const char *msg); + + SOCKET handle_; + Test test_; +}; + +// Run a peer session; assume there's a thread for each session, so this +// object has all it needs for context located in 'this' object, and it can +// block at any point as long as one remembers that there is one or more +// test threads running and some attention must be paid to the encapsulated +// socket handle over which this object receives control commands from the +// host test driver. +int +Peer::svc (void) +{ + // Read commands until EOF (peer closed) or protocol error + while (0 == this->command ()) + ; + closesocket (this->handle_); + this->handle_ = INVALID_SOCKET; + return 0; +} + +int +Peer::command (void) +{ + // The protocol exchanges with the peer are execpted to be lock-step + // request-reply command lines, so we can make assumptions about a complete + // line being available to make life easier. + const int MAX_RECV = 1024; + char line[MAX_RECV], *p; + p = &line[0]; + int count = 0, len = 0; + while ((count = recv (this->handle_, p, MAX_RECV - len, 0)) > 0) + { + p[count] = '\0'; + len += count; + p += count; + char *nl; + if ((nl = strchr (line, '\n')) == 0) + continue; + + // At this point we have a 0-terminated string with a newline ending + // the command line. Break out and process the command. + break; + } + if (count <= 0) + return -1; // Relay closed/error socket to caller + + char *cmd = strtok (line, "\t \n\r"); + if (cmd == 0) + { + char err[1024]; + sprintf (err, "Can't parse input: %s\n", line); + this->reply (err); + return -1; + } + // Which command is it? These commands are known: + // + // run <test-dll> [args] + // Run test in the named test-dll; respond with "OK" or an error string. + // status + // If test still running return "RUNNING" else return exit status. + // wait + // Wait for test to exit; return "OK" + // kill + // Kill the thread with the most recent test; return "OK". + // snaplog + // Take a snapshot of the current stdout/stderr log to a new file + // name and reset the stdout/stderr log. + if (strcmp ("run", cmd) == 0) + { + char *test = strtok (0, "\t \n\r"); + if (test == 0) + { + this->reply ("Malformed run command\n"); + return -1; + } + // start() pulls apart the rest of the command line... + const char *errmsg = this->test_.start (test); + if (errmsg == 0) + this->reply ("OK\n"); + else + this->reply (errmsg); + } + else if (strcmp ("status", cmd) == 0) + { + int status; + if (this->test_.status (&status)) + { + char retvalmsg[64]; + sprintf (retvalmsg, "%d\n", status); + this->reply (retvalmsg); + } + else + this->reply ("RUNNING\n"); + } + else if (strcmp ("wait", cmd) == 0) + { + int status = this->test_.wait (); + char retvalmsg[64]; + sprintf (retvalmsg, "%d\n", status); + this->reply (retvalmsg); + } + else if (strcmp ("kill", cmd) == 0) + { + // Killing things is bad... say we can't and the host should reboot us. + this->reply ("NO - please reboot me\n"); + } + else if (strcmp ("waitforfile", cmd) == 0) + { + char *name = strtok (0, "\t \n\r"); + if (name == 0) + { + this->reply ("Malformed waitforfile command\n"); + return -1; + } + char *secs_s = strtok (0, "\t \n\r"); + int secs = 0; + if (secs_s == 0 || (secs = atoi (secs_s)) <= 0) + { + this->reply ("Malformed waitforfile command\n"); + return -1; + } + struct _stat info; + const char *msg = 0; + bool found = false; + while (secs > 0) + { + if (_stat (name, &info) == -1) // No file yet + { + if (errno != ENOENT) + { + // Something more serious than no file yet; bail out. + msg = format_errmsg (errno, name); + break; + } + } + else + { + if (info.st_size > 0) + { + found = true; + break; + } + } + // Either no file yet, or it's there but with no content yet. + Sleep (1 * 1000); // arg is in msec + --secs; + } + if (found) + this->reply ("OK\n"); + else if (secs == 0) + this->reply ("TIMEOUT\n"); + else + this->reply (msg); + } + else if (strcmp ("snaplog", cmd) == 0) + { + if (logf == INVALID_HANDLE_VALUE) + { + this->reply ("NONE\n"); + } + else + { + CloseHandle (logf); + if (0 == rename (LogName, "snapshot.txt")) + { + char abspath[1024]; + if (_fullpath (abspath, "snapshot.txt", 1024)) + { + strcat (abspath, "\n"); + this->reply (abspath); + } + else + { + // Last ditch effort to get a name back to the client + this->reply ("\\ni-rt\\system\\snapshot.txt\n"); + } + } + else + { + this->reply ("NONE\n"); + } + // Reset stdout/stderr to a new file + logf = CreateFile (LogName, + FILE_ALL_ACCESS, + FILE_SHARE_READ, + 0, // security + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + 0); + SetStdHandle (STD_OUTPUT_HANDLE, logf); + SetStdHandle (STD_ERROR_HANDLE, logf); + } + } + else + { + this->reply ("Unrecognized command\n"); + return -1; + } + return 0; +} + +int +Peer::reply (const char *msg) +{ + int len = (int)strlen (msg); // size_t -> int + return send (this->handle_, msg, len, 0) > 0 ? 0 : -1; +} + +Test::~Test () +{ + this->cleanup (); +} + +int +Test::run (void) +{ + this->running_ = true; + try + { + this->status_ = (this->entry_) (this->argc_, this->argv_); + } + catch (...) + { + // Try to note this exception then save the log file before bailing out. + DWORD bl; + char msg[256]; + sprintf (msg, "Exception in %s caught by labview_test_controller\n", + this->name_); + WriteFile (logf, msg, (DWORD)strlen(msg), &bl, 0); + FlushFileBuffers (logf); + CloseHandle (logf); + throw; + } + this->running_ = false; + // It's possible to cleanup() here; however, that would introduce a race + // with start() following beginthreadex(). So do all the cleanup on user + // action - either getting status, waiting, killing, or running another + // test. Or, terminating the connection. + return 0; +} + +const char * +Test::start (const char *name) +{ + if (this->running_) + return "Already running\n"; + + const char *msg = 0; + + // Reset test status to not inadvertantly report a previous test. + this->status_ = -1; + this->cleanup (); // Resets cmdline_, argc_, argv_ + + // The command line is part-way through being tokenized by strtok(). It + // left off after the program name. Anything remaining are the command + // line arguments for the program. Pick off whatever is there, copy it + // to the cmdline_ array and fill in argc_/argv_ for the eventual run. + strcpy (this->name_, name); + this->argv_[0] = this->name_; + this->argc_ = 1; + size_t cmdchars = 0; + for (char *token = strtok (0, "\t \n\r"); + token != 0 && (cmdchars + strlen (token) + 1) < CMDLINE_LEN; + token = strtok (0, "\t \n\r")) + { + // We have a new token and it will fit in cmdline_. Copy it to the + // next spot in cmdline_, add it to argv_/argc_ then update cmdchars + // to account for the copied-in token and its nul terminator. + strcpy (&this->cmdline_[cmdchars], token); + this->argv_[this->argc_] = &this->cmdline_[cmdchars]; + ++this->argc_; + cmdchars += (strlen (token) + 1); + } + char libspec[1024]; + sprintf (libspec, "%s.dll", name); + if ((this->dll_handle_ = LoadLibrary (libspec)) == NULL) + return format_errmsg (GetLastError (), libspec); + + this->entry_ = (TEST_FUNC) GetProcAddress (this->dll_handle_, "main"); + if (this->entry_ == NULL) + { + msg = format_errmsg (GetLastError (), "main"); + this->cleanup (); + return msg; + } + else + { + unsigned int thread_id; /* unused */ + uintptr_t h = _beginthreadex (0, // security + 1024 * 1024, // stack size + run_test, // entrypoint + (void *)this, // arglist + 0, // initflag + &thread_id); // thread ID + this->thr_handle_ = (HANDLE) h; + if (h == 0) // Test thread may have access to thr_handle_ + { + msg = format_errmsg (GetLastError (), "spawn"); + this->cleanup (); + return msg; + } + } + return 0; +} + +bool +Test::status (int *exit_status) +{ + if (this->running_) + return false; // still running + + *exit_status = this->status_; + this->cleanup (); + return true; +} + +int +Test::wait (void) +{ + WaitForSingleObject (this->thr_handle_, INFINITE); + if (!this->running_) + this->cleanup (); + return this->status_; +} + +void +Test::kill (void) +{ + TerminateThread (this->thr_handle_, -1); + this->cleanup (); + this->running_ = false; + this->status_ = -1; +} + +// Clean up remnants of a test run. +void +Test::cleanup (void) +{ + if (this->dll_handle_ != NULL) + { + FreeLibrary (this->dll_handle_); + this->dll_handle_ = NULL; + } + if (this->thr_handle_ != 0) + { + CloseHandle (this->thr_handle_); + this->thr_handle_ = 0; + } + this->entry_ = 0; + this->argc_ = 0; + for (int i = 0; i < ARGV_SIZE; ++i) + this->argv_[i] = 0; + memset (this->cmdline_, 0, CMDLINE_LEN); +} + +static unsigned int __stdcall +test_control (void * /* param */) +{ + // cd to ace dir?? (can this be an env variable?) + + // redirect stdout/stderr to a file + logf = CreateFile (LogName, + FILE_ALL_ACCESS, + FILE_SHARE_READ, + 0, // security + OPEN_ALWAYS, // Don't crush a previous one + FILE_ATTRIBUTE_NORMAL, + 0); + if (logf == INVALID_HANDLE_VALUE) + perror (LogName); + else + { + SetFilePointer (logf, 0, 0, FILE_END); // Append new content + SetStdHandle (STD_OUTPUT_HANDLE, logf); + SetStdHandle (STD_ERROR_HANDLE, logf); + } + + WORD want; + WSADATA offer; + want = MAKEWORD (2, 2); + if (0 != WSAStartup (want, &offer)) + { + perror ("WSAStartup"); + CloseHandle (logf); + return WSAGetLastError (); + } + + // listen on port 8888 (can I set an env variable for this?) + SOCKET acceptor = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); + sockaddr_in listen_addr; + memset (&listen_addr, 0, sizeof (listen_addr)); + listen_addr.sin_family = AF_INET; + listen_addr.sin_addr.s_addr = INADDR_ANY; + listen_addr.sin_port = htons (8888); + if (SOCKET_ERROR == bind (acceptor, + (struct sockaddr *)&listen_addr, + sizeof (listen_addr))) + { + perror ("bind"); + } + else + { + listen (acceptor, 10); + SOCKET peer; + while ((peer = accept (acceptor, 0, 0)) != INVALID_SOCKET) + { + Peer *p = new Peer (peer); + if (p == 0) + { + perror ("Out of memory"); + closesocket (peer); + peer = INVALID_SOCKET; + continue; + } + if (0 == _beginthreadex (0, // security + 64 * 1024, // stack size + peer_svc, // entrypoint + (void *)p, // param + 0, // creation flags + 0)) // ptr to thread id + { + perror ("beginthreadex peer"); + closesocket (peer); + delete p; + } + p = 0; + peer = INVALID_SOCKET; + } + perror ("accept"); + } + + closesocket (acceptor); + WSACleanup (); + return 0; +} + +// Entrypoint for thread that's spawned to run a peer's session. Direct +// control to the peer class. +static unsigned int __stdcall +peer_svc (void *peer_p) +{ + Peer *p = (Peer *)peer_p; + DWORD status = p->svc (); + delete p; + return status; +} + +// Entrypoint for the thread spawned to run a test. The thread arg is the +// Test * - call back to the test's run() method; return the test exit code +// as the thread's return value. +static unsigned int __stdcall +run_test (void *test_p) +{ + Test *t = (Test *)test_p; + return t->run (); +} + +// Format a Windows system or Winsock error message given an error code. +static const char * +format_errmsg (unsigned int errcode, const char *prefix) +{ + static const size_t errmsgsize = 1024; + static char errmsg[errmsgsize]; + + sprintf (errmsg, "%s: ", prefix); + size_t len = strlen (errmsg); + char *next = &errmsg[len]; + size_t max_fmt = errmsgsize - len; + if (0 != FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + 0, + errcode, + 0, // Use default language + next, + (DWORD)max_fmt, + 0)) + { + strcat (errmsg, "\n"); + return errmsg; + } + + errno = errcode; + char *msg = _strerror (prefix); + sprintf (errmsg, "err %d: %s", errcode, msg); + return errmsg; +} + +#ifdef TEST_RUNNER_EXPORTS +#define TEST_RUNNER_API __declspec(dllexport) +#else +#define TEST_RUNNER_API __declspec(dllimport) +#endif + +__declspec(dllexport) int test_entry(void) +{ + return 0; +} |