<commit_before>/*
 * Copyright 2016 Andrei Pangin
 *
 * Licensed 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.
 */

#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include "vmEntry.h"
#include "arguments.h"
#include "javaApi.h"
#include "os.h"
#include "profiler.h"
#include "instrument.h"
#include "lockTracer.h"
#include "log.h"
#include "vmStructs.h"

// JVM TI agent return codes
const int ARGUMENTS_ERROR = 100;
const int COMMAND_ERROR = 200;

static Arguments _agent_args;

JavaVM* VM::_vm;
jvmtiEnv* VM::_jvmti = NULL;
int VM::_hotspot_version = 0;
void* VM::_libjvm;
void* VM::_libjava;
AsyncGetCallTrace VM::_asyncGetCallTrace;
JVM_GetManagement VM::_getManagement;
jvmtiError (JNICALL *VM::_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
jvmtiError (JNICALL *VM::_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
jvmtiError (JNICALL *VM::_orig_GenerateEvents)(jvmtiEnv* jvmti, jvmtiEvent event_type);
volatile int VM::_in_redefine_classes = 0;

static void wakeupHandler(int signo) {
    // Dummy handler for interrupting syscalls
}

bool VM::init(JavaVM* vm, bool attach) {
    if (_jvmti != NULL) return true;

    _vm = vm;
    if (_vm->GetEnv((void**)&_jvmti, JVMTI_VERSION_1_0) != 0) {
        return false;
    }

    char* prop;
    if (_jvmti->GetSystemProperty("java.vm.name", &prop) == 0) {
        bool is_hotspot = strstr(prop, "OpenJDK") != NULL ||
                          strstr(prop, "HotSpot") != NULL ||
                          strstr(prop, "GraalVM") != NULL ||
                          strstr(prop, "Dynamic Code Evolution") != NULL;
        _jvmti->Deallocate((unsigned char*)prop);

        if (is_hotspot && _jvmti->GetSystemProperty("java.vm.version", &prop) == 0) {
            if (strncmp(prop, "25.", 3) == 0) {
                _hotspot_version = 8;
            } else if (strncmp(prop, "24.", 3) == 0) {
                _hotspot_version = 7;
            } else if (strncmp(prop, "20.", 3) == 0) {
                _hotspot_version = 6;
            } else if ((_hotspot_version = atoi(prop)) < 9) {
                _hotspot_version = 9;
            }
            _jvmti->Deallocate((unsigned char*)prop);
        }
    }

    _libjvm = getLibraryHandle("libjvm.so");
    _asyncGetCallTrace = (AsyncGetCallTrace)dlsym(_libjvm, "AsyncGetCallTrace");
    _getManagement = (JVM_GetManagement)dlsym(_libjvm, "JVM_GetManagement");

    if (attach) {
        ready();
    }

    jvmtiCapabilities capabilities = {0};
    capabilities.can_generate_all_class_hook_events = 1;
    capabilities.can_retransform_classes = 1;
    capabilities.can_retransform_any_class = 1;
    capabilities.can_get_bytecodes = 1;
    capabilities.can_get_constant_pool = 1;
    capabilities.can_get_source_file_name = 1;
    capabilities.can_get_line_numbers = 1;
    capabilities.can_generate_compiled_method_load_events = 1;
    capabilities.can_generate_monitor_events = 1;
    capabilities.can_tag_objects = 1;
    _jvmti->AddCapabilities(&capabilities);

    jvmtiEventCallbacks callbacks = {0};
    callbacks.VMInit = VMInit;
    callbacks.VMDeath = VMDeath;
    callbacks.ClassLoad = ClassLoad;
    callbacks.ClassPrepare = ClassPrepare;
    callbacks.ClassFileLoadHook = Instrument::ClassFileLoadHook;
    callbacks.CompiledMethodLoad = Profiler::CompiledMethodLoad;
    callbacks.CompiledMethodUnload = Profiler::CompiledMethodUnload;
    callbacks.DynamicCodeGenerated = Profiler::DynamicCodeGenerated;
    callbacks.ThreadStart = Profiler::ThreadStart;
    callbacks.ThreadEnd = Profiler::ThreadEnd;
    callbacks.MonitorContendedEnter = LockTracer::MonitorContendedEnter;
    callbacks.MonitorContendedEntered = LockTracer::MonitorContendedEntered;
    _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));

    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);

    if (attach) {
        loadAllMethodIDs(jvmti(), jni());
        DisableSweeper ds;
        _jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
        _jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD);
    }

    if (hotspot_version() > 0 && hotspot_version() < 11) {
        // Avoid GenerateEvents conflict with another agent
        JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
        _orig_GenerateEvents = functions->GenerateEvents;
        functions->GenerateEvents = GenerateEventsHook;
    }

    OS::installSignalHandler(WAKEUP_SIGNAL, NULL, wakeupHandler);

    return true;
}

// Run late initialization when JVM is ready
void VM::ready() {
    Profiler* profiler = Profiler::instance();
    profiler->updateSymbols(false);
    NativeCodeCache* libjvm = profiler->findNativeLibrary((const void*)_asyncGetCallTrace);
    if (libjvm != NULL) {
        JitWriteProtection jit(true);  // workaround for JDK-8262896
        VMStructs::init(libjvm);
    }

    profiler->setupTrapHandler();

    _libjava = getLibraryHandle("libjava.so");

    // Make sure we reload method IDs upon class retransformation
    JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
    _orig_RedefineClasses = functions->RedefineClasses;
    _orig_RetransformClasses = functions->RetransformClasses;
    functions->RedefineClasses = RedefineClassesHook;
    functions->RetransformClasses = RetransformClassesHook;
}

void* VM::getLibraryHandle(const char* name) {
    if (!OS::isJavaLibraryVisible()) {
        void* handle = dlopen(name, RTLD_LAZY);
        if (handle != NULL) {
            return handle;
        }
        Log::warn("Failed to load %s: %s", name, dlerror());
    }
    return RTLD_DEFAULT;
}

void VM::loadMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) {
    if (VMStructs::hasClassLoaderData()) {
        VMKlass* vmklass = VMKlass::fromJavaClass(jni, klass);
        int method_count = vmklass->methodCount();
        if (method_count > 0) {
            ClassLoaderData* cld = vmklass->classLoaderData();
            cld->lock();
            // Workaround for JVM bug: preallocate space for jmethodIDs
            // at the beginning of the list (rather than at the end)
            for (int i = 0; i < method_count; i += MethodList::SIZE) {
                *cld->methodList() = new MethodList(*cld->methodList());
            }
            cld->unlock();
        }
    }

    jint method_count;
    jmethodID* methods;
    if (jvmti->GetClassMethods(klass, &method_count, &methods) == 0) {
        jvmti->Deallocate((unsigned char*)methods);
    }
}

void VM::loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni) {
    jint class_count;
    jclass* classes;
    if (jvmti->GetLoadedClasses(&class_count, &classes) == 0) {
        for (int i = 0; i < class_count; i++) {
            loadMethodIDs(jvmti, jni, classes[i]);
        }
        jvmti->Deallocate((unsigned char*)classes);
    }
}

void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
    ready();
    loadAllMethodIDs(jvmti, jni);

    // Delayed start of profiler if agent has been loaded at VM bootstrap
    Error error = Profiler::instance()->run(_agent_args);
    if (error) {
        Log::error("%s", error.message());
    }
}

void JNICALL VM::VMDeath(jvmtiEnv* jvmti, JNIEnv* jni) {
    Profiler::instance()->shutdown(_agent_args);
}

jvmtiError VM::RedefineClassesHook(jvmtiEnv* jvmti, jint class_count, const jvmtiClassDefinition* class_definitions) {
    atomicInc(_in_redefine_classes);
    jvmtiError result = _orig_RedefineClasses(jvmti, class_count, class_definitions);

    if (result == 0) {
        // jmethodIDs are invalidated after RedefineClasses
        JNIEnv* env = jni();
        for (int i = 0; i < class_count; i++) {
            if (class_definitions[i].klass != NULL) {
                loadMethodIDs(jvmti, env, class_definitions[i].klass);
            }
        }
    }

    atomicInc(_in_redefine_classes, -1);
    return result;
}

jvmtiError VM::RetransformClassesHook(jvmtiEnv* jvmti, jint class_count, const jclass* classes) {
    atomicInc(_in_redefine_classes);
    jvmtiError result = _orig_RetransformClasses(jvmti, class_count, classes);

    if (result == 0) {
        // jmethodIDs are invalidated after RetransformClasses
        JNIEnv* env = jni();
        for (int i = 0; i < class_count; i++) {
            if (classes[i] != NULL) {
                loadMethodIDs(jvmti, env, classes[i]);
            }
        }
    }

    atomicInc(_in_redefine_classes, -1);
    return result;
}

jvmtiError VM::GenerateEventsHook(jvmtiEnv* jvmti, jvmtiEvent event_type) {
    if (event_type == JVMTI_EVENT_COMPILED_METHOD_LOAD) {
        // Workaround for JDK-8222072: prepare to receive events designated for another agent
        Log::warn("async-profiler conflicts with another agent calling GenerateEvents()");
        Profiler::instance()->resetJavaMethods();
    }
    return _orig_GenerateEvents(jvmti, event_type);
}

extern "C" JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
    Error error = _agent_args.parse(options);
    Log::open(_agent_args._log);
    if (error) {
        Log::error("%s", error.message());
        return ARGUMENTS_ERROR;
    }

    if (!VM::init(vm, false)) {
        Log::error("JVM does not support Tool Interface");
        return COMMAND_ERROR;
    }

    return 0;
}

extern "C" JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
    Arguments args;
    Error error = args.parse(options);
    Log::open(args._log);
    if (error) {
        Log::error("%s", error.message());
        return ARGUMENTS_ERROR;
    }

    if (!VM::init(vm, true)) {
        Log::error("JVM does not support Tool Interface");
        return COMMAND_ERROR;
    }

    // Save the arguments in case of shutdown
    if (args._action == ACTION_START || args._action == ACTION_RESUME) {
        _agent_args.save(args);
    }

    error = Profiler::instance()->run(args);
    if (error) {
        Log::error("%s", error.message());
        return COMMAND_ERROR;
    }

    return 0;
}

extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
    if (!VM::init(vm, true)) {
        return 0;
    }

    JavaAPI::registerNatives(VM::jvmti(), VM::jni());
    return JNI_VERSION_1_6;
}
<commit_msg>Fixed RedefineClasses recursion when loading via JNI<commit_after>/*
 * Copyright 2016 Andrei Pangin
 *
 * Licensed 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.
 */

#include <dlfcn.h>
#include <stdlib.h>
#include <string.h>
#include "vmEntry.h"
#include "arguments.h"
#include "javaApi.h"
#include "os.h"
#include "profiler.h"
#include "instrument.h"
#include "lockTracer.h"
#include "log.h"
#include "vmStructs.h"

// JVM TI agent return codes
const int ARGUMENTS_ERROR = 100;
const int COMMAND_ERROR = 200;

static Arguments _agent_args;

JavaVM* VM::_vm;
jvmtiEnv* VM::_jvmti = NULL;
int VM::_hotspot_version = 0;
void* VM::_libjvm;
void* VM::_libjava;
AsyncGetCallTrace VM::_asyncGetCallTrace;
JVM_GetManagement VM::_getManagement;
jvmtiError (JNICALL *VM::_orig_RedefineClasses)(jvmtiEnv*, jint, const jvmtiClassDefinition*);
jvmtiError (JNICALL *VM::_orig_RetransformClasses)(jvmtiEnv*, jint, const jclass* classes);
jvmtiError (JNICALL *VM::_orig_GenerateEvents)(jvmtiEnv* jvmti, jvmtiEvent event_type);
volatile int VM::_in_redefine_classes = 0;

static void wakeupHandler(int signo) {
    // Dummy handler for interrupting syscalls
}

bool VM::init(JavaVM* vm, bool attach) {
    if (_jvmti != NULL) return true;

    _vm = vm;
    if (_vm->GetEnv((void**)&_jvmti, JVMTI_VERSION_1_0) != 0) {
        return false;
    }

    char* prop;
    if (_jvmti->GetSystemProperty("java.vm.name", &prop) == 0) {
        bool is_hotspot = strstr(prop, "OpenJDK") != NULL ||
                          strstr(prop, "HotSpot") != NULL ||
                          strstr(prop, "GraalVM") != NULL ||
                          strstr(prop, "Dynamic Code Evolution") != NULL;
        _jvmti->Deallocate((unsigned char*)prop);

        if (is_hotspot && _jvmti->GetSystemProperty("java.vm.version", &prop) == 0) {
            if (strncmp(prop, "25.", 3) == 0) {
                _hotspot_version = 8;
            } else if (strncmp(prop, "24.", 3) == 0) {
                _hotspot_version = 7;
            } else if (strncmp(prop, "20.", 3) == 0) {
                _hotspot_version = 6;
            } else if ((_hotspot_version = atoi(prop)) < 9) {
                _hotspot_version = 9;
            }
            _jvmti->Deallocate((unsigned char*)prop);
        }
    }

    _libjvm = getLibraryHandle("libjvm.so");
    _asyncGetCallTrace = (AsyncGetCallTrace)dlsym(_libjvm, "AsyncGetCallTrace");
    _getManagement = (JVM_GetManagement)dlsym(_libjvm, "JVM_GetManagement");

    if (attach) {
        ready();
    }

    jvmtiCapabilities capabilities = {0};
    capabilities.can_generate_all_class_hook_events = 1;
    capabilities.can_retransform_classes = 1;
    capabilities.can_retransform_any_class = 1;
    capabilities.can_get_bytecodes = 1;
    capabilities.can_get_constant_pool = 1;
    capabilities.can_get_source_file_name = 1;
    capabilities.can_get_line_numbers = 1;
    capabilities.can_generate_compiled_method_load_events = 1;
    capabilities.can_generate_monitor_events = 1;
    capabilities.can_tag_objects = 1;
    _jvmti->AddCapabilities(&capabilities);

    jvmtiEventCallbacks callbacks = {0};
    callbacks.VMInit = VMInit;
    callbacks.VMDeath = VMDeath;
    callbacks.ClassLoad = ClassLoad;
    callbacks.ClassPrepare = ClassPrepare;
    callbacks.ClassFileLoadHook = Instrument::ClassFileLoadHook;
    callbacks.CompiledMethodLoad = Profiler::CompiledMethodLoad;
    callbacks.CompiledMethodUnload = Profiler::CompiledMethodUnload;
    callbacks.DynamicCodeGenerated = Profiler::DynamicCodeGenerated;
    callbacks.ThreadStart = Profiler::ThreadStart;
    callbacks.ThreadEnd = Profiler::ThreadEnd;
    callbacks.MonitorContendedEnter = LockTracer::MonitorContendedEnter;
    callbacks.MonitorContendedEntered = LockTracer::MonitorContendedEntered;
    _jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));

    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_PREPARE, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_LOAD, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_COMPILED_METHOD_UNLOAD, NULL);
    _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_DYNAMIC_CODE_GENERATED, NULL);

    if (attach) {
        loadAllMethodIDs(jvmti(), jni());
        DisableSweeper ds;
        _jvmti->GenerateEvents(JVMTI_EVENT_DYNAMIC_CODE_GENERATED);
        _jvmti->GenerateEvents(JVMTI_EVENT_COMPILED_METHOD_LOAD);
    } else {
        _jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_INIT, NULL);
    }

    if (hotspot_version() > 0 && hotspot_version() < 11) {
        // Avoid GenerateEvents conflict with another agent
        JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
        _orig_GenerateEvents = functions->GenerateEvents;
        functions->GenerateEvents = GenerateEventsHook;
    }

    OS::installSignalHandler(WAKEUP_SIGNAL, NULL, wakeupHandler);

    return true;
}

// Run late initialization when JVM is ready
void VM::ready() {
    Profiler* profiler = Profiler::instance();
    profiler->updateSymbols(false);
    NativeCodeCache* libjvm = profiler->findNativeLibrary((const void*)_asyncGetCallTrace);
    if (libjvm != NULL) {
        JitWriteProtection jit(true);  // workaround for JDK-8262896
        VMStructs::init(libjvm);
    }

    profiler->setupTrapHandler();

    _libjava = getLibraryHandle("libjava.so");

    // Make sure we reload method IDs upon class retransformation
    JVMTIFunctions* functions = *(JVMTIFunctions**)_jvmti;
    _orig_RedefineClasses = functions->RedefineClasses;
    _orig_RetransformClasses = functions->RetransformClasses;
    functions->RedefineClasses = RedefineClassesHook;
    functions->RetransformClasses = RetransformClassesHook;
}

void* VM::getLibraryHandle(const char* name) {
    if (!OS::isJavaLibraryVisible()) {
        void* handle = dlopen(name, RTLD_LAZY);
        if (handle != NULL) {
            return handle;
        }
        Log::warn("Failed to load %s: %s", name, dlerror());
    }
    return RTLD_DEFAULT;
}

void VM::loadMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni, jclass klass) {
    if (VMStructs::hasClassLoaderData()) {
        VMKlass* vmklass = VMKlass::fromJavaClass(jni, klass);
        int method_count = vmklass->methodCount();
        if (method_count > 0) {
            ClassLoaderData* cld = vmklass->classLoaderData();
            cld->lock();
            // Workaround for JVM bug: preallocate space for jmethodIDs
            // at the beginning of the list (rather than at the end)
            for (int i = 0; i < method_count; i += MethodList::SIZE) {
                *cld->methodList() = new MethodList(*cld->methodList());
            }
            cld->unlock();
        }
    }

    jint method_count;
    jmethodID* methods;
    if (jvmti->GetClassMethods(klass, &method_count, &methods) == 0) {
        jvmti->Deallocate((unsigned char*)methods);
    }
}

void VM::loadAllMethodIDs(jvmtiEnv* jvmti, JNIEnv* jni) {
    jint class_count;
    jclass* classes;
    if (jvmti->GetLoadedClasses(&class_count, &classes) == 0) {
        for (int i = 0; i < class_count; i++) {
            loadMethodIDs(jvmti, jni, classes[i]);
        }
        jvmti->Deallocate((unsigned char*)classes);
    }
}

void JNICALL VM::VMInit(jvmtiEnv* jvmti, JNIEnv* jni, jthread thread) {
    ready();
    loadAllMethodIDs(jvmti, jni);

    // Delayed start of profiler if agent has been loaded at VM bootstrap
    Error error = Profiler::instance()->run(_agent_args);
    if (error) {
        Log::error("%s", error.message());
    }
}

void JNICALL VM::VMDeath(jvmtiEnv* jvmti, JNIEnv* jni) {
    Profiler::instance()->shutdown(_agent_args);
}

jvmtiError VM::RedefineClassesHook(jvmtiEnv* jvmti, jint class_count, const jvmtiClassDefinition* class_definitions) {
    atomicInc(_in_redefine_classes);
    jvmtiError result = _orig_RedefineClasses(jvmti, class_count, class_definitions);

    if (result == 0) {
        // jmethodIDs are invalidated after RedefineClasses
        JNIEnv* env = jni();
        for (int i = 0; i < class_count; i++) {
            if (class_definitions[i].klass != NULL) {
                loadMethodIDs(jvmti, env, class_definitions[i].klass);
            }
        }
    }

    atomicInc(_in_redefine_classes, -1);
    return result;
}

jvmtiError VM::RetransformClassesHook(jvmtiEnv* jvmti, jint class_count, const jclass* classes) {
    atomicInc(_in_redefine_classes);
    jvmtiError result = _orig_RetransformClasses(jvmti, class_count, classes);

    if (result == 0) {
        // jmethodIDs are invalidated after RetransformClasses
        JNIEnv* env = jni();
        for (int i = 0; i < class_count; i++) {
            if (classes[i] != NULL) {
                loadMethodIDs(jvmti, env, classes[i]);
            }
        }
    }

    atomicInc(_in_redefine_classes, -1);
    return result;
}

jvmtiError VM::GenerateEventsHook(jvmtiEnv* jvmti, jvmtiEvent event_type) {
    if (event_type == JVMTI_EVENT_COMPILED_METHOD_LOAD) {
        // Workaround for JDK-8222072: prepare to receive events designated for another agent
        Log::warn("async-profiler conflicts with another agent calling GenerateEvents()");
        Profiler::instance()->resetJavaMethods();
    }
    return _orig_GenerateEvents(jvmti, event_type);
}

extern "C" JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM* vm, char* options, void* reserved) {
    Error error = _agent_args.parse(options);
    Log::open(_agent_args._log);
    if (error) {
        Log::error("%s", error.message());
        return ARGUMENTS_ERROR;
    }

    if (!VM::init(vm, false)) {
        Log::error("JVM does not support Tool Interface");
        return COMMAND_ERROR;
    }

    return 0;
}

extern "C" JNIEXPORT jint JNICALL
Agent_OnAttach(JavaVM* vm, char* options, void* reserved) {
    Arguments args;
    Error error = args.parse(options);
    Log::open(args._log);
    if (error) {
        Log::error("%s", error.message());
        return ARGUMENTS_ERROR;
    }

    if (!VM::init(vm, true)) {
        Log::error("JVM does not support Tool Interface");
        return COMMAND_ERROR;
    }

    // Save the arguments in case of shutdown
    if (args._action == ACTION_START || args._action == ACTION_RESUME) {
        _agent_args.save(args);
    }

    error = Profiler::instance()->run(args);
    if (error) {
        Log::error("%s", error.message());
        return COMMAND_ERROR;
    }

    return 0;
}

extern "C" JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM* vm, void* reserved) {
    if (!VM::init(vm, true)) {
        return 0;
    }

    JavaAPI::registerNatives(VM::jvmti(), VM::jni());
    return JNI_VERSION_1_6;
}
<|endoftext|>


<commit_before>
#include <functional>

#ifdef INCLUDE_BOTAN_ALL_H
#include "botan_all.h"
#else
#include <botan/botan.h>
#include <botan/tls_session.h>
#include <botan/tls_alert.h>
#include <botan/tls_policy.h>
#include <botan/credentials_manager.h>
#include <botan/tls_channel.h>
#include <botan/pkcs8.h>
#include <botan/tls_session_manager.h>
#include <botan/tls_server.h>
#endif

#include "../SecondTransfer/TLS/Botan_stub.h"

#include <cstdlib>

namespace second_transfer {

// Just because of the conversions, but it may be a handy place for
// other stuff later.
void output_dn_cb (void* botan_pad_ref, const unsigned char a[], size_t sz)
{
    iocba_push(botan_pad_ref, (char*)a, sz);
}

void data_cb (void* botan_pad_ref, const unsigned char a[], size_t sz)
{
    iocba_data_cb(botan_pad_ref, (char*)a, sz);
}

void alert_cb (void* botan_pad_ref, Botan::TLS::Alert const& alert, const unsigned char a[], size_t sz)
{
    if (alert.is_valid() && alert.is_fatal() )
    {
        // TODO: Propagate this softly.
        iocba_alert_cb(botan_pad_ref, -1);
    } // Else: don't care
}

bool handshake_cb(void* botan_pad_ref, const Botan::TLS::Session&)
{
    iocba_handshake_cb(botan_pad_ref);
    // TODO: Implement cache management
    return false;
}

// TODO: We can use stronger ciphers here. For now let's go simple
class HereTLSPolicy: public Botan::TLS::Policy {
public:
    virtual bool acceptable_protocol_version(const Botan::TLS::Protocol_Version& v)
    {
        return v == Botan::TLS::Protocol_Version::TLS_V12;
    }

    virtual std::vector<std::string> allowed_macs() const
    {
        std::vector<std::string> result;
        result.push_back("AEAD");
        result.push_back("SHA-384");
        result.push_back("SHA-256");
        return result;
    }

    virtual std::vector<std::string> allowed_ciphers() const
    {
        std::vector<std::string> result;
        result.push_back("ChaCha20Poly1305");
        result.push_back("AES-256/GCM");
        result.push_back("AES-128/GCM");
        result.push_back("Camellia-256/GCM");
        result.push_back("AES-256/OCB(12)");
        result.push_back("AES-256/CCM");
        return result;
    }

    virtual std::vector<std::string> allowed_key_exchange_methods() const
    {
        std::vector<std::string> result;
        result.push_back("ECDH");
        result.push_back("DH");
        result.push_back("RSA");
        return result;
    }

    virtual std::vector<std::string> allowed_signature_hashes() const
    {
        std::vector<std::string> result;
        result.push_back("SHA-256");
        return result;
    }

    virtual std::vector<std::string> allowed_signature_methods() const
    {
        std::vector<std::string> result;
        result.push_back("RSA");
        return result;
    }

    // This is experimental, may need to change.
    virtual bool server_uses_own_ciphersuite_preferences() const
    {
        return true;
    }

};

// TODO: We can use different certificates if needs come....
class HereCredentialsManager: public Botan::Credentials_Manager {
    Botan::X509_Certificate cert;
    std::vector<Botan::X509_Certificate> certs;
    Botan::Private_Key* privkey;
public:
    HereCredentialsManager(
        const char* cert_filename,
        const char* privkey_filename,
        Botan::AutoSeeded_RNG& rng
        ):
            cert(cert_filename)
    {
        certs.push_back(cert);
        privkey = Botan::PKCS8::load_key(
            privkey_filename, rng
        );
    }

    virtual std::vector<
        Botan::X509_Certificate > cert_chain(
            const std::vector< std::string >&,
            const std::string&,
            const std::string& )
    {
        return certs;
    }

    virtual Botan::Private_Key*  private_key_for(const Botan::X509_Certificate &cert, const std::string & type, const std::string &context)
    {
        return privkey;
    }

    ~HereCredentialsManager()
    {
        delete privkey;
    }
};

std::string defaultProtocolSelector(void* botan_pad_ref, std::vector<std::string> const& prots)
{
    std::string pass_to_haskell;
    bool is_first = true;
    for (int i=0; i < prots.size(); i++ )
    {
        if ( is_first )
        {
            is_first=false;
        } else
        {
            pass_to_haskell += '\0';
        }
        pass_to_haskell += prots[i];
    }
    int idx = iocba_select_protocol_cb( botan_pad_ref, (void*)pass_to_haskell.c_str(), pass_to_haskell.size());
    return prots[idx];
}

} // namespace

extern "C" void iocba_receive_data(
    void* tls_channel,
    char* data,
    int length )
{
    Botan::TLS::Channel* channel = (Botan::TLS::Channel*) tls_channel;
    channel->received_data( (const unsigned char*) data, length);
}

extern "C" void iocba_cleartext_push(
    void* tls_channel,
    char* data,
    int length )
{
    // TODO: Check for "can send"!!!!!
    // OTHERWISE THIS WON'T WORK
    Botan::TLS::Channel* channel = (Botan::TLS::Channel*) tls_channel;
    channel->send( (const unsigned char*) data, length);
}

struct botan_tls_context_t {
    second_transfer::HereCredentialsManager credentials_manager;
    Botan::TLS::Session_Manager_In_Memory* session_manager;
    Botan::AutoSeeded_RNG* rng;
    std::vector<std::string> protocols;
    second_transfer::HereTLSPolicy here_tls_policty;
};

extern "C" botan_tls_context_t* iocba_make_tls_context(
    const char* cert_filename,
    const char* privkey_filename
    )
{
    Botan::AutoSeeded_RNG rng;
    std::vector< std::string > protocols;

    // Not sure what is this good for....
    protocols.push_back("h2");
    protocols.push_back("http/1.1");

    return new botan_tls_context_t{
        second_transfer::HereCredentialsManager(cert_filename, privkey_filename, rng),
        new Botan::TLS::Session_Manager_In_Memory(rng),
        new Botan::AutoSeeded_RNG(),
        protocols,
        second_transfer::HereTLSPolicy()
    };
}

extern "C" void iocba_delete_tls_context(botan_tls_context_t* ctx)
{
    delete ctx->session_manager;
    delete ctx->rng;
    delete ctx;
}

extern "C" void* iocba_new_tls_server_channel (
       void* botan_pad_ref,
       botan_tls_context_t* ctxt)
{
    auto* server =
        new Botan::TLS::Server(
            std::bind(second_transfer::output_dn_cb, botan_pad_ref, std::placeholders::_1, std::placeholders::_2),
            std::bind(second_transfer::data_cb, botan_pad_ref, std::placeholders::_1, std::placeholders::_2),
            std::bind(second_transfer::alert_cb, botan_pad_ref, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
            std::bind(second_transfer::handshake_cb, botan_pad_ref, std::placeholders::_1),
            *(ctx->session_manager),
            ctx->credentials_manager,
            ctx->here_tls_policty,
            *(ctx->rng),
            std::bind(second_transfer::defaultProtocolSelector, botan_pad_ref, std::placeholders::_1)
        );
    return server;
}

extern "C" void iocba_delete_tls_server_channel (
    Botan::TLS::Server * srv
    )
{
    delete srv;
}
<commit_msg>Something crept in<commit_after>
#include <functional>

#ifdef INCLUDE_BOTAN_ALL_H
#include "botan_all.h"
#else
#include <botan/botan.h>
#include <botan/tls_session.h>
#include <botan/tls_alert.h>
#include <botan/tls_policy.h>
#include <botan/credentials_manager.h>
#include <botan/tls_channel.h>
#include <botan/pkcs8.h>
#include <botan/tls_session_manager.h>
#include <botan/tls_server.h>
#endif

#include "../SecondTransfer/TLS/Botan_stub.h"

#include <cstdlib>

namespace second_transfer {

// Just because of the conversions, but it may be a handy place for
// other stuff later.
void output_dn_cb (void* botan_pad_ref, const unsigned char a[], size_t sz)
{
    iocba_push(botan_pad_ref, (char*)a, sz);
}

void data_cb (void* botan_pad_ref, const unsigned char a[], size_t sz)
{
    iocba_data_cb(botan_pad_ref, (char*)a, sz);
}

void alert_cb (void* botan_pad_ref, Botan::TLS::Alert const& alert, const unsigned char a[], size_t sz)
{
    if (alert.is_valid() && alert.is_fatal() )
    {
        // TODO: Propagate this softly.
        iocba_alert_cb(botan_pad_ref, -1);
    } // Else: don't care
}

bool handshake_cb(void* botan_pad_ref, const Botan::TLS::Session&)
{
    iocba_handshake_cb(botan_pad_ref);
    // TODO: Implement cache management
    return false;
}

// TODO: We can use stronger ciphers here. For now let's go simple
class HereTLSPolicy: public Botan::TLS::Policy {
public:
    virtual bool acceptable_protocol_version(const Botan::TLS::Protocol_Version& v)
    {
        return v == Botan::TLS::Protocol_Version::TLS_V12;
    }

    virtual std::vector<std::string> allowed_macs() const
    {
        std::vector<std::string> result;
        result.push_back("AEAD");
        result.push_back("SHA-384");
        result.push_back("SHA-256");
        return result;
    }

    virtual std::vector<std::string> allowed_ciphers() const
    {
        std::vector<std::string> result;
        result.push_back("ChaCha20Poly1305");
        result.push_back("AES-256/GCM");
        result.push_back("AES-128/GCM");
        result.push_back("Camellia-256/GCM");
        result.push_back("AES-256/OCB(12)");
        result.push_back("AES-256/CCM");
        return result;
    }

    virtual std::vector<std::string> allowed_key_exchange_methods() const
    {
        std::vector<std::string> result;
        result.push_back("ECDH");
        result.push_back("DH");
        result.push_back("RSA");
        return result;
    }

    virtual std::vector<std::string> allowed_signature_hashes() const
    {
        std::vector<std::string> result;
        result.push_back("SHA-256");
        return result;
    }

    virtual std::vector<std::string> allowed_signature_methods() const
    {
        std::vector<std::string> result;
        result.push_back("RSA");
        return result;
    }

    // This is experimental, may need to change.
    virtual bool server_uses_own_ciphersuite_preferences() const
    {
        return true;
    }

};

// TODO: We can use different certificates if needs come....
class HereCredentialsManager: public Botan::Credentials_Manager {
    Botan::X509_Certificate cert;
    std::vector<Botan::X509_Certificate> certs;
    Botan::Private_Key* privkey;
public:
    HereCredentialsManager(
        const char* cert_filename,
        const char* privkey_filename,
        Botan::AutoSeeded_RNG& rng
        ):
            cert(cert_filename)
    {
        certs.push_back(cert);
        privkey = Botan::PKCS8::load_key(
            privkey_filename, rng
        );
    }

    virtual std::vector<
        Botan::X509_Certificate > cert_chain(
            const std::vector< std::string >&,
            const std::string&,
            const std::string& )
    {
        return certs;
    }

    virtual Botan::Private_Key*  private_key_for(const Botan::X509_Certificate &cert, const std::string & type, const std::string &context)
    {
        return privkey;
    }

    ~HereCredentialsManager()
    {
        delete privkey;
    }
};

std::string defaultProtocolSelector(void* botan_pad_ref, std::vector<std::string> const& prots)
{
    std::string pass_to_haskell;
    bool is_first = true;
    for (int i=0; i < prots.size(); i++ )
    {
        if ( is_first )
        {
            is_first=false;
        } else
        {
            pass_to_haskell += '\0';
        }
        pass_to_haskell += prots[i];
    }
    int idx = iocba_select_protocol_cb( botan_pad_ref, (void*)pass_to_haskell.c_str(), pass_to_haskell.size());
    return prots[idx];
}

} // namespace

extern "C" void iocba_receive_data(
    void* tls_channel,
    char* data,
    int length )
{
    Botan::TLS::Channel* channel = (Botan::TLS::Channel*) tls_channel;
    channel->received_data( (const unsigned char*) data, length);
}

extern "C" void iocba_cleartext_push(
    void* tls_channel,
    char* data,
    int length )
{
    // TODO: Check for "can send"!!!!!
    // OTHERWISE THIS WON'T WORK
    Botan::TLS::Channel* channel = (Botan::TLS::Channel*) tls_channel;
    channel->send( (const unsigned char*) data, length);
}

struct botan_tls_context_t {
    second_transfer::HereCredentialsManager credentials_manager;
    Botan::TLS::Session_Manager_In_Memory* session_manager;
    Botan::AutoSeeded_RNG* rng;
    std::vector<std::string> protocols;
    second_transfer::HereTLSPolicy here_tls_policty;
};

extern "C" botan_tls_context_t* iocba_make_tls_context(
    const char* cert_filename,
    const char* privkey_filename
    )
{
    Botan::AutoSeeded_RNG rng;
    std::vector< std::string > protocols;

    // Not sure what is this good for....
    protocols.push_back("h2");
    protocols.push_back("http/1.1");

    return new botan_tls_context_t{
        second_transfer::HereCredentialsManager(cert_filename, privkey_filename, rng),
        new Botan::TLS::Session_Manager_In_Memory(rng),
        new Botan::AutoSeeded_RNG(),
        protocols,
        second_transfer::HereTLSPolicy()
    };
}

extern "C" void iocba_delete_tls_context(botan_tls_context_t* ctx)
{
    delete ctx->session_manager;
    delete ctx->rng;
    delete ctx;
}

extern "C" void* iocba_new_tls_server_channel (
       void* botan_pad_ref,
       botan_tls_context_t* ctx)
{
    auto* server =
        new Botan::TLS::Server(
            std::bind(second_transfer::output_dn_cb, botan_pad_ref, std::placeholders::_1, std::placeholders::_2),
            std::bind(second_transfer::data_cb, botan_pad_ref, std::placeholders::_1, std::placeholders::_2),
            std::bind(second_transfer::alert_cb, botan_pad_ref, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3),
            std::bind(second_transfer::handshake_cb, botan_pad_ref, std::placeholders::_1),
            *(ctx->session_manager),
            ctx->credentials_manager,
            ctx->here_tls_policty,
            *(ctx->rng),
            std::bind(second_transfer::defaultProtocolSelector, botan_pad_ref, std::placeholders::_1)
        );
    return server;
}

extern "C" void iocba_delete_tls_server_channel (
    Botan::TLS::Server * srv
    )
{
    delete srv;
}
<|endoftext|>


<commit_before>#include "indexer.h"
#include "util.h"
#include <memory.h>

const int KMER = 16;
// 512M bloom filter
const unsigned int BLOOM_FILTER_SIZE = 1<<29;
const unsigned int BLOOM_FILTER_BITS = BLOOM_FILTER_SIZE - 1;

Indexer::Indexer(string refFile, vector<Fusion>& fusions) {
    mRefFile = refFile;
    mFusions = fusions;
    mReference = new FastaReader(refFile, false);
    mReference->readAll();
    mUniquePos = 0;
    mDupePos = 0;
    mBloomFilter = new unsigned char[BLOOM_FILTER_SIZE];
    memset(mBloomFilter, 0, sizeof(unsigned char) * BLOOM_FILTER_SIZE);
}

Indexer::~Indexer() {
    delete mReference;
    mReference = NULL;
    delete mBloomFilter;
    mBloomFilter = NULL;
}

FastaReader* Indexer::getRef() {
    return mReference;
}

void Indexer::makeIndex() {
    if(mReference == NULL)
        return ;

    map<string, string>& ref = mReference->mAllContigs;
    for(int ctg=0; ctg<mFusions.size(); ctg++){
        Gene gene = mFusions[ctg].mGene;
        string chr = gene.mChr;
        if(ref.count(chr) == 0) {
            if(ref.count("chr" + chr) >0)
                chr = "chr" + chr;
            else if(ref.count(replace(chr, "chr", "")) >0)
                chr = replace(chr, "chr", "");
            else{
                mFusionSeq.push_back("");
                continue;
            }
        }
        string s = ref[chr].substr(gene.mStart, gene.mEnd - gene.mStart);
        str2upper(s);
        mFusionSeq.push_back(s);
        //index forward
        indexContig(ctg, s, 0);
        //index reverse complement
        Sequence seq = ~(Sequence(s));
        indexContig(ctg, seq.mStr, -s.length()+1);
    }
    fillBloomFilter();
    loginfo("mapper indexing done");
}

void Indexer::fillBloomFilter() {
    unordered_map<long, GenePos>::iterator iter;
    for(iter = mKmerPos.begin(); iter!=mKmerPos.end(); iter++) {
        long kmer = iter->first;
        long pos = kmer>>3;
        long bit = kmer & 0x07;
        mBloomFilter[pos] |= (0x1<<bit);
    }
}

void Indexer::indexContig(int ctg, string seq, int start) {
    long kmer = -1;
    for(int i=0; i<seq.length() - KMER; ++i) {
        kmer = makeKmer(seq, i, kmer);
        //cout << kmer << "\t" << seq.substr(i, KMER) << endl;
        if(kmer < 0)
            continue;
        GenePos site;
        site.contig = ctg;
        site.position = i+start;
        // this is a dupe
        if(mKmerPos.count(kmer) >0 ){
            GenePos gp = mKmerPos[kmer];
            // already marked as a dupe
            if(gp.contig < 0) {
                mDupeList[gp.position].push_back(site);
            } else {
                // else make a new dupe entry
                vector<GenePos> gps;
                gps.push_back(gp);
                gps.push_back(site);
                mDupeList.push_back(gps);
                // and mark it as a dupe
                mKmerPos[kmer].contig = -1;
                mKmerPos[kmer].position = mDupeList.size() -1;
                mUniquePos--;
                mDupePos++;
            }
        } else {
            mKmerPos[kmer]=site;
            mUniquePos++;
        }
    }
}

vector<SeqMatch> Indexer::mapRead(Read* r) {
    unordered_map<long, int> kmerStat;
    kmerStat[0]=0;
    string seq = r->mSeq.mStr;
    const int step = 2;
    int seqlen = seq.length();
    // first pass, we only want to find if this seq can be partially aligned to the target
    long kmer = -1;
    for(int i=0; i< seqlen - KMER + 1; i += step) {
        kmer = makeKmer(seq, i, kmer, step);
        if(kmer < 0)
            continue;
        long pos = kmer>>3;
        long bit = kmer & 0x07;
        if( (mBloomFilter[pos] & (0x1<<bit)) == 0) {
            kmerStat[0]++;
            continue;
        }
        GenePos gp = mKmerPos[kmer];
        // is a dupe
        if(gp.contig < 0) {
            for(int g=0; g<mDupeList[gp.position].size();g++) {
                long gplong = gp2long(shift(mDupeList[gp.position][g], i));
                if(kmerStat.count(gplong)==0)
                    kmerStat[gplong] = 1;
                else
                    kmerStat[gplong] += 1;
            }
        } else {
            long gplong = gp2long(shift(gp, i));
            if(kmerStat.count(gplong)==0)
                kmerStat[gplong] = 1;
            else
                kmerStat[gplong] += 1;
        }
    }
    // get 1st and 2nd hit
    long gp1 = 0;
    int count1 = 0;
    long gp2 = 0;
    int count2 = 0;
    unordered_map<long, int>::iterator iter;
    //TODO: handle small difference caused by INDEL
    for(iter = kmerStat.begin(); iter!=kmerStat.end(); iter++){
        if(iter->first != 0 && iter->second > count1){
            gp2 = gp1;
            count2 = count1;
            gp1 = iter->first;
            count1 = iter->second;
        }  else if(iter->first != 0 && iter->second > count2 ){
            gp2 = iter->first;
            count2 = iter->second;
        }
    }
    if(count1 * step < 40 || count2 * step < 20){
        // return an null list
        return vector<SeqMatch>();
    }

    unsigned char* mask = new unsigned char[seqlen];
    memset(mask, MATCH_UNKNOWN, sizeof(unsigned char)*seqlen);

    // second pass, make the mask
    kmer = -1;
    for(int i=0; i< seqlen - KMER + 1; i += 1) {
        kmer = makeKmer(seq, i, kmer);
        if(kmer < 0)
            continue;
        long pos = kmer>>3;
        long bit = kmer & 0x07;
        if( (mBloomFilter[pos] & (0x1<<bit)) == 0)
            continue;
        GenePos gp = mKmerPos[kmer];
        // is a dupe
        if(gp.contig < 0) {
            for(int g=0; g<mDupeList[gp.position].size();g++) {
                long gplong = gp2long(shift(mDupeList[gp.position][g], i));
                if(abs(gplong - gp1) <= 1)
                    makeMask(mask, MATCH_TOP, seqlen, i, KMER);
                else if(abs(gplong - gp2) <= 1)
                    makeMask(mask, MATCH_SECOND, seqlen, i, KMER);
                else if(gplong == 0)
                    makeMask(mask, MATCH_NONE, seqlen, i, KMER);
            }
        } else {
            long gplong = gp2long(shift(gp, i));
            if(abs(gplong - gp1) <= 1)
                    makeMask(mask, MATCH_TOP, seqlen, i, KMER);
            else if(abs(gplong - gp2) <= 1)
                makeMask(mask, MATCH_SECOND, seqlen, i, KMER);
            else if(gplong == 0)
                makeMask(mask, MATCH_NONE, seqlen, i, KMER);
        }
    }

    int mismatches = 0;
    for(int i=0; i<seqlen; i++){
        if(mask[i] == MATCH_NONE || mask[i] == MATCH_UNKNOWN )
            mismatches++;
    }

    if(mismatches>10){
        // too many mismatch indicates not a real fusion
        return vector<SeqMatch>();
    }

    vector<SeqMatch> result = segmentMask(mask, seqlen, long2gp(gp1), long2gp(gp2));
    delete mask;

    return result;
}

void Indexer::makeMask(unsigned char* mask, unsigned char flag, int seqlen, int start, int kmerSize) {
    for(int i=start;i<seqlen && i<start+kmerSize;i++)
        mask[i]=max(mask[i], flag);
}

vector<SeqMatch> Indexer::segmentMask(unsigned char* mask, int seqlen, GenePos gp1, GenePos gp2) {
    vector<SeqMatch> result;

    const int ALLOWED_GAP = 10;
    const int THRESHOLD_LEN = 20;

    int targets[2] = {MATCH_TOP, MATCH_SECOND};
    GenePos gps[2] = {gp1, gp2};

    for(int t=0; t<2; t++){
        int maxStart = -1;
        int maxEnd = -1;

        // get gp1
        int target = targets[t];
        int start = 0;
        int end = 0;
        while(true){
            // get next start
            while(mask[start] != target && start != seqlen-1)
                start++;

            // reach the tail
            if(start >= seqlen-1)
                break;

            if(mask[start] == target){
                end = start+1;
                // get the end
                int g=0;
                while(g<ALLOWED_GAP && end+g<seqlen){
                    if(mask[end+g] > target)
                        break;
                    if(end+g < seqlen && mask[end+g] == target ){
                        end += g+1;
                        g = 0;
                        continue;
                    }
                    g++;
                }
                // left shift to remove the mismatched end
                end--;
                if(end - start > maxEnd - maxStart){
                    maxEnd = end;
                    maxStart = start;
                }
                start++;
            } else {
                // not found
                break;
            }
        }
        if(maxEnd - maxStart >  THRESHOLD_LEN){
            SeqMatch match;
            match.seqStart = maxStart;
            match.seqEnd = maxEnd;
            match.startGP = gps[t];
            result.push_back(match);
        }
    }

    /*if(result.size()>=2 && inRequiredDirection(result)){
        cout<<"gp1,"<<gp1.contig<<":"<<gp1.position<<", ";
        cout<<"gp2,"<<gp2.contig<<":"<<gp2.position<<endl;
        for(int i=0;i<seqlen;i++)
            cout<<(int)mask[i];
        cout << endl;
    }*/

    return result;
}

// this function is to gurantee that all the supporting reads will have same direction
bool Indexer::inRequiredDirection(vector<SeqMatch>& mapping) {
    if(mapping.size()<2)
        return false;

    SeqMatch left = mapping[0];
    SeqMatch right = mapping[1];
    if(left.seqStart > right.seqStart) {
        left = mapping[1];
        right = mapping[0];
    }

    // both are positive, good to go
    if(left.startGP.position >0 && right.startGP.position >0)
        return true;

    // if both are negative, we should use their reverse complement, which will be both positive
    if(left.startGP.position <0 && right.startGP.position <0)
        return false;

    // if one is positive, the other is negative, their reverse complement will be the same
    if( mFusions[left.startGP.contig].isReversed() && !mFusions[right.startGP.contig].isReversed()){
        // if left is reversed gene and right is forward gene
        // we should use their reverse complement, which keep the left forward gene
        return false;
    }
    else if( !mFusions[left.startGP.contig].isReversed() && mFusions[right.startGP.contig].isReversed()){
        // if left is forward gene and right is reversed gene, good to go
        return true;
    }
    else {
        // otherwise, we should keep the left has smaller contig
        if(left.startGP.contig < right.startGP.contig)
            return true;
        // or smaller positive if contig is the same
        if(left.startGP.contig == right.startGP.contig && abs(left.startGP.position) < abs(left.startGP.position))
            return true;
        else
            return false;
    }
    return false;
}

long Indexer::makeKmer(string & seq, int pos, long lastKmer, int step) {
    // else calculate it completely
    long kmer = 0;
    int start = 0;
    // re-use several bits
    if(lastKmer >= 0) {
        kmer = lastKmer;
        start = KMER - step;
        if(step == 1)
            kmer = ((kmer & 0x3FFFFFFF) << 2);
        else if (step == 2)
            kmer = ((kmer & 0x0FFFFFFF) << 2);
        else if (step == 3)
            kmer = ((kmer & 0x03FFFFFF) << 2);
        else if (step == 4)
            kmer = ((kmer & 0x00FFFFFF) << 2);
    }
    for(int i=start;i<KMER;i++){
        switch(seq[pos+i]){
            case 'A':
                kmer += 0;
                break;
            case 'T':
                kmer += 1;
                break;
            case 'C':
                kmer += 2;
                break;
            case 'G':
                kmer += 3;
                break;
            default:
                return -1;
        }
        // not the tail
        if(i<KMER-1)
            kmer = kmer << 2;
    }
    return kmer;
}

long Indexer::gp2long(const GenePos& gp){
    long ret = gp.contig;
    int temp[2]={gp.position, 0};
    return (ret<<32) | *((long*)temp);
}

GenePos Indexer::long2gp(const long val){
    GenePos gp;
    gp.position = (val & 0x00000000FFFFFFFF);
    gp.contig = val >> 32;
    return gp;
}

GenePos Indexer::shift(const GenePos& gp, int i){
    GenePos gpNew;
    gpNew.contig = gp.contig;
    gpNew.position = gp.position - i;
    return gpNew;
}

void Indexer::printStat() {
    cout<<"mUniquePos:"<<mUniquePos<<endl;
    cout<<"mDupePos:"<<mDupePos<<endl;
}

bool Indexer::test() {
    short contigs[10]={0, 1, 3, 220, -1, 0, 23, 4440, 110, 10};
    int positions[10]={0, 111, 222, -333, 444, 555555, 6, -7777777, 8888, -9999};
    for(int i=0;i<10;i++){
        GenePos gp;
        gp.contig = contigs[i];
        gp.position = positions[i];
        long val = gp2long(gp);
        GenePos gp2 = long2gp(val);
        long val2 = gp2long(gp2);
        cout << gp2.contig << ", " << gp2.position << endl;
        if ((gp.contig == gp2.contig && gp.position == gp2.position && val==val2) == false)
            return false;
    }
    return true;
}<commit_msg>change unordered_map back to map for mapRead<commit_after>#include "indexer.h"
#include "util.h"
#include <memory.h>

const int KMER = 16;
// 512M bloom filter
const unsigned int BLOOM_FILTER_SIZE = 1<<29;
const unsigned int BLOOM_FILTER_BITS = BLOOM_FILTER_SIZE - 1;

Indexer::Indexer(string refFile, vector<Fusion>& fusions) {
    mRefFile = refFile;
    mFusions = fusions;
    mReference = new FastaReader(refFile, false);
    mReference->readAll();
    mUniquePos = 0;
    mDupePos = 0;
    mBloomFilter = new unsigned char[BLOOM_FILTER_SIZE];
    memset(mBloomFilter, 0, sizeof(unsigned char) * BLOOM_FILTER_SIZE);
}

Indexer::~Indexer() {
    delete mReference;
    mReference = NULL;
    delete mBloomFilter;
    mBloomFilter = NULL;
}

FastaReader* Indexer::getRef() {
    return mReference;
}

void Indexer::makeIndex() {
    if(mReference == NULL)
        return ;

    map<string, string>& ref = mReference->mAllContigs;
    for(int ctg=0; ctg<mFusions.size(); ctg++){
        Gene gene = mFusions[ctg].mGene;
        string chr = gene.mChr;
        if(ref.count(chr) == 0) {
            if(ref.count("chr" + chr) >0)
                chr = "chr" + chr;
            else if(ref.count(replace(chr, "chr", "")) >0)
                chr = replace(chr, "chr", "");
            else{
                mFusionSeq.push_back("");
                continue;
            }
        }
        string s = ref[chr].substr(gene.mStart, gene.mEnd - gene.mStart);
        str2upper(s);
        mFusionSeq.push_back(s);
        //index forward
        indexContig(ctg, s, 0);
        //index reverse complement
        Sequence seq = ~(Sequence(s));
        indexContig(ctg, seq.mStr, -s.length()+1);
    }
    fillBloomFilter();
    loginfo("mapper indexing done");
}

void Indexer::fillBloomFilter() {
    unordered_map<long, GenePos>::iterator iter;
    for(iter = mKmerPos.begin(); iter!=mKmerPos.end(); iter++) {
        long kmer = iter->first;
        long pos = kmer>>3;
        long bit = kmer & 0x07;
        mBloomFilter[pos] |= (0x1<<bit);
    }
}

void Indexer::indexContig(int ctg, string seq, int start) {
    long kmer = -1;
    for(int i=0; i<seq.length() - KMER; ++i) {
        kmer = makeKmer(seq, i, kmer);
        //cout << kmer << "\t" << seq.substr(i, KMER) << endl;
        if(kmer < 0)
            continue;
        GenePos site;
        site.contig = ctg;
        site.position = i+start;
        // this is a dupe
        if(mKmerPos.count(kmer) >0 ){
            GenePos gp = mKmerPos[kmer];
            // already marked as a dupe
            if(gp.contig < 0) {
                mDupeList[gp.position].push_back(site);
            } else {
                // else make a new dupe entry
                vector<GenePos> gps;
                gps.push_back(gp);
                gps.push_back(site);
                mDupeList.push_back(gps);
                // and mark it as a dupe
                mKmerPos[kmer].contig = -1;
                mKmerPos[kmer].position = mDupeList.size() -1;
                mUniquePos--;
                mDupePos++;
            }
        } else {
            mKmerPos[kmer]=site;
            mUniquePos++;
        }
    }
}

vector<SeqMatch> Indexer::mapRead(Read* r) {
    map<long, int> kmerStat;
    kmerStat[0]=0;
    string seq = r->mSeq.mStr;
    const int step = 2;
    int seqlen = seq.length();
    // first pass, we only want to find if this seq can be partially aligned to the target
    long kmer = -1;
    for(int i=0; i< seqlen - KMER + 1; i += step) {
        kmer = makeKmer(seq, i, kmer, step);
        if(kmer < 0)
            continue;
        long pos = kmer>>3;
        long bit = kmer & 0x07;
        if( (mBloomFilter[pos] & (0x1<<bit)) == 0) {
            kmerStat[0]++;
            continue;
        }
        GenePos gp = mKmerPos[kmer];
        // is a dupe
        if(gp.contig < 0) {
            for(int g=0; g<mDupeList[gp.position].size();g++) {
                long gplong = gp2long(shift(mDupeList[gp.position][g], i));
                if(kmerStat.count(gplong)==0)
                    kmerStat[gplong] = 1;
                else
                    kmerStat[gplong] += 1;
            }
        } else {
            long gplong = gp2long(shift(gp, i));
            if(kmerStat.count(gplong)==0)
                kmerStat[gplong] = 1;
            else
                kmerStat[gplong] += 1;
        }
    }
    // get 1st and 2nd hit
    long gp1 = 0;
    int count1 = 0;
    long gp2 = 0;
    int count2 = 0;
    map<long, int>::iterator iter;
    //TODO: handle small difference caused by INDEL
    for(iter = kmerStat.begin(); iter!=kmerStat.end(); iter++){
        if(iter->first != 0 && iter->second > count1){
            gp2 = gp1;
            count2 = count1;
            gp1 = iter->first;
            count1 = iter->second;
        }  else if(iter->first != 0 && iter->second > count2 ){
            gp2 = iter->first;
            count2 = iter->second;
        }
    }
    if(count1 * step < 40 || count2 * step < 20){
        // return an null list
        return vector<SeqMatch>();
    }

    unsigned char* mask = new unsigned char[seqlen];
    memset(mask, MATCH_UNKNOWN, sizeof(unsigned char)*seqlen);

    // second pass, make the mask
    kmer = -1;
    for(int i=0; i< seqlen - KMER + 1; i += 1) {
        kmer = makeKmer(seq, i, kmer);
        if(kmer < 0)
            continue;
        long pos = kmer>>3;
        long bit = kmer & 0x07;
        if( (mBloomFilter[pos] & (0x1<<bit)) == 0)
            continue;
        GenePos gp = mKmerPos[kmer];
        // is a dupe
        if(gp.contig < 0) {
            for(int g=0; g<mDupeList[gp.position].size();g++) {
                long gplong = gp2long(shift(mDupeList[gp.position][g], i));
                if(abs(gplong - gp1) <= 1)
                    makeMask(mask, MATCH_TOP, seqlen, i, KMER);
                else if(abs(gplong - gp2) <= 1)
                    makeMask(mask, MATCH_SECOND, seqlen, i, KMER);
                else if(gplong == 0)
                    makeMask(mask, MATCH_NONE, seqlen, i, KMER);
            }
        } else {
            long gplong = gp2long(shift(gp, i));
            if(abs(gplong - gp1) <= 1)
                    makeMask(mask, MATCH_TOP, seqlen, i, KMER);
            else if(abs(gplong - gp2) <= 1)
                makeMask(mask, MATCH_SECOND, seqlen, i, KMER);
            else if(gplong == 0)
                makeMask(mask, MATCH_NONE, seqlen, i, KMER);
        }
    }

    int mismatches = 0;
    for(int i=0; i<seqlen; i++){
        if(mask[i] == MATCH_NONE || mask[i] == MATCH_UNKNOWN )
            mismatches++;
    }

    if(mismatches>10){
        // too many mismatch indicates not a real fusion
        return vector<SeqMatch>();
    }

    vector<SeqMatch> result = segmentMask(mask, seqlen, long2gp(gp1), long2gp(gp2));
    delete mask;

    return result;
}

void Indexer::makeMask(unsigned char* mask, unsigned char flag, int seqlen, int start, int kmerSize) {
    for(int i=start;i<seqlen && i<start+kmerSize;i++)
        mask[i]=max(mask[i], flag);
}

vector<SeqMatch> Indexer::segmentMask(unsigned char* mask, int seqlen, GenePos gp1, GenePos gp2) {
    vector<SeqMatch> result;

    const int ALLOWED_GAP = 10;
    const int THRESHOLD_LEN = 20;

    int targets[2] = {MATCH_TOP, MATCH_SECOND};
    GenePos gps[2] = {gp1, gp2};

    for(int t=0; t<2; t++){
        int maxStart = -1;
        int maxEnd = -1;

        // get gp1
        int target = targets[t];
        int start = 0;
        int end = 0;
        while(true){
            // get next start
            while(mask[start] != target && start != seqlen-1)
                start++;

            // reach the tail
            if(start >= seqlen-1)
                break;

            if(mask[start] == target){
                end = start+1;
                // get the end
                int g=0;
                while(g<ALLOWED_GAP && end+g<seqlen){
                    if(mask[end+g] > target)
                        break;
                    if(end+g < seqlen && mask[end+g] == target ){
                        end += g+1;
                        g = 0;
                        continue;
                    }
                    g++;
                }
                // left shift to remove the mismatched end
                end--;
                if(end - start > maxEnd - maxStart){
                    maxEnd = end;
                    maxStart = start;
                }
                start++;
            } else {
                // not found
                break;
            }
        }
        if(maxEnd - maxStart >  THRESHOLD_LEN){
            SeqMatch match;
            match.seqStart = maxStart;
            match.seqEnd = maxEnd;
            match.startGP = gps[t];
            result.push_back(match);
        }
    }

    /*if(result.size()>=2 && inRequiredDirection(result)){
        cout<<"gp1,"<<gp1.contig<<":"<<gp1.position<<", ";
        cout<<"gp2,"<<gp2.contig<<":"<<gp2.position<<endl;
        for(int i=0;i<seqlen;i++)
            cout<<(int)mask[i];
        cout << endl;
    }*/

    return result;
}

// this function is to gurantee that all the supporting reads will have same direction
bool Indexer::inRequiredDirection(vector<SeqMatch>& mapping) {
    if(mapping.size()<2)
        return false;

    SeqMatch left = mapping[0];
    SeqMatch right = mapping[1];
    if(left.seqStart > right.seqStart) {
        left = mapping[1];
        right = mapping[0];
    }

    // both are positive, good to go
    if(left.startGP.position >0 && right.startGP.position >0)
        return true;

    // if both are negative, we should use their reverse complement, which will be both positive
    if(left.startGP.position <0 && right.startGP.position <0)
        return false;

    // if one is positive, the other is negative, their reverse complement will be the same
    if( mFusions[left.startGP.contig].isReversed() && !mFusions[right.startGP.contig].isReversed()){
        // if left is reversed gene and right is forward gene
        // we should use their reverse complement, which keep the left forward gene
        return false;
    }
    else if( !mFusions[left.startGP.contig].isReversed() && mFusions[right.startGP.contig].isReversed()){
        // if left is forward gene and right is reversed gene, good to go
        return true;
    }
    else {
        // otherwise, we should keep the left has smaller contig
        if(left.startGP.contig < right.startGP.contig)
            return true;
        // or smaller positive if contig is the same
        if(left.startGP.contig == right.startGP.contig && abs(left.startGP.position) < abs(left.startGP.position))
            return true;
        else
            return false;
    }
    return false;
}

long Indexer::makeKmer(string & seq, int pos, long lastKmer, int step) {
    // else calculate it completely
    long kmer = 0;
    int start = 0;
    // re-use several bits
    if(lastKmer >= 0) {
        kmer = lastKmer;
        start = KMER - step;
        if(step == 1)
            kmer = ((kmer & 0x3FFFFFFF) << 2);
        else if (step == 2)
            kmer = ((kmer & 0x0FFFFFFF) << 2);
        else if (step == 3)
            kmer = ((kmer & 0x03FFFFFF) << 2);
        else if (step == 4)
            kmer = ((kmer & 0x00FFFFFF) << 2);
    }
    for(int i=start;i<KMER;i++){
        switch(seq[pos+i]){
            case 'A':
                kmer += 0;
                break;
            case 'T':
                kmer += 1;
                break;
            case 'C':
                kmer += 2;
                break;
            case 'G':
                kmer += 3;
                break;
            default:
                return -1;
        }
        // not the tail
        if(i<KMER-1)
            kmer = kmer << 2;
    }
    return kmer;
}

long Indexer::gp2long(const GenePos& gp){
    long ret = gp.contig;
    int temp[2]={gp.position, 0};
    return (ret<<32) | *((long*)temp);
}

GenePos Indexer::long2gp(const long val){
    GenePos gp;
    gp.position = (val & 0x00000000FFFFFFFF);
    gp.contig = val >> 32;
    return gp;
}

GenePos Indexer::shift(const GenePos& gp, int i){
    GenePos gpNew;
    gpNew.contig = gp.contig;
    gpNew.position = gp.position - i;
    return gpNew;
}

void Indexer::printStat() {
    cout<<"mUniquePos:"<<mUniquePos<<endl;
    cout<<"mDupePos:"<<mDupePos<<endl;
}

bool Indexer::test() {
    short contigs[10]={0, 1, 3, 220, -1, 0, 23, 4440, 110, 10};
    int positions[10]={0, 111, 222, -333, 444, 555555, 6, -7777777, 8888, -9999};
    for(int i=0;i<10;i++){
        GenePos gp;
        gp.contig = contigs[i];
        gp.position = positions[i];
        long val = gp2long(gp);
        GenePos gp2 = long2gp(val);
        long val2 = gp2long(gp2);
        cout << gp2.contig << ", " << gp2.position << endl;
        if ((gp.contig == gp2.contig && gp.position == gp2.position && val==val2) == false)
            return false;
    }
    return true;
}<|endoftext|>


<commit_before>#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "seccomp_bpf.h"

#include <iostream>

using namespace std;

static bool install_syscall_filter() {
    struct sock_filter filter[] = {
        VALIDATE_ARCHITECTURE,
        /* Grab the system call number. */
        EXAMINE_SYSCALL,
        /* List allowed syscalls. Look up via ausyscall. */
        ALLOW_SYSCALL(exit_group),
        ALLOW_SYSCALL(exit),
        ALLOW_SYSCALL(stat),
        ALLOW_SYSCALL(fstat),
        ALLOW_SYSCALL(read),
        ALLOW_SYSCALL(write),
        ALLOW_SYSCALL(getdents),
        ALLOW_SYSCALL(close),
        ALLOW_SYSCALL(mmap),
        ALLOW_SYSCALL(mprotect),
        ALLOW_SYSCALL(munmap),
        ALLOW_SYSCALL(brk),
        ALLOW_SYSCALL(futex),
        ALLOW_SYSCALL(lseek),
        ALLOW_SYSCALL(set_tid_address),
        ALLOW_SYSCALL(set_robust_list),
        ALLOW_SYSCALL(rt_sigaction),
        ALLOW_SYSCALL(rt_sigprocmask),
        ALLOW_SYSCALL(getrlimit),
        ALLOW_SYSCALL(arch_prctl),
        ALLOW_SYSCALL(access),
        ALLOW_SYSCALL(fstatfs),
        ALLOW_SYSCALL(readlink),
        ALLOW_SYSCALL(fadvise64),
        ALLOW_SYSCALL(clock_gettime),
        ALLOW_SYSCALL(sysinfo),

        ALLOW_SYSCALL(getuid),
        ALLOW_SYSCALL(geteuid),
        ALLOW_SYSCALL(getgid),
        ALLOW_SYSCALL(getegid),

        ALLOW_SYSCALL(fcntl),

        ALLOW_SYSCALL(mremap),
        ALLOW_SYSCALL(statfs),
        ALLOW_SYSCALL(readlink),
        ALLOW_SYSCALL(getpid),
        ALLOW_SYSCALL(gettid),
        ALLOW_SYSCALL(tgkill),

        ALLOW_SYSCALL(ftruncate),
        ALLOW_SYSCALL(ioctl),
        ALLOW_SYSCALL(sched_yield),

        ALLOW_SYSCALL(clone),
        ALLOW_SYSCALL(wait4),
        ALLOW_SYSCALL(mknod),
        ALLOW_SYSCALL(getrandom),
        ALLOW_SYSCALL(shmctl),
        ALLOW_SYSCALL(prlimit64),
        ALLOW_SYSCALL(dup),

        TRACE_SYSCALL(execve),
        TRACE_SYSCALL(mkdir),
        TRACE_SYSCALL(unlink),
        TRACE_SYSCALL(open),
        TRACE_SYSCALL(openat),

        // Uncomment the following when trying to figure out which new
        // syscall's are being made:

        // TRACE_ALL,
        // ALLOW_ALL,
        KILL_PROCESS,
    };
    struct sock_fprog prog = {
        sizeof(filter)/sizeof(filter[0]),
        filter,
    };

    // Lock down the app so that it can't get new privs, such as setuid.
    // Calling this is a requirement for an unprivileged process to use mode
    // 2 seccomp filters, ala SECCOMP_MODE_FILTER, otherwise we'd have to be
    // root.
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl(NO_NEW_PRIVS)");
        goto failed;
    }
    // Now call seccomp and restrict the system calls that can be made to only
    // the ones in the provided filter list.
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
        perror("prctl(SECCOMP)");
        goto failed;
    }
    return true;

failed:
    if (errno == EINVAL) {
        fprintf(stderr, "SECCOMP_FILTER is not available. :(\n");
    }
    return false;
}

static void setLimits() {
     struct rlimit n;

     // Limit to 20 seconds of CPU.
     n.rlim_cur = 20;
     n.rlim_max = 20;
     if (setrlimit(RLIMIT_CPU, &n)) {
         perror("setrlimit(RLIMIT_CPU)");
     }

     // Limit to 1G of Address space.
     n.rlim_cur = 1000000000;
     n.rlim_max = 1000000000;
     if (setrlimit(RLIMIT_AS, &n)) {
         perror("setrlimit(RLIMIT_AS)");
     }
 }

int do_child(int argc, char **argv) {

    char *args[argc+1];

    memcpy(args, argv, argc * sizeof(char *));
    args[argc] = NULL;

    if (ptrace(PTRACE_TRACEME, 0, 0, 0)) {
        perror("ptrace");
        exit(-1);
    }
    kill(getpid(), SIGSTOP);

    setLimits();
    if (!install_syscall_filter()) {
        perror("Failed to install syscall filter");
        return -1;
    }

    (void)execvp(args[0], args);
    // if execvp returns, we couldn't run the child.  Probably
    // because the compile failed.  Let's kill ourselves so the
    // parent sees the signal and exits appropriately.
    perror("Couldn't run child.");
    kill(getpid(), SIGKILL);
    return -1;
}

// read_string copies a null-terminated string out
// of the child's address space, one character at a time.
// It allocates memory and returns it to the caller;
// it is the caller's responsibility to free it.
char *read_string(pid_t child, unsigned long addr) {
#define INITIAL_ALLOCATION 4096
    char *val = (char *) malloc(INITIAL_ALLOCATION);
    size_t allocated = INITIAL_ALLOCATION;
    size_t read = 0;
    unsigned long tmp;

    while (1) {
        if (read + sizeof tmp > allocated) {
            allocated *= 2;
            val = (char *) realloc(val, allocated);
        }

        tmp = ptrace(PTRACE_PEEKDATA, child, addr + read);
        if (errno != 0) {
            val[read] = 0;
            break;
        }
        memcpy(val + read, &tmp, sizeof tmp);
        if (memchr(&tmp, 0, sizeof tmp) != NULL) {
            break;
        }
        read += sizeof tmp;
    }
    return val;
}

void child_fail(pid_t child, const char* message) {
    perror(message);
    kill(child, SIGKILL);
    exit(-1);
}

const char *mkdir_allowed_prefixes[] = {
    "/tmp",
    "/var/cache/fontconfig",
    NULL,
};

const char *unlink_allowed_prefixes[] = {
    "/tmp",
    NULL,
};

const char *writing_allowed_prefixes[] = {
    "/tmp/",
    NULL,
};

const char *readonly_allowed_prefixes[] = {
    "/etc/fonts",
    "/etc/fiddle/",
    "/etc/glvnd/",
    "/etc/ld.so.cache",
    "/lib/",
    "/mnt/pd0/",
    "/tmp/",
    "/usr/lib/",
    "/usr/local/share/fonts",
    "/usr/local/lib",
    "/usr/share/",
    "/sys/devices/",
    "/var/cache/fontconfig",
    "skia.conf",
    NULL,
};

void test_against_prefixes(pid_t child, const char * caller, char* name, const char** prefixes) {
    if (NULL != strstr(name, "../")) {
        perror(caller);
        perror(name);
        child_fail(child, "No relative paths...");
    }
    bool okay = false;
    for (; *prefixes != NULL; prefixes++) {
        if (!strncmp(*prefixes, name, strlen(*prefixes))) {
            okay = true;
            break;
        }
    }
    if (!okay) {
        perror(name);
        perror(caller);
        child_fail(child, "Invalid filename.");
    }
}

/*
 * The first six integer or pointer arguments are passed in registers RDI,
 * RSI, RDX, RCX (R10 in the Linux kernel interface), R8, and R9,
 * while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for
 * certain floating point arguments.
 */
int do_trace(pid_t child, char *allowed_exec) {
    int status;
    waitpid(child, &status, 0);
    ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACEEXEC | PTRACE_O_TRACESECCOMP);
    ptrace(PTRACE_CONT, child, 0, 0);

    while(1) {
        waitpid(child, &status, 0);
        if (WIFEXITED(status)) {
            return 0;
        }
        if (WIFSIGNALED(status)) {
            cerr << "Signal: "  << WTERMSIG(status) << endl;
            perror("WIFSIGNALED");
            return 1;
        }

        if (status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8))) {
            struct user_regs_struct regs;
            if(ptrace(PTRACE_GETREGS, child, NULL, &regs)) {
                  perror("The child failed...");
                  exit(-1);
            }

            int syscall = regs.orig_rax;
            if (syscall == SYS_execve) {
                char *name = read_string(child, regs.rdi);
                if (strcmp(name, allowed_exec)) {
                    child_fail(child, "Invalid exec.");
                }
                free(name);
            } else if (syscall == SYS_open) {
                char *name = read_string(child, regs.rdi);
                const char **prefixes = readonly_allowed_prefixes;
                int flags = regs.rsi;
                if (O_RDONLY != (flags & O_ACCMODE)) {
                    prefixes = writing_allowed_prefixes;
                }
                test_against_prefixes(child, "open", name, prefixes);
                free(name);
            } else if (syscall == SYS_openat) {
                char *name = read_string(child, regs.rsi);
                int flags = regs.rdx;
                const char **prefixes = readonly_allowed_prefixes;
                if (O_RDONLY != (flags & O_ACCMODE)) {
                    prefixes = writing_allowed_prefixes;
                }
                test_against_prefixes(child, "openat", name, prefixes);
                free(name);
            } else if (syscall == SYS_mkdir) {
                char *name = read_string(child, regs.rdi);
                test_against_prefixes(child, "mkdir", name, mkdir_allowed_prefixes);
                free(name);
            } else if (syscall == SYS_unlink) {
                char *name = read_string(child, regs.rdi);
                test_against_prefixes(child, "unlink", name, unlink_allowed_prefixes);
                free(name);
            } else {
                // this should never happen, but if we're in TRACE_ALL
                // mode for debugging, this lets me print out what system
                // calls are happening unexpectedly.
                cout << "WEIRD SYSTEM CALL: " << syscall << endl;
                child_fail(child, "Invalid system call.");
            }
        }
        ptrace(PTRACE_CONT, child, 0, 0);
    }
    return 0;
}

int main(int argc, char** argv) {
    pid_t child = fork();

    if (child == 0) {
        return do_child(argc-1, argv+1);
    } else {
        return do_trace(child, argv[1]);
    }
}
<commit_msg>fiddle - More syscalls to allow.<commit_after>#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/ptrace.h>
#include <sys/syscall.h>
#include <sys/user.h>
#include <sys/types.h>
#include <sys/wait.h>

#include "seccomp_bpf.h"

#include <iostream>

using namespace std;

static bool install_syscall_filter() {
    struct sock_filter filter[] = {
        VALIDATE_ARCHITECTURE,
        /* Grab the system call number. */
        EXAMINE_SYSCALL,
        /* List allowed syscalls. Look up via ausyscall. */
        ALLOW_SYSCALL(exit_group),
        ALLOW_SYSCALL(exit),
        ALLOW_SYSCALL(stat),
        ALLOW_SYSCALL(fstat),
        ALLOW_SYSCALL(read),
        ALLOW_SYSCALL(write),
        ALLOW_SYSCALL(getdents),
        ALLOW_SYSCALL(close),
        ALLOW_SYSCALL(mmap),
        ALLOW_SYSCALL(mprotect),
        ALLOW_SYSCALL(munmap),
        ALLOW_SYSCALL(brk),
        ALLOW_SYSCALL(futex),
        ALLOW_SYSCALL(lseek),
        ALLOW_SYSCALL(set_tid_address),
        ALLOW_SYSCALL(set_robust_list),
        ALLOW_SYSCALL(rt_sigaction),
        ALLOW_SYSCALL(rt_sigprocmask),
        ALLOW_SYSCALL(getrlimit),
        ALLOW_SYSCALL(arch_prctl),
        ALLOW_SYSCALL(access),
        ALLOW_SYSCALL(fstatfs),
        ALLOW_SYSCALL(readlink),
        ALLOW_SYSCALL(fadvise64),
        ALLOW_SYSCALL(clock_gettime),
        ALLOW_SYSCALL(sysinfo),

        ALLOW_SYSCALL(getuid),
        ALLOW_SYSCALL(geteuid),
        ALLOW_SYSCALL(getgid),
        ALLOW_SYSCALL(getegid),

        ALLOW_SYSCALL(fcntl),

        ALLOW_SYSCALL(mremap),
        ALLOW_SYSCALL(statfs),
        ALLOW_SYSCALL(readlink),
        ALLOW_SYSCALL(getpid),
        ALLOW_SYSCALL(gettid),
        ALLOW_SYSCALL(tgkill),

        ALLOW_SYSCALL(ftruncate),
        ALLOW_SYSCALL(ioctl),
        ALLOW_SYSCALL(sched_yield),

        ALLOW_SYSCALL(clone),
        ALLOW_SYSCALL(wait4),
        ALLOW_SYSCALL(mknod),
        ALLOW_SYSCALL(getrandom),
        ALLOW_SYSCALL(shmctl),
        ALLOW_SYSCALL(prlimit64),
        ALLOW_SYSCALL(dup),
        ALLOW_SYSCALL(link),
        ALLOW_SYSCALL(rename),
        ALLOW_SYSCALL(renameat),
        ALLOW_SYSCALL(renameat2),
        ALLOW_SYSCALL(chmod),
        ALLOW_SYSCALL(chown),

        TRACE_SYSCALL(execve),
        TRACE_SYSCALL(mkdir),
        TRACE_SYSCALL(unlink),
        TRACE_SYSCALL(open),
        TRACE_SYSCALL(openat),

        // Uncomment the following when trying to figure out which new
        // syscall's are being made:

        TRACE_ALL,
        // ALLOW_ALL,
        KILL_PROCESS,
    };
    struct sock_fprog prog = {
        sizeof(filter)/sizeof(filter[0]),
        filter,
    };

    // Lock down the app so that it can't get new privs, such as setuid.
    // Calling this is a requirement for an unprivileged process to use mode
    // 2 seccomp filters, ala SECCOMP_MODE_FILTER, otherwise we'd have to be
    // root.
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
        perror("prctl(NO_NEW_PRIVS)");
        goto failed;
    }
    // Now call seccomp and restrict the system calls that can be made to only
    // the ones in the provided filter list.
    if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
        perror("prctl(SECCOMP)");
        goto failed;
    }
    return true;

failed:
    if (errno == EINVAL) {
        fprintf(stderr, "SECCOMP_FILTER is not available. :(\n");
    }
    return false;
}

static void setLimits() {
     struct rlimit n;

     // Limit to 20 seconds of CPU.
     n.rlim_cur = 20;
     n.rlim_max = 20;
     if (setrlimit(RLIMIT_CPU, &n)) {
         perror("setrlimit(RLIMIT_CPU)");
     }

     // Limit to 1G of Address space.
     n.rlim_cur = 1000000000;
     n.rlim_max = 1000000000;
     if (setrlimit(RLIMIT_AS, &n)) {
         perror("setrlimit(RLIMIT_AS)");
     }
 }

int do_child(int argc, char **argv) {

    char *args[argc+1];

    memcpy(args, argv, argc * sizeof(char *));
    args[argc] = NULL;

    if (ptrace(PTRACE_TRACEME, 0, 0, 0)) {
        perror("ptrace");
        exit(-1);
    }
    kill(getpid(), SIGSTOP);

    setLimits();
    if (!install_syscall_filter()) {
        perror("Failed to install syscall filter");
        return -1;
    }

    (void)execvp(args[0], args);
    // if execvp returns, we couldn't run the child.  Probably
    // because the compile failed.  Let's kill ourselves so the
    // parent sees the signal and exits appropriately.
    perror("Couldn't run child.");
    kill(getpid(), SIGKILL);
    return -1;
}

// read_string copies a null-terminated string out
// of the child's address space, one character at a time.
// It allocates memory and returns it to the caller;
// it is the caller's responsibility to free it.
char *read_string(pid_t child, unsigned long addr) {
#define INITIAL_ALLOCATION 4096
    char *val = (char *) malloc(INITIAL_ALLOCATION);
    size_t allocated = INITIAL_ALLOCATION;
    size_t read = 0;
    unsigned long tmp;

    while (1) {
        if (read + sizeof tmp > allocated) {
            allocated *= 2;
            val = (char *) realloc(val, allocated);
        }

        tmp = ptrace(PTRACE_PEEKDATA, child, addr + read);
        if (errno != 0) {
            val[read] = 0;
            break;
        }
        memcpy(val + read, &tmp, sizeof tmp);
        if (memchr(&tmp, 0, sizeof tmp) != NULL) {
            break;
        }
        read += sizeof tmp;
    }
    return val;
}

void child_fail(pid_t child, const char* message) {
    perror(message);
    kill(child, SIGKILL);
    exit(-1);
}

const char *mkdir_allowed_prefixes[] = {
    "/tmp",
    "/var/cache/fontconfig",
    NULL,
};

const char *unlink_allowed_prefixes[] = {
    "/tmp",
    NULL,
};

const char *writing_allowed_prefixes[] = {
    "/tmp/",
    NULL,
};

const char *readonly_allowed_prefixes[] = {
    "/etc/fonts",
    "/etc/fiddle/",
    "/etc/glvnd/",
    "/etc/ld.so.cache",
    "/lib/",
    "/mnt/pd0/",
    "/tmp/",
    "/usr/lib/",
    "/usr/local/share/fonts",
    "/usr/local/lib",
    "/usr/share/",
    "/sys/devices/",
    "/var/cache/fontconfig",
    "skia.conf",
    NULL,
};

void test_against_prefixes(pid_t child, const char * caller, char* name, const char** prefixes) {
    if (NULL != strstr(name, "../")) {
        perror(caller);
        perror(name);
        child_fail(child, "No relative paths...");
    }
    bool okay = false;
    for (; *prefixes != NULL; prefixes++) {
        if (!strncmp(*prefixes, name, strlen(*prefixes))) {
            okay = true;
            break;
        }
    }
    if (!okay) {
        perror(name);
        perror(caller);
        child_fail(child, "Invalid filename.");
    }
}

/*
 * The first six integer or pointer arguments are passed in registers RDI,
 * RSI, RDX, RCX (R10 in the Linux kernel interface), R8, and R9,
 * while XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6 and XMM7 are used for
 * certain floating point arguments.
 */
int do_trace(pid_t child, char *allowed_exec) {
    int status;
    waitpid(child, &status, 0);
    ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACEEXEC | PTRACE_O_TRACESECCOMP);
    ptrace(PTRACE_CONT, child, 0, 0);

    while(1) {
        waitpid(child, &status, 0);
        if (WIFEXITED(status)) {
            return 0;
        }
        if (WIFSIGNALED(status)) {
            cerr << "Signal: "  << WTERMSIG(status) << endl;
            perror("WIFSIGNALED");
            return 1;
        }

        if (status>>8 == (SIGTRAP | (PTRACE_EVENT_SECCOMP<<8))) {
            struct user_regs_struct regs;
            if(ptrace(PTRACE_GETREGS, child, NULL, &regs)) {
                  perror("The child failed...");
                  exit(-1);
            }

            int syscall = regs.orig_rax;
            if (syscall == SYS_execve) {
                char *name = read_string(child, regs.rdi);
                if (strcmp(name, allowed_exec)) {
                    child_fail(child, "Invalid exec.");
                }
                free(name);
            } else if (syscall == SYS_open) {
                char *name = read_string(child, regs.rdi);
                const char **prefixes = readonly_allowed_prefixes;
                int flags = regs.rsi;
                if (O_RDONLY != (flags & O_ACCMODE)) {
                    prefixes = writing_allowed_prefixes;
                }
                test_against_prefixes(child, "open", name, prefixes);
                free(name);
            } else if (syscall == SYS_openat) {
                char *name = read_string(child, regs.rsi);
                int flags = regs.rdx;
                const char **prefixes = readonly_allowed_prefixes;
                if (O_RDONLY != (flags & O_ACCMODE)) {
                    prefixes = writing_allowed_prefixes;
                }
                test_against_prefixes(child, "openat", name, prefixes);
                free(name);
            } else if (syscall == SYS_mkdir) {
                char *name = read_string(child, regs.rdi);
                test_against_prefixes(child, "mkdir", name, mkdir_allowed_prefixes);
                free(name);
            } else if (syscall == SYS_unlink) {
                char *name = read_string(child, regs.rdi);
                test_against_prefixes(child, "unlink", name, unlink_allowed_prefixes);
                free(name);
            } else {
                // this should never happen, but if we're in TRACE_ALL
                // mode for debugging, this lets me print out what system
                // calls are happening unexpectedly.
                cout << "WEIRD SYSTEM CALL: " << syscall << endl;
                child_fail(child, "Invalid system call.");
            }
        }
        ptrace(PTRACE_CONT, child, 0, 0);
    }
    return 0;
}

int main(int argc, char** argv) {
    pid_t child = fork();

    if (child == 0) {
        return do_child(argc-1, argv+1);
    } else {
        return do_trace(child, argv[1]);
    }
}
<|endoftext|>


<commit_before>
#include "stdafx.h"

#include "Tese.h"

String^ TeseCpp::Tese::Serialize(Object^ obj)
{
	array<Byte>^ bytes;
	{
		MemoryStream^ mem = gcnew MemoryStream();
		Serialize(obj, mem);
		bytes = mem->ToArray();
		mem->Close();
	}
	return Encoding::UTF8->GetString(bytes);
}

generic <typename T>
T TeseCpp::Tese::Deserialize(Stream^ stream)
{
	try {
		JavaProperties^ props = gcnew JavaProperties();
		props->Load(stream);
		return (T)DeserializeFields(emptyPrefix, props, T::typeid);
	} catch (IOException^ e) {
		throw gcnew TeseReadException(e);
	}
}

generic <typename T>
T TeseCpp::Tese::Deserialize(String^ text)
{
	array<Byte>^ bytes = Encoding::UTF8->GetBytes(text);
	{
		MemoryStream^ mem = gcnew MemoryStream(bytes);
		return Deserialize<T>(mem);
	}
}

Object^ TeseCpp::Tese::DeserializeFields(String^ prefix, IDictionary^ props, Type^ type)
{
	try {
		Object^ obj = Activator::CreateInstance(type);
		array<FieldInfo^>^ fields = type->GetFields(BindingFlags::NonPublic | BindingFlags::Instance);
		for each (FieldInfo^ field in fields)
			DeserializeField(prefix, obj, field, props);
		return obj;
	} catch (Exception^ e) {
		throw gcnew TeseReadException(e);
	}
}

Void TeseCpp::Tese::DeserializeField(String^ prefix, Object^ obj, FieldInfo^ field, IDictionary^ props)
{
	try {
		String^ key = field->Name;
		String^ objKey = String::Format("{0}.{1}", prefix, key);
		Object^ val = props[objKey];
		if (val == nullptr) {
			if (FindLongerKey(props, objKey)) {
				field->SetValue(obj, DeserializeFields(objKey, props, field->FieldType));
			}
			return;
		}
		field->SetValue(obj, FromStr(val->ToString(), field));
	} catch (Exception^ e) {
		throw gcnew TeseReadException(e);
	}
}

bool TeseCpp::Tese::FindLongerKey(IDictionary^ props, String^ shortKey)
{
	for each (Object^ key in props->Keys)
		if (key->ToString()->StartsWith(shortKey, StringComparison::InvariantCulture))
			return true;
	return false;
}

Object^ TeseCpp::Tese::FromStr(String^ val, FieldInfo^ field)
{
	Type^ type = field->FieldType;
	if (type->IsEnum)
		return Enum::Parse(type, val);
	CultureInfo^ cult = CultureInfo::InvariantCulture;
	/*switch (type->Name) {
		case "Boolean":
			return Boolean.Parse(val);
		case "Byte":
			return Byte.Parse(val);
		case "Char":
			return Char.Parse(val);
		case "Single":
			return Single.Parse(val, cult);
		case "Double":
			return Double.Parse(val, cult);
		case "Int32":
			return Int32.Parse(val);
		case "Int16":
			return Int16.Parse(val);
		case "Int64":
			return Int64.Parse(val);
		case "BigInteger":
			return BigInteger.Parse(val, cult);
		case "Decimal":
			return decimal.Parse(val, cult);
		case "DateTime":
			return DateTime.Parse(val, cult).ToUniversalTime();
		case "String":
			return val;
		default:
			throw gcnew InvalidOperationException(type + " is not supported!");
	}*/
	throw gcnew InvalidOperationException(type + " is not supported!");
}

Void TeseCpp::Tese::Serialize(Object^ obj, Stream^ writer)
{
	try {
		JavaProperties^ props = gcnew JavaProperties();
		SerializeFields(emptyPrefix, obj, props);
		props->Store(writer, nullptr);
	} catch (IOException^ e) {
		throw gcnew TeseWriteException(e);
	}
}

Void TeseCpp::Tese::SerializeFields(String^ prefix, Object^ obj, IDictionary^ props)
{
	Type^ type = obj->GetType();
	array<FieldInfo^>^ fields = type->GetFields(BindingFlags::NonPublic | BindingFlags::Instance);
	for each (FieldInfo^ field in fields)
		SerializeField(prefix, obj, field, props);
}

Void TeseCpp::Tese::SerializeField(String^ prefix, Object^ obj, FieldInfo^ field, IDictionary^ props)
{
	try {
		String^ key = field->Name;
		Object^ val = field->GetValue(obj);
		String^ objKey = String::Format("{0}.{1}", prefix, key);
		try {
			props[objKey] = ToStr(val, field);
		} catch (InvalidOperationException^) {
			SerializeFields(objKey, val, props);
		}
	} catch (Exception^ e) {
		throw gcnew TeseWriteException(field, e);
	}
}

String^ TeseCpp::Tese::ToStr(Object^ value, FieldInfo^ field)
{
	Type^ type = field->FieldType;
	if (type->IsEnum)
		return value->ToString();
	CultureInfo^ cult = CultureInfo::InvariantCulture;
	String^ name = type->Name;
	if (name->Equals("DateTime"))
		return ((DateTime)value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz");
	if (name->Equals("Single"))
		return ((float)value).ToString(cult);
	if (name->Equals("Double"))
		return ((double)value).ToString(cult);
	if (name->Equals("Decimal"))
		return ((Decimal)value).ToString(cult);
	if (Array::BinarySearch(gcnew array<String^> { "Boolean","BigInteger","Int64",
		"Char","Int32","Byte","Int16","String" }, name) >= 0)
		return value->ToString();
	throw gcnew InvalidOperationException(type + " is not supported!");
}
<commit_msg>Implemented from string method<commit_after>
#include "stdafx.h"

#include "Tese.h"

String^ TeseCpp::Tese::Serialize(Object^ obj)
{
	array<Byte>^ bytes;
	{
		MemoryStream^ mem = gcnew MemoryStream();
		Serialize(obj, mem);
		bytes = mem->ToArray();
		mem->Close();
	}
	return Encoding::UTF8->GetString(bytes);
}

generic <typename T>
T TeseCpp::Tese::Deserialize(Stream^ stream)
{
	try {
		JavaProperties^ props = gcnew JavaProperties();
		props->Load(stream);
		return (T)DeserializeFields(emptyPrefix, props, T::typeid);
	} catch (IOException^ e) {
		throw gcnew TeseReadException(e);
	}
}

generic <typename T>
T TeseCpp::Tese::Deserialize(String^ text)
{
	array<Byte>^ bytes = Encoding::UTF8->GetBytes(text);
	{
		MemoryStream^ mem = gcnew MemoryStream(bytes);
		return Deserialize<T>(mem);
	}
}

Object^ TeseCpp::Tese::DeserializeFields(String^ prefix, IDictionary^ props, Type^ type)
{
	try {
		Object^ obj = Activator::CreateInstance(type);
		array<FieldInfo^>^ fields = type->GetFields(BindingFlags::NonPublic | BindingFlags::Instance);
		for each (FieldInfo^ field in fields)
			DeserializeField(prefix, obj, field, props);
		return obj;
	} catch (Exception^ e) {
		throw gcnew TeseReadException(e);
	}
}

Void TeseCpp::Tese::DeserializeField(String^ prefix, Object^ obj, FieldInfo^ field, IDictionary^ props)
{
	try {
		String^ key = field->Name;
		String^ objKey = String::Format("{0}.{1}", prefix, key);
		Object^ val = props[objKey];
		if (val == nullptr) {
			if (FindLongerKey(props, objKey)) {
				field->SetValue(obj, DeserializeFields(objKey, props, field->FieldType));
			}
			return;
		}
		field->SetValue(obj, FromStr(val->ToString(), field));
	} catch (Exception^ e) {
		throw gcnew TeseReadException(e);
	}
}

bool TeseCpp::Tese::FindLongerKey(IDictionary^ props, String^ shortKey)
{
	for each (Object^ key in props->Keys)
		if (key->ToString()->StartsWith(shortKey, StringComparison::InvariantCulture))
			return true;
	return false;
}

Object^ TeseCpp::Tese::FromStr(String^ val, FieldInfo^ field)
{
	Type^ type = field->FieldType;
	if (type->IsEnum)
		return Enum::Parse(type, val);
	CultureInfo^ cult = CultureInfo::InvariantCulture;
	String^ name = type->Name;
	if (name->Equals("Boolean"))
		return Boolean::Parse(val);
	if (name->Equals("Byte"))
		return Byte::Parse(val);
	if (name->Equals("Char"))
		return Char::Parse(val);
	if (name->Equals("Single"))
		return Single::Parse(val, cult);
	if (name->Equals("Double"))
		return Double::Parse(val, cult);
	if (name->Equals("Int32"))
		return Int32::Parse(val);
	if (name->Equals("Int16"))
		return Int16::Parse(val);
	if (name->Equals("Int64"))
		return Int64::Parse(val);
	if (name->Equals("BigInteger"))
		return BigInteger::Parse(val, cult);
	if (name->Equals("Decimal"))
		return Decimal::Parse(val, cult);
	if (name->Equals("DateTime"))
		return DateTime::Parse(val, cult).ToUniversalTime();
	if (name->Equals("String"))
		return val;
	throw gcnew InvalidOperationException(type + " is not supported!");
}

Void TeseCpp::Tese::Serialize(Object^ obj, Stream^ writer)
{
	try {
		JavaProperties^ props = gcnew JavaProperties();
		SerializeFields(emptyPrefix, obj, props);
		props->Store(writer, nullptr);
	} catch (IOException^ e) {
		throw gcnew TeseWriteException(e);
	}
}

Void TeseCpp::Tese::SerializeFields(String^ prefix, Object^ obj, IDictionary^ props)
{
	Type^ type = obj->GetType();
	array<FieldInfo^>^ fields = type->GetFields(BindingFlags::NonPublic | BindingFlags::Instance);
	for each (FieldInfo^ field in fields)
		SerializeField(prefix, obj, field, props);
}

Void TeseCpp::Tese::SerializeField(String^ prefix, Object^ obj, FieldInfo^ field, IDictionary^ props)
{
	try {
		String^ key = field->Name;
		Object^ val = field->GetValue(obj);
		String^ objKey = String::Format("{0}.{1}", prefix, key);
		try {
			props[objKey] = ToStr(val, field);
		} catch (InvalidOperationException^) {
			SerializeFields(objKey, val, props);
		}
	} catch (Exception^ e) {
		throw gcnew TeseWriteException(field, e);
	}
}

String^ TeseCpp::Tese::ToStr(Object^ value, FieldInfo^ field)
{
	Type^ type = field->FieldType;
	if (type->IsEnum)
		return value->ToString();
	CultureInfo^ cult = CultureInfo::InvariantCulture;
	String^ name = type->Name;
	if (name->Equals("DateTime"))
		return ((DateTime)value).ToString("yyyy-MM-dd'T'HH:mm:ss.fffzzz");
	if (name->Equals("Single"))
		return ((float)value).ToString(cult);
	if (name->Equals("Double"))
		return ((double)value).ToString(cult);
	if (name->Equals("Decimal"))
		return ((Decimal)value).ToString(cult);
	if (Array::BinarySearch(gcnew array<String^> { "Boolean","BigInteger","Int64",
		"Char","Int32","Byte","Int16","String" }, name) >= 0)
		return value->ToString();
	throw gcnew InvalidOperationException(type + " is not supported!");
}
<|endoftext|>