From 5d819b2776328e914a26ae011692084e1b614e26 Mon Sep 17 00:00:00 2001 From: Anton Tananaev Date: Fri, 14 Sep 2018 16:46:40 +1200 Subject: Add Windows service support --- pom.xml | 5 + src/org/traccar/Context.java | 8 +- src/org/traccar/Main.java | 24 +++- src/org/traccar/WindowsService.java | 216 ++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 src/org/traccar/WindowsService.java diff --git a/pom.xml b/pom.xml index 8d864cc66..8c4f52573 100644 --- a/pom.xml +++ b/pom.xml @@ -174,6 +174,11 @@ ch-smpp 6.0.0-netty4-beta-3 + + net.java.dev.jna + jna-platform + 4.5.2 + diff --git a/src/org/traccar/Context.java b/src/org/traccar/Context.java index 9a8bb97a9..f7bc18d1d 100644 --- a/src/org/traccar/Context.java +++ b/src/org/traccar/Context.java @@ -340,14 +340,10 @@ public final class Context { } } - public static void init(String[] arguments) throws Exception { + public static void init(String configFile) throws Exception { config = new Config(); - if (arguments.length <= 0) { - throw new RuntimeException("Configuration file is not provided"); - } - - config.load(arguments[0]); + config.load(configFile); loggerEnabled = config.getBoolean("logger.enable"); if (loggerEnabled) { diff --git a/src/org/traccar/Main.java b/src/org/traccar/Main.java index fd1f07dca..986731240 100644 --- a/src/org/traccar/Main.java +++ b/src/org/traccar/Main.java @@ -67,7 +67,29 @@ public final class Main { public static void main(String[] args) throws Exception { Locale.setDefault(Locale.ENGLISH); - Context.init(args); + if (args.length <= 0) { + throw new RuntimeException("Configuration file is not provided"); + } + + String configFile = args[args.length - 1]; + + if (args.length > 1) { + WindowsService windowsService = new WindowsService("traccar"); + switch (args[1]) { + case "--install": + windowsService.install("traccar", null, null, null, null, configFile); + return; + case "--uninstall": + windowsService.uninstall(); + return; + case "--service": + default: + windowsService.init(); + break; + } + } + + Context.init(configFile); logSystemInfo(); LOGGER.info("Version: " + Context.getAppVersion()); LOGGER.info("Starting server..."); diff --git a/src/org/traccar/WindowsService.java b/src/org/traccar/WindowsService.java new file mode 100644 index 000000000..398648b7b --- /dev/null +++ b/src/org/traccar/WindowsService.java @@ -0,0 +1,216 @@ +/* + * Copyright 2018 Anton Tananaev (anton@traccar.org) + * + * 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. + */ +package org.traccar; + +import com.sun.jna.Pointer; +import com.sun.jna.platform.win32.Advapi32; +import com.sun.jna.platform.win32.WinError; +import com.sun.jna.platform.win32.WinNT; +import com.sun.jna.platform.win32.Winsvc; +import com.sun.jna.platform.win32.Winsvc.HandlerEx; +import com.sun.jna.platform.win32.Winsvc.SC_HANDLE; +import com.sun.jna.platform.win32.Winsvc.SERVICE_DESCRIPTION; +import com.sun.jna.platform.win32.Winsvc.SERVICE_MAIN_FUNCTION; +import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS; +import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS_HANDLE; +import com.sun.jna.platform.win32.Winsvc.SERVICE_TABLE_ENTRY; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLClassLoader; + +public class WindowsService { + + private static final Advapi32 ADVAPI_32 = Advapi32.INSTANCE; + + private final Object waitObject = new Object(); + + private String serviceName; + private ServiceMain serviceMain; + private ServiceControl serviceControl; + private SERVICE_STATUS_HANDLE serviceStatusHandle; + + public WindowsService(String serviceName) { + this.serviceName = serviceName; + } + + public boolean install( + String displayName, String description, String[] dependencies, + String account, String password, String config) throws URISyntaxException { + + String javaHome = System.getProperty("java.home"); + String javaBinary = javaHome + "\\bin\\java.exe"; + URLClassLoader cl = (URLClassLoader) WindowsService.class.getClassLoader(); + URL jarPath = cl.getURLs()[0]; + + File jar = new File(jarPath.toURI()); + String command = javaBinary + " -jar \"" + jar.getAbsolutePath() + "\" --service \"" + config + "\""; + + boolean success = false; + StringBuilder dep = new StringBuilder(); + + if (dependencies != null) { + for (String s : dependencies) { + dep.append(s); + dep.append("\0"); + } + } + dep.append("\0"); + + SERVICE_DESCRIPTION desc = new SERVICE_DESCRIPTION(); + desc.lpDescription = description; + + SC_HANDLE serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS); + + if (serviceManager != null) { + SC_HANDLE service = ADVAPI_32.CreateService(serviceManager, serviceName, displayName, + Winsvc.SERVICE_ALL_ACCESS, WinNT.SERVICE_WIN32_OWN_PROCESS, WinNT.SERVICE_AUTO_START, + WinNT.SERVICE_ERROR_NORMAL, + command, + null, null, dep.toString(), account, password); + + if (service != null) { + success = ADVAPI_32.ChangeServiceConfig2(service, Winsvc.SERVICE_CONFIG_DESCRIPTION, desc); + ADVAPI_32.CloseServiceHandle(service); + } + ADVAPI_32.CloseServiceHandle(serviceManager); + } + return success; + } + + public boolean uninstall() { + boolean success = false; + + SC_HANDLE serviceManager = openServiceControlManager(null, Winsvc.SC_MANAGER_ALL_ACCESS); + + if (serviceManager != null) { + SC_HANDLE service = ADVAPI_32.OpenService(serviceManager, serviceName, Winsvc.SERVICE_ALL_ACCESS); + + if (service != null) { + success = ADVAPI_32.DeleteService(service); + ADVAPI_32.CloseServiceHandle(service); + } + ADVAPI_32.CloseServiceHandle(serviceManager); + } + return success; + } + + public boolean start() { + boolean success = false; + + SC_HANDLE serviceManager = openServiceControlManager(null, WinNT.GENERIC_EXECUTE); + + if (serviceManager != null) { + SC_HANDLE service = ADVAPI_32.OpenService(serviceManager, serviceName, WinNT.GENERIC_EXECUTE); + + if (service != null) { + success = ADVAPI_32.StartService(service, 0, null); + ADVAPI_32.CloseServiceHandle(service); + } + ADVAPI_32.CloseServiceHandle(serviceManager); + } + + return success; + } + + public boolean stop() { + boolean success = false; + + SC_HANDLE serviceManager = openServiceControlManager(null, WinNT.GENERIC_EXECUTE); + + if (serviceManager != null) { + SC_HANDLE service = Advapi32.INSTANCE.OpenService(serviceManager, serviceName, WinNT.GENERIC_EXECUTE); + + if (service != null) { + SERVICE_STATUS serviceStatus = new SERVICE_STATUS(); + success = Advapi32.INSTANCE.ControlService(service, Winsvc.SERVICE_CONTROL_STOP, serviceStatus); + Advapi32.INSTANCE.CloseServiceHandle(service); + } + Advapi32.INSTANCE.CloseServiceHandle(serviceManager); + } + + return (success); + } + + public void init() { + serviceMain = new ServiceMain(); + SERVICE_TABLE_ENTRY entry = new SERVICE_TABLE_ENTRY(); + entry.lpServiceName = serviceName; + entry.lpServiceProc = serviceMain; + + Advapi32.INSTANCE.StartServiceCtrlDispatcher((SERVICE_TABLE_ENTRY[]) entry.toArray(2)); + } + + private SC_HANDLE openServiceControlManager(String machine, int access) { + return ADVAPI_32.OpenSCManager(machine, null, access); + } + + private void reportStatus(int status, int win32ExitCode, int waitHint) { + SERVICE_STATUS serviceStatus = new SERVICE_STATUS(); + serviceStatus.dwServiceType = WinNT.SERVICE_WIN32_OWN_PROCESS; + serviceStatus.dwControlsAccepted = Winsvc.SERVICE_ACCEPT_STOP | Winsvc.SERVICE_ACCEPT_SHUTDOWN; + serviceStatus.dwWin32ExitCode = win32ExitCode; + serviceStatus.dwWaitHint = waitHint; + serviceStatus.dwCurrentState = status; + + ADVAPI_32.SetServiceStatus(serviceStatusHandle, serviceStatus); + } + + private class ServiceMain implements SERVICE_MAIN_FUNCTION { + + public void callback(int dwArgc, Pointer lpszArgv) { + serviceControl = new ServiceControl(); + serviceStatusHandle = ADVAPI_32.RegisterServiceCtrlHandlerEx(serviceName, serviceControl, null); + + reportStatus(Winsvc.SERVICE_START_PENDING, WinError.NO_ERROR, 3000); + reportStatus(Winsvc.SERVICE_RUNNING, WinError.NO_ERROR, 0); + + try { + synchronized (waitObject) { + waitObject.wait(); + } + } catch (InterruptedException ex) { + } + reportStatus(Winsvc.SERVICE_STOPPED, WinError.NO_ERROR, 0); + + // Avoid returning from ServiceMain, which will cause a crash + // See http://support.microsoft.com/kb/201349, which recommends + // having init() wait for this thread. + // Waiting on this thread in init() won't fix the crash, though. + } + + } + + private class ServiceControl implements HandlerEx { + + public int callback(int dwControl, int dwEventType, Pointer lpEventData, Pointer lpContext) { + switch (dwControl) { + case Winsvc.SERVICE_CONTROL_STOP: + case Winsvc.SERVICE_CONTROL_SHUTDOWN: + reportStatus(Winsvc.SERVICE_STOP_PENDING, WinError.NO_ERROR, 5000); + System.exit(0); + synchronized (waitObject) { + waitObject.notifyAll(); + } + default: + break; + } + return WinError.NO_ERROR; + } + + } + +} -- cgit v1.2.3