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.
18 #include <sys/types.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
33 typedef int (*TEST_FUNC
) (int argc
, char *argv
[]);
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
,
51 if (ul_reason_for_call
== DLL_PROCESS_ATTACH
)
53 return (0 != _beginthreadex (0, // security
54 8 * 1024, // stack size
55 test_control
, // entrypoint
58 0)); // ptr to thread id
67 Test () : dll_handle_ (NULL
),
75 HANDLE
handle () { return this->thr_handle_
; }
77 const char *start (const char *name
);
78 bool status (int *exit_status
);
82 // Clean up remnants of a test run.
91 enum { CMDLINE_LEN
= 1024, ARGV_SIZE
= 100 };
92 char name_
[CMDLINE_LEN
];
93 char cmdline_
[CMDLINE_LEN
];
95 char *argv_
[ARGV_SIZE
];
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.
110 // Process command input from socket.
113 // Send a reply string to the peer.
114 int reply (const char *msg
);
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
129 // Read commands until EOF (peer closed) or protocol error
130 while (0 == this->command ())
132 closesocket (this->handle_
);
133 this->handle_
= INVALID_SOCKET
;
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
;
146 int count
= 0, len
= 0;
147 while ((count
= recv (this->handle_
, p
, MAX_RECV
- len
, 0)) > 0)
153 if ((nl
= strchr (line
, '\n')) == 0)
156 // At this point we have a 0-terminated string with a newline ending
157 // the command line. Break out and process the command.
161 return -1; // Relay closed/error socket to caller
163 char *cmd
= strtok (line
, "\t \n\r");
167 sprintf (err
, "Can't parse input: %s\n", line
);
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.
176 // If test still running return "RUNNING" else return exit status.
178 // Wait for test to exit; return "OK"
180 // Kill the thread with the most recent test; return "OK".
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");
189 this->reply ("Malformed run command\n");
192 // start() pulls apart the rest of the command line...
193 const char *errmsg
= this->test_
.start (test
);
195 this->reply ("OK\n");
197 this->reply (errmsg
);
199 else if (strcmp ("status", cmd
) == 0)
202 if (this->test_
.status (&status
))
205 sprintf (retvalmsg
, "%d\n", status
);
206 this->reply (retvalmsg
);
209 this->reply ("RUNNING\n");
211 else if (strcmp ("wait", cmd
) == 0)
213 int status
= this->test_
.wait ();
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");
228 this->reply ("Malformed waitforfile command\n");
231 char *secs_s
= strtok (0, "\t \n\r");
233 if (secs_s
== 0 || (secs
= atoi (secs_s
)) <= 0)
235 this->reply ("Malformed waitforfile command\n");
243 if (_stat (name
, &info
) == -1) // No file yet
247 // Something more serious than no file yet; bail out.
248 msg
= format_errmsg (errno
, name
);
254 if (info
.st_size
> 0)
260 // Either no file yet, or it's there but with no content yet.
261 Sleep (1 * 1000); // arg is in msec
265 this->reply ("OK\n");
267 this->reply ("TIMEOUT\n");
271 else if (strcmp ("snaplog", cmd
) == 0)
273 if (logf
== INVALID_HANDLE_VALUE
)
275 this->reply ("NONE\n");
280 if (0 == rename (LogName
, "snapshot.txt"))
283 if (_fullpath (abspath
, "snapshot.txt", 1024))
285 strcat (abspath
, "\n");
286 this->reply (abspath
);
290 // Last ditch effort to get a name back to the client
291 this->reply ("\\ni-rt\\system\\snapshot.txt\n");
296 this->reply ("NONE\n");
298 // Reset stdout/stderr to a new file
299 logf
= CreateFile (LogName
,
304 FILE_ATTRIBUTE_NORMAL
,
306 SetStdHandle (STD_OUTPUT_HANDLE
, logf
);
307 SetStdHandle (STD_ERROR_HANDLE
, logf
);
312 this->reply ("Unrecognized command\n");
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;
333 this->running_
= true;
336 this->status_
= (this->entry_
) (this->argc_
, this->argv_
);
340 // Try to note this exception then save the log file before bailing out.
343 sprintf (msg
, "Exception in %s caught by labview_test_controller\n",
345 WriteFile (logf
, msg
, (DWORD
)strlen(msg
), &bl
, 0);
346 FlushFileBuffers (logf
);
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.
359 Test::start (const char *name
)
362 return "Already running\n";
366 // Reset test status to not inadvertantly report a previous test.
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_
;
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
];
388 cmdchars
+= (strlen (token
) + 1);
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");
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
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");
423 Test::status (int *exit_status
)
426 return false; // still running
428 *exit_status
= this->status_
;
436 WaitForSingleObject (this->thr_handle_
, INFINITE
);
439 return this->status_
;
445 TerminateThread (this->thr_handle_
, -1);
447 this->running_
= false;
451 // Clean up remnants of a test run.
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;
467 for (int i
= 0; i
< ARGV_SIZE
; ++i
)
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
,
482 OPEN_ALWAYS
, // Don't crush a previous one
483 FILE_ATTRIBUTE_NORMAL
,
485 if (logf
== INVALID_HANDLE_VALUE
)
489 SetFilePointer (logf
, 0, 0, FILE_END
); // Append new content
490 SetStdHandle (STD_OUTPUT_HANDLE
, logf
);
491 SetStdHandle (STD_ERROR_HANDLE
, logf
);
496 want
= MAKEWORD (2, 2);
497 if (0 != WSAStartup (want
, &offer
))
499 perror ("WSAStartup");
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
)))
519 listen (acceptor
, 10);
521 while ((peer
= accept (acceptor
, 0, 0)) != INVALID_SOCKET
)
523 Peer
*p
= new Peer (peer
);
526 perror ("Out of memory");
528 peer
= INVALID_SOCKET
;
531 if (0 == _beginthreadex (0, // security
532 64 * 1024, // stack size
533 peer_svc
, // entrypoint
536 0)) // ptr to thread id
538 perror ("beginthreadex peer");
543 peer
= INVALID_SOCKET
;
548 closesocket (acceptor
);
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 ();
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
;
574 // Format a Windows system or Winsock error message given an error code.
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
,
589 0, // Use default language
594 strcat (errmsg
, "\n");
599 char *msg
= _strerror (prefix
);
600 sprintf (errmsg
, "err %d: %s", errcode
, msg
);
604 #ifdef TEST_RUNNER_EXPORTS
605 #define TEST_RUNNER_API __declspec(dllexport)
607 #define TEST_RUNNER_API __declspec(dllimport)
610 __declspec(dllexport
) int test_entry()