blob: 0d7c53656a301ff07e4e598b62c50dc5e57061e7 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.internal.shared;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.net.SocketException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.sun.jna.Callback;
import com.sun.jna.LastErrorException;
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.NativeLibrary;
import com.sun.jna.NativeLong;
import com.sun.jna.Platform;
import com.sun.jna.Pointer;
import com.sun.jna.Structure;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.win32.StdCallLibrary;
import org.apache.logging.log4j.Logger;
import org.apache.geode.SystemFailure;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.annotations.internal.MakeNotStatic;
import org.apache.geode.internal.cache.DiskStoreImpl;
import org.apache.geode.internal.process.signal.Signal;
import org.apache.geode.logging.internal.log4j.api.LogService;
/**
* Implementation of {@link NativeCalls} interface that encapsulates native C calls via JNA. To
* obtain an instance of JNA based implementation for the current platform, use
* {@link NativeCallsJNAImpl#getInstance()}.
*
* BridJ is supposed to be cleaner, faster but it does not support Solaris/SPARC yet and its not a
* mature library yet, so not using it. Can revisit once this changes.
*
* @since GemFire 8.0
*/
public class NativeCallsJNAImpl {
private static final Logger logger = LogService.getLogger();
// no instance allowed
private NativeCallsJNAImpl() {}
/**
* The static instance of the JNA based implementation for this platform.
*/
@Immutable
private static final NativeCalls instance = getImplInstance();
private static NativeCalls getImplInstance() {
if (Platform.isLinux()) {
return new LinuxNativeCalls();
}
if (Platform.isWindows()) {
return new WinNativeCalls();
}
if (Platform.isSolaris()) {
return new SolarisNativeCalls();
}
if (Platform.isMac()) {
return new MacOSXNativeCalls();
}
if (Platform.isFreeBSD()) {
return new FreeBSDNativeCalls();
}
return new POSIXNativeCalls();
}
/**
* Get an instance of JNA based implementation of {@link NativeCalls} for the current platform.
*/
public static NativeCalls getInstance() {
return instance;
}
/**
* Implementation of {@link NativeCalls} for POSIX compatible platforms.
*/
private static class POSIXNativeCalls extends NativeCalls {
static {
Native.register("c");
}
public static native int setenv(String name, String value, int overwrite)
throws LastErrorException;
public static native int unsetenv(String name) throws LastErrorException;
public static native String getenv(String name);
public static native int getpid();
public static native int kill(int processId, int signal) throws LastErrorException;
public static native int setsid() throws LastErrorException;
public static native int umask(int mask);
public static native int signal(int signum, SignalHandler handler);
public static native int setsockopt(int sockfd, int level, int optName, IntByReference optVal,
int optSize) throws LastErrorException;
public static native int close(int fd) throws LastErrorException;
public static native int isatty(int fd) throws LastErrorException;
static final int EPERM = 1;
static final int ENOSPC = 28;
/** Signal callback handler for <code>signal</code> native call. */
interface SignalHandler extends Callback {
void callback(int signum);
}
/**
* holds a reference of {@link SignalHandler} installed in {@link #daemonize} for SIGHUP to
* avoid it being GCed. Assumes that the NativeCalls instance itself is a singleton and never
* GCed.
*/
SignalHandler hupHandler;
/**
* the {@link RehashServerOnSIGHUP} instance sent to {@link #daemonize}
*/
RehashServerOnSIGHUP rehashCallback;
/**
* @see NativeCalls#getOSType()
*/
@Override
public OSType getOSType() {
return OSType.GENERIC_POSIX;
}
/**
* @see NativeCalls#getEnvironment(String)
*/
@Override
public synchronized String getEnvironment(final String name) {
if (name == null) {
throw new UnsupportedOperationException("getEnvironment() for name=NULL");
}
return getenv(name);
}
/**
* @see NativeCalls#getProcessId()
*/
@Override
public int getProcessId() {
return getpid();
}
/**
* @see NativeCalls#isProcessActive(int)
*/
@Override
public boolean isProcessActive(final int processId) {
try {
return kill(processId, 0) == 0;
} catch (LastErrorException le) {
if (le.getErrorCode() == EPERM) {
// Process exists; it probably belongs to another user (bug 27698).
return true;
}
}
return false;
}
/**
* @see NativeCalls#killProcess(int)
*/
@Override
public boolean killProcess(final int processId) {
try {
return kill(processId, 9) == 0;
} catch (LastErrorException le) {
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public void daemonize(RehashServerOnSIGHUP callback) throws UnsupportedOperationException {
UnsupportedOperationException err = null;
try {
setsid();
} catch (LastErrorException le) {
// errno=EPERM indicates already group leader
if (le.getErrorCode() != EPERM) {
err = new UnsupportedOperationException("Failed in setsid() in daemonize() due to "
+ le.getMessage() + " (errno=" + le.getErrorCode() + ')');
}
}
// set umask to something consistent for servers
final int newMask = 022;
int oldMask = umask(newMask);
// check if old umask was more restrictive, and if so then set it back
if ((oldMask & 077) > newMask) {
umask(oldMask);
}
// catch the SIGHUP signal and invoke any callback provided
this.rehashCallback = callback;
this.hupHandler = new SignalHandler() {
@Override
public void callback(int signum) {
// invoke the rehash function if provided
final RehashServerOnSIGHUP rehashCb = rehashCallback;
if (signum == Signal.SIGHUP.getNumber() && rehashCb != null) {
rehashCb.rehash();
}
}
};
signal(Signal.SIGHUP.getNumber(), this.hupHandler);
// ignore SIGCHLD and SIGINT
signal(Signal.SIGCHLD.getNumber(), this.hupHandler);
signal(Signal.SIGINT.getNumber(), this.hupHandler);
if (err != null) {
throw err;
}
}
@Override
public void preBlow(String path, long maxSize, boolean preAllocate) throws IOException {
if (logger.isDebugEnabled()) {
logger.debug("DEBUG preBlow called for path = " + path);
}
if (!preAllocate || !hasFallocate(path)) {
super.preBlow(path, maxSize, preAllocate);
if (logger.isDebugEnabled()) {
logger.debug("DEBUG preBlow super.preBlow 1 called for path = " + path);
}
return;
}
int fd = -1;
boolean unknownError = false;
try {
fd = createFD(path, 00644);
if (!isOnLocalFileSystem(path)) {
super.preBlow(path, maxSize, preAllocate);
if (logger.isDebugEnabled()) {
logger.debug("DEBUG preBlow super.preBlow 2 called as path = " + path
+ " not on local file system");
}
if (DiskStoreImpl.TEST_NO_FALLOC_DIRS != null) {
DiskStoreImpl.TEST_NO_FALLOC_DIRS.add(path);
}
return;
}
fallocateFD(fd, 0L, maxSize);
if (DiskStoreImpl.TEST_CHK_FALLOC_DIRS != null) {
DiskStoreImpl.TEST_CHK_FALLOC_DIRS.add(path);
}
if (logger.isDebugEnabled()) {
logger.debug("DEBUG preBlow posix_fallocate called for path = " + path
+ " and ret = 0 maxsize = " + maxSize);
}
} catch (LastErrorException le) {
if (logger.isDebugEnabled()) {
logger.debug("DEBUG preBlow posix_fallocate called for path = " + path + " and ret = "
+ le.getErrorCode() + " maxsize = " + maxSize);
}
// check for no space left on device
if (le.getErrorCode() == ENOSPC) {
unknownError = false;
throw new IOException("Not enough space left on device");
} else {
unknownError = true;
}
} finally {
if (fd >= 0) {
try {
close(fd);
} catch (Exception e) {
// ignore
}
}
if (unknownError) {
super.preBlow(path, maxSize, preAllocate);
if (logger.isDebugEnabled()) {
logger.debug("DEBUG preBlow super.preBlow 3 called for path = " + path);
}
}
}
}
protected boolean hasFallocate(String path) {
return false;
}
protected int createFD(String path, int flags) throws LastErrorException {
throw new UnsupportedOperationException("not expected to be invoked");
}
protected void fallocateFD(int fd, long offset, long len) throws LastErrorException {
throw new UnsupportedOperationException("not expected to be invoked");
}
/**
* {@inheritDoc}
*/
@Override
public Map<TCPSocketOptions, Throwable> setSocketOptions(Socket sock, InputStream sockStream,
Map<TCPSocketOptions, Object> optValueMap) throws UnsupportedOperationException {
return super.setGenericSocketOptions(sock, sockStream, optValueMap);
}
@Override
protected int setPlatformSocketOption(int sockfd, int level, int optName, TCPSocketOptions opt,
Integer optVal, int optSize) throws NativeErrorException {
try {
return setsockopt(sockfd, level, optName, new IntByReference(optVal.intValue()), optSize);
} catch (LastErrorException le) {
throw new NativeErrorException(le.getMessage(), le.getErrorCode(), le.getCause());
}
}
@Override
public boolean isTTY() {
try {
return isatty(0) == 1;
} catch (Exception e) {
throw new RuntimeException("Couldn't find tty impl. ", e);
}
}
}
/**
* Implementation of {@link NativeCalls} for Linux platform.
*/
private static class LinuxNativeCalls extends POSIXNativeCalls {
static {
Native.register("c");
}
// #define values for keepalive options in /usr/include/netinet/tcp.h
static final int OPT_TCP_KEEPIDLE = 4;
static final int OPT_TCP_KEEPINTVL = 5;
static final int OPT_TCP_KEEPCNT = 6;
static final int ENOPROTOOPT = 92;
static final int ENOPROTOOPT_ALPHA = 42;
static final int ENOPROTOOPT_MIPS = 99;
static final int ENOPROTOOPT_PARISC = 220;
/** posix_fallocate returns error number rather than setting errno */
public static native int posix_fallocate64(int fd, long offset, long len);
public static native int creat64(String path, int flags) throws LastErrorException;
/**
* {@inheritDoc}
*/
@Override
public OSType getOSType() {
return OSType.LINUX;
}
@Override
protected int getPlatformOption(TCPSocketOptions opt) throws UnsupportedOperationException {
switch (opt) {
case OPT_KEEPIDLE:
return OPT_TCP_KEEPIDLE;
case OPT_KEEPINTVL:
return OPT_TCP_KEEPINTVL;
case OPT_KEEPCNT:
return OPT_TCP_KEEPCNT;
default:
throw new UnsupportedOperationException("unknown option " + opt);
}
}
@Override
protected boolean isNoProtocolOptionCode(int errno) {
switch (errno) {
case ENOPROTOOPT:
return true;
case ENOPROTOOPT_ALPHA:
return true;
case ENOPROTOOPT_MIPS:
return true;
case ENOPROTOOPT_PARISC:
return true;
default:
return false;
}
}
static {
if (Platform.is64Bit()) {
StatFS64.dummy();
} else {
StatFS.dummy();
}
}
private ThreadLocal<Structure> tSpecs = new ThreadLocal<Structure>();
@MakeNotStatic
private static boolean isStatFSEnabled;
public static class FSIDIntArr2 extends Structure {
public int[] fsid = new int[2];
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] {"fsid"});
}
}
public static class FSPAREIntArr5 extends Structure {
public int[] fspare = new int[5];
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] {"fspare"});
}
}
public static class StatFS extends Structure {
public int f_type;
public int f_bsize;
public int f_blocks;
public int f_bfree;
public int f_bavail;
public int f_files;
public int f_ffree;
public FSIDIntArr2 f_fsid;
public int f_namelen;
public int f_frsize;
public FSPAREIntArr5 f_spare;
static {
try {
Native.register("rt");
StatFS struct = new StatFS();
int ret = statfs(".", struct);
if (ret == 0) {
isStatFSEnabled = true;
} else {
isStatFSEnabled = false;
}
} catch (VirtualMachineError e) {
SystemFailure.initiateFailure(e);
throw e;
} catch (Throwable t) {
SystemFailure.checkFailure();
isStatFSEnabled = false;
}
}
public static native int statfs(String path, StatFS statfs) throws LastErrorException;
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] {"f_type", "f_bsize", "f_blocks", "f_bfree", "f_bavail",
"f_files", "f_ffree", "f_fsid", "f_namelen", "f_frsize", "f_spare"});
}
// KN: TODO need to add more types which are type of remote.
// Not sure about these file types which needs to be cheked and added in the list below
// COH, DEVFS, ISOFS, OPENPROM_SUPER_MAGIC, PROC_SUPER_MAGIC, UDF_SUPER_MAGIC
// Do man 2 statfs on linux and will give all the file types.
// Following identified as remote file system types
// CIFS_MAGIC_NUMBER, CODA_SUPER_MAGIC, NCP_SUPER_MAGIC, NFS_SUPER_MAGIC, SMB_SUPER_MAGIC,
// TMPFS_MAGIC
// 0xFF534D42 , 0x73757245 , 0x564c , 0x6969 , 0x517B , 0x01021994
// 4283649346 , 1937076805 , 22092 , 26985 , 20859 , 16914836
@Immutable
private static final int[] REMOTE_TYPES =
new int[] { /* 4283649346, */ 1937076805, 22092, 26985, 20859, 16914836};
public boolean isTypeLocal() {
for (int i = 0; i < REMOTE_TYPES.length; i++) {
if (REMOTE_TYPES[i] == f_type) {
return false;
}
}
return true;
}
public static void dummy() {
}
}
public static class FSPARELongArr5 extends Structure {
public long[] fspare = new long[5];
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] {"fspare"});
}
}
public static class StatFS64 extends Structure {
public long f_type;
public long f_bsize;
public long f_blocks;
public long f_bfree;
public long f_bavail;
public long f_files;
public long f_ffree;
public FSIDIntArr2 f_fsid;
public long f_namelen;
public long f_frsize;
public FSPARELongArr5 f_spare;
// KN: TODO need to add more types which are type of remote.
// Not sure about these file types which needs to be checked and added in the list below
// COH, DEVFS, ISOFS, OPENPROM_SUPER_MAGIC, PROC_SUPER_MAGIC, UDF_SUPER_MAGIC
// Do man 2 statfs on linux and will give all the file types.
// Following identified as remote file system types
// CIFS_MAGIC_NUMBER, CODA_SUPER_MAGIC, NCP_SUPER_MAGIC, NFS_SUPER_MAGIC, SMB_SUPER_MAGIC,
// TMPFS_MAGIC
// 0xFF534D42 , 0x73757245 , 0x564c , 0x6969 , 0x517B , 0x01021994
// 4283649346 , 1937076805 , 22092 , 26985 , 20859 , 16914836
@Immutable
private static final long[] REMOTE_TYPES =
new long[] {4283649346l, 1937076805l, 22092l, 26985l, 20859l, 16914836l};
static {
try {
Native.register("rt");
StatFS64 struct = new StatFS64();
int ret = statfs(".", struct);
if (ret == 0) {
isStatFSEnabled = true;
} else {
isStatFSEnabled = false;
}
} catch (Throwable t) {
System.out.println("got error t: " + t.getMessage());
t.printStackTrace();
isStatFSEnabled = false;
}
}
public static native int statfs(String path, StatFS64 statfs) throws LastErrorException;
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[] {"f_type", "f_bsize", "f_blocks", "f_bfree", "f_bavail",
"f_files", "f_ffree", "f_fsid", "f_namelen", "f_frsize", "f_spare"});
}
public boolean isTypeLocal() {
for (int i = 0; i < REMOTE_TYPES.length; i++) {
if (REMOTE_TYPES[i] == f_type) {
return false;
}
}
return true;
}
public static void dummy() {
}
}
/**
* Get the file store type of a path. for example, /dev/sdd1(store name) /w2-gst-dev40d(mount
* point) ext4(type)
*
* @return file store type
*/
public String getFileStoreType(final String path) {
File diskFile = new File(path);
if (!diskFile.exists()) {
diskFile = diskFile.getParentFile();
}
Path currentPath = diskFile.toPath();
if (currentPath.isAbsolute() && Files.exists(currentPath)) {
try {
FileStore store = Files.getFileStore(currentPath);
return store.type();
} catch (IOException e) {
return null;
}
}
return null;
}
/**
* This will return whether the path passed in as arg is part of a local file system or a remote
* file system. This method is mainly used by the DiskCapacityMonitor thread and we don't want
* to monitor remote fs available space as due to network problems/firewall issues the call to
* getUsableSpace can hang. See bug #49155. On platforms other than Linux this will return false
* even if it on local file system for now.
*/
@Override
public boolean isOnLocalFileSystem(final String path) {
if (!isStatFSEnabled) {
// if (logger != null && logger.fineEnabled()) {
// logger.info("DEBUG isOnLocalFileSystem returning false 1 for path = " + path);
// }
return false;
}
final int numTries = 10;
for (int i = 1; i <= numTries; i++) {
try {
if (Platform.is64Bit()) {
StatFS64 stat = new StatFS64();
StatFS64.statfs(path, stat);
// if (logger != null && logger.fineEnabled()) {
// logger.info("DEBUG 64 isOnLocalFileSystem returning " + stat.isTypeLocal() + " for
// path = " + path);
// }
return stat.isTypeLocal();
} else {
StatFS stat = new StatFS();
StatFS.statfs(path, stat);
// if (logger != null && logger.fineEnabled()) {
// logger.fine("DEBUG 32 isOnLocalFileSystem returning " + stat.isTypeLocal() + " for
// path = " + path);
// }
return stat.isTypeLocal();
}
} catch (LastErrorException le) {
// ignoring it as NFS mounted can give this exception
// and we just want to retry to remove transient problem.
if (logger.isDebugEnabled()) {
logger.debug("DEBUG isOnLocalFileSystem got ex = " + le + " msg = " + le.getMessage());
}
}
}
return false;
}
@Immutable
private static final String[] FallocateFileSystems = {"ext4", "xfs", "btrfs", "ocfs2"};
@Override
protected boolean hasFallocate(String path) {
String fstype = getFileStoreType(path);
for (String type : FallocateFileSystems) {
if (type.equalsIgnoreCase(fstype)) {
return true;
}
}
return false;
}
@Override
protected int createFD(String path, int flags) throws LastErrorException {
return creat64(path, flags);
}
@Override
protected void fallocateFD(int fd, long offset, long len) throws LastErrorException {
int errno = posix_fallocate64(fd, offset, len);
if (errno != 0) {
throw new LastErrorException(errno);
}
}
}
/**
* Implementation of {@link NativeCalls} for Solaris platform.
*/
private static class SolarisNativeCalls extends POSIXNativeCalls {
static {
Native.register("nsl");
Native.register("socket");
}
// #define values for keepalive options in /usr/include/netinet/tcp.h
// Below are only available on Solaris 11 and above but older platforms will
// throw an exception which higher layers will handle appropriately
static final int OPT_TCP_KEEPALIVE_THRESHOLD = 0x16;
static final int OPT_TCP_KEEPALIVE_ABORT_THRESHOLD = 0x17;
static final int ENOPROTOOPT = 99;
/**
* {@inheritDoc}
*/
@Override
public OSType getOSType() {
return OSType.SOLARIS;
}
@Override
protected int getPlatformOption(TCPSocketOptions opt) throws UnsupportedOperationException {
switch (opt) {
case OPT_KEEPIDLE:
return OPT_TCP_KEEPALIVE_THRESHOLD;
case OPT_KEEPINTVL:
case OPT_KEEPCNT:
return UNSUPPORTED_OPTION;
default:
throw new UnsupportedOperationException("unknown option " + opt);
}
}
@Override
protected int setPlatformSocketOption(int sockfd, int level, int optName, TCPSocketOptions opt,
Integer optVal, int optSize) throws NativeErrorException {
try {
switch (optName) {
case OPT_TCP_KEEPALIVE_THRESHOLD:
// value required is in millis
final IntByReference timeout = new IntByReference(optVal.intValue() * 1000);
int result = setsockopt(sockfd, level, optName, timeout, optSize);
if (result == 0) {
// setting ABORT_THRESHOLD to be same as KEEPALIVE_THRESHOLD
return setsockopt(sockfd, level, OPT_TCP_KEEPALIVE_ABORT_THRESHOLD, timeout, optSize);
} else {
return result;
}
default:
throw new UnsupportedOperationException("unsupported option " + opt);
}
} catch (LastErrorException le) {
throw new NativeErrorException(le.getMessage(), le.getErrorCode(), le.getCause());
}
}
@Override
protected boolean isNoProtocolOptionCode(int errno) {
return (errno == ENOPROTOOPT);
}
}
/**
* Implementation of {@link NativeCalls} for MacOSX platform.
*/
private static class MacOSXNativeCalls extends POSIXNativeCalls {
// #define values for keepalive options in /usr/include/netinet/tcp.h
static final int OPT_TCP_KEEPALIVE = 0x10;
static final int ENOPROTOOPT = 42;
/**
* {@inheritDoc}
*/
@Override
public OSType getOSType() {
return OSType.MACOSX;
}
@Override
protected int getPlatformOption(TCPSocketOptions opt) throws UnsupportedOperationException {
switch (opt) {
case OPT_KEEPIDLE:
return OPT_TCP_KEEPALIVE;
case OPT_KEEPINTVL:
case OPT_KEEPCNT:
return UNSUPPORTED_OPTION;
default:
throw new UnsupportedOperationException("unknown option " + opt);
}
}
@Override
protected boolean isNoProtocolOptionCode(int errno) {
return (errno == ENOPROTOOPT);
}
}
/**
* Implementation of {@link NativeCalls} for FreeBSD platform.
*/
private static class FreeBSDNativeCalls extends POSIXNativeCalls {
// #define values for keepalive options in /usr/include/netinet/tcp.h
static final int OPT_TCP_KEEPALIVE = 0x100;
static final int OPT_TCP_KEEPINTVL = 0x200;
static final int OPT_TCP_KEEPCNT = 0x400;
static final int ENOPROTOOPT = 42;
/**
* {@inheritDoc}
*/
@Override
public OSType getOSType() {
return OSType.FREEBSD;
}
@Override
protected int getPlatformOption(TCPSocketOptions opt) throws UnsupportedOperationException {
switch (opt) {
case OPT_KEEPIDLE:
return OPT_TCP_KEEPALIVE;
case OPT_KEEPINTVL:
return OPT_TCP_KEEPINTVL;
case OPT_KEEPCNT:
return OPT_TCP_KEEPCNT;
default:
throw new UnsupportedOperationException("unknown option " + opt);
}
}
@Override
protected boolean isNoProtocolOptionCode(int errno) {
return (errno == ENOPROTOOPT);
}
}
/**
* Implementation of {@link NativeCalls} for Windows platforms.
*/
private static class WinNativeCalls extends NativeCalls {
static {
// for socket operations
Native.register("Ws2_32");
}
@SuppressWarnings("unused")
public static class TcpKeepAlive extends Structure {
public int onoff;
public int keepalivetime;
public int keepaliveinterval;
@Override
protected List<String> getFieldOrder() {
return Arrays.asList(new String[] {"onoff", "keepalivetime", "keepaliveinterval"});
}
}
public static native int WSAIoctl(NativeLong sock, int controlCode, TcpKeepAlive value,
int valueSize, Pointer outValue, int outValueSize, IntByReference bytesReturned,
Pointer overlapped, Pointer completionRoutine) throws LastErrorException;
static final int WSAENOPROTOOPT = 10042;
static final int SIO_KEEPALIVE_VALS = -1744830460;
private static class Kernel32 {
static {
// kernel32 requires stdcall calling convention
Map<String, Object> kernel32Options = new HashMap<String, Object>();
kernel32Options.put(Library.OPTION_CALLING_CONVENTION, StdCallLibrary.STDCALL_CONVENTION);
kernel32Options.put(Library.OPTION_FUNCTION_MAPPER, StdCallLibrary.FUNCTION_MAPPER);
final NativeLibrary kernel32Lib = NativeLibrary.getInstance("kernel32", kernel32Options);
Native.register(kernel32Lib);
}
// Values below from windows.h header are hard-coded since there
// does not seem any simple way to get those at build or run time.
// Hopefully these will never change else all hell will break
// loose in Windows world ...
static final int PROCESS_QUERY_INFORMATION = 0x0400;
static final int PROCESS_TERMINATE = 0x0001;
static final int STILL_ACTIVE = 259;
static final int INVALID_HANDLE = -1;
public static native boolean SetEnvironmentVariableA(String name, String value)
throws LastErrorException;
public static native int GetEnvironmentVariableA(String name, byte[] pvalue, int psize);
public static native int GetCurrentProcessId();
public static native Pointer OpenProcess(int desiredAccess, boolean inheritHandle,
int processId) throws LastErrorException;
public static native boolean TerminateProcess(Pointer processHandle, int exitCode)
throws LastErrorException;
public static native boolean GetExitCodeProcess(Pointer processHandle,
IntByReference exitCode) throws LastErrorException;
public static native boolean CloseHandle(Pointer handle) throws LastErrorException;
}
/**
* @see NativeCalls#getOSType()
*/
@Override
public OSType getOSType() {
return OSType.WIN;
}
/**
* @see NativeCalls#getEnvironment(String)
*/
@Override
public synchronized String getEnvironment(final String name) {
if (name == null) {
throw new UnsupportedOperationException("getEnvironment() for name=NULL");
}
int psize = Kernel32.GetEnvironmentVariableA(name, null, 0);
if (psize > 0) {
for (;;) {
byte[] result = new byte[psize];
psize = Kernel32.GetEnvironmentVariableA(name, result, psize);
if (psize == (result.length - 1)) {
return new String(result, 0, psize);
} else if (psize <= 0) {
return null;
}
}
} else {
return null;
}
}
/**
* @see NativeCalls#getProcessId()
*/
@Override
public int getProcessId() {
return Kernel32.GetCurrentProcessId();
}
/**
* @see NativeCalls#isProcessActive(int)
*/
@Override
public boolean isProcessActive(final int processId) {
try {
final Pointer procHandle =
Kernel32.OpenProcess(Kernel32.PROCESS_QUERY_INFORMATION, false, processId);
final long hval;
if (procHandle == null
|| (hval = Pointer.nativeValue(procHandle)) == Kernel32.INVALID_HANDLE || hval == 0) {
return false;
} else {
final IntByReference status = new IntByReference();
final boolean result =
Kernel32.GetExitCodeProcess(procHandle, status)
&& status.getValue() == Kernel32.STILL_ACTIVE;
Kernel32.CloseHandle(procHandle);
return result;
}
} catch (LastErrorException le) {
// some problem in getting process status
return false;
}
}
/**
* @see NativeCalls#killProcess(int)
*/
@Override
public boolean killProcess(final int processId) {
try {
final Pointer procHandle =
Kernel32.OpenProcess(Kernel32.PROCESS_TERMINATE, false, processId);
final long hval;
if (procHandle == null
|| (hval = Pointer.nativeValue(procHandle)) == Kernel32.INVALID_HANDLE || hval == 0) {
return false;
} else {
final boolean result = Kernel32.TerminateProcess(procHandle, -1);
Kernel32.CloseHandle(procHandle);
return result;
}
} catch (LastErrorException le) {
// some problem in killing the process
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public void daemonize(RehashServerOnSIGHUP callback)
throws UnsupportedOperationException, IllegalStateException {
throw new IllegalStateException("daemonize() not applicable for Windows platform");
}
/**
* {@inheritDoc}
*/
@Override
public Map<TCPSocketOptions, Throwable> setSocketOptions(Socket sock, InputStream sockStream,
Map<TCPSocketOptions, Object> optValueMap) throws UnsupportedOperationException {
final TcpKeepAlive optValue = new TcpKeepAlive();
final int optSize = (Integer.SIZE / Byte.SIZE) * 3;
TCPSocketOptions errorOpt = null;
Throwable error = null;
for (Map.Entry<TCPSocketOptions, Object> e : optValueMap.entrySet()) {
TCPSocketOptions opt = e.getKey();
Object value = e.getValue();
// all options currently require an integer argument
if (value == null || !(value instanceof Integer)) {
throw new IllegalArgumentException("bad argument type "
+ (value != null ? value.getClass().getName() : "NULL") + " for " + opt);
}
switch (opt) {
case OPT_KEEPIDLE:
optValue.onoff = 1;
// in millis
optValue.keepalivetime = ((Integer) value).intValue() * 1000;
break;
case OPT_KEEPINTVL:
optValue.onoff = 1;
// in millis
optValue.keepaliveinterval = ((Integer) value).intValue() * 1000;
break;
case OPT_KEEPCNT:
errorOpt = opt;
error = new UnsupportedOperationException(getUnsupportedSocketOptionMessage(opt));
break;
default:
throw new UnsupportedOperationException("unknown option " + opt);
}
}
final int sockfd = getSocketKernelDescriptor(sock, sockStream);
final IntByReference nBytes = new IntByReference(0);
try {
if (WSAIoctl(new NativeLong(sockfd), SIO_KEEPALIVE_VALS, optValue, optSize, null, 0, nBytes,
null, null) != 0) {
errorOpt = TCPSocketOptions.OPT_KEEPIDLE; // using some option here
error = new SocketException(getOSType() + ": error setting options: " + optValueMap);
}
} catch (LastErrorException le) {
// check if the error indicates that option is not supported
errorOpt = TCPSocketOptions.OPT_KEEPIDLE; // using some option here
if (le.getErrorCode() == WSAENOPROTOOPT) {
error = new UnsupportedOperationException(getUnsupportedSocketOptionMessage(errorOpt),
new NativeErrorException(le.getMessage(), le.getErrorCode(), le.getCause()));
} else {
final SocketException se =
new SocketException(getOSType() + ": failed to set options: " + optValueMap);
se.initCause(le);
error = se;
}
}
return errorOpt != null ? Collections.singletonMap(errorOpt, error) : null;
}
}
}