/**
* Licensed to JumpMind Inc under one or more contributor
* license agreements. See the NOTICE file distributed
* with this work for additional information regarding
* copyright ownership. JumpMind Inc licenses this file
* to you under the GNU General Public License, version 3.0 (GPLv3)
* (the "License"); you may not use this file except in compliance
* with the License.
*
* You should have received a copy of the GNU General Public License,
* version 3.0 (GPLv3) along with this library; if not, see
* <http://www.gnu.org/licenses/>.
*
* 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.jumpmind.symmetric.wrapper;
import java.lang.reflect.Field;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.jumpmind.symmetric.wrapper.Constants.Status;
import org.jumpmind.symmetric.wrapper.jna.Advapi32Ex;
import org.jumpmind.symmetric.wrapper.jna.Advapi32Ex.HANDLER_FUNCTION;
import org.jumpmind.symmetric.wrapper.jna.Advapi32Ex.SERVICE_STATUS_HANDLE;
import org.jumpmind.symmetric.wrapper.jna.Kernel32Ex;
import org.jumpmind.symmetric.wrapper.jna.Shell32Ex;
import org.jumpmind.symmetric.wrapper.jna.WinsvcEx;
import org.jumpmind.symmetric.wrapper.jna.WinsvcEx.SERVICE_MAIN_FUNCTION;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.WString;
import com.sun.jna.platform.win32.Advapi32;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.Kernel32Util;
import com.sun.jna.platform.win32.WinNT;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.Winsvc;
import com.sun.jna.platform.win32.Winsvc.SC_HANDLE;
import com.sun.jna.platform.win32.Winsvc.SC_STATUS_TYPE;
import com.sun.jna.platform.win32.Winsvc.SERVICE_STATUS_PROCESS;
import com.sun.jna.ptr.IntByReference;
@IgnoreJRERequirement
public class WindowsService extends WrapperService {
private final static Logger logger = Logger.getLogger(WindowsService.class.getName());
protected ServiceControlHandler serviceControlHandler;
protected SERVICE_STATUS_HANDLE serviceStatusHandle;
protected Winsvc.SERVICE_STATUS serviceStatus;
@Override
protected boolean setWorkingDirectory(String dir) {
return Kernel32Ex.INSTANCE.SetCurrentDirectory(dir);
}
@Override
public void init() {
logger.log(Level.INFO, "Requesting service dispatch");
WinsvcEx.SERVICE_TABLE_ENTRY entry = new WinsvcEx.SERVICE_TABLE_ENTRY(config.getName(), new ServiceMain());
WinsvcEx.SERVICE_TABLE_ENTRY[] serviceTable = (WinsvcEx.SERVICE_TABLE_ENTRY[]) entry.toArray(2);
if (!Advapi32Ex.INSTANCE.StartServiceCtrlDispatcher(serviceTable)) {
logger.log(Level.SEVERE, "Error " + Native.getLastError());
System.exit(Native.getLastError());
}
}
@Override
public void start() {
if (!isInstalled()) {
super.start();
} else if (isRunning()) {
throw new WrapperException(Constants.RC_SERVER_ALREADY_RUNNING, 0, "Server is already running");
} else {
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
SC_HANDLE manager = openServiceManager();
SC_HANDLE service = advapi.OpenService(manager, config.getName(), Winsvc.SERVICE_ALL_ACCESS);
try {
if (service != null) {
System.out.println("Waiting for server to start");
if (!advapi.StartService(service, 0, null)) {
throwException("StartService");
}
Winsvc.SERVICE_STATUS_PROCESS status = waitForService(manager, service);
if (status.dwCurrentState == Winsvc.SERVICE_STOPPED) {
throw new WrapperException(Constants.RC_SERVER_EXITED, status.dwWin32ExitCode, "Unexpected exit from service");
}
System.out.println("Started");
} else {
throwException("OpenService");
}
} finally {
closeServiceHandle(service);
closeServiceHandle(manager);
}
}
}
@Override
public void stop() {
if (!isInstalled()) {
super.stop();
} else if (!isRunning()) {
throw new WrapperException(Constants.RC_SERVER_NOT_RUNNING, 0, "Server is not running");
} else {
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
SC_HANDLE manager = openServiceManager();
SC_HANDLE service = advapi.OpenService(manager, config.getName(), Winsvc.SERVICE_ALL_ACCESS);
try {
if (service != null) {
System.out.println("Waiting for server to stop");
if (!advapi.ControlService(service, Winsvc.SERVICE_CONTROL_STOP, new Winsvc.SERVICE_STATUS())) {
throwException("ControlService");
}
Winsvc.SERVICE_STATUS_PROCESS status = waitForService(manager, service);
if (status.dwCurrentState != Winsvc.SERVICE_STOPPED) {
throw new WrapperException(Constants.RC_FAIL_STOP_SERVER, status.dwWin32ExitCode, "Service did not stop");
}
System.out.println("Stopped");
} else {
throwException("OpenService");
}
} finally {
closeServiceHandle(service);
closeServiceHandle(manager);
}
}
}
@Override
public boolean isRunning() {
Advapi32 advapi = Advapi32.INSTANCE;
SC_HANDLE manager = advapi.OpenSCManager(null, null, Winsvc.SC_MANAGER_ENUMERATE_SERVICE);
if (manager == null) {
throwException("OpenSCManager");
} else {
SC_HANDLE service = advapi.OpenService(manager, config.getName(), Winsvc.SERVICE_QUERY_STATUS);
if (service != null) {
IntByReference bytesNeeded = new IntByReference();
advapi.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO, null, 0, bytesNeeded);
SERVICE_STATUS_PROCESS status = new SERVICE_STATUS_PROCESS(bytesNeeded.getValue());
if (!advapi.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO, status, status.size(), bytesNeeded)) {
throwException("QueryServiceStatusEx");
}
closeServiceHandle(service);
closeServiceHandle(manager);
return (status.dwCurrentState == Winsvc.SERVICE_RUNNING);
}
closeServiceHandle(manager);
}
return super.isRunning();
}
@Override
protected boolean isPidRunning(int pid) {
boolean isRunning = false;
if (pid != 0) {
Kernel32 kernel = Kernel32.INSTANCE;
HANDLE process = kernel.OpenProcess(Kernel32.SYNCHRONIZE, false, pid);
if (process != null) {
int rc = kernel.WaitForSingleObject(process, 0);
kernel.CloseHandle(process);
isRunning = (rc == Kernel32.WAIT_TIMEOUT);
}
}
return isRunning;
}
@Override
protected int getCurrentPid() {
return Kernel32.INSTANCE.GetCurrentProcessId();
}
@Override
public boolean isPrivileged() {
try {
closeServiceHandle(openServiceManager());
return true;
} catch (WrapperException e) {
return false;
}
}
@Override
public void relaunchAsPrivileged(String cmd, String args) {
Shell32Ex.SHELLEXECUTEINFO execInfo = new Shell32Ex.SHELLEXECUTEINFO();
execInfo.lpFile = new WString(cmd);
if (args != null) {
execInfo.lpParameters = new WString(args);
}
execInfo.nShow = Shell32Ex.SW_SHOWDEFAULT;
execInfo.fMask = Shell32Ex.SEE_MASK_NOCLOSEPROCESS;
execInfo.lpVerb = new WString("runas");
if (!Shell32Ex.INSTANCE.ShellExecuteEx(execInfo)) {
throwException("ShellExecuteEx");
}
System.exit(0);
}
@Override
public boolean isInstalled() {
Advapi32 advapi = Advapi32.INSTANCE;
boolean isInstalled = false;
SC_HANDLE manager = advapi.OpenSCManager(null, null, Winsvc.SC_MANAGER_ENUMERATE_SERVICE);
if (manager == null) {
throwException("OpenSCManager");
} else {
SC_HANDLE service = advapi.OpenService(manager, config.getName(), Winsvc.SERVICE_QUERY_STATUS);
isInstalled = (service != null);
closeServiceHandle(service);
closeServiceHandle(manager);
}
return isInstalled;
}
@Override
protected int getProcessPid(Process process) {
int pid = 0;
try {
Field field = process.getClass().getDeclaredField("handle");
field.setAccessible(true);
HANDLE processHandle = new HANDLE(Pointer.createConstant(field.getLong(process)));
pid = Kernel32.INSTANCE.GetProcessId(processHandle);
} catch (Exception e) {
}
return pid;
}
@Override
protected void killProcess(int pid, boolean isTerminate) {
Kernel32 kernel = Kernel32.INSTANCE;
HANDLE processHandle = kernel.OpenProcess(WinNT.PROCESS_TERMINATE, true, pid);
if (processHandle == null) {
throwException("OpenProcess");
}
kernel.TerminateProcess(processHandle, 99);
}
@Override
public void install() {
if (isRunning()) {
System.out.println("Server must be stopped before installing");
System.exit(Constants.RC_NO_INSTALL_WHEN_RUNNING);
}
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
SC_HANDLE manager = openServiceManager();
SC_HANDLE service = advapi.OpenService(manager, config.getName(), Winsvc.SERVICE_ALL_ACCESS);
try {
if (service != null) {
throw new WrapperException(Constants.RC_ALREADY_INSTALLED, 0, "Service " + config.getName() + " is already installed");
} else {
System.out.println("Installing " + config.getName() + " ...");
String dependencies = null;
if (config.getDependencies() != null && config.getDependencies().size() > 0) {
StringBuffer sb = new StringBuffer();
for (String dependency : config.getDependencies()) {
sb.append(dependency).append("\0");
}
dependencies = sb.append("\0").toString();
}
service = advapi.CreateService(manager, config.getName(), config.getDisplayName(), Winsvc.SERVICE_ALL_ACCESS,
WinsvcEx.SERVICE_WIN32_OWN_PROCESS, config.isAutoStart() || config.isDelayStart() ? WinsvcEx.SERVICE_AUTO_START
: WinsvcEx.SERVICE_DEMAND_START, WinsvcEx.SERVICE_ERROR_NORMAL,
commandToString(getWrapperCommand("init")), null, null, dependencies, null, null);
if (service != null) {
Advapi32Ex.SERVICE_DESCRIPTION desc = new Advapi32Ex.SERVICE_DESCRIPTION(config.getDescription());
advapi.ChangeServiceConfig2(service, WinsvcEx.SERVICE_CONFIG_DESCRIPTION, desc);
if (config.isDelayStart()) {
WinsvcEx.SERVICE_DELAYED_AUTO_START_INFO delayedInfo = new WinsvcEx.SERVICE_DELAYED_AUTO_START_INFO(true);
advapi.ChangeServiceConfig2(service, WinsvcEx.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayedInfo);
}
} else {
throwException("CreateService");
}
System.out.println("Done");
}
} finally {
closeServiceHandle(service);
closeServiceHandle(manager);
}
}
@Override
public void uninstall() {
if (isRunning()) {
throw new WrapperException(Constants.RC_NO_INSTALL_WHEN_RUNNING, 0, "Server must be stopped before uninstalling");
}
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
SC_HANDLE manager = openServiceManager();
SC_HANDLE service = advapi.OpenService(manager, config.getName(), Winsvc.SERVICE_ALL_ACCESS);
try {
if (service != null) {
System.out.println("Uninstalling " + config.getName() + " ...");
if (!advapi.DeleteService(service)) {
throwException("DeleteService");
}
} else {
throw new WrapperException(Constants.RC_NOT_INSTALLED, 0, "Service " + config.getName() + " is not installed");
}
} finally {
closeServiceHandle(service);
closeServiceHandle(manager);
}
int seconds = 0;
while (seconds <= 30) {
if (!isInstalled()) {
break;
}
System.out.print(".");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
seconds++;
}
if (seconds > 0) {
System.out.println("");
}
if (isInstalled()) {
System.out.println("Service manager did not complete");
} else {
System.out.println("Done");
}
}
protected SC_HANDLE openServiceManager() {
Advapi32 advapi = Advapi32.INSTANCE;
SC_HANDLE handle = advapi.OpenSCManager(null, null, Winsvc.SC_MANAGER_ALL_ACCESS);
if (handle == null) {
throwException("OpenSCManager");
}
return handle;
}
@Override
protected void updateStatus(Status status) {
switch (status) {
case START_PENDING:
updateStatus(Winsvc.SERVICE_START_PENDING, 0);
break;
case RUNNING:
updateStatus(Winsvc.SERVICE_RUNNING, Winsvc.SERVICE_ACCEPT_STOP);
break;
case STOP_PENDING:
updateStatus(Winsvc.SERVICE_STOP_PENDING, 0);
break;
case STOPPED:
updateStatus(Winsvc.SERVICE_STOPPED, 0);
break;
}
}
protected void updateStatus(int status, int controlsAccepted) {
if (serviceStatus != null) {
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
serviceStatus.dwCurrentState = status;
serviceStatus.dwControlsAccepted = controlsAccepted;
if (!advapi.SetServiceStatus(serviceStatusHandle.getPointer(), serviceStatus)) {
throwException("SetServiceStatus");
}
}
}
protected Winsvc.SERVICE_STATUS_PROCESS waitForService(SC_HANDLE manager, SC_HANDLE service) {
int seconds = 0;
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
IntByReference bytesNeeded = new IntByReference();
advapi.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO, null, 0, bytesNeeded);
Winsvc.SERVICE_STATUS_PROCESS status = new Winsvc.SERVICE_STATUS_PROCESS(bytesNeeded.getValue());
while (seconds <= 5) {
System.out.print(".");
if (!advapi.QueryServiceStatusEx(service, SC_STATUS_TYPE.SC_STATUS_PROCESS_INFO,
status, status.size(), bytesNeeded)) {
throwException("QueryServiceStatusEx");
}
if (status.dwCurrentState == Winsvc.SERVICE_STOPPED) {
break;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
seconds++;
}
System.out.println("");
return status;
}
protected void closeServiceHandle(SC_HANDLE handle) {
if (handle != null) {
Advapi32Ex advapi = Advapi32Ex.INSTANCE;
advapi.CloseServiceHandle(handle);
}
}
protected void throwException(String name) {
int rc = Native.getLastError();
throw new WrapperException(Constants.RC_NATIVE_ERROR, rc, name + " returned error " + rc + ": "
+ Kernel32Util.formatMessageFromLastErrorCode(rc));
}
protected String getWrapperCommandQuote() {
return "\"";
}
class ServiceMain implements SERVICE_MAIN_FUNCTION {
@Override
public void serviceMain(int argc, Pointer argv) {
logger.log(Level.INFO, "Getting service status");
serviceControlHandler = new ServiceControlHandler();
serviceStatusHandle = Advapi32Ex.INSTANCE.RegisterServiceCtrlHandlerEx(config.getName(), serviceControlHandler,
null);
if (serviceStatusHandle == null) {
System.exit(Constants.RC_FAIL_REGISTER_SERVICE);
}
serviceStatus = new Winsvc.SERVICE_STATUS();
serviceStatus.dwServiceType = WinsvcEx.SERVICE_WIN32_OWN_PROCESS;
if (!isRunning()) {
execJava(false);
} else {
updateStatus(Winsvc.SERVICE_STOPPED, 0);
}
}
}
class ServiceControlHandler implements HANDLER_FUNCTION {
public void serviceControlHandler(int controlCode) {
if (controlCode != Winsvc.SERVICE_CONTROL_INTERROGATE) {
logger.log(Level.INFO, "Service manager requesting control code " + controlCode);
}
if (controlCode == Winsvc.SERVICE_CONTROL_STOP) {
logger.log(Level.INFO, "Service manager requesting to stop service");
shutdown();
}
}
}
}