diff options
Diffstat (limited to 'erts/etc/win32/erlsrv')
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_global.h | 37 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_interactive.c | 1163 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_interactive.h | 24 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_logmess.mc | 33 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_main.c | 44 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_registry.c | 404 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_registry.h | 76 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_service.c | 966 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_service.h | 32 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_util.c | 154 | ||||
-rw-r--r-- | erts/etc/win32/erlsrv/erlsrv_util.h | 50 |
11 files changed, 2983 insertions, 0 deletions
diff --git a/erts/etc/win32/erlsrv/erlsrv_global.h b/erts/etc/win32/erlsrv/erlsrv_global.h new file mode 100644 index 0000000000..d3922dc1e3 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_global.h @@ -0,0 +1,37 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifndef _ERLSRV_GLOBAL_H +#define _ERLSRV_GLOBAL_H + +#define APP_NAME "ErlSrv" + +#define ERLANG_MACHINE "erl.exe" + +#define SERVICE_ENV "ERLSRV_SERVICE_NAME" +#define EXECUTABLE_ENV "ERLSRV_EXECUTABLE" +#define DEBUG_ENV "ERLSRV_DEBUG" + +#ifdef _DEBUG +#define HARDDEBUG 1 +#define DEBUG 1 +#else +#define NDEBUG 1 +#endif + +#endif /* _ERLSRV_GLOBAL_H */ diff --git a/erts/etc/win32/erlsrv/erlsrv_interactive.c b/erts/etc/win32/erlsrv/erlsrv_interactive.c new file mode 100644 index 0000000000..13e029b364 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_interactive.c @@ -0,0 +1,1163 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#include <windows.h> +#include <winsvc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include "erlsrv_global.h" +#include "erlsrv_registry.h" +#include "erlsrv_interactive.h" +#include "erlsrv_util.h" /* service_name */ + +#define DBG fprintf(stderr,"argv[0]:%s line %d\n",argv[0],__LINE__) + +/* Really HAS to correcpond to the enum in erlsrv_registry.h */ +static char *arg_tab[] = { + "stopaction", "st", + "onfail", "on", + "machine", "m", + "env", "e", + "workdir", "w", + "priority", "p", + "sname", "sn", + "name", "n", + "args", "ar", + "debugtype", "d", + "internalservicename","i", + "comment","c", + NULL, NULL +}; + +static char *generate_real_service_name(char *display_name){ + SYSTEMTIME systime; + FILETIME ftime; + char *buff = malloc(strlen(display_name)+ + (8*2)+1); + char *tmp = _strdup(display_name); + int i; + /* 2 Hex chars for each byte in a DWORD */ + GetSystemTime(&systime); + SystemTimeToFileTime(&systime,&ftime); + /* Remove trailing version info to avoid user confusion */ + for(i = (strlen(tmp)-1);i > 0; --i) + if(tmp[i] == '_'){ + tmp[i] = '\0'; + break; + } + sprintf(buff,"%s%08x%08x",tmp,ftime.dwHighDateTime, + ftime.dwLowDateTime); + free(tmp); + return buff; +} + +static int lookup_arg(char *arg){ + int i; + if(*arg != '-' && *arg != '/') + return -1; + for(i=0; arg_tab[i] != NULL; i += 2){ + if(!_strnicmp(arg_tab[i],arg+1,strlen(arg+1)) && + !_strnicmp(arg_tab[i+1],arg+1,strlen(arg_tab[i+1]))) + return (i / 2); + } + return -1; +} + + + +char *edit_env(char *edit, char *oldenv){ + char **arg; + char *value; + char *name = strdup(edit); + int i; + char *tmp; + arg = env_to_arg(oldenv); + value = strchr(name,'='); + if(value){ + *(value++) = '\0'; + if(*value == '\0') + value = NULL; + } + for(i=0;arg[i] != NULL; ++i){ + tmp = strchr(arg[i],'='); + if(((int) strlen(name)) == (tmp - arg[i]) && + !_strnicmp(name,arg[i], tmp - arg[i])) + break; + } + if(arg[i] != NULL){ + free(arg[i]); + if(value){ + arg[i] = strdup(edit); + } else { + do { + arg[i] = arg[i+1]; + ++i; + } while(arg[i] != NULL); + } + } else if(value){ /* add to arg, which is always allocated + to hold one extra environment variable*/ + arg[i] = strdup(edit); + arg[i+1] = NULL; + } + free(name); + return arg_to_env(arg); +} + +int last_error = 0; + +void print_last_error(void){ + char *mes; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + (last_error) ? last_error : GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &mes, + 0, + NULL ); + fprintf(stderr,"Error: %s",mes); + LocalFree(mes); +} + +static BOOL install_service(void){ + SC_HANDLE scm; + SC_HANDLE service; + char filename[MAX_PATH + 3]; + DWORD fnsiz=MAX_PATH; + char dependant[] = { 'L','a','n','m','a','n', + 'W','o','r','k','s','t', + 'a','t','i','o','n','\0','\0'}; + + if(!(fnsiz = GetModuleFileName(NULL, filename, fnsiz))) + return FALSE; + if(strchr(filename,' ')){ + memmove(filename+1,filename,fnsiz); + filename[0] ='\"'; /* " */ + filename[fnsiz+1] = '\"'; /* " */ + filename[fnsiz+2] = '\0'; + } + if((scm = OpenSCManager(NULL, + NULL, + SC_MANAGER_CONNECT | + SC_MANAGER_CREATE_SERVICE)) + == NULL){ + last_error = GetLastError(); + return FALSE; + } + service = CreateService(scm, + real_service_name, + service_name, + SERVICE_ALL_ACCESS & + ~(SERVICE_PAUSE_CONTINUE), + SERVICE_WIN32_OWN_PROCESS, + SERVICE_AUTO_START, + SERVICE_ERROR_NORMAL, + filename, + NULL, + NULL, + dependant, + NULL, + NULL); + if(service == NULL){ + CloseServiceHandle(scm); + last_error = GetLastError(); + return FALSE; + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return TRUE; +} + +static BOOL remove_service(void){ + SC_HANDLE scm; + SC_HANDLE service; + if((scm = OpenSCManager(NULL, + NULL, + GENERIC_WRITE)) + == NULL) + return FALSE; + service = OpenService(scm, + real_service_name, + SERVICE_ALL_ACCESS); + if(service == NULL){ + CloseServiceHandle(scm); + return FALSE; + } + if(!DeleteService(service)){ + last_error = GetLastError(); + return FALSE; + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return TRUE; +} + +static BOOL open_service_control(SC_HANDLE *scm, SC_HANDLE *service){ + if((*scm = OpenSCManager(NULL, + NULL, + SC_MANAGER_ALL_ACCESS)) + == NULL) + return FALSE; + *service = OpenService(*scm, + real_service_name, + SERVICE_ALL_ACCESS); + if(service == NULL){ + CloseServiceHandle(*scm); + return FALSE; + } + return TRUE; +} + +static BOOL open_service_config(SC_HANDLE *scm, SC_HANDLE *service){ + if((*scm = OpenSCManager(NULL, + NULL, + /*GENERIC_WRITE | GENERIC_EXECUTE*/ + SC_MANAGER_ALL_ACCESS)) + == NULL){ + last_error = GetLastError(); + return FALSE; + } + *service = OpenService(*scm, + real_service_name, + /*GENERIC_WRITE*/ + SERVICE_ALL_ACCESS); + if(service == NULL){ + last_error = GetLastError(); + CloseServiceHandle(*scm); + return FALSE; + } + return TRUE; +} + +static BOOL set_service_comment(char *comment) { + SC_HANDLE scm; + SC_HANDLE service; + SERVICE_DESCRIPTION sd; + BOOL ret = TRUE; + sd.lpDescription = comment; + if (!open_service_config(&scm,&service)) { + return FALSE; + } + if (!ChangeServiceConfig2(service,SERVICE_CONFIG_DESCRIPTION,&sd)) { + last_error = GetLastError(); + ret = FALSE; + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return ret; +} + +static BOOL wait_service_trans(DWORD initial, DWORD passes, DWORD goal, + int timeout) +{ + SC_HANDLE scm; + SC_HANDLE service; + int moved = 0; + BOOL ret; + int i; + SERVICE_STATUS stat; + + if(! open_service_config(&scm,&service)) + return FALSE; + for(i = 0; i < timeout; ++i){ + if(!QueryServiceStatus(service,&stat)){ + last_error = GetLastError(); + ret = FALSE; + goto out; + } + if(stat.dwCurrentState == initial){ + if(moved){ + ret = FALSE; + + /* + * The exitcode is usually strange when we tried to stop and failed, + * to report a timeout is more appropriate. + */ + if(goal == SERVICE_STOPPED) + last_error = ERROR_SERVICE_REQUEST_TIMEOUT; + else + last_error = stat.dwWin32ExitCode; + goto out; + } + } else if(stat.dwCurrentState == passes){ + moved = 1; + } else if(stat.dwCurrentState == goal){ + ret = TRUE; + goto out; + } + Sleep(1000); + } + ret = FALSE; + last_error = ERROR_SERVICE_REQUEST_TIMEOUT; +out: + CloseServiceHandle(scm); + CloseServiceHandle(service); + return ret; +} + +static BOOL stop_service(void){ + SC_HANDLE scm; + SC_HANDLE service; + BOOL ret; + SERVICE_STATUS ss; + + if(!open_service_control(&scm,&service)){ +#ifdef HARDDEBUG + fprintf(stderr,"Failed to open service.\n"); +#endif + return FALSE; + } + ret = ControlService(service,SERVICE_CONTROL_STOP,&ss); + if(!ret){ + last_error = GetLastError(); + } + CloseServiceHandle(service); + CloseServiceHandle(scm); +#ifdef HARDDEBUG + if(!ret) + { + fprintf(stderr,"Failed to control service.\n"); + print_last_error(); + } +#endif + return ret; +} + +static BOOL start_service(void){ + SC_HANDLE scm; + SC_HANDLE service; + BOOL ret; + + if(!open_service_control(&scm,&service)) + return FALSE; + + ret = StartService(service,0,NULL); + if(!ret){ + last_error = GetLastError(); + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return ret; +} + +static BOOL disable_service(void){ + SC_HANDLE scm; + SC_HANDLE service; + BOOL ret; + + if(!open_service_config(&scm,&service)) + return FALSE; + + ret = ChangeServiceConfig(service, + SERVICE_NO_CHANGE, + SERVICE_DISABLED, + SERVICE_NO_CHANGE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + if(!ret){ + last_error = GetLastError(); + } + + CloseServiceHandle(service); + CloseServiceHandle(scm); + return ret; +} + +static BOOL enable_service(void){ + SC_HANDLE scm; + SC_HANDLE service; + BOOL ret; + +if(!open_service_config(&scm,&service)) + return FALSE; + + ret = ChangeServiceConfig(service, + SERVICE_NO_CHANGE, + SERVICE_AUTO_START, + SERVICE_NO_CHANGE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + + if(!ret){ + last_error = GetLastError(); + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return ret; +} + +static BOOL set_interactive(BOOL interactive){ + SC_HANDLE scm; + SC_HANDLE service; + BOOL ret; + + if(!open_service_config(&scm,&service)) + return FALSE; + + ret = ChangeServiceConfig(service, + SERVICE_WIN32_OWN_PROCESS | ((interactive) ? + SERVICE_INTERACTIVE_PROCESS : 0), + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + + if(!ret){ + last_error = GetLastError(); + } + CloseServiceHandle(service); + CloseServiceHandle(scm); + return ret; +} + + +RegEntry *old_entries = NULL; + +BOOL fetch_current(RegEntry *new){ + int i; + + if(!(old_entries = get_keys(service_name))) + return FALSE; + for(i=0;i<num_reg_entries;++i) + new[i] = old_entries[i]; + return TRUE; +} + +void cleanup_old(){ + if(old_entries != NULL) + free_keys(old_entries); +} + +BOOL fill_in_defaults(RegEntry *new){ + char filename[MAX_PATH]; + char *ptr; + + + if(!GetModuleFileName(NULL, filename, MAX_PATH)) + return FALSE; + for(ptr = filename + strlen(filename) - 1; + ptr > filename && *ptr != '\\'; + --ptr) + ; + if(*ptr == '\\') + ++ptr; + *ptr = '\0'; + + ptr = malloc(strlen(filename)+strlen(ERLANG_MACHINE)+1); + strcpy(ptr,filename); + strcat(ptr,ERLANG_MACHINE); + + new[StopAction].data.bytes = ""; + new[OnFail].data.value = ON_FAIL_IGNORE; + new[Machine].data.bytes = ptr; + new[Machine].data.expand.unexpanded = ptr; + new[Env].data.bytes = "\0"; + new[WorkDir].data.bytes = new[WorkDir].data.expand.unexpanded = + ""; + new[Priority].data.value = NORMAL_PRIORITY_CLASS; + new[SName].data.bytes = service_name; + new[Name].data.bytes = ""; + new[Args].data.bytes = new[Args].data.expand.unexpanded = ""; + new[DebugType].data.value = DEBUG_TYPE_NO_DEBUG; + new[InternalServiceName].data.bytes = real_service_name; + new[Comment].data.bytes = ""; + return TRUE; +} + +int do_usage(char *arg0){ + printf("Usage:\n"); + printf("%s {set | add} <servicename>\n" + "\t[-st[opaction] [<erlang shell command>]]\n" + "\t[-on[fail] [{reboot | restart | restart_always}]]\n" + "\t[-m[achine] [<erl-command>]]\n" + "\t[-e[nv] [<variable>[=<value>]]]\n" + "\t[-w[orkdir] [<directory>]]\n" + "\t[-p[riority] [{low|high|realtime}]]\n" + "\t[{-sn[ame] | -n[ame]} [<nodename>]]\n" + "\t[-d[ebugtype] [{new|reuse|console}]]\n" + "\t[-ar[gs] [<limited erl arguments>]]\n\n" + "%s {start | stop | disable | enable} <servicename>\n\n" + "%s remove <servicename>\n\n" + "%s rename <servicename> <servicename>\n\n" + "%s list [<servicename>]\n\n" + "%s help\n\n", + arg0,arg0,arg0,arg0,arg0,arg0); + printf("Manipulates Erlang system services on Windows NT.\n\n"); + printf("When no parameter to an option is specified, the option\n" + "is reset to it's default value. To set an empty argument\n" + "list, give option -args as last option on command line " + "with\n" + "no arguments.\n\n"); + printf("Se Erlang documentation for full description.\n"); + return 0; +} + +int do_manage(int argc,char **argv){ + char *action = argv[1]; + RegEntry *current = empty_reg_tab(); + + if(argc < 3){ + fprintf(stderr,"%s: No servicename given!\n",argv[0]); + do_usage(argv[0]); + return 1; + } + service_name = argv[2]; + if(!fetch_current(current)){ + fprintf(stderr,"%s: The service %s is not an erlsrv controlled service.\n", + argv[0],service_name); + return 1; + } + real_service_name = _strdup(current[InternalServiceName].data.bytes); + free_keys(current); + + if(!_stricmp(action,"start")){ + if(!start_service()){ + fprintf(stderr,"%s: Failed to start service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } else { + if(!wait_service_trans(SERVICE_STOPPED, SERVICE_START_PENDING, + SERVICE_RUNNING, 60)){ + fprintf(stderr,"%s: Failed to start service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } + printf("%s: Service %s started.\n", + argv[0],service_name); + return 0; + } + } + if(!_stricmp(action,"stop")){ + if(!stop_service()){ + fprintf(stderr,"%s: Failed to stop service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } else { + if(!wait_service_trans(SERVICE_RUNNING, SERVICE_STOP_PENDING, + SERVICE_STOPPED, 60)){ + fprintf(stderr,"%s: Failed to stop service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } + printf("%s: Service %s stopped.\n", + argv[0],service_name); + return 0; + } + } + if(!_stricmp(action,"disable")){ +#if 0 + if(stop_service()){ + printf("%s: Service %s stopped.\n", + argv[0],service_name); + } +#endif + if(!disable_service()){ + fprintf(stderr,"%s: Failed to disable service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } else { + printf("%s: Service %s disabled.\n", + argv[0],service_name); + return 0; + } + } + if(!_stricmp(action,"enable")){ + if(!enable_service()){ + fprintf(stderr,"%s: Failed to enable service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } else { + printf("%s: Service %s enabled.\n", + argv[0],service_name); + return 0; + } + } + fprintf(stderr,"%s: Unrecignized argument %s.\n", + argv[0],action); + return 1; +} + +int do_add_or_set(int argc, char **argv){ + RegEntry *new_entries; + RegEntry *default_entries; + int add = 0; + int i; + int current; + int set_comment = 0; + new_entries = empty_reg_tab(); + default_entries = empty_reg_tab(); + if(argc < 3){ + fprintf(stderr,"%s: No servicename given!\n",argv[0]); + do_usage(argv[0]); + return 1; + } + service_name = argv[2]; + if(!_stricmp(argv[1],"add")){ + if(fetch_current(default_entries)){ + fprintf(stderr,"%s: A service with the name %s already " + "exists.\n", + argv[0],service_name); + return 1; + } + real_service_name = generate_real_service_name(service_name); + if(!fill_in_defaults(new_entries)){ + fprintf(stderr,"%s: Internal error.\n", argv[0]); + return 1; + } + add = 1; + } else { + if(!fetch_current(new_entries)){ + fprintf(stderr,"%s: No service with the name %s exists.\n", + argv[0], service_name); + return 1; + } + real_service_name = new_entries[InternalServiceName].data.bytes; + } + + if(!fill_in_defaults(default_entries)){ + fprintf(stderr,"%s: Internal error.\n", argv[0]); + return 1; + } + + /* make sure env is malloced... */ + new_entries[Env].data.bytes = envdup(new_entries[Env].data.bytes); + + for(i = 3; i < argc; ++i){ + switch((current = lookup_arg(argv[i]))){ + case Comment: + set_comment = 1; + case Machine: + case WorkDir: + case Args: + if(i+1 >= argc){ + new_entries[current].data.bytes = + default_entries[current].data.bytes; + new_entries[current].data.expand.unexpanded = + default_entries[current].data.expand.unexpanded; + } else { + new_entries[current].data.expand.unexpanded = + new_entries[current].data.bytes = argv[i+1]; + ++i; + } + break; + case SName: + new_entries[Name].data.bytes = ""; + case StopAction: + case Name: + if(i+1 >= argc || + *argv[i+1] == '-' || *argv[i+1] == '/'){ + new_entries[current].data.bytes = + default_entries[current].data.bytes; + } else { + new_entries[current].data.bytes = argv[i+1]; + ++i; + } + break; + case OnFail: + if(i+1 >= argc || + *argv[i+1] == '-' || *argv[i+1] == '/'){ + new_entries[current].data.value = + default_entries[current].data.value; + } else { + if(!_stricmp(argv[i+1],"reboot")) + new_entries[current].data.value = ON_FAIL_REBOOT; + else if(!_stricmp(argv[i+1],"restart")) + new_entries[current].data.value = ON_FAIL_RESTART; + else if(!_stricmp(argv[i+1],"restart_always")) + new_entries[current].data.value = ON_FAIL_RESTART_ALWAYS; + else { + fprintf(stderr,"%s: Unrecognized keyword value %s.\n", + argv[0],argv[i+1]); + return 1; + } + ++i; + } + break; + case DebugType: + if(i+1 >= argc || + *argv[i+1] == '-' || *argv[i+1] == '/'){ + new_entries[current].data.value = + default_entries[current].data.value; + } else { + if(!_stricmp(argv[i+1],"new")) + new_entries[current].data.value = DEBUG_TYPE_NEW; + else if(!_stricmp(argv[i+1],"reuse")) + new_entries[current].data.value = DEBUG_TYPE_REUSE; + else if(!_stricmp(argv[i+1],"console")) + new_entries[current].data.value = DEBUG_TYPE_CONSOLE; + else { + fprintf(stderr,"%s: Unrecognized keyword value %s.\n", + argv[0],argv[i+1]); + return 1; + } + ++i; + } + break; + case Priority: + if(i+1 >= argc || + *argv[i+1] == '-' || *argv[i+1] == '/'){ + new_entries[current].data.value = + default_entries[current].data.value; + } else { + if(!_stricmp(argv[i+1],"high")) + new_entries[current].data.value = HIGH_PRIORITY_CLASS; + else if(!_stricmp(argv[i+1],"low")) + new_entries[current].data.value = IDLE_PRIORITY_CLASS; + else if(!_stricmp(argv[i+1],"realtime")) + new_entries[current].data.value = REALTIME_PRIORITY_CLASS; + else { + fprintf(stderr,"%s: Unrecognized keyword value %s.\n", + argv[0],argv[i+1]); + return 1; + } + ++i; + } + break; + + case Env: + if(i+1 >= argc || + *argv[i+1] == '-' || *argv[i+1] == '/'){ + fprintf(stderr,"%s: %s requires a parameter.\n", + argv[0],argv[i]); + return 1; + } + new_entries[current].data.bytes = + edit_env(argv[i+1], + new_entries[current].data.bytes); + ++i; + break; + case InternalServiceName: + if (!add) { + fprintf(stderr,"%s: %s only allowed when adding a new service.\n", + argv[0],argv[i]); + return 1; + } + if(i+1 >= argc){ + fprintf(stderr,"%s: %s requires a parameter.\n", + argv[0],argv[i]); + return 1; + } + new_entries[InternalServiceName].data.expand.unexpanded = + new_entries[InternalServiceName].data.bytes = argv[i+1]; + ++i; + /* Discard old, should maybe be fred' but we'll exit anyway */ + real_service_name = new_entries[InternalServiceName].data.bytes; + break; + default: + fprintf(stderr,"%s: Unrecognized option %s.\n", argv[0], + argv[i]); + return 1; + } + } + if(*new_entries[SName].data.bytes && + *new_entries[Name].data.bytes){ +#if 0 + fprintf(stderr,"%s: Both -sname and -name specified.\n", + argv[0]); + return 1; +#else + new_entries[SName].data.bytes = ""; +#endif + } + if(add && !(*new_entries[SName].data.bytes) && + !(*new_entries[Name].data.bytes)){ + fprintf(stderr,"%s: Neither -sname nor -name specified.\n", + argv[0]); + return 1; + } + if(add && !install_service()){ + fprintf(stderr,"%s: Unable to register service with service manager.\n", + argv[0], service_name); + print_last_error(); + return 1; + } + if(!set_interactive(new_entries[DebugType].data.value == + DEBUG_TYPE_CONSOLE)){ + fprintf(stderr,"%s: Warning, could not set correct interactive mode.\n", + argv[0], service_name); + print_last_error(); + /* Not severe or??? */ + } + /* Update registry */ + register_logkeys(); + set_keys(service_name, new_entries); + /* Update service comment if needed */ + if(set_comment) { + if (!set_service_comment(new_entries[Comment].data.bytes)) { + fprintf(stderr,"%s: Warning, could not set correct " + "service description (comment)", + argv[0], service_name); + print_last_error(); + } + } + + /* As I do this, I should also clean up the new entries, which is + somewhat harder as I really dont know what is and what is not + malloced, but we'll exit anyway, so... */ + cleanup_old(); + if(add) + printf("%s: Service %s added to system.\n", + argv[0], service_name); + else + printf("%s: Service %s updated.\n", + argv[0], service_name); + return 0; +} +int do_rename(int argc, char **argv){ + RegEntry *current = empty_reg_tab(); + RegEntry *dummy = empty_reg_tab(); + SC_HANDLE scm; + SC_HANDLE service; + if(argc < 3){ + fprintf(stderr,"%s: No old servicename given!\n",argv[0]); + do_usage(argv[0]); + return 1; + } + if(argc < 4){ + fprintf(stderr,"%s: No new servicename given!\n",argv[0]); + do_usage(argv[0]); + return 1; + } + service_name = argv[3]; + if(fetch_current(dummy)){ + fprintf(stderr,"%s: A service with the name %s already " + "exists.\n", + argv[0],service_name); + return 1; + } + service_name = argv[2]; + + if(!fetch_current(current)){ + fprintf(stderr,"%s: Error, old service name %s does not exist.\n", + argv[0],service_name); + return 1; + } + real_service_name = _strdup(current[InternalServiceName].data.bytes); + if(!open_service_config(&scm,&service)){ + fprintf(stderr,"%s: Error, unable to communicate with service control" + " manager.\n", + argv[0]); + print_last_error(); + return 1; + } + if(!ChangeServiceConfig(service, + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + argv[3])){ + fprintf(stderr,"%s: Error, unable to communicate with service control" + " manager.\n", + argv[0]); + print_last_error(); + CloseServiceHandle(scm); + CloseServiceHandle(service); + return 1; + } + CloseServiceHandle(scm); + CloseServiceHandle(service); + + if(remove_keys(service_name) != 0) + fprintf(stderr,"%s: Warning, old service parameter keys could not " + "be removed, continuing.\n", argv[0]); + /* Update registry */ + register_logkeys(); + set_keys(argv[3], current); + + printf("%s: Service %s renamed to %s.\n", + argv[0], service_name, argv[3]); + return 0; +} + +int do_remove(int argc, char **argv){ + RegEntry *current = empty_reg_tab(); + int rem_res; + BOOL found; + + if(argc < 3){ + fprintf(stderr,"%s: No servicename given!\n",argv[0]); + do_usage(argv[0]); + return 1; + } + service_name = argv[2]; + found = fetch_current(current); + if(found){ + real_service_name = _strdup(current[InternalServiceName].data.bytes); + } else { + real_service_name = _strdup(service_name); + } + if(found) + free_keys(current); + if(stop_service() && !wait_service_trans(SERVICE_RUNNING, + SERVICE_STOP_PENDING, + SERVICE_STOPPED, 60)){ + fprintf(stderr,"%s: Failed to stop running service %s.\n", + argv[0],service_name); + print_last_error(); + return 1; + } + if(!remove_service()){ + fprintf(stderr,"%s: Unable to remove service (not enough " + "privileges?)\n",argv[0]); + print_last_error(); + return 1; + } + + if((rem_res = remove_keys(service_name)) > 0){ + fprintf(stderr,"%s: Warning, service parameter keys belonged to old " + "erlsrv version.\n", argv[0]); + /* Backward compatibility... */ + } else if(rem_res < 0) { + fprintf(stderr,"%s: Error, service parameter keys nonexistent.\n", + argv[0]); + return 1; + } + printf("%s: Service %s removed from system.\n", + argv[0], service_name); + return 0; +} + +BOOL list_one(char *servicename, RegEntry *keys, BOOL longlist){ + char *onfail; + char *prio; + char *debugtype; + switch(keys[OnFail].data.value){ + case ON_FAIL_RESTART: + onfail = "restart"; + break; + case ON_FAIL_RESTART_ALWAYS: + onfail = "restart_always"; + break; + case ON_FAIL_REBOOT: + onfail = "reboot"; + break; + default: + onfail = "ignore"; + } + switch(keys[DebugType].data.value){ + case DEBUG_TYPE_NEW: + debugtype = "new"; + break; + case DEBUG_TYPE_REUSE: + debugtype = "reuse"; + break; + case DEBUG_TYPE_CONSOLE: + debugtype = "console"; + break; + default: + debugtype = "none"; + } + switch(keys[Priority].data.value){ + case HIGH_PRIORITY_CLASS: + prio = "high"; + break; + case IDLE_PRIORITY_CLASS: + prio = "low"; + break; + case REALTIME_PRIORITY_CLASS: + prio = "realtime"; + break; + case NORMAL_PRIORITY_CLASS: + prio = "default"; + break; + default: + prio = "unknown/faulty"; + } + + + if(longlist){ + char *env = envdup(keys[Env].data.bytes); + char **arg = env_to_arg(env); + char **pek = arg; + printf("Service name: %s\n", + servicename); + printf("StopAction: %s\n", + keys[StopAction].data.bytes); + printf("OnFail: %s\n",onfail); + printf("Machine: %s\n", + keys[Machine].data.expand.unexpanded); + printf("WorkDir: %s\n", + keys[WorkDir].data.expand.unexpanded); + if(*keys[SName].data.bytes) + printf("SName: %s\n", + keys[SName].data.bytes); + else + printf("Name: %s\n", + keys[Name].data.bytes); + printf("Priority: %s\n",prio); + printf("DebugType: %s\n",debugtype); + printf("Args: %s\n", + keys[Args].data.expand.unexpanded); + printf("InternalServiceName: %s\n", + keys[InternalServiceName].data.bytes); + printf("Comment: %s\n", + keys[Comment].data.bytes); + printf("Env:\n"); + while(*pek){ + printf("\t%s\n",*pek); + ++pek; + } + /* env is easier to free...*/ + env = arg_to_env(arg); + free(env); + } else { + printf("%s\t%s\t%s\t%s\t%s\n", + servicename, + (*keys[Name].data.bytes) ? + keys[Name].data.bytes : + keys[SName].data.bytes, + prio, + onfail, + keys[Args].data.expand.unexpanded); + } + return TRUE; +} + + +int do_list(int argc, char **argv){ + if(argc < 3){ + RegEntryDesc *all_keys = get_all_keys(); + if(!all_keys){ + fprintf(stderr,"%s: No services found in registry.\n", + argv[0]); + return 0; + } + printf("Service\t(S)Name\tPrio\tOnFail\tArgs\n"); + while(all_keys->servicename){ + list_one(all_keys->servicename,all_keys->entries,FALSE); + ++all_keys; + } + return 0; + } else { + RegEntry *keys; + service_name = argv[2]; + keys = get_keys(service_name); + if(!keys){ + fprintf(stderr,"%s: Could not retrieve any " + "registered data for %s.\n",argv[0],service_name); + return 1; + } + list_one(service_name, keys, TRUE); + } + return 0; +} + +#define READ_CHUNK 100 +#define ARGV_CHUNK 20 + +char *safe_get_line(void){ + int lsize = READ_CHUNK; + char *line = malloc(READ_CHUNK); + int pos = 0; + int ch; + + while((ch = getchar()) != EOF && ch != '\n'){ + if(pos + 1 >= lsize){ + line = realloc(line,(lsize += READ_CHUNK)); + assert(line); + } + line[pos++] = ch; + } + if(ch == EOF || !pos){ + free(line); + return NULL; + } + line[pos] = '\0'; + return line; +} + + +void read_arguments(int *pargc, char ***pargv){ + int argc = 0; + int asize = ARGV_CHUNK; + char **argv = malloc(ARGV_CHUNK*sizeof(char *)); + char *tmp; + + argv[0] = (*pargv)[0]; + argc = 1; + while((tmp = safe_get_line()) != NULL){ + if(argc + 1 >= asize){ + argv = realloc(argv,(asize += ARGV_CHUNK)*sizeof(char *)); + assert(argv); + } + argv[argc++] = tmp; + } + argv[argc] = NULL; + *pargc = argc; + *pargv = argv; +} + + +int interactive_main(int argc, char **argv){ + char *action = argv[1]; + + if(!_stricmp(action,"readargs")){ + read_arguments(&argc,&argv); + action = argv[1]; + } + if(!_stricmp(action,"set") || !_stricmp(action,"add")) + return do_add_or_set(argc,argv); + if(!_stricmp(action,"rename")) + return do_rename(argc,argv); + if(!_stricmp(action,"remove")) + return do_remove(argc,argv); + if(!_stricmp(action,"list")) + return do_list(argc,argv); + if(!_stricmp(action,"start") || + !_stricmp(action,"stop") || + !_stricmp(action,"enable") || + !_stricmp(action,"disable")) + return do_manage(argc,argv); + if(_stricmp(action,"?") && + _stricmp(action,"/?") && + _stricmp(action,"-?") && + *action != 'h' && + *action != 'H') + fprintf(stderr,"%s: action %s not implemented.\n",argv[0],action); + do_usage(argv[0]); + return 1; +} + diff --git a/erts/etc/win32/erlsrv/erlsrv_interactive.h b/erts/etc/win32/erlsrv/erlsrv_interactive.h new file mode 100644 index 0000000000..deacf81899 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_interactive.h @@ -0,0 +1,24 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifndef _ERLSRV_INTERACTIVE_H +#define _ERLSRV_INTERACTIVE_H + +int interactive_main(int argc, char **argv); + +#endif /* _ERLSRV_INTERACTIVE_H */ diff --git a/erts/etc/win32/erlsrv/erlsrv_logmess.mc b/erts/etc/win32/erlsrv/erlsrv_logmess.mc new file mode 100644 index 0000000000..354ac14c9f --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_logmess.mc @@ -0,0 +1,33 @@ +;/*MessageIDTypedef=WORD*/ +; +;/*MessageID=0x1*/ +;/*SymbolicName=CAT_GENERIC*/ +;/*Language=English*/ +;/*Generic Category*/ +;/*.*/ +; +MessageIDTypedef=DWORD + +MessageID=0x10 +Severity=Warning +Facility=Application +SymbolicName=MSG_WARNING +Language=English +%1: %2 +. +MessageID=0x11 +Severity=Error +Facility=Application +SymbolicName=MSG_ERROR +Language=English +%1: %2 +. +MessageID=0x12 +Severity=Informational +Facility=Application +SymbolicName=MSG_INFO +Language=English +%1: %2 +. + + diff --git a/erts/etc/win32/erlsrv/erlsrv_main.c b/erts/etc/win32/erlsrv/erlsrv_main.c new file mode 100644 index 0000000000..920a4a1827 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_main.c @@ -0,0 +1,44 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#include <windows.h> +#include <winsvc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "erlsrv_global.h" +#include "erlsrv_interactive.h" +#include "erlsrv_service.h" + +int main(int argc, char **argv){ + if(argc > 1) + return interactive_main(argc,argv); + else + return service_main(argc,argv); +} + + + + + + + + + + + diff --git a/erts/etc/win32/erlsrv/erlsrv_registry.c b/erts/etc/win32/erlsrv/erlsrv_registry.c new file mode 100644 index 0000000000..c1aa9f2b67 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_registry.c @@ -0,0 +1,404 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#include <windows.h> +#include <winsvc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "erlsrv_global.h" +#include "erlsrv_registry.h" + +#define LOG_TYPE "System" +#define LOG_ROOT \ +"SYSTEM\\CurrentControlSet\\Services\\EventLog\\" LOG_TYPE "\\" +#define LOG_APP_KEY APP_NAME + + +#define BASE_KEY HKEY_LOCAL_MACHINE +#define PRODUCT_NAME APP_NAME +#define OLD_PRODUCT_VERSION "1.0" +#define PRODUCT_VERSION "1.1" +#define PROG_KEY "SOFTWARE\\Ericsson\\Erlang\\" PRODUCT_NAME "\\" PRODUCT_VERSION +#define OLD_PROG_KEY "SOFTWARE\\Ericsson\\Erlang\\" PRODUCT_NAME "\\" OLD_PRODUCT_VERSION + +#define MAX_KEY_LEN MAX_PATH + +static const char * const noString = "\0"; + +#define MAX_MANDATORY_REG_ENTRY 10 /* InternalServiceName == reg_entries[10] */ +static RegEntry reg_entries[] = { + {"StopAction",REG_SZ,NULL}, + {"OnFail",REG_DWORD,NULL}, + {"Machine",REG_EXPAND_SZ,NULL}, + {"Env", REG_MULTI_SZ,NULL}, + {"WorkDir", REG_EXPAND_SZ,NULL}, + {"Priority",REG_DWORD,NULL}, + {"SName",REG_SZ,NULL}, + {"Name",REG_SZ,NULL}, + {"Args",REG_EXPAND_SZ,NULL}, + {"DebugType",REG_DWORD,NULL}, + {"InternalServiceName",REG_SZ,NULL}, + /* Non mandatory follows */ + {"Comment",REG_SZ,NULL} +}; + + +int num_reg_entries = sizeof(reg_entries)/sizeof(RegEntry); + +RegEntry *empty_reg_tab(void){ + RegEntry *ret = malloc(num_reg_entries * sizeof(RegEntry)); + memcpy(ret,reg_entries,num_reg_entries * sizeof(RegEntry)); + return ret; +} + +void free_keys(RegEntry *keys){ + int i; + + for(i=0;i<num_reg_entries && keys[i].name != NULL;++i){ + if((keys[i].type == REG_SZ || keys[i].type == REG_EXPAND_SZ || + keys[i].type == REG_MULTI_SZ) && + keys[i].data.bytes != noString){ + free(keys[i].data.bytes); + if(keys[i].type == REG_EXPAND_SZ && + keys[i].data.expand.unexpanded != noString) + free(keys[i].data.expand.unexpanded); + } + } + free(keys); +} + +void free_all_keys(RegEntryDesc *descs){ + RegEntryDesc *tmp = descs; + for(;tmp->servicename != NULL; ++tmp){ + free_keys(tmp->entries); + free(tmp->servicename); + } + free(descs); +} + +RegEntry *get_keys(char *servicename){ + RegEntry *res = NULL; + HKEY prog_key; + int key_opened = 0; + int i; + DWORD ret; + char *copy; + char *tmpbuf; + DWORD tmpbuflen; + + char key_to_open[MAX_KEY_LEN]; + + DWORD val_type; + char *val_data = malloc(MAX_KEY_LEN); + DWORD val_datalen; + DWORD val_datasiz = MAX_KEY_LEN; + + if(strlen(PROG_KEY) + strlen(servicename) + 2 > MAX_KEY_LEN) + goto error; + sprintf(key_to_open,"%s\\%s",PROG_KEY,servicename); + + if(RegOpenKeyEx(BASE_KEY, + key_to_open, + 0, + KEY_QUERY_VALUE, + &prog_key) != ERROR_SUCCESS) + goto error; + key_opened = 1; + + res = malloc(num_reg_entries*sizeof(RegEntry)); + for(i=0;i<num_reg_entries;++i) + res[i].name = NULL; + + for(i=0;i<num_reg_entries;++i){ + for(;;){ + val_datalen = val_datasiz; + ret = RegQueryValueEx(prog_key, + reg_entries[i].name, + NULL, + &val_type, + (BYTE *) val_data, + &val_datalen); + if(ret == ERROR_SUCCESS){ + if(reg_entries[i].type == val_type) + break; + else + goto error; + } else if(ret == ERROR_MORE_DATA){ + val_data = realloc(val_data,val_datasiz = val_datalen); + } else if (i > MAX_MANDATORY_REG_ENTRY && ret == ERROR_FILE_NOT_FOUND) { + /* Non mandatory entries, look at the type... */ + switch (reg_entries[i].type){ + case REG_EXPAND_SZ: + case REG_SZ: + case REG_MULTI_SZ: + val_datalen = 0; + break; + case REG_DWORD: + { + DWORD dummy = 0; + memcpy(val_data,&dummy,(val_datalen = sizeof(DWORD))); + } + break; + default: + goto error; + } + break; /* for(;;) */ + } else { + goto error; + } + } + res[i] = reg_entries[i]; + copy = NULL; + switch(reg_entries[i].type){ + case REG_EXPAND_SZ: + if(!val_datalen || val_data[0] == '\0'){ + copy = (char *) noString; + res[i].data.expand.unexpanded = (char *) noString; + } else { + tmpbuf = malloc(MAX_KEY_LEN); + tmpbuflen = (DWORD) MAX_KEY_LEN; + for(;;){ + ret = ExpandEnvironmentStrings(val_data,tmpbuf,tmpbuflen); + if(!ret){ + free(tmpbuf); + goto error; + }else if(ret > tmpbuflen){ + tmpbuf=realloc(tmpbuf,tmpbuflen=ret); + } else { + copy = strdup(tmpbuf); + free(tmpbuf); + break; + } + } + res[i].data.expand.unexpanded = strdup(val_data); + } + case REG_MULTI_SZ: + case REG_SZ: + if(!copy){ + if(!val_datalen || + ((val_datalen == 1 && val_data[0] == '\0') || + (val_datalen == 2 && val_data[0] == '\0' && + val_data[1] == '\0'))){ + copy = (char *) noString; + } else { + copy = malloc(val_datalen); + memcpy(copy,val_data,val_datalen); + } + } + res[i].data.bytes = copy; + break; + case REG_DWORD: + memcpy(&res[i].data.value,val_data,sizeof(DWORD)); + break; + default: + goto error; + } + } + RegCloseKey(prog_key); + free(val_data); + return res; +error: + free(val_data); + if(res != NULL) + free_keys(res); + if(key_opened) + RegCloseKey(prog_key); + return NULL; +} + +int set_keys(char *servicename, RegEntry *keys){ + HKEY prog_key; + int key_opened = 0; + int i; + char key_to_open[MAX_KEY_LEN]; + DWORD disposition; + + if(strlen(PROG_KEY) + strlen(servicename) + 2 > MAX_KEY_LEN) + goto error; + sprintf(key_to_open,"%s\\%s",PROG_KEY,servicename); + + if(RegOpenKeyEx(BASE_KEY, + key_to_open, + 0, + KEY_SET_VALUE, + &prog_key) != ERROR_SUCCESS){ + if(RegCreateKeyEx(BASE_KEY, + key_to_open, + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_SET_VALUE, + NULL, + &prog_key, + &disposition) != ERROR_SUCCESS) + goto error; + } + key_opened = 1; + + + for(i=0;i<num_reg_entries;++i){ + void *ptr; + DWORD siz; + int j; + switch(keys[i].type){ + case REG_SZ: + ptr = keys[i].data.bytes; + siz = strlen(ptr)+1; + break; + case REG_EXPAND_SZ: + ptr = keys[i].data.expand.unexpanded; + siz = strlen(ptr)+1; + break; + case REG_MULTI_SZ: + ptr = keys[i].data.bytes; + for(j=0;!(((char *)ptr)[j] == '\0' && + ((char *)ptr)[j+1] == '\0');++j) + ; + siz=(DWORD)j+2; + break; + case REG_DWORD: + ptr = &keys[i].data.value; + siz = sizeof(DWORD); + break; + default: + goto error; + } +#ifdef HARDDEBUG + fprintf(stderr,"%s %s:%d\n",keys[i].name, + (keys[i].type == REG_DWORD) ? "(dword)" : ptr,siz); +#endif + if(RegSetValueEx(prog_key, + keys[i].name, + 0, + keys[i].type, + ptr, + siz) != ERROR_SUCCESS) + goto error; + } + RegCloseKey(prog_key); + return 0; +error: + if(key_opened) + RegCloseKey(prog_key); + return 1; +} + +static int do_remove_keys(char *servicename, const char *prog_key_name){ + HKEY prog_key; + if(RegOpenKeyEx(BASE_KEY, + prog_key_name, + 0, + KEY_ALL_ACCESS, + &prog_key) != ERROR_SUCCESS) + return -1; + if(RegDeleteKey(prog_key,servicename) != ERROR_SUCCESS){ + RegCloseKey(prog_key); + return -1; + } + RegCloseKey(prog_key); + return 0; +} + +int remove_keys(char *servicename){ + int ret; + + if((ret = do_remove_keys(servicename, PROG_KEY)) < 0){ + if(!do_remove_keys(servicename, OLD_PROG_KEY)) + return 1; + else + return -1; + } + return ret; +} + + +RegEntryDesc *get_all_keys(void){ + RegEntryDesc *res = malloc(10*sizeof(RegEntryDesc)); + int res_siz = 10; + int ndx = 0; + HKEY prog_key; + int key_opened = 0; + DWORD enum_index; + char name[MAX_KEY_LEN]; + DWORD namelen; + char class[MAX_KEY_LEN]; + DWORD classlen; + FILETIME ft; + + res[ndx].servicename = NULL; + if(RegOpenKeyEx(BASE_KEY, PROG_KEY, 0, + KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, + &prog_key) != ERROR_SUCCESS) + goto error; + key_opened = 1; + for(enum_index = 0, namelen = MAX_KEY_LEN, classlen = MAX_KEY_LEN; + ERROR_SUCCESS == RegEnumKeyEx(prog_key, + enum_index, + name, + &namelen, + NULL, + class, + &classlen, + &ft); + ++enum_index, namelen = MAX_KEY_LEN, classlen = MAX_KEY_LEN){ + if(ndx >= res_siz - 1) + res = realloc(res, (res_siz += 10)*sizeof(RegEntryDesc)); + if(!(res[ndx].entries = get_keys(name))) + goto error; + res[ndx].servicename = strdup(name); + res[++ndx].servicename = NULL; + } + RegCloseKey(prog_key); + return res; +error: + if(key_opened) + RegCloseKey(prog_key); + free_all_keys(res); + return NULL; +} + +int register_logkeys(void){ + HKEY key; + DWORD disposition; + DWORD types = EVENTLOG_ERROR_TYPE | + EVENTLOG_WARNING_TYPE | + EVENTLOG_INFORMATION_TYPE; + DWORD catcount=1; + char filename[2048]; + DWORD fnsiz=2048; + + if(RegCreateKeyEx(HKEY_LOCAL_MACHINE, + LOG_ROOT LOG_APP_KEY, 0, + NULL, REG_OPTION_NON_VOLATILE, + KEY_SET_VALUE, NULL, + &key, &disposition) != ERROR_SUCCESS) + return -1; + if(!GetModuleFileName(NULL, filename, fnsiz)) + return -1; + if(RegSetValueEx(key, "EventMessageFile", + 0, REG_EXPAND_SZ, (LPBYTE) filename, + strlen(filename)+1) != ERROR_SUCCESS) + return -1; + if(RegSetValueEx(key, "TypesSupported", + 0, REG_DWORD, (LPBYTE) &types, + sizeof(DWORD)) != ERROR_SUCCESS) + return -1; + return 0; +} + diff --git a/erts/etc/win32/erlsrv/erlsrv_registry.h b/erts/etc/win32/erlsrv/erlsrv_registry.h new file mode 100644 index 0000000000..fbccc5416a --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_registry.h @@ -0,0 +1,76 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifndef _ERLSRV_REGISTRY_H +#define _ERLSRV_REGISTRY_H + +typedef struct _reg_entry { + char *name; + DWORD type; + union { + char *bytes; + DWORD value; + struct { + char *bytes; + char *unexpanded; + } expand; + } data; +} RegEntry; + +typedef struct _reg_entry_desc { + char *servicename; + RegEntry *entries; +} RegEntryDesc; + +enum { + StopAction, + OnFail, + Machine, + Env, + WorkDir, + Priority, + SName, + Name, + Args, + DebugType, + InternalServiceName, + Comment +}; + +#define ON_FAIL_IGNORE 0 +#define ON_FAIL_RESTART 1 +#define ON_FAIL_REBOOT 2 +#define ON_FAIL_RESTART_ALWAYS 3 + +#define DEBUG_TYPE_NO_DEBUG 0 +#define DEBUG_TYPE_NEW 1 +#define DEBUG_TYPE_REUSE 2 +#define DEBUG_TYPE_CONSOLE 3 + +extern int num_reg_entries; + +RegEntry *empty_reg_tab(void); +void free_keys(RegEntry *keys); +void free_all_keys(RegEntryDesc *descs); +RegEntry *get_keys(char *servicename); +int set_keys(char *servicename, RegEntry *keys); +RegEntryDesc *get_all_keys(void); +int remove_keys(char *servicename); +int register_logkeys(void); +#endif /* _ERLSRV_REGISTRY_H */ + diff --git a/erts/etc/win32/erlsrv/erlsrv_service.c b/erts/etc/win32/erlsrv/erlsrv_service.c new file mode 100644 index 0000000000..a58ee862c5 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_service.c @@ -0,0 +1,966 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#include <windows.h> +#include <winsvc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "erlsrv_global.h" +#include "erlsrv_registry.h" +#include "erlsrv_util.h" +#include "erlsrv_service.h" + +static HANDLE eventStop; + +static HANDLE eventKillErlang; + +static CRITICAL_SECTION crit; + +static SERVICE_STATUS_HANDLE statusHandle; + +static DWORD currentState; + +static void fill_status(SERVICE_STATUS *status){ + status->dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status->dwCurrentState = 0; + status->dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; + status->dwWin32ExitCode = NO_ERROR; + status->dwServiceSpecificExitCode = 0; + status->dwCheckPoint = 0; + status->dwWaitHint = 0; +} + +static BOOL set_start_pending(int waithint, int checkpoint){ + SERVICE_STATUS stat; + fill_status(&stat); + EnterCriticalSection(&crit); + currentState = stat.dwCurrentState = SERVICE_START_PENDING; + LeaveCriticalSection(&crit); + stat.dwControlsAccepted = 0; + stat.dwCheckPoint = checkpoint; + stat.dwWaitHint = waithint; + return SetServiceStatus(statusHandle, &stat); +} + +static BOOL set_stop_pending(int waithint, int checkpoint){ + SERVICE_STATUS stat; + fill_status(&stat); + EnterCriticalSection(&crit); + currentState = stat.dwCurrentState = SERVICE_STOP_PENDING; + LeaveCriticalSection(&crit); + stat.dwControlsAccepted = 0; + stat.dwCheckPoint = checkpoint; + stat.dwWaitHint = waithint; + return SetServiceStatus(statusHandle, &stat); +} + +static BOOL set_running(){ + SERVICE_STATUS stat; + fill_status(&stat); + EnterCriticalSection(&crit); + currentState = stat.dwCurrentState = SERVICE_RUNNING; + LeaveCriticalSection(&crit); + return SetServiceStatus(statusHandle, &stat); +} + +static BOOL set_stopped(int error){ + SERVICE_STATUS stat; + fill_status(&stat); + EnterCriticalSection(&crit); + currentState = stat.dwCurrentState = SERVICE_STOPPED; + LeaveCriticalSection(&crit); + stat.dwWin32ExitCode = error; + return SetServiceStatus(statusHandle, &stat); +} + +static BOOL reset_current(){ + SERVICE_STATUS stat; + fill_status(&stat); + EnterCriticalSection(&crit); + stat.dwCurrentState = currentState; + LeaveCriticalSection(&crit); + return SetServiceStatus(statusHandle, &stat); +} + +static VOID WINAPI handler(DWORD control){ + char buffer[1024]; + sprintf(buffer,"handler called with control = %d.",(int) control); + log_debug(buffer); + switch(control){ + case SERVICE_CONTROL_STOP: + set_stop_pending(30000,1); + SetEvent(eventStop); + return; + case SERVICE_CONTROL_SHUTDOWN: + return; + default: + reset_current(); + break; + } + return; +} + +typedef struct _server_info { + RegEntry *keys; + PROCESS_INFORMATION info; + HANDLE erl_stdin; + char *event_name; +} ServerInfo; + + +typedef struct { + BOOL initialized; + TOKEN_DEFAULT_DACL *defdacl; + PACL newacl; + PSID adminsid; +} SaveAclStruct; + + +static BOOL reset_acl(SaveAclStruct *save_acl){ + HANDLE tokenh; + + if(!save_acl->initialized) + return FALSE; + if(!OpenProcessToken(GetCurrentProcess(), + TOKEN_READ|TOKEN_WRITE,&tokenh)){ + log_warning("Failed to open access token."); + return FALSE; + } + save_acl->initialized = FALSE; + if(!SetTokenInformation(tokenh, + TokenDefaultDacl, + save_acl->defdacl, + sizeof(TOKEN_DEFAULT_DACL))){ + log_warning("Failed to get default ACL from token."); + CloseHandle(tokenh); + LocalFree(save_acl->defdacl); + LocalFree(save_acl->newacl); + FreeSid(save_acl->adminsid); + return FALSE; + } + CloseHandle(tokenh); + LocalFree(save_acl->defdacl); + LocalFree(save_acl->newacl); + FreeSid(save_acl->adminsid); + return TRUE; +} + + +static BOOL new_acl(SaveAclStruct *save_acl){ + HANDLE tokenh; + TOKEN_DEFAULT_DACL newdacl; + DWORD required; + PACL oldacl; + PACL newacl; + int i; + ACL_SIZE_INFORMATION si; + size_t newsize; + PSID extra_sid; + SID_IDENTIFIER_AUTHORITY nt_auth = SECURITY_NT_AUTHORITY; + TOKEN_DEFAULT_DACL dummy; + + save_acl->initialized = FALSE; + if(!OpenProcessToken(GetCurrentProcess(), + TOKEN_READ|TOKEN_WRITE,&tokenh)){ + log_warning("Failed to open access token."); + return FALSE; + } + save_acl->defdacl = &dummy; + required = sizeof(TOKEN_DEFAULT_DACL); + GetTokenInformation(tokenh, + TokenDefaultDacl, + &(save_acl->defdacl), + sizeof(TOKEN_DEFAULT_DACL), + &required); + if(required == 0){ + log_warning("Failed to get any ACL info from token."); + CloseHandle(tokenh); + return FALSE; + } + save_acl->defdacl = LocalAlloc(LPTR,required); + if(!GetTokenInformation(tokenh, + TokenDefaultDacl, + save_acl->defdacl, + required, + &required)){ +#ifdef HARDDEBUG + { + char *mes; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &mes, + 0, + NULL ); + log_info(mes); + LocalFree(mes); + } +#endif + log_warning("Failed to get default ACL from token."); + CloseHandle(tokenh); + return FALSE; + } + + oldacl = save_acl->defdacl->DefaultDacl; + if(!GetAclInformation(oldacl, &si, sizeof(si), + AclSizeInformation)){ + log_warning("Failed to get size information for ACL"); + CloseHandle(tokenh); + return FALSE; + } + + if(!AllocateAndInitializeSid(&nt_auth, + 2, + SECURITY_BUILTIN_DOMAIN_RID, + DOMAIN_ALIAS_RID_ADMINS, + 0, + 0, + 0, + 0, + 0, + 0, + &extra_sid)){ + log_warning("Failed to initialize administrator SID."); + CloseHandle(tokenh); + return FALSE; + } + + newsize = si.AclBytesInUse + sizeof(ACL) + + sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(extra_sid); + + newacl = LocalAlloc(LPTR,newsize); + + if(!InitializeAcl(newacl, newsize, ACL_REVISION)){ + log_warning("Failed to initialize new ACL."); + LocalFree(newacl); + FreeSid(extra_sid); + CloseHandle(tokenh); + return FALSE; + } + + for(i=0;i<((int)si.AceCount);++i){ + ACE_HEADER *ace_header; + if (!GetAce (oldacl, i, &ace_header)){ + log_warning("Failed to get ACE from old ACL."); + LocalFree(newacl); + FreeSid(extra_sid); + CloseHandle(tokenh); + return FALSE; + } + if(!AddAce(newacl,ACL_REVISION,0xffffffff,ace_header, + ace_header->AceSize)){ + log_warning("Failed to set ACE in new ACL."); + LocalFree(newacl); + FreeSid(extra_sid); + CloseHandle(tokenh); + return FALSE; + } + } + if(!AddAccessAllowedAce(newacl, + ACL_REVISION2, + PROCESS_ALL_ACCESS, + extra_sid)){ + log_warning("Failed to add system ACE to new ACL."); + LocalFree(newacl); + FreeSid(extra_sid); + return FALSE; + } + + newdacl.DefaultDacl = newacl; + if(!SetTokenInformation(tokenh, + TokenDefaultDacl, + &newdacl, + sizeof(newdacl))){ + log_warning("Failed to set token information"); + LocalFree(newacl); + FreeSid(extra_sid); + CloseHandle(tokenh); + return FALSE; + } + save_acl->initialized = TRUE; + save_acl->newacl = newacl; + save_acl->adminsid = extra_sid; + CloseHandle(tokenh); + + return TRUE; +} + +static char **find_arg(char **arg, char *str){ + char *tmp; + int len; + + str = strdup(str); + if((tmp = strchr(str,'=')) == NULL) + goto fail; + tmp++; + *tmp = '\0'; + len = tmp - str; + while(*arg != NULL){ + if(!_strnicmp(*arg,str,len)){ + free(str); + return arg; + } + ++arg; + } +fail: + free(str); + return NULL; +} + +static char **merge_environment(char *current, char *add){ + char **c_arg = env_to_arg(envdup(current)); + char **a_arg = env_to_arg(envdup(add)); + char **new; + char **tmp; + int i,j; + + for(i=0;c_arg[i] != NULL;++i) + ; + for(j=0;a_arg[j] != NULL;++j) + ; + + new = malloc(sizeof(char *)*(i + j + 3)); + + for(i = 0; c_arg[i] != NULL; ++i) + new[i] = strdup(c_arg[i]); + + new[i] = NULL; + + for(j = 0; a_arg[j] != NULL; ++j){ + if((tmp = find_arg(new,a_arg[j])) != NULL){ + free(*tmp); + *tmp = strdup(a_arg[j]); + } else { + new[i++] = strdup(a_arg[j]); + new[i] = NULL; + } + } + free(arg_to_env(c_arg)); + free(arg_to_env(a_arg)); + return new; +} + + +static char *get_next_debug_file(char *prefix){ + char *buffer = malloc(strlen(prefix)+12); + int i; + for(i=1;i<100;++i){ + sprintf(buffer,"%s.%d",prefix,i); + if(GetFileAttributes(buffer) == 0xFFFFFFFF) + return buffer; + } + return NULL; +} + + + +static BOOL start_a_service(ServerInfo *srvi){ + STARTUPINFO start; + char execbuff[MAX_PATH*4]; /* FIXME: Can get overflow! */ + char namebuff[MAX_PATH]; + char errbuff[MAX_PATH*4]; /* hmmm.... */ + HANDLE write_pipe = NULL, read_pipe = NULL; + SECURITY_ATTRIBUTES pipe_security; + SECURITY_ATTRIBUTES attr; + HANDLE nul; + SaveAclStruct save_acl; + char *my_environ; + BOOL console_allocated = FALSE; + + if(!(*(srvi->keys[Env].data.bytes))){ + my_environ = NULL; + } else { + char *tmp; + char **merged = merge_environment((tmp = GetEnvironmentStrings()), + srvi->keys[Env].data.bytes); + FreeEnvironmentStrings(tmp); + my_environ = arg_to_env(merged); + } + + if(!*(srvi->keys[Machine].data.bytes) || + (!*(srvi->keys[SName].data.bytes) && + !*(srvi->keys[Name].data.bytes))){ + log_error("Not enough parameters for erlang service."); + if(my_environ) + free(my_environ); + return FALSE; + } + + if(*(srvi->keys[SName].data.bytes)) + sprintf(namebuff,"-nohup -sname %s",srvi->keys[SName].data.bytes); + else + sprintf(namebuff,"-nohup -name %s",srvi->keys[Name].data.bytes); + + if(srvi->keys[DebugType].data.value == DEBUG_TYPE_CONSOLE) + strcat(namebuff," -keep_window"); + + if (srvi->event_name != NULL) { + sprintf(execbuff,"\"%s\" -service_event %s %s %s", + srvi->keys[Machine].data.bytes, + srvi->event_name, + namebuff, + srvi->keys[Args].data.bytes); + } else { + sprintf(execbuff,"\"%s\" %s %s", + srvi->keys[Machine].data.bytes, + namebuff, + srvi->keys[Args].data.bytes); + } + + memset (&start, 0, sizeof (start)); + start.cb = sizeof (start); + start.dwFlags = STARTF_USESHOWWINDOW; + start.wShowWindow = SW_HIDE; + + /* Console debugging implies no working StopAction */ + if(srvi->keys[DebugType].data.value == DEBUG_TYPE_CONSOLE) { + COORD coord = {80,999}; + if(console_allocated = AllocConsole()) + SetConsoleScreenBufferSize(GetStdHandle(STD_OUTPUT_HANDLE),coord); + else + log_warning("Unable to allocate debugging console!"); + } else if(*(srvi->keys[StopAction].data.bytes) || + srvi->keys[DebugType].data.value != DEBUG_TYPE_NO_DEBUG){ + pipe_security.nLength = sizeof(pipe_security); + pipe_security.lpSecurityDescriptor = NULL; + pipe_security.bInheritHandle = TRUE; + if(!CreatePipe(&read_pipe,&write_pipe,&pipe_security,0)){ + log_error("Could not create pipe for erlang service."); + if(my_environ) + free(my_environ); + return FALSE; + } + if(srvi->keys[DebugType].data.value != DEBUG_TYPE_NO_DEBUG){ + char *filename; + if(*(srvi->keys[WorkDir].data.bytes)){ + filename = malloc(strlen(srvi->keys[WorkDir].data.bytes) + 1 + + strlen(service_name)+strlen(".debug")+1); + sprintf(filename,"%s\\%s.debug", + srvi->keys[WorkDir].data.bytes, + service_name); + } else { + filename = malloc(strlen(service_name)+strlen(".debug")+1); + sprintf(filename,"%s.debug",service_name); + } + log_debug(filename); + + if(srvi->keys[DebugType].data.value == DEBUG_TYPE_NEW){ + char *tmpfn = get_next_debug_file(filename); + if(tmpfn){ + free(filename); + filename = tmpfn; + } else { + log_warning("Number of debug files exceeds system defined " + "limit, reverting to DebugType: reuse. "); + } + } + + + nul = CreateFile(filename, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &pipe_security, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + free(filename); + } else { /* Not debugging */ + nul = CreateFile("NUL", + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + &pipe_security, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + if(nul == NULL){ + log_error((srvi->keys[DebugType].data.value != DEBUG_TYPE_NO_DEBUG) + ? "Could not create debug file. " + "(Working directory not valid?)" + : "Cold not open NUL!"); + start.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + start.hStdError = GetStdHandle(STD_ERROR_HANDLE); + } + start.hStdOutput = nul; + start.hStdError = nul; + start.hStdInput = read_pipe; + start.dwFlags |= STARTF_USESTDHANDLES; + } + + attr.nLength = sizeof(attr); + attr.lpSecurityDescriptor = NULL; + attr.bInheritHandle = TRUE; + + new_acl(&save_acl); + + if(!CreateProcess(NULL, + execbuff, + &attr, + NULL, + (read_pipe != NULL), + CREATE_DEFAULT_ERROR_MODE | + (srvi->keys[Priority].data.value), + my_environ, + (*(srvi->keys[WorkDir].data.bytes)) ? + srvi->keys[WorkDir].data.bytes : NULL, + &start, + &(srvi->info))){ + sprintf(errbuff,"Could not start erlang service" + "with commandline \"%s\".", + service_name, + execbuff + ); + log_error(errbuff); + if(read_pipe != NULL){ + CloseHandle(read_pipe); + CloseHandle(write_pipe); + if(nul != NULL) + CloseHandle(nul); + } + if(console_allocated) + FreeConsole(); + reset_acl(&save_acl); + if(my_environ) + free(my_environ); + return FALSE; + } + if(console_allocated) + FreeConsole(); +#ifdef HARDDEBUG + sprintf(errbuff, + "Started %s with the following commandline: " + "%s",service_name,execbuff); + log_debug(errbuff); +#endif + if(read_pipe != NULL){ + CloseHandle(read_pipe); + if(nul != NULL) + CloseHandle(nul); + srvi->erl_stdin = write_pipe; + } + + reset_acl(&save_acl); + if(my_environ) + free(my_environ); + return TRUE; +} + +static HANDLE create_erlang_event(char *event_name) +{ + HANDLE e; + if ((e = OpenEvent(EVENT_ALL_ACCESS,FALSE,event_name)) == NULL) { + if ((e = CreateEvent(NULL, TRUE, FALSE, event_name)) == NULL) { + log_warning("Could not create or access erlang termination event"); + } + } else { + if (!ResetEvent(e)) { + log_warning("Could not reset erlang termination event."); + } + } + return e; +} + +static BOOL stop_erlang(ServerInfo *srvi, int waithint, + int *checkpoint){ + DWORD written = 0; + char *action = srvi->keys[StopAction].data.bytes; + DWORD towrite = strlen(action)+1; + char *toerl; + DWORD exitcode; + int i; + int kill; + + if(towrite > 2 && srvi->erl_stdin != NULL){ + toerl = malloc(towrite+1); + strcpy(toerl,action); + strcat(toerl,"\n"); + WriteFile(srvi->erl_stdin, toerl, towrite, &written,0); + free(toerl); + /* Give it 45 seconds to terminate */ + for(i=0;i<45;++i){ + if(WaitForSingleObject(srvi->info.hProcess, 1000) == + WAIT_OBJECT_0){ + GetExitCodeProcess(srvi->info.hProcess,&exitcode); + CloseHandle(srvi->info.hProcess); + CloseHandle(srvi->info.hThread); + return TRUE; + } + ++(*checkpoint); + set_stop_pending(waithint,*checkpoint); + } + log_warning("StopAction did not terminate erlang. Trying forced kill."); + } + log_debug("Terminating erlang..."); + kill = 1; + if(eventKillErlang != NULL && SetEvent(eventKillErlang) != 0){ + for(i=0;i<10;++i){ + if(WaitForSingleObject(srvi->info.hProcess, 1000) == WAIT_OBJECT_0){ + kill = 0; + break; + } + ++(*checkpoint); + set_stop_pending(waithint,*checkpoint); + } + } else { +#ifdef HARDDEBUG + { + char *mes; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &mes, + 0, + NULL ); + log_info(mes); + LocalFree(mes); + } +#endif + log_debug("Could not send control event to Erlang process"); + } + if(kill){ + log_warning("Using TerminateProcess to kill erlang."); + if(!TerminateProcess(srvi->info.hProcess,NO_ERROR)) + log_error("TerminateProcess failed"); + } + GetExitCodeProcess(srvi->info.hProcess,&exitcode); + CloseHandle(srvi->info.hProcess); + CloseHandle(srvi->info.hThread); + if (eventKillErlang != NULL) { + ResetEvent(eventKillErlang); + } + return TRUE; +} + +static BOOL enable_privilege(void) { + HANDLE ProcessHandle; + DWORD DesiredAccess = TOKEN_ADJUST_PRIVILEGES; + HANDLE TokenHandle; + TOKEN_PRIVILEGES Tpriv; + LUID luid; + ProcessHandle = GetCurrentProcess(); + OpenProcessToken(ProcessHandle, DesiredAccess, &TokenHandle); + LookupPrivilegeValue(0,SE_SHUTDOWN_NAME,&luid); + Tpriv.PrivilegeCount = 1; + Tpriv.Privileges[0].Luid = luid; + Tpriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + return AdjustTokenPrivileges(TokenHandle,FALSE,&Tpriv,0,0,0); +} + +static BOOL pull_service_name(void){ + SC_HANDLE scm; + DWORD sz = 1024; + static char service_name_buff[1024]; + if((scm = OpenSCManager(NULL, + NULL, + GENERIC_READ)) + == NULL){ + return FALSE; + } + if(!GetServiceDisplayName(scm,real_service_name,service_name_buff,&sz)) + return FALSE; + CloseServiceHandle(scm); + service_name = service_name_buff; + return TRUE; +} + + +static VOID WINAPI service_main_loop(DWORD argc, char **argv){ + int waithint = 30000; + int checkpoint = 1; + RegEntry *keys; + RegEntry *save_keys; + ServerInfo srvi; + HANDLE harr[2]; + FILETIME creationt,exitt,kernelt,usert; + LONGLONG creationl,exitl,diffl; + char event_name[MAX_PATH] = "ErlSrv_"; + char executable_name[MAX_PATH]; +#ifdef DEBUG + char errorbuff[2048]; /* FIXME... */ +#endif + int success_wait = NO_SUCCESS_WAIT; + + real_service_name = argv[0]; + if(!pull_service_name()){ + log_error("Could not get Display name of erlang service."); + set_stopped(ERROR_CANTREAD); + return; + } + + SetEnvironmentVariable((LPCTSTR) SERVICE_ENV, (LPCTSTR) service_name); + + strncat(event_name, service_name, MAX_PATH - strlen(event_name)); + event_name[MAX_PATH - 1] = '\0'; + + if(!GetModuleFileName(NULL, executable_name, MAX_PATH)){ + log_error("Unable to retrieve module file name, " EXECUTABLE_ENV + " will not be set."); + } else { + char quoted_exe_name[MAX_PATH+4]; + sprintf(quoted_exe_name, "\"%s\"", executable_name); + SetEnvironmentVariable((LPCTSTR) EXECUTABLE_ENV, + (LPCTSTR) quoted_exe_name); + } + + log_debug("Here we go, service_main_loop..."); + currentState = SERVICE_START_PENDING; + InitializeCriticalSection(&crit); + eventStop = CreateEvent(NULL,FALSE,FALSE,NULL); + if ((eventKillErlang = create_erlang_event(event_name)) != NULL) { + srvi.event_name = event_name; + } else { + srvi.event_name = NULL; + } + statusHandle = RegisterServiceCtrlHandler(real_service_name, &handler); + if(!statusHandle) + return; + set_start_pending(waithint,checkpoint); + keys = get_keys(service_name); + if(!keys){ + log_error("Could not get registry keys for erlang service."); + set_stopped(ERROR_CANTREAD); + return; + } + srvi.keys = keys; + srvi.erl_stdin = NULL; + + ++checkpoint; + if(!start_a_service(&srvi)){ + log_error("Could not start erlang machine"); + set_stopped(ERROR_PROCESS_ABORTED); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + set_start_pending(waithint,checkpoint); + set_running(); + success_wait = INITIAL_SUCCESS_WAIT; + harr[0] = srvi.info.hProcess; + harr[1] = eventStop; + for(;;){ + DWORD ret; + ret = WaitForMultipleObjects((DWORD) 2, + harr, + FALSE, + (success_wait == NO_SUCCESS_WAIT) ? + INFINITE : + SUCCESS_WAIT_TIME); + if(ret == WAIT_TIMEOUT){ + /* Just do the "success reporting" and continue */ + if(success_wait == INITIAL_SUCCESS_WAIT){ + log_info("Erlang service started successfully."); + } else { + log_warning("Erlang service restarted"); + } + success_wait = NO_SUCCESS_WAIT; + continue; + } + if(ret == WAIT_FAILED || (int)(ret-WAIT_OBJECT_0) >= 2){ + set_stopped(WAIT_FAILED); + log_error("Internal error, could not wait for objects."); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + ret -= WAIT_OBJECT_0; + if(((int) ret) == 1){ + /* Stop service... */ + checkpoint = 2; /* 1 is taken by the handler */ + set_stop_pending(waithint,checkpoint); + if(stop_erlang(&srvi,waithint,&checkpoint)){ + log_debug("Erlang machine is stopped"); + CloseHandle(eventStop); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + set_stopped(NO_ERROR); + if(srvi.erl_stdin) + CloseHandle(srvi.erl_stdin); + free_keys(keys); + return; + } else { + log_warning("Unable to stop erlang service."); + set_running(); + continue; + } + } + /* Reload the registry keys, they may have changed. */ + save_keys = keys; + keys = get_keys(service_name); + if(!keys){ + log_error("Could not reload registry keys."); + keys = srvi.keys = save_keys; + } else { +#ifdef HARDDEBUG + sprintf(errorbuff,"Reloaded the registry keys because %s stopped.", + service_name); + log_debug(errorbuff); +#endif /* HARDDEBUG */ + free_keys(save_keys); + srvi.keys = keys; + } + if(srvi.keys[OnFail].data.value == ON_FAIL_RESTART || + srvi.keys[OnFail].data.value == ON_FAIL_RESTART_ALWAYS){ + if(!GetProcessTimes(srvi.info.hProcess,&creationt, + &exitt,&kernelt,&usert)){ + DWORD rcode = GetLastError(); + log_error("Could not get process time of terminated process."); + CloseHandle(srvi.info.hProcess); + CloseHandle(srvi.info.hThread); + CloseHandle(eventStop); + if(srvi.erl_stdin) + CloseHandle(srvi.erl_stdin); + set_stopped(rcode); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + CloseHandle(srvi.info.hProcess); + CloseHandle(srvi.info.hThread); + if(srvi.erl_stdin) + CloseHandle(srvi.erl_stdin); + srvi.erl_stdin = NULL; + memcpy(&creationl,&creationt,sizeof(FILETIME)); + memcpy(&exitl,&exitt,sizeof(FILETIME)); + diffl = exitl - creationl; + diffl /= 10000000; +#ifdef DEBUG + sprintf(errorbuff,"Process lived for %d seconds", (int) diffl); + log_debug(errorbuff); +#endif + + if(diffl > CYCLIC_RESTART_LIMIT || + srvi.keys[OnFail].data.value == ON_FAIL_RESTART_ALWAYS){ + if(!start_a_service(&srvi)){ + log_error("Unable to restart failed erlang service, " + "aborting."); + CloseHandle(eventStop); + set_stopped(ERROR_PROCESS_ABORTED); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + log_warning("Restarted erlang machine."); + if(diffl <= CYCLIC_RESTART_LIMIT) + log_warning("Possible cyclic restarting of erlang machine."); + success_wait = RESTART_SUCCESS_WAIT; + harr[0] = srvi.info.hProcess; + } else { + if(success_wait == INITIAL_SUCCESS_WAIT){ + log_error("Erlang machine stopped instantly " + "(distribution name conflict?). " + "The service is not restarted, ignoring OnFail option."); + } else { + log_error("Erlang machine seems to die " + "continously, not restarted."); + } + CloseHandle(eventStop); + set_stopped(ERROR_PROCESS_ABORTED); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + } else if(srvi.keys[OnFail].data.value == ON_FAIL_REBOOT){ + log_error("Rebooting because erlang machine stopped."); + enable_privilege(); + if(!InitiateSystemShutdown("",NULL,0,TRUE,TRUE)){ + log_error("Failed to reboot!"); +#ifdef HARDDEBUG + { + char *mes; + FormatMessage( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &mes, + 0, + NULL ); + log_debug(mes); + LocalFree(mes); + } +#endif + CloseHandle(srvi.info.hProcess); + CloseHandle(eventStop); + if(srvi.erl_stdin != NULL) + CloseHandle(srvi.erl_stdin); + set_stopped(NO_ERROR); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + } else { + DWORD ecode = NO_ERROR; + if(success_wait == NO_SUCCESS_WAIT){ + log_warning("Erlang machine volountarily stopped. " + "The service is not restarted as OnFail " + "is set to ignore."); + } else { + log_error("Erlang machine stopped instantly " + "(distribution name conflict?). " + "The service is not restarted as OnFail is set to ignore."); + ecode = ERROR_PROCESS_ABORTED; + } + CloseHandle(srvi.info.hProcess); + CloseHandle(eventStop); + if(srvi.erl_stdin != NULL) + CloseHandle(srvi.erl_stdin); + set_stopped(ecode); + if (eventKillErlang != NULL) { + CloseHandle(eventKillErlang); + } + free_keys(keys); + return; + } + } +} + +int service_main(int argc, char **argv){ + char dummy_name[] = ""; + SERVICE_TABLE_ENTRY serviceTable[] = + { + { dummy_name, + (LPSERVICE_MAIN_FUNCTION) service_main_loop}, + { NULL, NULL } + }; + BOOL success; + success = + StartServiceCtrlDispatcher(serviceTable); + if (!success) + log_error("Could not initiate service"); + log_debug("service_main done its job"); + return 0; +} + diff --git a/erts/etc/win32/erlsrv/erlsrv_service.h b/erts/etc/win32/erlsrv/erlsrv_service.h new file mode 100644 index 0000000000..3eab275836 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_service.h @@ -0,0 +1,32 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifndef _ERLSRV_SERVICE_H +#define _ERLSRV_SERVICE_H + +#define CYCLIC_RESTART_LIMIT 10 /* Seconds */ +#define SUCCESS_WAIT_TIME (10*1000) /* Wait 5 s before reporting a service + as really started */ +#define NO_SUCCESS_WAIT 0 +#define INITIAL_SUCCESS_WAIT 1 +#define RESTART_SUCCESS_WAIT 2 + + +int service_main(int argc, char **argv); + +#endif /* _ERLSRV_SERVICE_H */ diff --git a/erts/etc/win32/erlsrv/erlsrv_util.c b/erts/etc/win32/erlsrv/erlsrv_util.c new file mode 100644 index 0000000000..da3c6f5ef7 --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_util.c @@ -0,0 +1,154 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#include <windows.h> +#include <winsvc.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "erlsrv_global.h" +#include "erlsrv_util.h" +#include "erlsrv_logmess.h" + +char *service_name = ""; +char *real_service_name = ""; + +void log_warning(char *mess){ + HANDLE logh; + char *strings[] = {service_name, mess , NULL}; + + if(!(logh = RegisterEventSource(NULL,APP_NAME))) + return; + ReportEvent(logh, EVENTLOG_WARNING_TYPE, 0, MSG_WARNING, + NULL, 2, 0, strings, NULL); + DeregisterEventSource(logh); +} + +void log_error(char *mess){ + HANDLE logh; + char *strings[] = {service_name, mess , NULL}; + + if(!(logh = RegisterEventSource(NULL,APP_NAME))) + return; + ReportEvent(logh, EVENTLOG_ERROR_TYPE, 0, MSG_ERROR, + NULL, 2, 0, strings, NULL); + DeregisterEventSource(logh); +} + +void log_info(char *mess){ + HANDLE logh; + char *strings[] = {service_name, mess , NULL}; + + if(!(logh = RegisterEventSource(NULL,APP_NAME))) + return; + ReportEvent(logh, EVENTLOG_INFORMATION_TYPE, 0, MSG_INFO, + NULL, 2, 0, strings, NULL); + DeregisterEventSource(logh); +} + +#ifndef NDEBUG +void log_debug(char *mess){ + char *buff=malloc(strlen(mess)+100); + sprintf(buff,"DEBUG! %s",mess); + log_info(buff); + free(buff); +} +#endif + +char *envdup(char *env){ + char *tmp; + int len; + for(tmp = env; *tmp != '\0'; tmp += strlen(tmp)+1) + ; + len = (tmp - env) + 1; + if(len == 1) + ++len; + tmp = malloc(len); + memcpy(tmp,env,len); + return tmp; +} + +char **env_to_arg(char *env){ + char **ret; + char *tmp; + int i; + int num_strings = 0; + for(tmp = env; *tmp != '\0'; tmp += strlen(tmp)+1) + ++num_strings; + /* malloc enough to insert ONE string */ + ret = malloc(sizeof(char *) * (num_strings + 2)); + i = 0; + for(tmp = env; *tmp != '\0'; tmp += strlen(tmp)+1){ + ret[i++] = strdup(tmp); + } + ret[i] = NULL; + free(env); + return ret; +} + +static int compare(const void *a, const void *b){ + char *s1 = *((char **) a); + char *s2 = *((char **) b); + char *e1 = strchr(s1,'='); + char *e2 = strchr(s2,'='); + int ret; + int len; + + if(!e1) + e1 = s1 + strlen(s1); + if(!e2) + e2 = s2 + strlen(s2); + + if((e1 - s1) > (e2 - s2)) + len = (e2 - s2); + else + len = (e1 - s1); + + ret = _strnicmp(s1,s2,len); + if(ret == 0) + return ((e1 - s1) - (e2 - s2)); + else + return ret; +} + +char *arg_to_env(char **arg){ + char *block; + char *pek; + int i; + int totlen = 1; /* extra '\0' */ + + for(i=0;arg[i] != NULL;++i) + totlen += strlen(arg[i])+1; + /* sort the environment vector */ + qsort(arg,i,sizeof(char *),&compare); + if(totlen == 1){ + block = malloc(2); + block[0] = block[1] = '\0'; + } else { + block = malloc(totlen); + pek = block; + for(i=0; arg[i] != NULL; ++i){ + strcpy(pek, arg[i]); + free(arg[i]); + pek += strlen(pek)+1; + } + *pek = '\0'; + } + free(arg); + return block; +} diff --git a/erts/etc/win32/erlsrv/erlsrv_util.h b/erts/etc/win32/erlsrv/erlsrv_util.h new file mode 100644 index 0000000000..b98a6cd3ef --- /dev/null +++ b/erts/etc/win32/erlsrv/erlsrv_util.h @@ -0,0 +1,50 @@ +/* + * %CopyrightBegin% + * + * Copyright Ericsson AB 1998-2009. All Rights Reserved. + * + * The contents of this file are subject to the Erlang Public License, + * Version 1.1, (the "License"); you may not use this file except in + * compliance with the License. You should have received a copy of the + * Erlang Public License along with this software. If not, it can be + * retrieved online at http://www.erlang.org/. + * + * Software distributed under the License is distributed on an "AS IS" + * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See + * the License for the specific language governing rights and limitations + * under the License. + * + * %CopyrightEnd% + */ +#ifndef _ERLSRV_UTIL_H +#define _ERLSRV_UTIL_H + +extern char *service_name; +extern char *real_service_name; +void log_warning(char *mess); +void log_error(char *mess); +void log_info(char *mess); + +char *envdup(char *env); +/* +** Call before env_to_arg to get a 'freeable' environment block. +*/ + +char *arg_to_env(char **arg); +/* +** Frees the argument list before returning! +*/ + +char **env_to_arg(char *env); +/* +** Frees the environment block before returning! +*/ + + +#ifndef NDEBUG +void log_debug(char *mess); +#else +#define log_debug(mess) /* Debug removed */ +#endif + +#endif /* _ERLSRV_UTIL_H */ |