blob: 403231807b60dab26effb02cdfbfa72409fd4898 [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 static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileStore;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import com.sun.jna.Callback;
import com.sun.jna.LastErrorException;
import com.sun.jna.Native;
import com.sun.jna.Platform;
import com.sun.jna.Structure;
import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.WinBase;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.ptr.IntByReference;
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();
}
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 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 close(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;
RehashServerOnSIGHUP rehashCallback;
@Override
public synchronized String getEnvironment(final String name) {
if (name == null) {
throw new UnsupportedOperationException("getEnvironment() for name=NULL");
}
return getenv(name);
}
@Override
public int getProcessId() {
return getpid();
}
@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;
}
@Override
public boolean killProcess(final int processId) {
try {
return kill(processId, 9) == 0;
} catch (LastErrorException le) {
return false;
}
}
@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
@SuppressWarnings("OctalInteger")
final int newMask = 022;
int oldMask = umask(newMask);
// check if old umask was more restrictive, and if so then set it back
@SuppressWarnings("OctalInteger")
final int OCTAL_077 = 077;
if ((oldMask & OCTAL_077) > newMask) {
umask(oldMask);
}
// catch the SIGHUP signal and invoke any callback provided
rehashCallback = callback;
hupHandler = signum -> {
// invoke the rehash function if provided
final RehashServerOnSIGHUP rehashCb = rehashCallback;
if (signum == Signal.SIGHUP.getNumber() && rehashCb != null) {
rehashCb.rehash();
}
};
signal(Signal.SIGHUP.getNumber(), hupHandler);
// ignore SIGCHLD and SIGINT
signal(Signal.SIGCHLD.getNumber(), hupHandler);
signal(Signal.SIGINT.getNumber(), 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 {
@SuppressWarnings("OctalInteger")
final int OCTAL_0644 = 00644;
fd = createFD(path, OCTAL_0644);
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) {
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");
}
}
/**
* Implementation of {@link NativeCalls} for Linux platform.
*/
private static class LinuxNativeCalls extends POSIXNativeCalls {
static {
Native.register("c");
}
/** 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;
static {
if (Platform.is64Bit()) {
StatFS64.dummy();
} else {
StatFS.dummy();
}
}
@MakeNotStatic
private static boolean isStatFSEnabled;
@SuppressWarnings("unused")
public static class FSIDIntArr2 extends Structure {
public int[] fsid = new int[2];
@Override
protected List<String> getFieldOrder() {
return singletonList("fsid");
}
}
@SuppressWarnings("unused")
public static class FSPAREIntArr5 extends Structure {
public int[] fspare = new int[5];
@Override
protected List<String> getFieldOrder() {
return singletonList("fspare");
}
}
@SuppressWarnings("unused")
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);
isStatFSEnabled = ret == 0;
} 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<String> getFieldOrder() {
return asList("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 remoteType : REMOTE_TYPES) {
if (remoteType == f_type) {
return false;
}
}
return true;
}
public static void dummy() {
}
}
@SuppressWarnings("unused")
public static class FSPARELongArr5 extends Structure {
public long[] fspare = new long[5];
@Override
protected List<String> getFieldOrder() {
return singletonList("fspare");
}
}
@SuppressWarnings("unused")
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);
isStatFSEnabled = ret == 0;
} 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<String> getFieldOrder() {
return asList("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 (long remoteType : REMOTE_TYPES) {
if (remoteType == 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 Windows platforms.
*/
private static class WinNativeCalls extends NativeCalls {
@Override
public synchronized String getEnvironment(final String name) {
if (name == null) {
throw new UnsupportedOperationException("getEnvironment() for name=NULL");
}
int psize = Kernel32.INSTANCE.GetEnvironmentVariable(name, null, 0);
if (psize > 0) {
for (;;) {
char[] result = new char[psize];
psize = Kernel32.INSTANCE.GetEnvironmentVariable(name, result, psize);
if (psize == (result.length - 1)) {
return new String(result, 0, psize);
} else if (psize <= 0) {
return null;
}
}
} else {
return null;
}
}
@Override
public int getProcessId() {
return Kernel32.INSTANCE.GetCurrentProcessId();
}
@Override
public boolean isProcessActive(final int processId) {
try {
final HANDLE procHandle =
Kernel32.INSTANCE.OpenProcess(Kernel32.PROCESS_QUERY_INFORMATION, false, processId);
if (procHandle == null || WinBase.INVALID_HANDLE_VALUE.equals(procHandle)) {
return false;
} else {
final IntByReference status = new IntByReference();
final boolean result = Kernel32.INSTANCE.GetExitCodeProcess(procHandle, status)
&& status.getValue() == Kernel32.STILL_ACTIVE;
Kernel32.INSTANCE.CloseHandle(procHandle);
return result;
}
} catch (LastErrorException le) {
// some problem in getting process status
return false;
}
}
@Override
public boolean killProcess(final int processId) {
try {
final HANDLE procHandle =
Kernel32.INSTANCE.OpenProcess(Kernel32.PROCESS_TERMINATE, false, processId);
if (procHandle == null || Kernel32.INVALID_HANDLE_VALUE.equals(procHandle)) {
return false;
} else {
final boolean result = Kernel32.INSTANCE.TerminateProcess(procHandle, -1);
Kernel32.INSTANCE.CloseHandle(procHandle);
return result;
}
} catch (LastErrorException le) {
// some problem in killing the process
return false;
}
}
@Override
public void daemonize(RehashServerOnSIGHUP callback)
throws UnsupportedOperationException, IllegalStateException {
throw new IllegalStateException("daemonize() not applicable for Windows platform");
}
}
}