| /* |
| * 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"); |
| } |
| } |
| } |