package jogamp.common.os;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
import jogamp.common.Debug;
import jogamp.common.os.elf.ElfHeaderPart1;
import jogamp.common.os.elf.ElfHeaderPart2;
import jogamp.common.os.elf.SectionArmAttributes;
import com.jogamp.common.nio.Buffers;
import com.jogamp.common.os.AndroidVersion;
import com.jogamp.common.os.NativeLibrary;
import com.jogamp.common.os.Platform;
import com.jogamp.common.os.Platform.ABIType;
import com.jogamp.common.os.Platform.CPUFamily;
import com.jogamp.common.os.Platform.CPUType;
import com.jogamp.common.os.Platform.OSType;
import com.jogamp.common.util.VersionNumber;
/**
* Abstract parent class of {@link Platform} initializing and holding
* platform information, which are initialized independent
* of other classes.
* <p>
* This class is not intended to be exposed in the public namespace
* and solely exist to solve initialization interdependencies.<br>
* Please use {@link Platform} to access the public fields!
* </p>
*/
public abstract class PlatformPropsImpl {
static final boolean DEBUG = Debug.debug("Platform");
/** Selected {@link Platform.OSType#MACOS} {@link VersionNumber}s. */
public static class OSXVersion {
/** OSX Tiger, i.e. 10.4.0 */
public static final VersionNumber Tiger = new VersionNumber(10,4,0);
/** OSX Lion, i.e. 10.7.0 */
public static final VersionNumber Lion = new VersionNumber(10,7,0);
/** OSX Mavericks, i.e. 10.9.0 */
public static final VersionNumber Mavericks = new VersionNumber(10,9,0);
}
/**
* Returns {@code true} if the given {@link CPUType}s and {@link ABIType}s are compatible.
*/
public static final boolean isCompatible(final CPUType cpu1, final ABIType abi1, final CPUType cpu2, final ABIType abi2) {
return cpu1.isCompatible(cpu2) && abi1.isCompatible(abi2);
}
//
// static initialization order:
//
/** Version 1.6. As a JVM version, it enables certain JVM 1.6 features. */
public static final VersionNumber Version16;
/** Version 1.7. As a JVM version, it enables certain JVM 1.7 features. */
public static final VersionNumber Version17;
/** Version 1.8. As a JVM version, it enables certain JVM 1.8 features. */
public static final VersionNumber Version18;
/** Version 1.9. As a JVM version, it enables certain JVM 1.9 features. */
public static final VersionNumber Version19;
public static final String OS;
public static final String OS_lower;
public static final String OS_VERSION;
public static final VersionNumber OS_VERSION_NUMBER;
public static final String ARCH;
public static final String ARCH_lower;
public static final String JAVA_VENDOR;
public static final String JAVA_VENDOR_URL;
public static final String JAVA_VERSION;
public static final VersionNumber JAVA_VERSION_NUMBER;
public static final int JAVA_VERSION_UPDATE;
public static final String JAVA_VM_NAME;
public static final String JAVA_RUNTIME_NAME;
/** True if having {@link java.nio.LongBuffer} and {@link java.nio.DoubleBuffer} available. */
public static final boolean JAVA_SE;
/**
* True only if being compatible w/ language level 6, e.g. JRE 1.6.
* <p>
* Implies {@link #isJavaSE()}.
* </p>
* <p>
* <i>Note</i>: We claim Android is compatible.
* </p>
*/
public static final boolean JAVA_6;
public static final String NEWLINE;
public static final boolean LITTLE_ENDIAN;
public static final CPUType CPU_ARCH;
public static final ABIType ABI_TYPE;
public static final OSType OS_TYPE;
public static final String os_and_arch;
static {
Version16 = new VersionNumber(1, 6, 0);
Version17 = new VersionNumber(1, 7, 0);
Version18 = new VersionNumber(1, 8, 0);
Version19 = new VersionNumber(1, 9, 0);
// We don't seem to need an AccessController.doPrivileged() block
// here as these system properties are visible even to unsigned Applets.
final boolean isAndroid = AndroidVersion.isAvailable; // also triggers it's static initialization
JAVA_VENDOR = System.getProperty("java.vendor");
JAVA_VENDOR_URL = System.getProperty("java.vendor.url");
JAVA_VERSION = System.getProperty("java.version");
JAVA_VERSION_NUMBER = new VersionNumber(JAVA_VERSION);
{
int usIdx = JAVA_VERSION.lastIndexOf("-u"); // OpenJDK update notation
int usOff;
if( 0 < usIdx ) {
usOff = 2;
} else {
usIdx = JAVA_VERSION.lastIndexOf("_"); // Oracle update notation
usOff = 1;
}
if( 0 < usIdx ) {
final String buildS = PlatformPropsImpl.JAVA_VERSION.substring(usIdx+usOff);
final VersionNumber update = new VersionNumber(buildS);
JAVA_VERSION_UPDATE = update.getMajor();
} else {
JAVA_VERSION_UPDATE = 0;
}
}
JAVA_VM_NAME = System.getProperty("java.vm.name");
JAVA_RUNTIME_NAME = getJavaRuntimeNameImpl();
JAVA_SE = initIsJavaSE();
JAVA_6 = JAVA_SE && ( isAndroid || JAVA_VERSION_NUMBER.compareTo(Version16) >= 0 ) ;
NEWLINE = System.getProperty("line.separator");
OS = System.getProperty("os.name");
OS_lower = OS.toLowerCase();
OS_VERSION = System.getProperty("os.version");
OS_VERSION_NUMBER = new VersionNumber(OS_VERSION);
OS_TYPE = getOSTypeImpl(OS_lower, isAndroid);
// Hard values, i.e. w/ probing binaries
final String elfCpuName;
final CPUType elfCpuType;
final ABIType elfABIType;
final int elfLittleEndian;
final boolean elfValid;
{
final String[] _elfCpuName = { null };
final CPUType[] _elfCpuType = { null };
final ABIType[] _elfAbiType = { null };
final int[] _elfLittleEndian = { 0 }; // 1 - little, 2 - big
final boolean[] _elfValid = { false };
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
RandomAccessFile in = null;
try {
final File file = queryElfFile(OS_TYPE);
if(DEBUG) {
System.err.println("ELF-1: Using "+file);
}
in = new RandomAccessFile(file, "r");
final ElfHeaderPart1 eh1 = readElfHeaderPart1(OS_TYPE, in);
if(DEBUG) {
System.err.println("ELF-1: Got "+eh1);
}
if( null != eh1 ) {
final ElfHeaderPart2 eh2 = readElfHeaderPart2(eh1, in);
if(DEBUG) {
System.err.println("ELF-2: Got "+eh2);
}
if( null != eh2 ) {
_elfCpuName[0] = eh2.cpuName;
_elfCpuType[0] = eh2.cpuType;
_elfAbiType[0] = eh2.abiType;
if( eh1.isLittleEndian() ) {
_elfLittleEndian[0] = 1;
} else if( eh1.isBigEndian() ) {
_elfLittleEndian[0] = 2;
}
_elfValid[0] = true;
}
}
} catch (final Throwable t) {
if(DEBUG) {
t.printStackTrace();
}
} finally {
if(null != in) {
try {
in.close();
} catch (final IOException e) { }
}
}
return null;
} });
elfCpuName = _elfCpuName[0];
elfCpuType = _elfCpuType[0];
elfABIType = _elfAbiType[0];
elfLittleEndian = _elfLittleEndian[0];
elfValid = _elfValid[0];
if( DEBUG ) {
System.err.println("Platform.Elf: valid "+elfValid+", elfCpuName "+elfCpuName+", cpuType "+elfCpuType+", abiType "+elfABIType+", elfLittleEndian "+elfLittleEndian);
}
}
// Determine endianess, favor ELF value
final boolean littleEndian = queryIsLittleEndianImpl();
if( elfValid ) {
switch( elfLittleEndian ) {
case 1:
LITTLE_ENDIAN = true;
break;
case 2:
LITTLE_ENDIAN = false;
break;
default:
LITTLE_ENDIAN = littleEndian;
break;
}
} else {
LITTLE_ENDIAN = littleEndian;
}
if( DEBUG ) {
System.err.println("Platform.Endian: test-little "+littleEndian+", elf[valid "+elfValid+", val "+elfLittleEndian+"] -> LITTLE_ENDIAN "+LITTLE_ENDIAN);
}
// Property values for comparison
// We might take the property values even if ELF values are available,
// since the latter only reflect the CPU/ABI version of the binary files!
final String propARCH = System.getProperty("os.arch");
final String propARCH_lower = propARCH.toLowerCase();
final CPUType propCpuType = CPUType.query(propARCH_lower);
final ABIType propABIType = ABIType.query(propCpuType, propARCH_lower);
if( DEBUG ) {
System.err.println("Platform.Property: ARCH "+propARCH+", CpuType "+propCpuType+", ABIType "+propABIType);
}
final int strategy;
if( isAndroid ) {
if( DEBUG ) {
System.err.println("Android: CPU_ABI1 str "+AndroidVersion.CPU_ABI+", CPU_TYPE "+AndroidVersion.CPU_TYPE+", ABI_TYPE "+AndroidVersion.ABI_TYPE);
System.err.println("Android: CPU_ABI2 str "+AndroidVersion.CPU_ABI2+", CPU_TYPE2 "+AndroidVersion.CPU_TYPE2+", ABI_TYPE2 "+AndroidVersion.ABI_TYPE2);
}
if( elfValid ) {
if( null != AndroidVersion.CPU_TYPE &&
isCompatible(elfCpuType, elfABIType, AndroidVersion.CPU_TYPE, AndroidVersion.ABI_TYPE) )
{
// ELF matches Android-1
ARCH = AndroidVersion.CPU_ABI;
ARCH_lower = ARCH;
CPU_ARCH = AndroidVersion.CPU_TYPE;
strategy = 110;
} else if( null != AndroidVersion.CPU_TYPE2 &&
isCompatible(elfCpuType, elfABIType, AndroidVersion.CPU_TYPE2, AndroidVersion.ABI_TYPE2) )
{
// ELF matches Android-2
ARCH = AndroidVersion.CPU_ABI2;
ARCH_lower = ARCH;
CPU_ARCH = AndroidVersion.CPU_TYPE2;
strategy = 111;
} else {
// We assume our ELF data beats AndroidVersion info (correctness)
ARCH = elfCpuType.toString();
ARCH_lower = ARCH.toLowerCase();
CPU_ARCH = elfCpuType;
strategy = 112;
}
ABI_TYPE = elfABIType;
} else {
if( AndroidVersion.CPU_TYPE.family == CPUFamily.ARM ||
null == AndroidVersion.CPU_TYPE2 ) {
// Favor Android-1: Either b/c ARM Family, or no Android-2
ARCH = AndroidVersion.CPU_ABI;
ARCH_lower = ARCH;
CPU_ARCH = AndroidVersion.CPU_TYPE;
ABI_TYPE = AndroidVersion.ABI_TYPE;
strategy = 120;
} else {
// Last resort Android-2
ARCH = AndroidVersion.CPU_ABI2;
ARCH_lower = ARCH;
CPU_ARCH = AndroidVersion.CPU_TYPE2;
ABI_TYPE = AndroidVersion.ABI_TYPE2;
strategy = 121;
}
}
} else {
if( elfValid ) {
if( isCompatible(elfCpuType, elfABIType, propCpuType, propABIType) ) {
// Use property ARCH, compatible w/ ELF
ARCH = propARCH;
ARCH_lower = propARCH_lower;
CPU_ARCH = propCpuType;
ABI_TYPE = propABIType;
strategy = 210;
} else {
// use ELF ARCH
ARCH = elfCpuName;
ARCH_lower = elfCpuName;
CPU_ARCH = elfCpuType;
ABI_TYPE = elfABIType;
strategy = 211;
}
} else {
// Last resort: properties
ARCH = propARCH;
ARCH_lower = propARCH_lower;
CPU_ARCH = propCpuType;
ABI_TYPE = propABIType;
strategy = 220;
}
}
if( DEBUG ) {
System.err.println("Platform.Hard: ARCH "+ARCH+", CPU_ARCH "+CPU_ARCH+", ABI_TYPE "+ABI_TYPE+" - strategy "+strategy+"(isAndroid "+isAndroid+", elfValid "+elfValid+")");
}
os_and_arch = getOSAndArch(OS_TYPE, CPU_ARCH, ABI_TYPE, LITTLE_ENDIAN);
}
protected PlatformPropsImpl() {}
private static final String getJavaRuntimeNameImpl() {
// the fast path, check property Java SE instead of traversing through the ClassLoader
return AccessController.doPrivileged(new PrivilegedAction<String>() {
@Override
public String run() {
return System.getProperty("java.runtime.name");
}
});
}
private static final boolean initIsJavaSE() {
if( null != JAVA_RUNTIME_NAME && JAVA_RUNTIME_NAME.indexOf("Java SE") != -1) {
return true;
}
// probe for classes we need on a SE environment
try {
Class.forName("java.nio.LongBuffer");
Class.forName("java.nio.DoubleBuffer");
return true;
} catch(final ClassNotFoundException ex) {
// continue with Java SE check
}
return false;
}
private static final boolean queryIsLittleEndianImpl() {
final ByteBuffer tst_b = Buffers.newDirectByteBuffer(Buffers.SIZEOF_INT); // 32bit in native order
final IntBuffer tst_i = tst_b.asIntBuffer();
final ShortBuffer tst_s = tst_b.asShortBuffer();
tst_i.put(0, 0x0A0B0C0D);
return 0x0C0D == tst_s.get(0);
}
@SuppressWarnings("unused")
private static final boolean contains(final String data, final String[] search) {
if(null != data && null != search) {
for(int i=0; i<search.length; i++) {
if(data.indexOf(search[i]) >= 0) {
return true;
}
}
}
return false;
}
/**
* Returns the {@link ABIType} of the current platform using given {@link CPUType cpuType}
* and {@link OSType osType} as a hint.
* <p>
* For Elf parsing one of the following binaries is used:
* <ul>
* <li>Linux: Current executable</li>
* <li>Android: Found gluegen-rt library</li>
* <li>Other: A found java/jvm native library.</li>
* </ul>
* </p>
* <p>
* Elf ARM Tags are read using {@link ElfHeader}, .. and {@link SectionArmAttributes#abiVFPArgsAcceptsVFPVariant(byte)}.
* </p>
*/
private static final File queryElfFile(final OSType osType) {
File file = null;
try {
if( OSType.ANDROID == osType ) {
file = new File(NativeLibrary.findLibrary("gluegen-rt", PlatformPropsImpl.class.getClassLoader()));
} else {
if( OSType.LINUX == osType ) {
file = new File("/proc/self/exe");
if( !checkFileReadAccess(file) ) {
file = null;
}
}
if( null == file ) {
file = findSysLib("java");
}
if( null == file ) {
file = findSysLib("jvm");
}
}
} catch(final Throwable t) {
if(DEBUG) {
t.printStackTrace();
}
}
return file;
}
private static final ElfHeaderPart1 readElfHeaderPart1(final OSType osType, final RandomAccessFile in) {
ElfHeaderPart1 res = null;
try {
res = ElfHeaderPart1.read(osType, in);
} catch(final Throwable t) {
if(DEBUG) {
System.err.println("Caught: "+t.getMessage());
t.printStackTrace();
}
}
return res;
}
private static final ElfHeaderPart2 readElfHeaderPart2(final ElfHeaderPart1 eh1, final RandomAccessFile in) {
ElfHeaderPart2 res = null;
try {
res = ElfHeaderPart2.read(eh1, in);
} catch(final Throwable t) {
if(DEBUG) {
System.err.println("Caught: "+t.getMessage());
t.printStackTrace();
}
}
return res;
}
private static boolean checkFileReadAccess(final File file) {
try {
return file.isFile() && file.canRead();
} catch (final Throwable t) { }
return false;
}
private static File findSysLib(final String libName) {
final ClassLoader cl = PlatformPropsImpl.class.getClassLoader();
final List<String> possibleLibPaths = NativeLibrary.enumerateLibraryPaths(libName, libName, libName, true, cl);
for(int i=0; i<possibleLibPaths.size(); i++) {
final String libPath = possibleLibPaths.get(i);
final File lib = new File(libPath);
if(DEBUG) {
System.err.println("findSysLib #"+i+": test "+lib);
}
if( checkFileReadAccess(lib) ) {
return lib;
}
if(DEBUG) {
System.err.println("findSysLib #"+i+": "+lib+" not readable");
}
}
return null;
}
private static final OSType getOSTypeImpl(final String osLower, final boolean isAndroid) throws RuntimeException {
if ( isAndroid ) {
return OSType.ANDROID;
}
if ( osLower.startsWith("linux") ) {
return OSType.LINUX;
}
if ( osLower.startsWith("freebsd") ) {
return OSType.FREEBSD;
}
if ( osLower.startsWith("android") ) {
return OSType.ANDROID;
}
if ( osLower.startsWith("mac os x") ||
osLower.startsWith("darwin") ) {
return OSType.MACOS;
}
if ( osLower.startsWith("sunos") ) {
return OSType.SUNOS;
}
if ( osLower.startsWith("hp-ux") ) {
return OSType.HPUX;
}
if ( osLower.startsWith("windows") ) {
return OSType.WINDOWS;
}
if ( osLower.startsWith("kd") ) {
return OSType.OPENKODE;
}
throw new RuntimeException("Please port OS detection to your platform (" + OS_lower + "/" + ARCH_lower + ")");
}
/**
* kick off static initialization of <i>platform property information</i>
*/
public static void initSingleton() { }
/**
* Returns the GlueGen common name for the given
* {@link OSType}, {@link CPUType}, {@link ABIType} and {@code littleEndian}.
* <p>
* Consult 'gluegen/make/gluegen-cpptasks-base.xml' to complete/sync mapping!
* </p>
*
* An excerpt of supported <code>os.and.arch</code> strings:
* <ul>
* <li>android-armv6</li>
* <li>android-aarch64</li>
* <li>linux-armv6</li>
* <li>linux-armv6hf</li>
* <li>linux-i586</li>
* <li>linux-ppc</li>
* <li>linux-mips</li>
* <li>linux-mipsel</li>
* <li>linux-superh</li>
* <li>linux-sparc</li>
* <li>linux-aarch64</li>
* <li>linux-amd64</li>
* <li>linux-ppc64</li>
* <li>linux-mips64</li>
* <li>linux-ia64</li>
* <li>linux-sparcv9</li>
* <li>linux-risc2.0</li>
* <li>freebsd-i586</li>
* <li>freebsd-amd64</li>
* <li>hpux-hppa</li>
* <li>macosx-universal</li>
* <li>solaris-sparc</li>
* <li>solaris-sparcv9</li>
* <li>solaris-amd64</li>
* <li>solaris-i586</li>
* <li>windows-amd64</li>
* <li>windows-i586</li>
* </ul>
* @return The <i>os.and.arch</i> value.
*/
public static final String getOSAndArch(final OSType osType, final CPUType cpuType, final ABIType abiType, final boolean littleEndian) {
final String os_;
final String _and_arch_tmp, _and_arch_final;
switch( cpuType ) {
case ARM:
case ARMv5:
case ARMv6:
case ARMv7:
if( ABIType.EABI_GNU_ARMHF == abiType ) {
_and_arch_tmp = "armv6hf";
} else {
_and_arch_tmp = "armv6";
}
break;
case X86_32:
_and_arch_tmp = "i586";
break;
case PPC:
_and_arch_tmp = "ppc";
break;
case MIPS_32:
_and_arch_tmp = littleEndian ? "mipsel" : "mips";
break;
case SuperH:
_and_arch_tmp = "superh";
break;
case SPARC_32:
_and_arch_tmp = "sparc";
break;
case ARM64:
case ARMv8_A:
_and_arch_tmp = "aarch64";
break;
case X86_64:
_and_arch_tmp = "amd64";
break;
case PPC64:
_and_arch_tmp = "ppc64";
break;
case MIPS_64:
_and_arch_tmp = "mips64";
break;
case IA64:
_and_arch_tmp = "ia64";
break;
case SPARCV9_64:
_and_arch_tmp = "sparcv9";
break;
case PA_RISC2_0:
_and_arch_tmp = "risc2.0";
break;
default:
throw new InternalError("Unhandled CPUType: "+cpuType);
}
switch( osType ) {
case ANDROID:
os_ = "android";
_and_arch_final = _and_arch_tmp;
break;
case MACOS:
os_ = "macosx";
_and_arch_final = "universal";
break;
case WINDOWS:
os_ = "windows";
_and_arch_final = _and_arch_tmp;
break;
case OPENKODE:
os_ = "openkode"; // TODO: think about that
_and_arch_final = _and_arch_tmp;
break;
case LINUX:
os_ = "linux";
_and_arch_final = _and_arch_tmp;
break;
case FREEBSD:
os_ = "freebsd";
_and_arch_final = _and_arch_tmp;
break;
case SUNOS:
os_ = "solaris";
_and_arch_final = _and_arch_tmp;
break;
case HPUX:
os_ = "hpux";
_and_arch_final = "hppa"; // TODO: really only hppa ?
break;
default:
throw new InternalError("Unhandled OSType: "+osType);
}
return os_ + "-" + _and_arch_final;
}
}