Merge pull request #2309 from mitza-oci/warnings
[ACE_TAO.git] / ACE / bin / LabVIEW_RT / labview_test_controller / labview_test_controller.cpp
blobd6235f2b6b5666cc3f870f471d988e90d72fd7bb
1 //
2 // Defines the entry point for the LabVIEW RT test controller DLL application.
3 // This DLL is loaded at system boot by LabVIEW RT. The controller waits for
4 // TCP connections from the ACE+TAO test scripts. The test scripts will direct
5 // operation of the tests via commands sent over TCP. In order to be ready for
6 // connections without intervention via VI, the initial load will spawn a
7 // thread that sets up the listening socket.
9 #include "stdafx.h"
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <io.h>
13 #include <memory.h>
14 #include <process.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <string.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
21 #include <Winsock2.h>
23 // NULL is the documented way to check DLL handles, and this is plain
24 // Windows code, not ACE, so we stick to the Microsoft way...
25 // FUZZ: disable check_for_NULL
27 // This is plain Windows code, not ACE. Therefore we disable
28 // the check for ACE_OS
29 // FUZZ: disable check_for_lack_ACE_OS
31 // TEST_FUNC is the prototype for the called test's main entrypoint. It's
32 // the normal C main.
33 typedef int (*TEST_FUNC) (int argc, char *argv[]);
35 // Thread entrypoints
36 static unsigned int __stdcall test_control (void *param);
37 static unsigned int __stdcall peer_svc (void *peer_p);
38 static unsigned int __stdcall run_test (void *test_p);
40 static const char *format_errmsg (unsigned int errcode, const char *prefix);
42 // Logging information
43 static const char *LogName = "acetao.log";
44 static HANDLE logf = INVALID_HANDLE_VALUE;
46 BOOL APIENTRY DllMain( HANDLE hModule,
47 DWORD ul_reason_for_call,
48 LPVOID lpReserved
51 if (ul_reason_for_call == DLL_PROCESS_ATTACH)
53 return (0 != _beginthreadex (0, // security
54 8 * 1024, // stack size
55 test_control, // entrypoint
56 0, // param
57 0, // creation flags
58 0)); // ptr to thread id
61 return TRUE;
64 class Test
66 public:
67 Test () : dll_handle_ (NULL),
68 thr_handle_ (0),
69 entry_ (0),
70 running_ (false),
71 status_ (-1)
73 ~Test ();
75 HANDLE handle () { return this->thr_handle_; }
76 int run ();
77 const char *start (const char *name);
78 bool status (int *exit_status);
79 int wait ();
80 void kill ();
82 // Clean up remnants of a test run.
83 void cleanup ();
85 private:
86 HMODULE dll_handle_;
87 HANDLE thr_handle_;
88 TEST_FUNC entry_;
89 bool running_;
90 int status_;
91 enum { CMDLINE_LEN = 1024, ARGV_SIZE = 100 };
92 char name_[CMDLINE_LEN];
93 char cmdline_[CMDLINE_LEN];
94 int argc_;
95 char *argv_[ARGV_SIZE];
98 class Peer
100 public:
101 Peer (SOCKET h) : handle_ (h) {}
103 // Run the Peer's session; intended to be called from a new thread devoted
104 // to this peer's session.
105 int svc ();
107 private:
108 Peer () {};
110 // Process command input from socket.
111 int command ();
113 // Send a reply string to the peer.
114 int reply (const char *msg);
116 SOCKET handle_;
117 Test test_;
120 // Run a peer session; assume there's a thread for each session, so this
121 // object has all it needs for context located in 'this' object, and it can
122 // block at any point as long as one remembers that there is one or more
123 // test threads running and some attention must be paid to the encapsulated
124 // socket handle over which this object receives control commands from the
125 // host test driver.
127 Peer::svc ()
129 // Read commands until EOF (peer closed) or protocol error
130 while (0 == this->command ())
132 closesocket (this->handle_);
133 this->handle_ = INVALID_SOCKET;
134 return 0;
138 Peer::command ()
140 // The protocol exchanges with the peer are execpted to be lock-step
141 // request-reply command lines, so we can make assumptions about a complete
142 // line being available to make life easier.
143 const int MAX_RECV = 1024;
144 char line[MAX_RECV], *p;
145 p = &line[0];
146 int count = 0, len = 0;
147 while ((count = recv (this->handle_, p, MAX_RECV - len, 0)) > 0)
149 p[count] = '\0';
150 len += count;
151 p += count;
152 char *nl;
153 if ((nl = strchr (line, '\n')) == 0)
154 continue;
156 // At this point we have a 0-terminated string with a newline ending
157 // the command line. Break out and process the command.
158 break;
160 if (count <= 0)
161 return -1; // Relay closed/error socket to caller
163 char *cmd = strtok (line, "\t \n\r");
164 if (cmd == 0)
166 char err[1024];
167 sprintf (err, "Can't parse input: %s\n", line);
168 this->reply (err);
169 return -1;
171 // Which command is it? These commands are known:
173 // run <test-dll> [args]
174 // Run test in the named test-dll; respond with "OK" or an error string.
175 // status
176 // If test still running return "RUNNING" else return exit status.
177 // wait
178 // Wait for test to exit; return "OK"
179 // kill
180 // Kill the thread with the most recent test; return "OK".
181 // snaplog
182 // Take a snapshot of the current stdout/stderr log to a new file
183 // name and reset the stdout/stderr log.
184 if (strcmp ("run", cmd) == 0)
186 char *test = strtok (0, "\t \n\r");
187 if (test == 0)
189 this->reply ("Malformed run command\n");
190 return -1;
192 // start() pulls apart the rest of the command line...
193 const char *errmsg = this->test_.start (test);
194 if (errmsg == 0)
195 this->reply ("OK\n");
196 else
197 this->reply (errmsg);
199 else if (strcmp ("status", cmd) == 0)
201 int status;
202 if (this->test_.status (&status))
204 char retvalmsg[64];
205 sprintf (retvalmsg, "%d\n", status);
206 this->reply (retvalmsg);
208 else
209 this->reply ("RUNNING\n");
211 else if (strcmp ("wait", cmd) == 0)
213 int status = this->test_.wait ();
214 char retvalmsg[64];
215 sprintf (retvalmsg, "%d\n", status);
216 this->reply (retvalmsg);
218 else if (strcmp ("kill", cmd) == 0)
220 // Killing things is bad... say we can't and the host should reboot us.
221 this->reply ("NO - please reboot me\n");
223 else if (strcmp ("waitforfile", cmd) == 0)
225 char *name = strtok (0, "\t \n\r");
226 if (name == 0)
228 this->reply ("Malformed waitforfile command\n");
229 return -1;
231 char *secs_s = strtok (0, "\t \n\r");
232 int secs = 0;
233 if (secs_s == 0 || (secs = atoi (secs_s)) <= 0)
235 this->reply ("Malformed waitforfile command\n");
236 return -1;
238 struct _stat info;
239 const char *msg = 0;
240 bool found = false;
241 while (secs > 0)
243 if (_stat (name, &info) == -1) // No file yet
245 if (errno != ENOENT)
247 // Something more serious than no file yet; bail out.
248 msg = format_errmsg (errno, name);
249 break;
252 else
254 if (info.st_size > 0)
256 found = true;
257 break;
260 // Either no file yet, or it's there but with no content yet.
261 Sleep (1 * 1000); // arg is in msec
262 --secs;
264 if (found)
265 this->reply ("OK\n");
266 else if (secs == 0)
267 this->reply ("TIMEOUT\n");
268 else
269 this->reply (msg);
271 else if (strcmp ("snaplog", cmd) == 0)
273 if (logf == INVALID_HANDLE_VALUE)
275 this->reply ("NONE\n");
277 else
279 CloseHandle (logf);
280 if (0 == rename (LogName, "snapshot.txt"))
282 char abspath[1024];
283 if (_fullpath (abspath, "snapshot.txt", 1024))
285 strcat (abspath, "\n");
286 this->reply (abspath);
288 else
290 // Last ditch effort to get a name back to the client
291 this->reply ("\\ni-rt\\system\\snapshot.txt\n");
294 else
296 this->reply ("NONE\n");
298 // Reset stdout/stderr to a new file
299 logf = CreateFile (LogName,
300 FILE_ALL_ACCESS,
301 FILE_SHARE_READ,
302 0, // security
303 CREATE_ALWAYS,
304 FILE_ATTRIBUTE_NORMAL,
306 SetStdHandle (STD_OUTPUT_HANDLE, logf);
307 SetStdHandle (STD_ERROR_HANDLE, logf);
310 else
312 this->reply ("Unrecognized command\n");
313 return -1;
315 return 0;
319 Peer::reply (const char *msg)
321 int len = (int)strlen (msg); // size_t -> int
322 return send (this->handle_, msg, len, 0) > 0 ? 0 : -1;
325 Test::~Test ()
327 this->cleanup ();
331 Test::run ()
333 this->running_ = true;
336 this->status_ = (this->entry_) (this->argc_, this->argv_);
338 catch (...)
340 // Try to note this exception then save the log file before bailing out.
341 DWORD bl;
342 char msg[256];
343 sprintf (msg, "Exception in %s caught by labview_test_controller\n",
344 this->name_);
345 WriteFile (logf, msg, (DWORD)strlen(msg), &bl, 0);
346 FlushFileBuffers (logf);
347 CloseHandle (logf);
348 throw;
350 this->running_ = false;
351 // It's possible to cleanup() here; however, that would introduce a race
352 // with start() following beginthreadex(). So do all the cleanup on user
353 // action - either getting status, waiting, killing, or running another
354 // test. Or, terminating the connection.
355 return 0;
358 const char *
359 Test::start (const char *name)
361 if (this->running_)
362 return "Already running\n";
364 const char *msg = 0;
366 // Reset test status to not inadvertantly report a previous test.
367 this->status_ = -1;
368 this->cleanup (); // Resets cmdline_, argc_, argv_
370 // The command line is part-way through being tokenized by strtok(). It
371 // left off after the program name. Anything remaining are the command
372 // line arguments for the program. Pick off whatever is there, copy it
373 // to the cmdline_ array and fill in argc_/argv_ for the eventual run.
374 strcpy (this->name_, name);
375 this->argv_[0] = this->name_;
376 this->argc_ = 1;
377 size_t cmdchars = 0;
378 for (char *token = strtok (0, "\t \n\r");
379 token != 0 && (cmdchars + strlen (token) + 1) < CMDLINE_LEN;
380 token = strtok (0, "\t \n\r"))
382 // We have a new token and it will fit in cmdline_. Copy it to the
383 // next spot in cmdline_, add it to argv_/argc_ then update cmdchars
384 // to account for the copied-in token and its nul terminator.
385 strcpy (&this->cmdline_[cmdchars], token);
386 this->argv_[this->argc_] = &this->cmdline_[cmdchars];
387 ++this->argc_;
388 cmdchars += (strlen (token) + 1);
390 char libspec[1024];
391 sprintf (libspec, "%s.dll", name);
392 if ((this->dll_handle_ = LoadLibrary (libspec)) == NULL)
393 return format_errmsg (GetLastError (), libspec);
395 this->entry_ = (TEST_FUNC) GetProcAddress (this->dll_handle_, "main");
396 if (this->entry_ == NULL)
398 msg = format_errmsg (GetLastError (), "main");
399 this->cleanup ();
400 return msg;
402 else
404 unsigned int thread_id; /* unused */
405 uintptr_t h = _beginthreadex (0, // security
406 1024 * 1024, // stack size
407 run_test, // entrypoint
408 (void *)this, // arglist
409 0, // initflag
410 &thread_id); // thread ID
411 this->thr_handle_ = (HANDLE) h;
412 if (h == 0) // Test thread may have access to thr_handle_
414 msg = format_errmsg (GetLastError (), "spawn");
415 this->cleanup ();
416 return msg;
419 return 0;
422 bool
423 Test::status (int *exit_status)
425 if (this->running_)
426 return false; // still running
428 *exit_status = this->status_;
429 this->cleanup ();
430 return true;
434 Test::wait ()
436 WaitForSingleObject (this->thr_handle_, INFINITE);
437 if (!this->running_)
438 this->cleanup ();
439 return this->status_;
442 void
443 Test::kill ()
445 TerminateThread (this->thr_handle_, -1);
446 this->cleanup ();
447 this->running_ = false;
448 this->status_ = -1;
451 // Clean up remnants of a test run.
452 void
453 Test::cleanup ()
455 if (this->dll_handle_ != NULL)
457 FreeLibrary (this->dll_handle_);
458 this->dll_handle_ = NULL;
460 if (this->thr_handle_ != 0)
462 CloseHandle (this->thr_handle_);
463 this->thr_handle_ = 0;
465 this->entry_ = 0;
466 this->argc_ = 0;
467 for (int i = 0; i < ARGV_SIZE; ++i)
468 this->argv_[i] = 0;
469 memset (this->cmdline_, 0, CMDLINE_LEN);
472 static unsigned int __stdcall
473 test_control (void * /* param */)
475 // cd to ace dir?? (can this be an env variable?)
477 // redirect stdout/stderr to a file
478 logf = CreateFile (LogName,
479 FILE_ALL_ACCESS,
480 FILE_SHARE_READ,
481 0, // security
482 OPEN_ALWAYS, // Don't crush a previous one
483 FILE_ATTRIBUTE_NORMAL,
485 if (logf == INVALID_HANDLE_VALUE)
486 perror (LogName);
487 else
489 SetFilePointer (logf, 0, 0, FILE_END); // Append new content
490 SetStdHandle (STD_OUTPUT_HANDLE, logf);
491 SetStdHandle (STD_ERROR_HANDLE, logf);
494 WORD want;
495 WSADATA offer;
496 want = MAKEWORD (2, 2);
497 if (0 != WSAStartup (want, &offer))
499 perror ("WSAStartup");
500 CloseHandle (logf);
501 return WSAGetLastError ();
504 // listen on port 8888 (can I set an env variable for this?)
505 SOCKET acceptor = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);
506 sockaddr_in listen_addr;
507 memset (&listen_addr, 0, sizeof (listen_addr));
508 listen_addr.sin_family = AF_INET;
509 listen_addr.sin_addr.s_addr = INADDR_ANY;
510 listen_addr.sin_port = htons (8888);
511 if (SOCKET_ERROR == bind (acceptor,
512 (struct sockaddr *)&listen_addr,
513 sizeof (listen_addr)))
515 perror ("bind");
517 else
519 listen (acceptor, 10);
520 SOCKET peer;
521 while ((peer = accept (acceptor, 0, 0)) != INVALID_SOCKET)
523 Peer *p = new Peer (peer);
524 if (p == 0)
526 perror ("Out of memory");
527 closesocket (peer);
528 peer = INVALID_SOCKET;
529 continue;
531 if (0 == _beginthreadex (0, // security
532 64 * 1024, // stack size
533 peer_svc, // entrypoint
534 (void *)p, // param
535 0, // creation flags
536 0)) // ptr to thread id
538 perror ("beginthreadex peer");
539 closesocket (peer);
540 delete p;
542 p = 0;
543 peer = INVALID_SOCKET;
545 perror ("accept");
548 closesocket (acceptor);
549 WSACleanup ();
550 return 0;
553 // Entrypoint for thread that's spawned to run a peer's session. Direct
554 // control to the peer class.
555 static unsigned int __stdcall
556 peer_svc (void *peer_p)
558 Peer *p = (Peer *)peer_p;
559 DWORD status = p->svc ();
560 delete p;
561 return status;
564 // Entrypoint for the thread spawned to run a test. The thread arg is the
565 // Test * - call back to the test's run() method; return the test exit code
566 // as the thread's return value.
567 static unsigned int __stdcall
568 run_test (void *test_p)
570 Test *t = (Test *)test_p;
571 return t->run ();
574 // Format a Windows system or Winsock error message given an error code.
575 static const char *
576 format_errmsg (unsigned int errcode, const char *prefix)
578 static const size_t errmsgsize = 1024;
579 static char errmsg[errmsgsize];
581 sprintf (errmsg, "%s: ", prefix);
582 size_t len = strlen (errmsg);
583 char *next = &errmsg[len];
584 size_t max_fmt = errmsgsize - len;
585 if (0 != FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM |
586 FORMAT_MESSAGE_IGNORE_INSERTS,
588 errcode,
589 0, // Use default language
590 next,
591 (DWORD)max_fmt,
594 strcat (errmsg, "\n");
595 return errmsg;
598 errno = errcode;
599 char *msg = _strerror (prefix);
600 sprintf (errmsg, "err %d: %s", errcode, msg);
601 return errmsg;
604 #ifdef TEST_RUNNER_EXPORTS
605 #define TEST_RUNNER_API __declspec(dllexport)
606 #else
607 #define TEST_RUNNER_API __declspec(dllimport)
608 #endif
610 __declspec(dllexport) int test_entry()
612 return 0;