blob: 8dd427149e274b4a6784136d1151833990150cd8 [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.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketImpl;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.geode.SystemFailure;
import org.apache.geode.annotations.Immutable;
/**
* Encapsulates native C/C++ calls via JNA. To obtain an instance of implementation for a platform,
* use {@link NativeCalls#getInstance()}.
*
* @since GemFire 8.0
*/
public abstract class NativeCalls {
/**
* Static instance of NativeCalls implementation. This can be one of JNA implementations in
* <code>NativeCallsJNAImpl</code> or can fall back to a generic implementation in case JNA is not
* available for the platform.
*
* Note: this variable is deliberately not final so that other clients can plug in their own
* native implementations of NativeCalls.
*/
@Immutable
protected static final NativeCalls instance;
static {
NativeCalls inst;
try {
// try to load JNA implementation first
// we do it via reflection since some clients
// may not have it
final Class<?> c = Class.forName("org.apache.geode.internal.shared.NativeCallsJNAImpl");
inst = (NativeCalls) c.getMethod("getInstance").invoke(null);
} catch (VirtualMachineError e) {
SystemFailure.initiateFailure(e);
throw e;
} catch (Throwable t) {
SystemFailure.checkFailure();
inst = null;
}
if (inst == null) {
// fall back to generic implementation in case of a problem
inst = new NativeCallsGeneric();
}
instance = inst;
}
public NativeCalls() {}
/**
* Get an instance of implementation of {@link NativeCalls} for the current platform.
*/
public static NativeCalls getInstance() {
return instance;
}
/**
* Get the native kernel descriptor given the java Socket. This is a horribly implementation
* dependent code checking various cases to get to the underlying kernel socket descriptor but
* works for the JDK's we support or intend to support directly or indirectly (e.g. GCJ for ODBC
* clients).
*
* @param sock the java socket
* @param sockStream the {@link InputStream} of the java socket, if available
*
* @throws UnsupportedOperationException if the kernel descriptor could not be extracted
*/
protected int getSocketKernelDescriptor(Socket sock, InputStream sockStream)
throws UnsupportedOperationException {
Method m;
Field f;
Object obj;
FileDescriptor fd = null;
// in some cases (for SSL) the Socket can be a wrapper one
try {
f = getAnyField(sock.getClass(), "self");
if (f != null) {
f.setAccessible(true);
final Object self = f.get(sock);
if (self instanceof Socket) {
sock = (Socket) self;
sockStream = sock.getInputStream();
}
}
} catch (NoSuchFieldException fe) {
// ignore if there is no such field
} catch (RuntimeException re) {
throw re;
} catch (Exception ex) {
throw new UnsupportedOperationException(ex);
}
// first try using FileInputStream
if (sockStream instanceof FileInputStream) {
try {
fd = ((FileInputStream) sockStream).getFD();
} catch (Exception e) {
// go the fallback route
}
}
// else fallback to SocketImpl route
try {
if (fd == null) {
try {
// package private Socket.getImpl() to get SocketImpl
m = getAnyMethod(sock.getClass(), "getImpl");
} catch (Exception ex) {
try {
m = getAnyMethod(sock.getClass(), "getPlainSocketImpl");
} catch (Exception e) {
// try forcing the InputStream route
m = null;
if (sockStream == null) {
sockStream = sock.getInputStream();
if (sockStream instanceof FileInputStream) {
fd = ((FileInputStream) sockStream).getFD();
}
} else {
throw e;
}
}
}
if (m != null) {
m.setAccessible(true);
final SocketImpl sockImpl = (SocketImpl) m.invoke(sock);
if (sockImpl != null) {
try {
m = getAnyMethod(sockImpl.getClass(), "getFileDescriptor");
if (m != null) {
m.setAccessible(true);
fd = (FileDescriptor) m.invoke(sockImpl);
}
} catch (NoSuchMethodException nme) {
// go to field reflection route
}
}
}
}
if (fd != null) {
// get the kernel descriptor using reflection
f = getAnyField(fd.getClass(), "fd");
if (f != null) {
f.setAccessible(true);
obj = f.get(fd);
if (obj instanceof Integer) {
return ((Integer) obj).intValue();
}
}
}
throw new UnsupportedOperationException();
} catch (SecurityException se) {
throw new UnsupportedOperationException(se);
} catch (RuntimeException re) {
throw re;
} catch (Exception ex) {
throw new UnsupportedOperationException(ex);
}
}
protected static Method getAnyMethod(Class<?> c, String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
NoSuchMethodException firstEx = null;
for (;;) {
try {
return c.getDeclaredMethod(name, parameterTypes);
} catch (NoSuchMethodException nsme) {
if (firstEx == null) {
firstEx = nsme;
}
if ((c = c.getSuperclass()) == null) {
throw firstEx;
}
// else continue searching in superClass
}
}
}
protected static Field getAnyField(Class<?> c, String name)
throws NoSuchFieldException, SecurityException {
NoSuchFieldException firstEx = null;
for (;;) {
try {
return c.getDeclaredField(name);
} catch (NoSuchFieldException nsfe) {
if (firstEx == null) {
firstEx = nsfe;
}
if ((c = c.getSuperclass()) == null) {
throw firstEx;
}
// else continue searching in superClass
}
}
}
protected String getUnsupportedSocketOptionMessage(TCPSocketOptions opt) {
return "setSocketOption(): socket option " + opt + " not supported by current platform "
+ getOSType();
}
/**
* Get the {@link OSType} of current system.
*/
public abstract OSType getOSType();
/**
* Get the value of given environment variable. This is different from
* {@link System#getenv(String)} in that it returns the current value of the environment variable
* in the process rather than from a static unmodifiable map created on the first call.
*
* @param name the name of the environment variable to be modified
*/
public abstract String getEnvironment(String name);
/**
* Get the process ID of the current process.
*/
public abstract int getProcessId();
/**
* Check whether a process with given ID is still running.
*
* @throws UnsupportedOperationException if no native API to determine the process status could be
* invoked
*/
public abstract boolean isProcessActive(int processId) throws UnsupportedOperationException;
/**
* Kill the process with given process ID immediately (i.e. without giving it a chance to cleanup
* properly).
*
* @param processId the PID of the process to be kill
*
* @throws UnsupportedOperationException if no native API to kill the process could be invoked
*/
public abstract boolean killProcess(int processId) throws UnsupportedOperationException;
/**
* Perform the steps necessary to make the current JVM a proper UNIX daemon.
*
* @param callback register callback to be invoked on catching a SIGHUP signal; SIGHUP signal is
* ignored if the callback is null
*
* @throws UnsupportedOperationException if the native calls could not be completed for some
* reason or are not available
* @throws IllegalStateException for a non-UNIX platform
*/
public void daemonize(RehashServerOnSIGHUP callback)
throws UnsupportedOperationException, IllegalStateException {
throw new UnsupportedOperationException("daemonize() not available in base implementation");
}
public void preBlow(String path, long maxSize, boolean preAllocate) throws IOException {
RandomAccessFile raf = new RandomAccessFile(path, "rw");
try {
raf.setLength(maxSize);
} finally {
raf.close();
}
}
/**
* 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.
*/
public boolean isOnLocalFileSystem(final String path) {
return false;
}
/**
* Set given extended socket options on a Java {@link Socket}.
*
* @throws UnsupportedOperationException if the native API to set the option could not be found or
* invoked
*
* @return the unsupported {@link TCPSocketOptions} for the current platform and the underlying
* exception
*
* @see TCPSocketOptions
*/
public abstract Map<TCPSocketOptions, Throwable> setSocketOptions(Socket sock,
InputStream sockStream, Map<TCPSocketOptions, Object> optValueMap)
throws UnsupportedOperationException;
// IPPROTO_TCP is used by setsockopt to denote a TCP option
protected static final int OPT_IPPROTO_TCP = 6;
// indicates an unsupported TCPSocketOptions enum
protected static final int UNSUPPORTED_OPTION = Integer.MIN_VALUE;
/**
* A generic implementation of {@link #setSocketOptions} for POSIX like systems that requires the
* child classes to implement a few platform specific methods.
*/
protected Map<TCPSocketOptions, Throwable> setGenericSocketOptions(Socket sock,
InputStream sockStream, Map<TCPSocketOptions, Object> optValueMap)
throws UnsupportedOperationException {
final Set<Map.Entry<TCPSocketOptions, Object>> optValueEntries = optValueMap.entrySet();
for (Map.Entry<TCPSocketOptions, Object> e : optValueEntries) {
TCPSocketOptions opt = e.getKey();
Object value = e.getValue();
// just to check for unsupported option
getPlatformOption(opt);
// 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);
}
}
Map<TCPSocketOptions, Throwable> failures = new HashMap<TCPSocketOptions, Throwable>(4);
final int sockfd = getSocketKernelDescriptor(sock, sockStream);
for (Map.Entry<TCPSocketOptions, Object> e : optValueEntries) {
TCPSocketOptions opt = e.getKey();
Object value = e.getValue();
final int optName = getPlatformOption(opt);
if (optName == UNSUPPORTED_OPTION) {
failures.put(opt,
new UnsupportedOperationException(getUnsupportedSocketOptionMessage(opt)));
continue;
}
final int optSize = Integer.SIZE / Byte.SIZE;
try {
if (setPlatformSocketOption(sockfd, OPT_IPPROTO_TCP, optName, opt, (Integer) value,
optSize) != 0) {
failures.put(opt,
new SocketException(getOSType() + ": error setting option " + opt + " to " + value));
}
} catch (NativeErrorException ne) {
// check if the error indicates that option is not supported
if (isNoProtocolOptionCode(ne.getErrorCode())) {
failures.put(opt,
new UnsupportedOperationException(getUnsupportedSocketOptionMessage(opt), ne));
} else {
final SocketException se =
new SocketException(getOSType() + ": failed to set " + opt + " to " + value);
se.initCause(ne);
failures.put(opt, se);
}
}
}
return failures.size() > 0 ? failures : null;
}
protected int getPlatformOption(TCPSocketOptions opt) throws UnsupportedOperationException {
// no generic POSIX specification for this
throw new UnsupportedOperationException(
"setSocketOption not supported for generic POSIX platform");
}
protected int setPlatformSocketOption(int sockfd, int level, int optName, TCPSocketOptions opt,
Integer optVal, int optSize) throws UnsupportedOperationException, NativeErrorException {
// no generic POSIX specification for this
throw new UnsupportedOperationException(
"setSocketOption not supported for generic POSIX platform");
}
protected boolean isNoProtocolOptionCode(int errno) throws UnsupportedOperationException {
// no generic POSIX specification for this
throw new UnsupportedOperationException(
"setSocketOption not supported for generic POSIX platform");
}
/**
* Callback invoked when an OS-level SIGHUP signal is caught after handler has been installed by
* {@link NativeCalls#daemonize}. This is provided to allow for re-reading configuration files or
* any other appropriate actions on receiving HUP signal as is the convention in other servers.
*
* @since GemFire 8.0
*/
public interface RehashServerOnSIGHUP {
/**
* Perform the actions required to "rehash" the server.
*/
void rehash();
}
/**
* whether o/s supports high resolution clock or equivalent perf counter.
*
* @return true if implemented, otherwise false.
*/
public boolean isNativeTimerEnabled() {
return false;
}
/**
* This is fall back for jni based library implementation of NanoTimer which is more efficient
* than current impl through jna.
*
* Linux impls create temporary timespec object and marshals that for invoking native api.
* Shouldn't be used if to be called too many times, instead jni implementation is more desirable.
*
* @return nanosecond precision performance counter.
*/
public long nanoTime(int clock_id) {
return System.nanoTime();
}
public long clockResolution(int clock_id) {
return 0;
}
public boolean isTTY() {
return false;
}
/**
* A generic fallback implementation of {@link NativeCalls} when no JNA based implementation could
* be initialized (e.g. if JNA itself does not provide an implementation for the platform, or JNA
* is not found).
*
* @since GemFire 8.0
*/
public static class NativeCallsGeneric extends NativeCalls {
private static final boolean isWin;
static {
isWin = System.getProperty("os.name").indexOf("Windows") >= 0;
}
/**
* @see NativeCalls#getOSType()
*/
@Override
public OSType getOSType() {
return isWin ? OSType.WIN : OSType.GENERIC;
}
/**
* @see NativeCalls#getEnvironment(String)
*/
@Override
public String getEnvironment(final String name) {
return System.getenv(name);
}
/**
* @see NativeCalls#getProcessId()
*/
@Override
public int getProcessId() {
final String name = java.lang.management.ManagementFactory.getRuntimeMXBean().getName();
final int idx = name.indexOf('@');
if (idx > 0) {
try {
return Integer.parseInt(name.substring(0, idx));
} catch (NumberFormatException nfe) {
// something changed in the RuntimeMXBean name
}
}
return 0;
}
/**
* @see NativeCalls#isProcessActive(int)
*/
@Override
public boolean isProcessActive(int processId) throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"isProcessActive() not available in generic implementation");
}
/**
* @see NativeCalls#killProcess(int)
*/
@Override
public boolean killProcess(int processId) throws UnsupportedOperationException {
throw new UnsupportedOperationException(
"killProcess() not available in generic implementation");
}
/**
* {@inheritDoc}
*/
@Override
public Map<TCPSocketOptions, Throwable> setSocketOptions(Socket sock, InputStream sockStream,
Map<TCPSocketOptions, Object> optValueMap) throws UnsupportedOperationException {
throw new UnsupportedOperationException("setting native socket options "
+ optValueMap.keySet() + " not possible in generic implementation");
}
}
}