/*
 * %CopyrightBegin%
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Copyright Ericsson AB 1998-2025. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions 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 fwprintf(stderr,L"argv[0]:%s line %d\n",argv[0],__LINE__)
/* #define HARDDEBUG 1 */

#include <fcntl.h>

/* Really HAS to correcpond to the enum in erlsrv_registry.h */
static wchar_t *arg_tab[] = {
  L"stopaction", L"st",
  L"onfail", L"on",
  L"machine", L"m",
  L"env", L"e",
  L"workdir", L"w",
  L"priority", L"p",
  L"sname", L"sn",
  L"name", L"n",
  L"args", L"ar",
  L"debugtype", L"d",
  L"internalservicename",L"i",
  L"comment",L"c",
  NULL, NULL
};

static wchar_t *generate_real_service_name(wchar_t *display_name){
  SYSTEMTIME systime;
  FILETIME ftime;
  int len=(wcslen(display_name)+(8*2)+1);
  wchar_t *buff;
  wchar_t *tmp = _wcsdup(display_name);
  int i;
  buff = (wchar_t*) malloc(len*sizeof(wchar_t));

  /* 2 Hex chars for each byte in a DWORD */
  GetSystemTime(&systime);
  SystemTimeToFileTime(&systime,&ftime);
  /* Remove trailing version info to avoid user confusion */
  for(i = (wcslen(tmp)-1);i > 0; --i)
    if(tmp[i] == L'_'){
      tmp[i] = L'\0';
      break;
    }
  swprintf(buff,len,L"%s%08x%08x",tmp,
	   ftime.dwHighDateTime,
	   ftime.dwLowDateTime);
  free(tmp);
  return buff;
}

static int lookup_arg(wchar_t *arg){
  int i;
  if(*arg != L'-' && *arg != L'/')
    return -1;
  for(i=0; arg_tab[i] != NULL; i += 2){
    if(!_wcsnicmp(arg_tab[i],arg+1,wcslen(arg+1)) &&
       !_wcsnicmp(arg_tab[i+1],arg+1,wcslen(arg_tab[i+1])))
      return (i / 2);
  }
  return -1;
}



wchar_t *edit_env(wchar_t *edit, wchar_t *oldenv){
  wchar_t **arg;
  wchar_t *value;
  wchar_t *name = wcsdup(edit);
  int i;
  wchar_t *tmp;
  arg = env_to_arg(oldenv);
  value = wcschr(name,L'=');
  if(value){
    *(value++) = L'\0';
    if(*value == L'\0')
      value = NULL;
  }
  for(i=0;arg[i] != NULL; ++i){
    tmp = wcschr(arg[i],L'=');
    if(((int) wcslen(name)) == (tmp - arg[i]) &&
       !_wcsnicmp(name,arg[i], tmp - arg[i]))
      break;
  }
  if(arg[i] != NULL){
    free(arg[i]);
    if(value){
      arg[i] = wcsdup(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] = wcsdup(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 );
  fwprintf(stderr,L"Error: %S",mes);
  LocalFree(mes);
}

static int get_last_error(void)
{
  return (last_error) ? last_error : GetLastError();
}

static BOOL install_service(void){
  SC_HANDLE scm;
  SC_HANDLE service;
  wchar_t filename[MAX_PATH + 3];
  DWORD fnsiz=MAX_PATH;
  wchar_t dependant[] = { L'L',L'a',L'n',L'm',L'a',L'n',
			  L'W',L'o',L'r',L'k',L's',L't',
			  L'a',L't',L'i',L'o',L'n',L'\0',L'\0'};
  
  if(!(fnsiz = GetModuleFileNameW(NULL, filename, fnsiz)))
    return FALSE;
  if(wcschr(filename,L' ')){
    memmove(filename+1,filename,fnsiz*sizeof(wchar_t));
    filename[0] = L'\"'; /* " */
    filename[fnsiz+1] = L'\"'; /* " */
    filename[fnsiz+2] = L'\0';
  }
  if((scm = OpenSCManager(NULL, 
			  NULL,  
			  SC_MANAGER_CONNECT | 
			  SC_MANAGER_CREATE_SERVICE))
     == NULL){
      last_error = GetLastError();
      return FALSE;
  }
  service = CreateServiceW(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 = OpenServiceW(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 = OpenServiceW(*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 = OpenServiceW(*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(wchar_t *comment) {
    SC_HANDLE scm; 
    SC_HANDLE service;
    SERVICE_DESCRIPTIONW sd;
    BOOL ret = TRUE;
    sd.lpDescription = comment;
    if (!open_service_config(&scm,&service)) {
	return FALSE;
    }
    if (!ChangeServiceConfig2W(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
    fwprintf(stderr,L"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)
  {
    fwprintf(stderr,L"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){
  wchar_t filename[MAX_PATH];
  wchar_t *ptr;


  if(!GetModuleFileNameW(NULL, filename, MAX_PATH))
    return FALSE;
  for(ptr = filename + wcslen(filename) - 1;
      ptr > filename && *ptr != L'\\';
      --ptr)
    ;
  if(*ptr == L'\\')
    ++ptr;
  *ptr = L'\0';

  ptr = (wchar_t*) malloc((wcslen(filename)+wcslen(ERLANG_MACHINE)+1)*sizeof(wchar_t));
  wcscpy(ptr,filename);
  wcscat(ptr,ERLANG_MACHINE);

  new[StopAction].data.string = L"";
  new[OnFail].data.value = ON_FAIL_IGNORE;
  new[Machine].data.string = ptr;
  new[Machine].data.expand.unexpanded =  ptr;
  new[Env].data.string = L"\0";
  new[WorkDir].data.string = new[WorkDir].data.expand.unexpanded = L"";
  new[Priority].data.value = NORMAL_PRIORITY_CLASS;
  new[SName].data.string = service_name;
  new[Name].data.string = L"";
  new[Args].data.string = new[Args].data.expand.unexpanded = L"";
  new[DebugType].data.value = DEBUG_TYPE_NO_DEBUG;
  new[InternalServiceName].data.string = real_service_name;
  new[Comment].data.string = L"";
  return TRUE;
}

int do_usage(wchar_t *arg0){
  wprintf(L"Usage:\n");
  wprintf(L"%s {set | add} <servicename>\n"
	 L"\t[-st[opaction] [<erlang shell command>]]\n"
	 L"\t[-on[fail] [{reboot | restart | restart_always}]]\n"
	 L"\t[-m[achine] [<erl-command>]]\n"
	 L"\t[-e[nv] [<variable>[=<value>]]]\n"
	 L"\t[-w[orkdir] [<directory>]]\n"
	 L"\t[-p[riority] [{low|high|realtime}]]\n"
	 L"\t[{-sn[ame] | -n[ame]} [<nodename>]]\n"
	 L"\t[-d[ebugtype] [{new|reuse|console}]]\n"
	 L"\t[-ar[gs] [<limited erl arguments>]]\n\n"
	 L"%s {start | start_disabled | stop | disable | enable} <servicename>\n\n"
	 L"%s remove <servicename>\n\n"
	 L"%s rename <servicename> <servicename>\n\n"
	 L"%s list [<servicename>]\n\n"
	 L"%s help\n\n",
	 arg0,arg0,arg0,arg0,arg0,arg0);
  wprintf(L"Manipulates Erlang system services on Windows NT.\n\n");
  wprintf(L"When no parameter to an option is specified, the option\n"
	 L"is reset to it's default value. To set an empty argument\n"
	 L"list, give option -args as last option on command line "
	 L"with\n"
	 L"no arguments.\n\n");
  wprintf(L"See Erlang documentation for full description.\n");
  return 0;
}

int do_manage(int argc, wchar_t **argv){
  wchar_t *action = argv[1];
  RegEntry *current = empty_reg_tab();
  
  if(argc < 3){
    fwprintf(stderr,L"%s: No servicename given!\n",argv[0]);
    do_usage(argv[0]);
    return 1;
  } 
  service_name = argv[2];
  if(!fetch_current(current)){
    fwprintf(stderr,L"%s: The service %s is not an erlsrv controlled service.\n",
	    argv[0],service_name);    
    return 1;
  }
  real_service_name = _wcsdup(current[InternalServiceName].data.string);
  free_keys(current);
  
  if(!_wcsicmp(action,L"start")){
    if(!start_service()){
      fwprintf(stderr,L"%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)){
	fwprintf(stderr,L"%s: Failed to start service %s.\n",
		argv[0],service_name);
	print_last_error();
	return 1;
      }
      wprintf(L"%s: Service %s started.\n",
	     argv[0],service_name);
      return 0;
    }
  }
  if(!_wcsicmp(action,L"start_disabled")){
    if(!enable_service()){
      fwprintf(stderr,L"%s: Failed to enable service %s.\n",
	      argv[0],service_name);
      print_last_error();
      return 1;
    } 
    if(!start_service() && get_last_error() != ERROR_SERVICE_ALREADY_RUNNING){
      fwprintf(stderr,L"%s: Failed to start service %s.\n",
	      argv[0],service_name);
      print_last_error();
      goto failure_starting;
    }
    
    if(!wait_service_trans(SERVICE_STOPPED, SERVICE_START_PENDING,
			   SERVICE_RUNNING, 60)){
      fwprintf(stderr,L"%s: Failed to start service %s.\n",
	      argv[0],service_name);
      print_last_error();
      goto failure_starting;
    }

    if(!disable_service()){
      fwprintf(stderr,L"%s: Failed to disable service %s.\n",
	      argv[0],service_name);
      print_last_error();
      return 1;
    } 
    wprintf(L"%s: Service %s started.\n",
	   argv[0],service_name);
    return 0;
  failure_starting:
    if(!disable_service()){
      fwprintf(stderr,L"%s: Failed to disable service %s.\n",
	      argv[0],service_name);
      print_last_error();
    } 
    return 1;
  }
  if(!_wcsicmp(action,L"stop")){
    if(!stop_service()){
      fwprintf(stderr,L"%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)){
	fwprintf(stderr,L"%s: Failed to stop service %s.\n",
		argv[0],service_name);
	print_last_error();
	return 1;
      }
      wprintf(L"%s: Service %s stopped.\n",
	     argv[0],service_name);
      return 0;
    }
  }
  if(!_wcsicmp(action,L"disable")){
#if 0
    if(stop_service()){
      wprintf(L"%s: Service %s stopped.\n",
	     argv[0],service_name);
    }
#endif
    if(!disable_service()){
      fwprintf(stderr,L"%s: Failed to disable service %s.\n",
	      argv[0],service_name);
      print_last_error();
      return 1;
    } else {
      wprintf(L"%s: Service %s disabled.\n",
	     argv[0],service_name);
      return 0;
    }
  }
  if(!_wcsicmp(action,L"enable")){
    if(!enable_service()){
      fwprintf(stderr,L"%s: Failed to enable service %s.\n",
	      argv[0],service_name);
      print_last_error();
      return 1;
    } else {
      wprintf(L"%s: Service %s enabled.\n",
	     argv[0],service_name);
      return 0;
    }
  }
  fwprintf(stderr,L"%s: Unrecignized argument %s.\n",
	  argv[0],action);
  return 1;
}

int do_add_or_set(int argc, wchar_t **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){
    fwprintf(stderr,L"%s: No servicename given!\n",argv[0]);
    do_usage(argv[0]);
    return 1;
  } 
  service_name = argv[2];
  if(!_wcsicmp(argv[1],L"add")){
    if(fetch_current(default_entries)){
      fwprintf(stderr,L"%s: A service with the name %s already "
	      L"exists.\n",
	      argv[0],service_name);
      return 1;
    }
    real_service_name = generate_real_service_name(service_name);
    if(!fill_in_defaults(new_entries)){
      fwprintf(stderr,L"%s: Internal error.\n", argv[0]);
      return 1;
    }
    add = 1;
  } else {
    if(!fetch_current(new_entries)){
      fwprintf(stderr,L"%s: No service with the name %s exists.\n",
	      argv[0], service_name);
      return 1;
    }
    real_service_name = new_entries[InternalServiceName].data.string;
  }

  if(!fill_in_defaults(default_entries)){
    fwprintf(stderr,L"%s: Internal error.\n", argv[0]);
    return 1;
  }

  /* make sure env is malloced... */
  new_entries[Env].data.string = envdup(new_entries[Env].data.string);

  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.string = 
	  default_entries[current].data.string;
	new_entries[current].data.expand.unexpanded = 
	  default_entries[current].data.expand.unexpanded;
      } else {
	new_entries[current].data.expand.unexpanded =
	  new_entries[current].data.string = argv[i+1];
	++i;
      }
      break;
    case SName:
      new_entries[Name].data.string = L"";
    case StopAction:
    case Name:
      if(i+1 >= argc ||
	 *argv[i+1] == L'-' || *argv[i+1] == L'/'){
	new_entries[current].data.string = 
	  default_entries[current].data.string;
      } else {
	new_entries[current].data.string = argv[i+1];
	++i;
      }
      break;
    case OnFail:
      if(i+1 >= argc ||
	 *argv[i+1] == L'-' || *argv[i+1] == L'/'){
	new_entries[current].data.value = 
	  default_entries[current].data.value;
      } else {
	if(!_wcsicmp(argv[i+1],L"reboot"))
	  new_entries[current].data.value = ON_FAIL_REBOOT;
	else if(!_wcsicmp(argv[i+1],L"restart"))
	  new_entries[current].data.value = ON_FAIL_RESTART;
	else if(!_wcsicmp(argv[i+1],L"restart_always"))
	  new_entries[current].data.value = ON_FAIL_RESTART_ALWAYS;
	else {
	  fwprintf(stderr,L"%s: Unrecognized keyword value %s.\n",
		  argv[0],argv[i+1]);
	  return 1;
	}
	++i;
      }
      break;
    case DebugType:
      if(i+1 >= argc ||
	 *argv[i+1] == L'-' || *argv[i+1] == L'/'){
	new_entries[current].data.value = 
	  default_entries[current].data.value;
      } else {
	if(!_wcsicmp(argv[i+1],L"new"))
	  new_entries[current].data.value = DEBUG_TYPE_NEW;
	else if(!_wcsicmp(argv[i+1],L"reuse"))
	  new_entries[current].data.value = DEBUG_TYPE_REUSE;
	else if(!_wcsicmp(argv[i+1],L"console"))
	  new_entries[current].data.value = DEBUG_TYPE_CONSOLE;
	else {
	  fwprintf(stderr,L"%s: Unrecognized keyword value %s.\n",
		  argv[0],argv[i+1]);
	  return 1;
	}
	++i;
      }
      break;
    case Priority:
      if(i+1 >= argc ||
	 *argv[i+1] == L'-' || *argv[i+1] == L'/'){
	new_entries[current].data.value = 
	  default_entries[current].data.value;
      } else {
	if(!_wcsicmp(argv[i+1],L"high"))
	  new_entries[current].data.value = HIGH_PRIORITY_CLASS;
	else if(!_wcsicmp(argv[i+1],L"low"))
	  new_entries[current].data.value = IDLE_PRIORITY_CLASS;
	else if(!_wcsicmp(argv[i+1],L"realtime"))
	  new_entries[current].data.value = REALTIME_PRIORITY_CLASS;
	else {
	  fwprintf(stderr,L"%s: Unrecognized keyword value %s.\n",
		  argv[0],argv[i+1]);
	  return 1;
	}
	++i;
      }
      break;
      
    case Env:
      if(i+1 >= argc ||
	 *argv[i+1] == L'-' || *argv[i+1] == L'/'){
	fwprintf(stderr,L"%s: %s requires a parameter.\n",
		argv[0],argv[i]);
	return 1;
      }
      new_entries[current].data.string = 
	edit_env(argv[i+1], new_entries[current].data.string);
      ++i;
      break;
    case InternalServiceName:
	if (!add) {
	    fwprintf(stderr,L"%s: %s only allowed when adding a new service.\n",
		    argv[0],argv[i]);
	    return 1;
	}
	if(i+1 >= argc){
	    fwprintf(stderr,L"%s: %s requires a parameter.\n",
		    argv[0],argv[i]);
	    return 1;
	}
	new_entries[InternalServiceName].data.expand.unexpanded =
	    new_entries[InternalServiceName].data.string = argv[i+1];
	++i;
	/* Discard old, should maybe be fred' but we'll exit anyway */
	real_service_name = new_entries[InternalServiceName].data.string;
	break;
    default:
      fwprintf(stderr,L"%s: Unrecognized option %s.\n", argv[0],
	      argv[i]);
      return 1;
    }
  }
  if(*new_entries[SName].data.string && 
     *new_entries[Name].data.string){
#if 0
    fwprintf(stderr,L"%s: Both -sname and -name specified.\n",
	    argv[0]);
    return 1;
#else
    new_entries[SName].data.string = L"";
#endif
  }
  if(add && !(*new_entries[SName].data.string) &&
     !(*new_entries[Name].data.string)){
    fwprintf(stderr,L"%s: Neither -sname nor -name specified.\n",
	    argv[0]);
    return 1;
  }

  if(add && !install_service()){
    fwprintf(stderr,L"%s: Unable to register %s 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)){
      fwprintf(stderr,L"%s: Warning, could not set correct interactive mode. %s\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.string)) {
	  fwprintf(stderr,L"%s: Warning, could not set correct "
		  L"service description (comment) %s",
		  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)
    wprintf(L"%s: Service %s added to system.\n",
	   argv[0], service_name);
  else
    wprintf(L"%s: Service %s updated.\n",
	   argv[0], service_name);
  return 0;
}

int do_rename(int argc, wchar_t **argv){
  RegEntry *current = empty_reg_tab();
  RegEntry *dummy = empty_reg_tab();
  SC_HANDLE scm;
  SC_HANDLE service;
  if(argc < 3){
    fwprintf(stderr,L"%s: No old servicename given!\n",argv[0]);
    do_usage(argv[0]);
    return 1;
  } 
  if(argc < 4){
    fwprintf(stderr,L"%s: No new servicename given!\n",argv[0]);
    do_usage(argv[0]);
    return 1;
  }
  service_name = argv[3];
  if(fetch_current(dummy)){
    fwprintf(stderr,L"%s: A service with the name %s already "
	    L"exists.\n",
	    argv[0],service_name);
    return 1;
  }
  service_name = argv[2];

  if(!fetch_current(current)){
    fwprintf(stderr,L"%s: Error, old service name %s does not exist.\n",
	    argv[0],service_name);
    return 1;
  }
  real_service_name = _wcsdup(current[InternalServiceName].data.string);
  if(!open_service_config(&scm,&service)){
    fwprintf(stderr,L"%s: Error, unable to communicate with service control"
	    L" manager.\n",
	    argv[0]);
    print_last_error();
    return 1;
  }
  if(!ChangeServiceConfigW(service,
			   SERVICE_NO_CHANGE,
			   SERVICE_NO_CHANGE,
			   SERVICE_NO_CHANGE,
			   NULL,
			   NULL,
			   NULL,
			   NULL,
			   NULL,
			   NULL,
			   argv[3])){
    fwprintf(stderr,L"%s: Error, unable to communicate with service control"
	    L" manager.\n",
	    argv[0]);
    print_last_error();
    CloseServiceHandle(scm);
    CloseServiceHandle(service);
    return 1;
  }
  CloseServiceHandle(scm);
  CloseServiceHandle(service);
    
  if(remove_keys(service_name) != 0)
    fwprintf(stderr,L"%s: Warning, old service parameter keys could not "
	    L"be removed, continuing.\n", argv[0]);
  /* Update registry */
  register_logkeys();
  set_keys(argv[3], current);
  
  wprintf(L"%s: Service %s renamed to %s.\n",
	 argv[0], service_name, argv[3]);
  return 0;
}

int do_remove(int argc, wchar_t **argv){
  RegEntry *current = empty_reg_tab();
  int rem_res;
  BOOL found;

  if(argc < 3){
    fwprintf(stderr,L"%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 = _wcsdup(current[InternalServiceName].data.string);
  } else {
      real_service_name = _wcsdup(service_name);
  }
  if(found)
      free_keys(current);
  if(stop_service() && !wait_service_trans(SERVICE_RUNNING, 
					   SERVICE_STOP_PENDING,
					   SERVICE_STOPPED, 60)){
      fwprintf(stderr,L"%s: Failed to stop running service %s.\n",
	      argv[0],service_name);
      print_last_error();
      return 1;
  }
  if(!remove_service()){
    fwprintf(stderr,L"%s: Unable to remove service (not enough "
	    L"privileges?)\n",argv[0]);
    print_last_error();
    return 1;
  }

  if((rem_res = remove_keys(service_name)) > 0){
    fwprintf(stderr,L"%s: Warning, service parameter keys belonged to old "
	    L"erlsrv version.\n", argv[0]);
    /* Backward compatibility... */
  } else if(rem_res < 0) {
    fwprintf(stderr,L"%s: Error, service parameter keys nonexistent.\n", 
	    argv[0]);
    return 1;
  }
  wprintf(L"%s: Service %s removed from system.\n",
	 argv[0], service_name);
  return 0;
}

BOOL list_one(wchar_t *servicename, RegEntry *keys, BOOL longlist){
  wchar_t *onfail;
  wchar_t *prio;
  wchar_t *debugtype;
  switch(keys[OnFail].data.value){
  case ON_FAIL_RESTART:
    onfail = L"restart";
    break;
  case ON_FAIL_RESTART_ALWAYS:
    onfail = L"restart_always";
    break;
  case ON_FAIL_REBOOT:
    onfail = L"reboot";
    break;
  default:
    onfail = L"ignore";
  }
  switch(keys[DebugType].data.value){
  case DEBUG_TYPE_NEW:
    debugtype = L"new";
    break;
  case DEBUG_TYPE_REUSE:
    debugtype = L"reuse";
    break;
  case DEBUG_TYPE_CONSOLE:
    debugtype = L"console";
    break;
  default:
    debugtype = L"none";
  }
  switch(keys[Priority].data.value){
  case HIGH_PRIORITY_CLASS:
    prio = L"high";
    break;
  case IDLE_PRIORITY_CLASS:
    prio = L"low";
    break;
  case REALTIME_PRIORITY_CLASS:
    prio = L"realtime";
    break;
  case NORMAL_PRIORITY_CLASS:
    prio = L"default";
    break;
  default:
    prio = L"unknown/faulty";
  }
  

  if(longlist){
    wchar_t *env = envdup(keys[Env].data.string);
    wchar_t **arg = env_to_arg(env);
    wchar_t **pek = arg;
    wprintf(L"Service name: %s\n",
	   servicename);
    wprintf(L"StopAction: %s\n",
	   keys[StopAction].data.string);
    wprintf(L"OnFail: %s\n",onfail);
    wprintf(L"Machine: %s\n",
	   keys[Machine].data.expand.unexpanded);
    wprintf(L"WorkDir: %s\n",
	   keys[WorkDir].data.expand.unexpanded);
    if(*keys[SName].data.string)
      wprintf(L"SName: %s\n",
	     keys[SName].data.string);
    else
      wprintf(L"Name: %s\n",
	     keys[Name].data.string);
    wprintf(L"Priority: %s\n",prio);
    wprintf(L"DebugType: %s\n",debugtype);
    wprintf(L"Args: %s\n",
	   keys[Args].data.expand.unexpanded);
    wprintf(L"InternalServiceName: %s\n",
	   keys[InternalServiceName].data.string);
    wprintf(L"Comment: %s\n",
	   keys[Comment].data.string);
    wprintf(L"Env:\n");
    while(*pek){
      wprintf(L"\t%s\n",*pek);
      ++pek;
    }
    /* env is easier to free...*/
    env = arg_to_env(arg);
    free(env);
  } else {
    wprintf(L"%s\t%s\t%s\t%s\t%s\n",
	   servicename,
	   (*keys[Name].data.string) ?
	   keys[Name].data.string :
	   keys[SName].data.string,
	   prio,
	   onfail,
	   keys[Args].data.expand.unexpanded);
  }
  return TRUE;
}
      

int do_list(int argc, wchar_t **argv){
  if(argc < 3){
    RegEntryDesc *all_keys = get_all_keys();
    if(!all_keys){
      fwprintf(stderr,L"%s: No services found in registry.\n",
	      argv[0]);
      return 0;
    }
    wprintf(L"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){
      fwprintf(stderr,L"%s: Could not retrieve any "
	       L"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

wchar_t *safe_get_line(void){
    int lsize = READ_CHUNK;
    wchar_t *line = malloc(READ_CHUNK*sizeof(wchar_t));
    int pos = 0;
    int ch;

    while((ch = getwchar()) != EOF && ch != L'\n'){
	if(pos + 1 >= lsize){
	    line = realloc(line,(lsize += READ_CHUNK)*sizeof(wchar_t));
	    assert(line);
	}
	line[pos++] = ch;
    }
    if(ch == EOF || !pos){
	free(line);
	return NULL;
    }
    line[pos] = L'\0';
    return line;
}


void read_arguments(int *pargc, wchar_t ***pargv){
    int argc = 0;
    int asize = ARGV_CHUNK;
    wchar_t **argv = malloc(ARGV_CHUNK*sizeof(wchar_t *));
    wchar_t *tmp;

    argv[0] = (*pargv)[0];
    argc = 1;
    while((tmp = safe_get_line()) != NULL){
	if(argc + 1 >= asize){
	    argv = realloc(argv,(asize += ARGV_CHUNK)*sizeof(wchar_t *));
	    assert(argv);
	}
	argv[argc++] = tmp;
    }
    argv[argc] = NULL;
    *pargc = argc;
    *pargv = argv;
}

/* Create a free-for-all ACL to set on the semaphore */
PACL get_acl(PSECURITY_DESCRIPTOR secdescp)  
{
  DWORD acl_length = 0;  
  PSID auth_users_sidp = NULL;  
  PACL aclp = NULL;  
  SID_IDENTIFIER_AUTHORITY ntauth = SECURITY_NT_AUTHORITY;  
  
  if(!InitializeSecurityDescriptor(secdescp, SECURITY_DESCRIPTOR_REVISION)) {
    return NULL;
  }
  
  if(!AllocateAndInitializeSid(&ntauth, 
			       1, 
			       SECURITY_AUTHENTICATED_USER_RID, 
			       0, 0, 0, 0, 0, 0, 0, 
			       &auth_users_sidp)) {
    return NULL;
  }
  
  acl_length =   sizeof(ACL) +  
    sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) +  
    GetLengthSid(auth_users_sidp);  
  
  if((aclp = (PACL) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, acl_length)) == NULL) {
    FreeSid(auth_users_sidp);  
    return NULL;
  }
  
  if(!InitializeAcl(aclp, acl_length, ACL_REVISION)) {
    FreeSid(auth_users_sidp);  
    HeapFree(GetProcessHeap(), 0, aclp);
    return NULL;
  }
  
  if(!AddAccessAllowedAce(aclp, ACL_REVISION, SEMAPHORE_ALL_ACCESS, auth_users_sidp)) {
    FreeSid(auth_users_sidp);  
    HeapFree(GetProcessHeap(), 0, aclp);
    return NULL;
  }
  
  if(!SetSecurityDescriptorDacl(secdescp, TRUE, aclp, FALSE)) {
    FreeSid(auth_users_sidp);  
    HeapFree(GetProcessHeap(), 0, aclp);
    return NULL;
  }
  return aclp;  
} 

static HANDLE lock_semaphore = NULL;

int take_lock(void) {
  SECURITY_ATTRIBUTES attr;  
  PACL aclp;  
  SECURITY_DESCRIPTOR secdesc;  
  
  if ((aclp = get_acl(&secdesc)) == NULL) {
    return -1;
  }
  
  memset(&attr,0,sizeof(attr));
  attr.nLength = sizeof(attr);  
  attr.lpSecurityDescriptor = &secdesc;  
  attr.bInheritHandle = FALSE;  
  
  if ((lock_semaphore = CreateSemaphore(&attr, 1, 1, ERLSRV_INTERACTIVE_GLOBAL_SEMAPHORE)) == NULL) {
    return -1;
  }
  
  if (WaitForSingleObject(lock_semaphore,INFINITE) != WAIT_OBJECT_0) {
    return -1;
  }
  
  HeapFree(GetProcessHeap(), 0, aclp);
  return 0;
}

void release_lock(void) {
  ReleaseSemaphore(lock_semaphore,1,NULL);
}
    


int interactive_main(int argc, wchar_t **argv){
  wchar_t *action = argv[1];
  int res;

  _setmode(_fileno(stdin), _O_U8TEXT);  /* set stdin to UTF8 */
  _setmode(_fileno(stdout), _O_U8TEXT); /* set stdout to UTF8 */
  _setmode(_fileno(stderr), _O_U8TEXT); /* set stderr to UTF8 */

  if (take_lock() != 0) {
    fwprintf(stderr,L"%s: unable to acquire global lock (%S).\n",argv[0],
	    ERLSRV_INTERACTIVE_GLOBAL_SEMAPHORE);
    return 1;
  }
  
  if(!_wcsicmp(action,L"readargs")){
    read_arguments(&argc,&argv);
    action = argv[1];
  }

#ifdef HARDDEBUG
  {int i;
      for(i=0; i < argc; i++) {
	  fwprintf(stderr, L"%s ", argv[i]);
      }
      fwprintf(stderr, L"\n");
  }
#endif

  if(!_wcsicmp(action,L"set") || !_wcsicmp(action,L"add"))
    res = do_add_or_set(argc,argv);
  else if(!_wcsicmp(action,L"rename"))
    res = do_rename(argc,argv);
  else if(!_wcsicmp(action,L"remove"))
    res = do_remove(argc,argv);
  else if(!_wcsicmp(action,L"list"))
    res = do_list(argc,argv);
  else if(!_wcsicmp(action,L"start") ||
	  !_wcsicmp(action,L"start_disabled") ||
	  !_wcsicmp(action,L"stop") ||
	  !_wcsicmp(action,L"enable") ||
	  !_wcsicmp(action,L"disable"))
    res =  do_manage(argc,argv);
  else if(_wcsicmp(action,L"?") &&
	  _wcsicmp(action,L"/?") &&
	  _wcsicmp(action,L"-?") &&
	  *action != L'h' &&
	  *action != L'H') {
    fwprintf(stderr,L"%s: action %s not implemented.\n",argv[0],action);
    do_usage(argv[0]);
    res = 1;
  } else {
    do_usage(argv[0]);
    res = 0;
  }
  release_lock();
  return res;
}

