It can be useful to set up a VM to auto-login on Windows and have TestAgentd start automatically on boot. However in such a configuration it can be hard to detect when a test causes Windows to reboot: the client reconnects automatically just like after a network glitch and the VM desktop looks normal. So with this patch TestAgentd can keep track of its restarts, lets the client query that value, and brings up a dialog if an unexpected restart occurs. The start count can also be overriden to deal with cases where some automated process causes a Windows reboot as part of the VM set up process.
Signed-off-by: Francois Gouget fgouget@codeweavers.com --- testbot/lib/WineTestBot/TestAgent.pm | 20 +++++ testbot/scripts/TestAgent | 15 +++- testbot/src/testagentd/platform.h | 8 ++ testbot/src/testagentd/platform_unix.c | 9 ++ testbot/src/testagentd/platform_windows.c | 31 +++++++ testbot/src/testagentd/testagentd.c | 131 +++++++++++++++++++++++++++++- 6 files changed, 210 insertions(+), 4 deletions(-)
diff --git a/testbot/lib/WineTestBot/TestAgent.pm b/testbot/lib/WineTestBot/TestAgent.pm index 255c1a19b..2f7ec4def 100644 --- a/testbot/lib/WineTestBot/TestAgent.pm +++ b/testbot/lib/WineTestBot/TestAgent.pm @@ -42,6 +42,7 @@ my $RPC_GETPROPERTIES = 8; my $RPC_UPGRADE = 9; my $RPC_RMCHILDPROC = 10; my $RPC_GETCWD = 11; +my $RPC_SETPROPERTY = 12;
my %RpcNames=( $RPC_PING => 'ping', @@ -56,6 +57,7 @@ my %RpcNames=( $RPC_UPGRADE => 'upgrade', $RPC_RMCHILDPROC => 'rmchildproc', $RPC_GETCWD => 'getcwd', + $RPC_SETPROPERTY => 'setproperty', );
my $Debug = 0; @@ -1386,6 +1388,24 @@ sub GetProperties($;$) return $Properties; }
+sub SetProperty($$$) +{ + my ($self, $PropName, $PropValue) = @_; + debug("SetProperty\n"); + + # Send the command + if (!$self->_StartRPC($RPC_SETPROPERTY) or + !$self->_SendListSize('ArgC', 2) or + !$self->_SendString('PropName', $PropName) or + !$self->_SendString('PropValue', $PropValue)) + { + return undef; + } + + # Get the reply + return $self->_RecvList(''); +} + sub Upgrade($$) { my ($self, $Filename) = @_; diff --git a/testbot/scripts/TestAgent b/testbot/scripts/TestAgent index c41055616..25fc0d426 100755 --- a/testbot/scripts/TestAgent +++ b/testbot/scripts/TestAgent @@ -52,7 +52,7 @@ sub error(@) print STDERR "$name0:error: ", @_; }
-my ($Cmd, $Hostname, $LocalFilename, $ServerFilename, $PropName, @Rm); +my ($Cmd, $Hostname, $LocalFilename, $ServerFilename, $PropName, $PropValue, @Rm); my (@Run, $RunIn, $RunOut, $RunErr, $WaitPid); my $SendFlags = 0; my $RunFlags = 0; @@ -229,6 +229,12 @@ while (@ARGV) set_cmd($arg); $PropName = @ARGV ? check_opt_val($arg, $PropName) : '*'; } + elsif ($arg eq "setproperty") + { + set_cmd($arg); + $PropName = check_opt_val($arg, $PropName); + $PropValue = check_opt_val($arg, $PropValue); + } elsif ($arg eq "upgrade") { set_cmd($arg); @@ -319,6 +325,8 @@ if (defined $Usage) print "or $name0 [options] <hostname> wait <pid>\n"; print "or $name0 [options] <hostname> settime\n"; print "or $name0 [options] <hostname> rm <serverfiles>\n"; + print "or $name0 [options] <hostname> getproperty <name>\n"; + print "or $name0 [options] <hostname> setproperty <name> <value>\n"; print "or $name0 [options] <hostname> [getcwd|ping|version]\n"; print "\n"; print "This is a testagentd client. It can be used to send/receive files and to run commands on the server.\n"; @@ -346,6 +354,7 @@ if (defined $Usage) print " getproperty <name> Retrieves and prints the specified server property, for\n"; print " instance its architecture, 'server.arch'. One can print all the\n"; print " properties at once by omitting the name or setting it to '*'.\n"; + print " setproperty <name> <value> Sets the specified property.\n"; print " getcwd Returns the server's current working directory.\n"; print " ping Makes sure the server is still alive.\n"; print " upgrade Replaces the server executable with the specified file and\n"; @@ -480,6 +489,10 @@ elsif ($Cmd eq "getversion") $Result = $TA->GetVersion(); print "Version=$Result\n" if (defined $Result); } +elsif ($Cmd eq "setproperty") +{ + $Result = $TA->SetProperty($PropName, $PropValue); +} elsif ($Cmd eq "ping") { $Result = $TA->Ping(); diff --git a/testbot/src/testagentd/platform.h b/testbot/src/testagentd/platform.h index 84ab4917c..98dfe394c 100644 --- a/testbot/src/testagentd/platform.h +++ b/testbot/src/testagentd/platform.h @@ -103,6 +103,14 @@ int platform_settime(uint64_t epoch, uint32_t leeway); */ int platform_upgrade_script(const char* script, const char* tmpserver, char** argv);
+/* Called when the message has been dismissed by the user. + */ +typedef void (*message_dismissed_func)(void); + +/* Shows the specified message. + */ +void platform_show_message(const char* message, message_dismissed_func func); + /* Returns a string describing the last socket-related error */ int sockeintr(void); const char* sockerror(void); diff --git a/testbot/src/testagentd/platform_unix.c b/testbot/src/testagentd/platform_unix.c index dcd4d69b6..fbaa995b0 100644 --- a/testbot/src/testagentd/platform_unix.c +++ b/testbot/src/testagentd/platform_unix.c @@ -270,6 +270,15 @@ int platform_upgrade_script(const char* script, const char* tmpserver, char** ar return 1; }
+void platform_show_message(const char* message, message_dismissed_func dismissed) +{ + /* Don't bother trying to pop up a GUI. There may not be one anyway. + * Since the user has no way to dismiss the dialog the dismissed function + * is not called. + */ + fprintf(stderr, "%s", message); +} + int sockeintr(void) { return errno == EINTR; diff --git a/testbot/src/testagentd/platform_windows.c b/testbot/src/testagentd/platform_windows.c index 1efac1c77..5c55b2004 100644 --- a/testbot/src/testagentd/platform_windows.c +++ b/testbot/src/testagentd/platform_windows.c @@ -312,6 +312,37 @@ int platform_upgrade_script(const char* script, const char* tmpserver, char** ar return 1; }
+struct msg_thread_t +{ + char* message; + message_dismissed_func dismissed; +}; + +DWORD WINAPI msg_thread(LPVOID parameter) +{ + struct msg_thread_t* data = parameter; + + MessageBoxA(NULL, data->message, "Message", MB_OK); + free(data->message); + if (data->dismissed) + (*data->dismissed)(); + free(data); + return 0; +} + +void platform_show_message(const char* message, message_dismissed_func dismissed) +{ + HANDLE thread; + struct msg_thread_t* data = malloc(sizeof(struct msg_thread_t)); + + fprintf(stderr, message); + + data->message = strdup(message); + data->dismissed = dismissed; + thread = CreateThread(NULL, 0, &msg_thread, data, 0, NULL); + CloseHandle(thread); +} + int sockretry(void) { return (WSAGetLastError() == WSAEINTR); diff --git a/testbot/src/testagentd/testagentd.c b/testbot/src/testagentd/testagentd.c index 5f0f54ca2..79016c067 100644 --- a/testbot/src/testagentd/testagentd.c +++ b/testbot/src/testagentd/testagentd.c @@ -37,9 +37,10 @@ * 1.3: Fix the zero / infinite timeouts in the wait2 RPC. * 1.4: Add the settime RPC. * 1.5: Add support for upgrading the server. - * 1.6: Add support for the rmchildproc and getcwd RPC. + * 1.6: Add the rmchildproc and getcwd RPCs. + * 1.7: Add --show-restarts and the setproperty RPC. */ -#define PROTOCOL_VERSION "testagentd 1.6" +#define PROTOCOL_VERSION "testagentd 1.7"
#define BLOCK_SIZE 65536
@@ -91,6 +92,7 @@ enum rpc_ids_t RPCID_UPGRADE, RPCID_RMCHILDPROC, RPCID_GETCWD, + RPCID_SETPROPERTY, };
/* This is the RPC currently being processed */ @@ -113,6 +115,7 @@ static const char* rpc_name(uint32_t id) "upgrade", "rmchildproc", "getcwd", + "setproperty", };
if (id < sizeof(names) / sizeof(*names)) @@ -619,6 +622,47 @@ static int send_file(SOCKET client, int fd, const char* filename) }
+/* + * TestAgentd restart / Windows reboot tracking. + */ + +static unsigned int start_count = 0; + +static char* start_count_filename(void) +{ + char* filename = malloc(strlen(name0) + 5 + 1); + sprintf(filename, "%s.data", name0); + return filename; +} + +static void load_start_count(void) +{ + FILE *fh; + char* filename = start_count_filename(); + fh = fopen(filename, "r"); + if (fh) + { + if (!fscanf(fh, "%d", &start_count)) + start_count = 0; + fclose(fh); + } + free(filename); +} + +static void save_start_count(void) +{ + FILE *fh; + char* filename = start_count_filename(); + fh = fopen(filename, "w"); + if (fh) + { + fprintf(fh, "%d\n", start_count); + fclose(fh); + } + free(filename); +} + + /* * High-level operations. */ @@ -969,7 +1013,7 @@ static void do_getproperties(SOCKET client) send_error(client); return; } - send_list_size(client, 2); + send_list_size(client, 3);
format_msg(&buf, &size, "protocol.version=%s", PROTOCOL_VERSION); send_string(client, buf); @@ -984,9 +1028,53 @@ static void do_getproperties(SOCKET client) #endif format_msg(&buf, &size, "server.arch=%s", arch); send_string(client, buf); + + format_msg(&buf, &size, "start.count=%u", start_count); + send_string(client, buf); + free(buf); }
+static void do_setproperty(SOCKET client) +{ + char *name = NULL, *value = NULL; + + if (!expect_list_size(client, 2) || + !recv_string(client, &name) || + !recv_string(client, &value)) + { + send_error(client); + return; + } + + if (strcmp(name, "start.count") == 0) + { + unsigned int val; + if (sscanf(value, "%u", &val) == 1) + { + start_count = val; + save_start_count(); + send_list_size(client, 0); + } + else + { + set_status(ST_ERROR, "'%s' is not a valid %s value", value, name); + send_error(client); + } + } + else if (strcmp(name, "protocol.version") == 0 || + strcmp(name, "server.arch") == 0) + { + set_status(ST_ERROR, "%s is read-only", name); + send_error(client); + } + else + { + set_status(ST_ERROR, "unknown property %s", name); + send_error(client); + } +} + static void do_upgrade(SOCKET client) { static const char *filename = "testagentd.tmp"; @@ -1033,6 +1121,10 @@ static void do_upgrade(SOCKET client) free(args[0]); if (success) { + /* Decrement the start count since this one is intentional */ + start_count--; + save_start_count(); + broken = 1; quit = 1; } @@ -1129,6 +1221,9 @@ static void process_rpc(SOCKET client) case RPCID_RMCHILDPROC: do_rmchildproc(client); break; + case RPCID_SETPROPERTY: + do_setproperty(client); + break; default: do_unknown(client, rpcid); } @@ -1214,11 +1309,33 @@ static int is_host_allowed(SOCKET client, const char* srchost, int addrlen) return 0; }
+ +static void reset_start_count(void) +{ + start_count = 1; + save_start_count(); +} + +static void check_start_count(void) +{ + load_start_count(); + start_count++; + save_start_count(); + + if (start_count > 1) + { + char msg[255]; + sprintf(msg, "%s was restarted (%d). Did Windows reboot?\n", name0, start_count); + platform_show_message(msg, &reset_start_count); + } +} + int main(int argc, char** argv) { const char* p; char** arg; int opt_detach = 0; + int opt_show_restarts = 0; char* opt_port = NULL; char* opt_srchost = NULL; struct addrinfo *addresses, *addrp; @@ -1247,6 +1364,10 @@ int main(int argc, char** argv) { opt_detach = 1; } + else if (strcmp(*arg, "--show-restarts") == 0) + { + opt_show_restarts = 1; + } else if (strcmp(*arg, "--help") == 0) { opt_usage = 1; @@ -1328,11 +1449,15 @@ int main(int argc, char** argv) printf(" --debug Prints detailed information about what happens.\n"); printf(" --detach Detach from the console / terminal. Note that on Windows you should\n"); printf(" combine this with start: start %s --detach ...\n", name0); + printf(" --show-restarts Shows a message if %s has been restarted (for instance\n", name0); + printf(" because of a Windows reboot).\n"); printf(" --help Shows this usage message.\n"); exit(0); } if (opt_detach) platform_detach_console(); + if (opt_show_restarts) + check_start_count();
/* Bind to the host in a protocol neutral way */ #ifdef SOCK_CLOEXEC