/* * Win32Service.java * * Created on 12. September 2007, 12:05 * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package jnacontrib.win32; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import jnacontrib.jna.Advapi32; import jnacontrib.jna.WINERROR; import jnacontrib.jna.WINNT; import jnacontrib.jna.WINSVC; import jnacontrib.jna.Advapi32.ENUM_SERVICE_STATUS_PROCESS; import jnacontrib.jna.Advapi32.QUERY_SERVICE_CONFIG; import jnacontrib.jna.Advapi32.SERVICE_DESCRIPTION; import jnacontrib.jna.Advapi32.SERVICE_FAILURE_ACTIONS; import jnacontrib.jna.Advapi32.SERVICE_STATUS_PROCESS; import org.rzo.yajsw.os.Service; import org.rzo.yajsw.os.ServiceInfo; import org.rzo.yajsw.os.ServiceInfoImpl; import com.sun.jna.Memory; import com.sun.jna.Native; import com.sun.jna.Platform; import com.sun.jna.Pointer; import com.sun.jna.platform.win32.Kernel32; import com.sun.jna.platform.win32.Kernel32Util; import com.sun.jna.ptr.IntByReference; /** * Baseclass for a Win32 service. */ abstract public class Win32Service { protected String serviceName; private ServiceMain serviceMain; private ServiceControl serviceControl; private Pointer serviceStatusHandle; protected Object waitObject = new Object(); private int stopTimeout = 5000; private int startupTimeout = 30000; protected volatile int checkPoint = 0; private boolean autoReportStartup = true; private Lock startupLock = new ReentrantLock(); private Condition startupCondition = startupLock.newCondition(); private volatile boolean _stopping = false; public volatile String _stopReason = null; private static int lastWinError = -1; /** * Creates a new instance of Win32Service. * * @param serviceName * internal name of the service */ public Win32Service(String serviceName) { this.serviceName = serviceName; } public Win32Service() { } public void setServiceName(String serviceName) { this.serviceName = serviceName; } public String getServiceName() { return serviceName; } /** * Install the service. * * @param displayName * visible name * @param description * description * @param dependencies * array of other services to depend on or null * @param account * service account or null for LocalSystem * @param password * password for service account or null * @throws java.lang.Exception * @return true on success */ public boolean install(String displayName, String description, String[] dependencies, String account, String password, boolean delayedAutostart) { return (install(displayName, description, dependencies, account, password, "java.exe -cp \"" + System.getProperty("java.class.path") + "\" -Xrs " + this.getClass().getName(), "AUTO_START", false, null)); } /** * Install the service. * * @return true on success * @param displayName * visible name * @param description * description * @param dependencies * array of other services to depend on or null * @param account * service account or null for LocalSystem * @param password * password for service account or null * @param command * command line to start the service * @throws java.lang.Exception */ public boolean install(String displayName, String description, String[] dependencies, String account, String password, String command, String startType, boolean interactive, Object failureActions) { Advapi32 advapi32; Advapi32.SERVICE_DESCRIPTION desc; Pointer serviceManager, service; boolean success = false; String dep = ""; if (dependencies != null) { for (String s : dependencies) { dep += s + "\0"; } } dep += "\0"; desc = new Advapi32.SERVICE_DESCRIPTION(); desc.lpDescription = description; advapi32 = Advapi32.INSTANCE; serviceManager = openServiceControlManager(null, WINSVC.SC_MANAGER_ALL_ACCESS); int winStartType = "DEMAND_START".equals(startType) ? WINSVC.SERVICE_DEMAND_START : WINSVC.SERVICE_AUTO_START; int dwServiceType = WINSVC.SERVICE_WIN32_OWN_PROCESS; if (interactive) dwServiceType |= WINSVC.SERVICE_INTERACTIVE_PROCESS; if (serviceManager != null) { service = advapi32.CreateService(serviceManager, serviceName, displayName, WINSVC.SERVICE_ALL_ACCESS, dwServiceType, winStartType, WINSVC.SERVICE_ERROR_NORMAL, command, null, null, dep, account, password); if (service != null) { if (failureActions != null) { success = advapi32.ChangeServiceConfig2(service, WINSVC.SERVICE_CONFIG_FAILURE_ACTIONS, (SERVICE_FAILURE_ACTIONS)failureActions); if (!success) { int err = Native.getLastError(); System.out.println("ERROR Setting failure actions #"+err+ " "+Kernel32Util.formatMessageFromLastErrorCode(err)); } } success = advapi32.ChangeServiceConfig2(service, WINSVC.SERVICE_CONFIG_DESCRIPTION, desc); if (Platform.isWinVista() && "DELAYED_AUTO_START".equals(startType)) { Advapi32.SERVICE_DELAYED_AUTO_START_INFO delayedDesc = new Advapi32.SERVICE_DELAYED_AUTO_START_INFO(); delayedDesc.fDelayedAutostart = true; success = advapi32.ChangeServiceConfig2(service, WINSVC.SERVICE_CONFIG_DELAYED_AUTO_START_INFO, delayedDesc); } advapi32.CloseServiceHandle(service); } else { int err = Kernel32.INSTANCE.GetLastError(); System.out.println("error during install " + err); System.out.println(Kernel32Util.formatMessageFromLastErrorCode(err)); } advapi32.CloseServiceHandle(serviceManager); } return (success); } /** * Uninstall the service. * * @throws java.lang.Exception * @return true on success */ public boolean uninstall() { Advapi32 advapi32; Pointer serviceManager, service; boolean success = false; advapi32 = Advapi32.INSTANCE; serviceManager = openServiceControlManager(null, WINSVC.SC_MANAGER_ALL_ACCESS); if (serviceManager != null) { service = advapi32.OpenService(serviceManager, serviceName, WINSVC.SERVICE_ALL_ACCESS); if (service != null) { success = advapi32.DeleteService(service); advapi32.CloseServiceHandle(service); } advapi32.CloseServiceHandle(serviceManager); } return (success); } public static ServiceInfo serviceInfo(String name) { ServiceInfoImpl result = new ServiceInfoImpl(); result.setName(name); Advapi32 advapi32; Pointer serviceManager, service; int state = Service.STATE_UNKNOWN; advapi32 = Advapi32.INSTANCE; serviceManager = openServiceControlManager(null, WINNT.GENERIC_READ); if (serviceManager != null) { state = 0; service = advapi32.OpenService(serviceManager, name, WINNT.GENERIC_READ); if (service != null) { IntByReference pcbBytesNeeded = new IntByReference(); state |= Service.STATE_INSTALLED; // get size required if (!advapi32.QueryServiceConfig(service, null, 0, pcbBytesNeeded)) { // now get the data int cbBufSize = pcbBytesNeeded.getValue(); Memory buffer = new Memory(cbBufSize); buffer.clear(); if (advapi32.QueryServiceConfig(service, buffer, cbBufSize, pcbBytesNeeded)) { QUERY_SERVICE_CONFIG lpServiceConfig = new QUERY_SERVICE_CONFIG(); lpServiceConfig.init(buffer); if (lpServiceConfig.dwStartType == Advapi32.SERVICE_DISABLED) state |= Service.STATE_DISABLED; if (lpServiceConfig.dwStartType == Advapi32.SERVICE_BOOT_START | lpServiceConfig.dwStartType == Advapi32.SERVICE_SYSTEM_START | lpServiceConfig.dwStartType == Advapi32.SERVICE_AUTO_START) state |= Service.STATE_AUTOMATIC; if (lpServiceConfig.dwStartType == Advapi32.SERVICE_DEMAND_START) state |= Service.STATE_MANUAL; if ((lpServiceConfig.dwServiceType & Advapi32.SERVICE_INTERACTIVE_PROCESS) != 0) state |= Service.STATE_INTERACTIVE; result.setAccount(lpServiceConfig.lpServiceStartName); result.setCommand(lpServiceConfig.lpBinaryPathName); result.setDependencies(lpServiceConfig.getDependencies()); result.setDisplayName(lpServiceConfig.lpDisplayName); } else { state |= Service.STATE_UNKNOWN; System.out.println("Error in QueryServiceConfig: " + Native.getLastError()); } } else { state |= Service.STATE_UNKNOWN; System.out.println("Error in QueryServiceConfig: " + Native.getLastError()); } if (!advapi32.QueryServiceStatusEx(service, (byte) advapi32.SC_STATUS_PROCESS_INFO, null, 0, pcbBytesNeeded)) { // now get the data int cbBufSize = pcbBytesNeeded.getValue(); Memory buffer = new Memory(cbBufSize); buffer.clear(); if (advapi32.QueryServiceStatusEx(service, (byte) advapi32.SC_STATUS_PROCESS_INFO, buffer, cbBufSize, pcbBytesNeeded)) { SERVICE_STATUS_PROCESS lpBuffer = new SERVICE_STATUS_PROCESS(); lpBuffer.init(buffer); if (lpBuffer.dwCurrentState == advapi32.SERVICE_RUNNING) state |= Service.STATE_RUNNING; if (lpBuffer.dwCurrentState == advapi32.SERVICE_PAUSED) state |= Service.STATE_PAUSED; if (lpBuffer.dwCurrentState == advapi32.SERVICE_START_PENDING) state |= Service.STATE_STARTING; if (lpBuffer.dwCurrentState == advapi32.SERVICE_STOP_PENDING) state |= Service.STATE_STOPPING; result.setPid(lpBuffer.dwProcessId); } else { state |= Service.STATE_UNKNOWN; System.out.println("Error in QueryServiceStatusEx: " + Native.getLastError()); } } if (!advapi32.QueryServiceConfig2(service, (byte) advapi32.SERVICE_CONFIG_DESCRIPTION, null, 0, pcbBytesNeeded)) { // now get the data int cbBufSize = pcbBytesNeeded.getValue(); Memory buffer = new Memory(cbBufSize); buffer.clear(); if (advapi32.QueryServiceConfig2(service, (byte) advapi32.SERVICE_CONFIG_DESCRIPTION, buffer, cbBufSize, pcbBytesNeeded)) { SERVICE_DESCRIPTION lpBuffer = new SERVICE_DESCRIPTION(); lpBuffer.init(buffer); result.setDescription(lpBuffer.lpDescription); } else { state |= Service.STATE_UNKNOWN; System.out.println("Error in QueryServiceStatusEx: " + Native.getLastError()); } } else { state |= Service.STATE_UNKNOWN; System.out.println("Error in QueryServiceStatusEx: " + Native.getLastError()); } advapi32.CloseServiceHandle(service); } advapi32.CloseServiceHandle(serviceManager); } result.setState(state); return result; } public int state() { Advapi32 advapi32; Pointer serviceManager, service; int result = Service.STATE_UNKNOWN; advapi32 = Advapi32.INSTANCE; serviceManager = openServiceControlManager(null, WINNT.GENERIC_READ); // System.out.println("Win32Service.state() serviceManager "+serviceManager); if (serviceManager != null) { result = 0; service = advapi32.OpenService(serviceManager, serviceName, WINNT.GENERIC_READ); // System.out.println("Win32Service.state() service "+service); if (service != null) { IntByReference pcbBytesNeeded = new IntByReference(); result |= Service.STATE_INSTALLED; // get size required if (!advapi32.QueryServiceConfig(service, null, 0, pcbBytesNeeded)) { // now get the data int cbBufSize = pcbBytesNeeded.getValue(); if (cbBufSize > 8192) cbBufSize = 8192; Memory buffer = new Memory(cbBufSize); buffer.clear(); if (advapi32.QueryServiceConfig(service, buffer, cbBufSize, pcbBytesNeeded)) { QUERY_SERVICE_CONFIG lpServiceConfig = new QUERY_SERVICE_CONFIG(); lpServiceConfig.init(buffer); if (lpServiceConfig.dwStartType == Advapi32.SERVICE_DISABLED) result |= Service.STATE_DISABLED; if (lpServiceConfig.dwStartType == Advapi32.SERVICE_BOOT_START | lpServiceConfig.dwStartType == Advapi32.SERVICE_SYSTEM_START | lpServiceConfig.dwStartType == Advapi32.SERVICE_AUTO_START) result |= Service.STATE_AUTOMATIC; if (lpServiceConfig.dwStartType == Advapi32.SERVICE_DEMAND_START) result |= Service.STATE_MANUAL; if ((lpServiceConfig.dwServiceType & Advapi32.SERVICE_INTERACTIVE_PROCESS) != 0) result |= Service.STATE_INTERACTIVE; } else { result |= Service.STATE_UNKNOWN; int error = Native.getLastError(); System.out.println("Error getting buffer size in QueryServiceConfig: " + error + " " + Kernel32Util.formatMessageFromLastErrorCode(error)); } } else { result |= Service.STATE_UNKNOWN; int error = Native.getLastError(); System.out.println("Error in QueryServiceConfig: " + error + " " + Kernel32Util.formatMessageFromLastErrorCode(error)); } if (!advapi32.QueryServiceStatusEx(service, (byte) advapi32.SC_STATUS_PROCESS_INFO, null, 0, pcbBytesNeeded)) { // now get the data int cbBufSize = pcbBytesNeeded.getValue(); Memory buffer = new Memory(cbBufSize); buffer.clear(); if (advapi32.QueryServiceStatusEx(service, (byte) advapi32.SC_STATUS_PROCESS_INFO, buffer, cbBufSize, pcbBytesNeeded)) { SERVICE_STATUS_PROCESS lpBuffer = new SERVICE_STATUS_PROCESS(); lpBuffer.init(buffer); if (lpBuffer.dwCurrentState == advapi32.SERVICE_RUNNING) result |= Service.STATE_RUNNING; if (lpBuffer.dwCurrentState == advapi32.SERVICE_PAUSED) result |= Service.STATE_PAUSED; // System.out.println("Win32Service.state() dwCurrentState "+lpBuffer.dwCurrentState); } else { result |= Service.STATE_UNKNOWN; int error = Native.getLastError(); System.out.println("Error getting buffer size in QueryServiceStatusEx: " + error + " " + Kernel32Util.formatMessageFromLastErrorCode(error)); } } else { result |= Service.STATE_UNKNOWN; int error = Native.getLastError(); System.out.println("Error in QueryServiceStatusEx: " + error + " " + Kernel32Util.formatMessageFromLastErrorCode(error)); } advapi32.CloseServiceHandle(service); } advapi32.CloseServiceHandle(serviceManager); } return result; } /** * Ask the ServiceControlManager to start the service. * * @return true on success */ public boolean start() { Advapi32 advapi32; Pointer serviceManager, service; boolean success = false; advapi32 = Advapi32.INSTANCE; serviceManager = openServiceControlManager(null, WINNT.GENERIC_EXECUTE); // System.out.println("service.start() serviceManager "+serviceManager); if (serviceManager != null) { service = advapi32.OpenService(serviceManager, serviceName, WINNT.GENERIC_EXECUTE); // System.out.println("service.start() service "+service); if (service != null) { success = advapi32.StartService(service, 0, null); // System.out.println("service.start() StartService "+success); advapi32.CloseServiceHandle(service); } advapi32.CloseServiceHandle(serviceManager); } return (success); } /** * Ask the ServiceControlManager to stop the service. * * @return true on success */ public boolean stop() throws Exception { Advapi32 advapi32; Pointer serviceManager, service; Advapi32.SERVICE_STATUS serviceStatus; boolean success = false; advapi32 = Advapi32.INSTANCE; serviceManager = openServiceControlManager(null, WINNT.GENERIC_EXECUTE); if (serviceManager != null) { service = advapi32.OpenService(serviceManager, serviceName, WINNT.GENERIC_EXECUTE); if (service != null) { serviceStatus = new Advapi32.SERVICE_STATUS(); success = advapi32.ControlService(service, WINSVC.SERVICE_CONTROL_STOP, serviceStatus); advapi32.CloseServiceHandle(service); } advapi32.CloseServiceHandle(serviceManager); } return (success); } /** * Initialize the service, connect to the ServiceControlManager. */ public void init() { Advapi32 advapi32; // Advapi32.SERVICE_TABLE_ENTRY[] entries = new // Advapi32.SERVICE_TABLE_ENTRY[2]; Advapi32.SERVICE_TABLE_ENTRY entry; serviceMain = new ServiceMain(); advapi32 = Advapi32.INSTANCE; entry = new Advapi32.SERVICE_TABLE_ENTRY(); entry.size(); entry.lpServiceName = serviceName; entry.lpServiceProc = serviceMain; entry.write(); if (!advapi32.StartServiceCtrlDispatcher(entry.toArray(2))) { log("error in StartServiceCtrlDispatcher"); int err = Native.getLastError(); lastWinError = err; log(err + ":" + Kernel32Util.formatMessageFromLastErrorCode(err)); } } public void setStopTimeout(int t) { stopTimeout = t; } public int getStopTimeout() { return stopTimeout; } public void reportStartup() { reportStatus(WINSVC.SERVICE_RUNNING, WINERROR.NO_ERROR, 0); onStart(); 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. // System.exit(0); } /** * Get a handle to the ServiceControlManager. * * @param machine * name of the machine or null for localhost * @param access * access flags * @return handle to ServiceControlManager or null when failed */ static private Pointer openServiceControlManager(String machine, int access) { Pointer handle = null; Advapi32 advapi32; advapi32 = Advapi32.INSTANCE; handle = advapi32.OpenSCManager(machine, null, access); if (handle == null) { int err = Native.getLastError(); lastWinError = err; System.out.println("Error in OpenSCManager: " + Integer.toHexString(err)); if (err == 5) System.out.println("Access denied: please check the user credentials"); } return (handle); } static public Map<String, ENUM_SERVICE_STATUS_PROCESS> enumerateServices(String machine) { Map<String, ENUM_SERVICE_STATUS_PROCESS> result = new HashMap(); // Open the Service Control Manager Pointer sc = openServiceControlManager(machine, WINSVC.SC_MANAGER_ENUMERATE_SERVICE); // Check if OpenSCManager returns NULL. Otherwise proceed if (sc != null && !sc.equals(null)) { Memory service_data = null; int service_data_size = 0; int infoLevel = WINSVC.SC_ENUM_PROCESS_INFO; boolean retVal; IntByReference bytesNeeded = new IntByReference(0); IntByReference srvCount = new IntByReference(0); IntByReference resumeHandle = new IntByReference(0); int srvType = WINSVC.SERVICE_WIN32; int srvState = WINSVC.SERVICE_STATE_ALL; // Call EnumServicesStatus with null data and data_size == 0, so we // get the required memory size retVal = Advapi32.INSTANCE.EnumServicesStatusExW(sc, infoLevel, srvType, srvState, service_data, service_data_size, bytesNeeded, srvCount, resumeHandle, null); int err = Native.getLastError(); // EnumServicesStatus should need more memory space if ((!retVal) || err == WINERROR.ERROR_MORE_DATA) { int bytesCount = bytesNeeded.getValue(); service_data = new Memory(bytesCount); service_data.clear(); service_data_size = bytesCount; // System.out.println(resumeHandle.getValue()); resumeHandle.setValue(0); retVal = Advapi32.INSTANCE.EnumServicesStatusExW(sc, infoLevel, srvType, srvState, service_data, service_data_size, bytesNeeded, srvCount, resumeHandle, null); if (!retVal) { err = Native.getLastError(); System.out.println("Error in EnumServicesStatusExA " + Integer.toHexString(err)); return null; } } else return null; ENUM_SERVICE_STATUS_PROCESS serviceStatus = new ENUM_SERVICE_STATUS_PROCESS(); serviceStatus.init(service_data); for (int i = 0; i < srvCount.getValue(); i++) { result.put(serviceStatus.getServiceName().toLowerCase(), serviceStatus); serviceStatus = serviceStatus.next(); } } // Close the SC_HANLDE returned by OpenSCManager Advapi32.INSTANCE.CloseServiceHandle(sc); return result; } /** * Report service status to the ServiceControlManager. * * @param status * status * @param win32ExitCode * exit code * @param waitHint * time to wait */ protected void reportStatus(int status, int win32ExitCode, int waitHint) { Advapi32 advapi32; Advapi32.SERVICE_STATUS serviceStatus; advapi32 = Advapi32.INSTANCE; serviceStatus = new Advapi32.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; serviceStatus.dwCheckPoint = checkPoint; log("reporting status " + checkPoint); advapi32.SetServiceStatus(serviceStatusHandle, serviceStatus); } /** * Called when service is starting. */ public abstract void onStart(); /* * Called when service should stop. */ public abstract void onStop(); public abstract void log(String txt); /** * Implementation of the service main function. */ private class ServiceMain implements Advapi32.SERVICE_MAIN_FUNCTION { /** * Called when the service is starting. * * @param dwArgc * number of arguments * @param lpszArgv * pointer to arguments */ public void callback(int dwArgc, Pointer lpszArgv) { Advapi32 advapi32; advapi32 = Advapi32.INSTANCE; log("+ ServiceMain callback"); serviceControl = new ServiceControl(); serviceStatusHandle = advapi32.RegisterServiceCtrlHandlerEx(serviceName, serviceControl, null); // if we are waiting for application to report startup if (!autoReportStartup) try { // report the startup time reportStatus(WINSVC.SERVICE_START_PENDING, WINERROR.NO_ERROR, startupTimeout); // wait for application to send startup notification startupLock.lock(); if (!startupCondition.await(startupTimeout, TimeUnit.MILLISECONDS)) { log("service startup timeout -> aborting"); System.exit(999); } } catch (InterruptedException e) { e.printStackTrace(); } finally { startupLock.unlock(); } else // if we are not waiting for the application, give us at most 5 // seconds to report startup reportStatus(WINSVC.SERVICE_START_PENDING, WINERROR.NO_ERROR, 5000); // this method will hang until the service terminates reportStartup(); } } /** * Implementation of the service control function. */ private class ServiceControl implements Advapi32.HandlerEx { /** * Called when the service get a control code. * * @param dwControl * @param dwEventType * @param lpEventData * @param lpContext */ public int callback(int dwControl, int dwEventType, Pointer lpEventData, Pointer lpContext) { log("received service control " + dwControl); switch (dwControl) { case WINSVC.SERVICE_CONTROL_STOP: case WINSVC.SERVICE_CONTROL_SHUTDOWN: checkPoint = 1; reportStatus(WINSVC.SERVICE_STOP_PENDING, WINERROR.NO_ERROR, stopTimeout); _stopping = true; if (dwControl == WINSVC.SERVICE_CONTROL_STOP) _stopReason = "SERVICE"; if (dwControl == WINSVC.SERVICE_CONTROL_SHUTDOWN) _stopReason = "COMPUTER"; onStop(); } return WINERROR.NO_ERROR; } } public int getStartupTimeout() { return startupTimeout; } public boolean isAutoReportStartup() { return autoReportStartup; } public void setStartupTimeout(int startupTimeout) { this.startupTimeout = startupTimeout; } public void setAutoReportStartup(boolean autoReportStartup) { this.autoReportStartup = autoReportStartup; } public void notifyStartup() { try { startupLock.lock(); startupCondition.signal(); } finally { startupLock.unlock(); } } public void signalStopping(long waitHint) { if (_stopping) reportStatus(WINSVC.SERVICE_STOP_PENDING, WINERROR.NO_ERROR, (int)waitHint); } public boolean requestElevation() { return lastWinError == 5; } }