edouard@3937: edouard@3940: #include edouard@3940: #include edouard@3940: #include edouard@3940: #include edouard@3940: #include edouard@3949: #include edouard@3937: edouard@3937: #include "Logging.hpp" edouard@3937: edouard@3937: #include "PLCObject.hpp" edouard@3937: edouard@3940: #include "beremiz.h" edouard@3940: edouard@3940: edouard@3940: // File name of the last transferred PLC md5 hex digest edouard@3940: // with typo in the name, for compatibility with Python runtime edouard@3940: #define LastTransferredPLC "lasttransferedPLC.md5" edouard@3940: edouard@3940: // File name of the extra files list edouard@3940: #define ExtraFilesList "extra_files.txt" edouard@3940: edouard@3940: edouard@3940: edouard@3940: PLCObject::PLCObject(void) edouard@3940: { edouard@3945: m_status.PLCstatus = Empty; edouard@3945: m_handle = NULL; edouard@3945: m_debugToken = 0; edouard@3945: m_argc = 0; edouard@3945: m_argv = NULL; edouard@3949: m_PSK_ID = ""; edouard@3949: m_PSK_secret = ""; edouard@3940: } edouard@3940: edouard@3937: PLCObject::~PLCObject(void) edouard@3937: { edouard@3937: } edouard@3937: edouard@3940: uint32_t PLCObject::AppendChunkToBlob( edouard@3940: const binary_t *data, const binary_t *blobID, binary_t *newBlobID) edouard@3940: { edouard@3940: // Append data to blob with given blobID edouard@3940: // Output new blob's md5 into newBlobID edouard@3940: // Return 0 if success edouard@3940: edouard@3949: newBlobID->data = (uint8_t *)malloc(MD5::digestsize); edouard@3949: if (newBlobID->data == NULL) edouard@3949: { edouard@3949: return ENOMEM; edouard@3949: } edouard@3949: edouard@3949: std::vector k(blobID->data, blobID->data + blobID->dataLength); edouard@3949: edouard@3949: auto nh = m_mapBlobIDToBlob.extract(k); edouard@3940: if (nh.empty()) edouard@3940: { edouard@3940: return ENOENT; edouard@3940: } edouard@3940: edouard@3940: Blob *blob = nh.mapped(); edouard@3940: edouard@3940: uint32_t res = blob->appendChunk(data->data, data->dataLength); edouard@3940: if (res != 0) edouard@3940: { edouard@3940: return res; edouard@3940: } edouard@3940: edouard@3940: MD5::digest_t digest = blob->digest(); edouard@3940: edouard@3949: std::vector nk((uint8_t*)digest.data, (uint8_t*)digest.data + MD5::digestsize); edouard@3949: nh.key() = nk; edouard@3940: edouard@3940: m_mapBlobIDToBlob.insert(std::move(nh)); edouard@3940: edouard@3949: memcpy(newBlobID->data, digest.data, MD5::digestsize); edouard@3949: newBlobID->dataLength = MD5::digestsize; edouard@3949: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: #define LOG_READ_BUFFER_SIZE 1 << 10 // 1KB edouard@3940: edouard@3940: uint32_t PLCObject::GetLogMessage( edouard@3940: uint8_t level, uint32_t msgID, log_message *message) edouard@3940: { edouard@3940: char buf[LOG_READ_BUFFER_SIZE]; edouard@3940: uint32_t tick; edouard@3940: uint32_t tv_sec; edouard@3940: uint32_t tv_nsec; edouard@3940: edouard@3940: uint32_t resultLen = m_PLCSyms.GetLogMessage( edouard@3940: level, msgID, buf, LOG_READ_BUFFER_SIZE - 1, edouard@3940: &tick, &tv_sec, &tv_nsec); edouard@3940: edouard@3940: if (resultLen == 0) edouard@3940: { edouard@3940: return ENOENT; edouard@3940: } edouard@3940: edouard@3940: // Get log message with given msgID edouard@3940: message->msg = (char *)malloc(resultLen); edouard@3940: if (message->msg == NULL) edouard@3940: { edouard@3940: return ENOMEM; edouard@3940: } edouard@3940: // Copy the log message into eRPC message edouard@3940: memcpy(message->msg, buf, resultLen); edouard@3940: message->msg[resultLen + 1] = '\0'; edouard@3940: edouard@3940: message->tick = tick; edouard@3940: message->sec = tv_sec; edouard@3940: message->nsec = tv_nsec; edouard@3940: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::GetPLCID(PSKID *plcID) edouard@3940: { edouard@3949: // Get PSK ID edouard@3949: plcID->ID = (char *)malloc(m_PSK_ID.size() + 1); edouard@3949: if (plcID->ID == NULL) edouard@3949: { edouard@3949: return ENOMEM; edouard@3949: } edouard@3949: memcpy(plcID->ID, m_PSK_ID.c_str(), m_PSK_ID.size()); edouard@3949: plcID->ID[m_PSK_ID.size()] = '\0'; edouard@3949: edouard@3949: // Get PSK secret edouard@3949: plcID->PSK = (char *)malloc(m_PSK_secret.size() + 1); edouard@3949: if (plcID->PSK == NULL) edouard@3949: { edouard@3949: free(plcID->ID); edouard@3949: return ENOMEM; edouard@3949: } edouard@3949: memcpy(plcID->PSK, m_PSK_secret.c_str(), m_PSK_secret.size()); edouard@3949: plcID->PSK[m_PSK_secret.size()] = '\0'; edouard@3949: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::GetPLCstatus(PLCstatus *status) edouard@3940: { edouard@3940: // Get PLC status edouard@3940: *status = m_status; edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::GetTraceVariables( edouard@3940: uint32_t debugToken, TraceVariables *traces) edouard@3940: { edouard@3945: if(debugToken != m_debugToken) edouard@3945: { edouard@3945: return EINVAL; edouard@3945: } edouard@3945: edouard@3945: // Check if there are any traces edouard@3945: m_tracesMutex.lock(); edouard@3945: size_t sz = m_traces.size(); edouard@3949: if(sz > 0) edouard@3949: { edouard@3949: // Allocate memory for traces edouard@3949: traces->traces.elements = (trace_sample *)malloc(sz * sizeof(trace_sample)); edouard@3949: if(traces->traces.elements == NULL) edouard@3949: { edouard@3949: m_tracesMutex.unlock(); edouard@3949: return ENOMEM; edouard@3949: } edouard@3949: // Copy traces from vector edouard@3949: memcpy(traces->traces.elements, m_traces.data(), sz * sizeof(trace_sample)); edouard@3949: edouard@3949: // Clear the vector edouard@3949: // note that the data is not freed here, it is meant to be freed by eRPC server code edouard@3949: m_traces.clear(); edouard@3949: } edouard@3949: m_tracesMutex.unlock(); edouard@3949: edouard@3945: traces->traces.elementsCount = sz; edouard@3949: traces->PLCstatus = m_status.PLCstatus; edouard@3945: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::MatchMD5(const char *MD5, bool *match) edouard@3940: { edouard@3949: // an empty PLC is never considered to match edouard@3949: if(m_status.PLCstatus == Empty) edouard@3949: { edouard@3949: *match = false; edouard@3949: return 0; edouard@3949: } edouard@3949: edouard@3940: // Load the last transferred PLC md5 hex digest edouard@3940: std::string md5sum; edouard@3949: try { edouard@3949: std::ifstream(std::string(LastTransferredPLC), std::ios::binary) >> md5sum; edouard@3949: } catch (std::exception e) { edouard@3949: *match = false; edouard@3949: return 0; edouard@3949: } edouard@3940: edouard@3940: // Compare the given MD5 with the last transferred PLC md5 edouard@3940: *match = (md5sum == MD5); edouard@3940: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: #if defined(_WIN32) || defined(_WIN64) edouard@3940: // For Windows platform edouard@3940: #define SHARED_OBJECT_EXT ".dll" edouard@3940: #elif defined(__APPLE__) || defined(__MACH__) edouard@3940: // For MacOS platform edouard@3940: #define SHARED_OBJECT_EXT ".dylib" edouard@3940: #else edouard@3940: // For Linux/Unix platform edouard@3940: #define SHARED_OBJECT_EXT ".so" edouard@3940: #endif edouard@3940: edouard@3940: uint32_t PLCObject::BlobAsFile( edouard@3940: const binary_t *BlobID, std::filesystem::path filename) edouard@3940: { edouard@3940: // Extract the blob from the map edouard@3940: auto nh = m_mapBlobIDToBlob.extract( edouard@3940: std::vector(BlobID->data, BlobID->data + BlobID->dataLength)); edouard@3940: if (nh.empty()) edouard@3940: { edouard@3940: return ENOENT; edouard@3940: } edouard@3940: Blob *blob = nh.mapped(); edouard@3940: edouard@3940: // Realize the blob into a file edouard@3940: uint32_t res = blob->asFile(filename); edouard@3940: edouard@3940: delete blob; edouard@3940: edouard@3940: if (res != 0) edouard@3940: { edouard@3940: return res; edouard@3940: } edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::NewPLC( edouard@3940: const char *md5sum, const binary_t *plcObjectBlobID, edouard@3940: const list_extra_file_1_t *extrafiles, bool *success) edouard@3940: { edouard@3945: if(m_status.PLCstatus == Started) edouard@3945: { edouard@3949: *success = false; edouard@3945: return EBUSY; edouard@3945: } edouard@3945: edouard@3945: if(m_status.PLCstatus == Broken) edouard@3945: { edouard@3949: *success = false; edouard@3945: return EINVAL; edouard@3945: } edouard@3945: edouard@3945: // Unload the PLC object edouard@3945: UnLoadPLC(); edouard@3945: edouard@3945: // Purge the PLC object edouard@3945: PurgePLC(); edouard@3945: edouard@3940: // Concatenate md5sum and shared object extension to obtain filename edouard@3940: std::filesystem::path filename = edouard@3940: std::filesystem::path(md5sum) += SHARED_OBJECT_EXT; edouard@3940: edouard@3940: // Create the PLC object shared object file edouard@3940: BlobAsFile(plcObjectBlobID, filename); edouard@3940: edouard@3940: // create "lasttransferedPLC.md5" file and Save md5sum in it edouard@3940: std::ofstream(std::string(LastTransferredPLC), std::ios::binary) << md5sum; edouard@3940: edouard@3940: // create "extra_files.txt" file edouard@3940: std::ofstream extra_files_log(std::string(ExtraFilesList), std::ios::binary); edouard@3940: edouard@3940: // Create extra files edouard@3940: for (int i = 0; i < extrafiles->elementsCount; i++) edouard@3940: { edouard@3940: extra_file *extrafile = extrafiles->elements + i; edouard@3940: edouard@3940: BlobAsFile(plcObjectBlobID, extrafile->fname); edouard@3940: edouard@3940: // Save the extra file name in "extra_files.txt" edouard@3940: extra_files_log << extrafile->fname << std::endl; edouard@3940: } edouard@3940: edouard@3949: // Load the PLC object edouard@3949: uint32_t res = LoadPLC(); edouard@3949: if (res != 0) edouard@3949: { edouard@3949: *success = false; edouard@3949: return res; edouard@3949: } edouard@3949: edouard@3949: m_status.PLCstatus = Stopped; edouard@3949: *success = true; edouard@3949: edouard@3949: return 0; edouard@3949: } edouard@3949: edouard@3949: #define DLSYM(sym) \ edouard@3949: do \ edouard@3949: { \ edouard@3949: m_PLCSyms.sym = (decltype(m_PLCSyms.sym))dlsym(m_handle, #sym); \ edouard@3949: if (m_PLCSyms.sym == NULL) \ edouard@3949: { \ edouard@3949: /* TODO: use log instead */ \ edouard@3949: std::cout << "Error dlsym " #sym ": " << dlerror() << std::endl; \ edouard@3949: return errno; \ edouard@3949: } \ edouard@3940: } while (0); edouard@3940: edouard@3940: uint32_t PLCObject::LoadPLC(void) edouard@3940: { edouard@3940: // Load the last transferred PLC md5 hex digest edouard@3940: std::string md5sum; edouard@3949: try { edouard@3949: std::ifstream(std::string(LastTransferredPLC), std::ios::binary) >> md5sum; edouard@3949: } catch (std::exception e) { edouard@3949: return ENOENT; edouard@3949: } edouard@3940: edouard@3940: // Concatenate md5sum and shared object extension to obtain filename edouard@3949: std::filesystem::path filename(md5sum + SHARED_OBJECT_EXT); edouard@3940: edouard@3940: // Load the shared object file edouard@3949: m_handle = dlopen(std::filesystem::absolute(filename).c_str(), RTLD_NOW); edouard@3940: if (m_handle == NULL) edouard@3940: { edouard@3949: std::cout << "Error: " << dlerror() << std::endl; edouard@3940: return errno; edouard@3940: } edouard@3940: edouard@3940: // Resolve shared object symbols edouard@3940: FOR_EACH_PLC_SYMBOLS_DO(DLSYM); edouard@3940: edouard@3949: // Set content of PLC_ID to md5sum edouard@3949: m_PLCSyms.PLC_ID = (uint8_t *)malloc(md5sum.size() + 1); edouard@3949: if (m_PLCSyms.PLC_ID == NULL) edouard@3949: { edouard@3949: return ENOMEM; edouard@3949: } edouard@3949: memcpy(m_PLCSyms.PLC_ID, md5sum.c_str(), md5sum.size()); edouard@3949: m_PLCSyms.PLC_ID[md5sum.size()] = '\0'; edouard@3949: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: #define ULSYM(sym) \ edouard@3940: do \ edouard@3940: { \ edouard@3940: m_PLCSyms.sym = NULL; \ edouard@3940: } while (0); edouard@3940: edouard@3940: uint32_t PLCObject::UnLoadPLC(void) edouard@3940: { edouard@3940: // Unload the shared object file edouard@3940: FOR_EACH_PLC_SYMBOLS_DO(ULSYM); edouard@3945: if(m_handle != NULL) edouard@3945: { edouard@3945: dlclose(m_handle); edouard@3945: m_handle = NULL; edouard@3945: } edouard@3937: return 0; edouard@3937: } edouard@3937: edouard@3937: uint32_t PLCObject::PurgeBlobs(void) edouard@3937: { edouard@3940: // Purge all blobs edouard@3940: edouard@3940: for (auto &blob : m_mapBlobIDToBlob) edouard@3940: { edouard@3940: delete blob.second; edouard@3940: } edouard@3940: m_mapBlobIDToBlob.clear(); edouard@3940: edouard@3937: return 0; edouard@3937: } edouard@3937: edouard@3949: uint32_t PLCObject::PurgePLC(void) edouard@3949: { edouard@3945: edouard@3945: // Open the extra files list edouard@3945: std::ifstream extra_files_log(std::string(ExtraFilesList), std::ios::binary); edouard@3945: edouard@3945: // Remove extra files edouard@3945: std::string extra_file; edouard@3945: while (std::getline(extra_files_log, extra_file)) edouard@3945: { edouard@3945: std::filesystem::remove(extra_file); edouard@3945: } edouard@3945: edouard@3945: // Load the last transferred PLC md5 hex digest edouard@3945: std::string md5sum; edouard@3949: try { edouard@3949: std::ifstream(std::string(LastTransferredPLC), std::ios::binary) >> md5sum; edouard@3949: edouard@3949: // Remove the PLC object shared object file edouard@3949: std::filesystem::remove(md5sum + SHARED_OBJECT_EXT); edouard@3949: } catch (std::exception e) { edouard@3949: // ignored edouard@3949: } edouard@3949: edouard@3949: try { edouard@3949: // Remove the last transferred PLC md5 hex digest edouard@3949: std::filesystem::remove(std::string(LastTransferredPLC)); edouard@3949: edouard@3949: // Remove the extra files list edouard@3949: std::filesystem::remove(std::string(ExtraFilesList)); edouard@3949: } catch (std::exception e) { edouard@3949: // ignored edouard@3949: } edouard@3945: edouard@3945: return 0; edouard@3945: } edouard@3945: edouard@3937: uint32_t PLCObject::RepairPLC(void) edouard@3937: { edouard@3945: // Repair the PLC object edouard@3945: edouard@3945: if(m_status.PLCstatus == Broken) edouard@3945: { edouard@3945: // Unload the PLC object edouard@3945: UnLoadPLC(); edouard@3945: edouard@3945: // Purge the PLC object edouard@3945: PurgePLC(); edouard@3945: } edouard@3945: edouard@3945: edouard@3940: LogMessage(LOG_WARNING, "RepairPLC not implemented"); edouard@3937: return 0; edouard@3937: } edouard@3937: edouard@3937: uint32_t PLCObject::ResetLogCount(void) edouard@3937: { edouard@3940: m_PLCSyms.ResetLogCount(); edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::SeedBlob(const binary_t *seed, binary_t *blobID) edouard@3940: { edouard@3940: // Create a blob with given seed edouard@3940: // Output new blob's md5 into blobID edouard@3940: // Return 0 if success edouard@3940: edouard@3940: Blob *blob = NULL; edouard@3940: try edouard@3940: { edouard@3940: blob = new Blob(seed->data, seed->dataLength); edouard@3940: } edouard@3940: catch (int e) edouard@3940: { edouard@3940: return e; edouard@3940: } edouard@3940: edouard@3940: MD5::digest_t digest = blob->digest(); edouard@3940: edouard@3949: std::vector k((uint8_t*)digest.data, (uint8_t*)digest.data + MD5::digestsize); edouard@3949: edouard@3949: m_mapBlobIDToBlob[k] = blob; edouard@3940: edouard@3940: blobID->data = (uint8_t *)malloc(MD5::digestsize); edouard@3940: if (blobID->data == NULL) edouard@3940: { edouard@3940: return ENOMEM; edouard@3940: } edouard@3940: memcpy(blobID->data, digest.data, MD5::digestsize); edouard@3940: blobID->dataLength = MD5::digestsize; edouard@3940: edouard@3940: return 0; edouard@3940: } edouard@3949: void PLCObject::PurgeTraceBuffer(void) edouard@3949: { edouard@3949: // Free trace buffer edouard@3949: m_tracesMutex.lock(); edouard@3949: for(trace_sample s : m_traces){ edouard@3949: free(s.TraceBuffer.data); edouard@3949: } edouard@3949: m_traces.clear(); edouard@3949: m_tracesMutex.unlock(); edouard@3949: } edouard@3940: edouard@3940: uint32_t PLCObject::SetTraceVariablesList( edouard@3945: const list_trace_order_1_t *orders, int32_t *debugtoken) edouard@3945: { edouard@3949: if(m_status.PLCstatus == Empty) edouard@3949: { edouard@3949: return EINVAL; edouard@3949: } edouard@3949: edouard@3945: // increment debug token edouard@3945: m_debugToken++; edouard@3945: edouard@3945: if(orders->elementsCount == 0) edouard@3945: { edouard@3945: // actually disables debug edouard@3945: m_PLCSyms.suspendDebug(1); edouard@3945: *debugtoken = -5; // DEBUG_SUSPENDED edouard@3945: return 0; edouard@3945: } edouard@3945: edouard@3945: // suspend debug before any operation edouard@3945: int res = m_PLCSyms.suspendDebug(0); edouard@3945: if(res == 0) edouard@3945: { edouard@3945: // forget about all previous debug variables edouard@3945: m_PLCSyms.ResetDebugVariables(); edouard@3945: edouard@3945: // call RegisterTraceVariables for each trace order edouard@3945: for (int i = 0; i < orders->elementsCount; i++) edouard@3945: { edouard@3945: trace_order *order = orders->elements + i; edouard@3945: res = m_PLCSyms.RegisterDebugVariable(order->idx, order->force.data, order->force.dataLength); edouard@3945: if(res != 0) edouard@3945: { edouard@3945: // if any error, disable debug edouard@3945: // since debug is already suspended, resume it first edouard@3949: m_PLCSyms.resumeDebug(); edouard@3945: m_PLCSyms.suspendDebug(1); edouard@3945: *debugtoken = -res; edouard@3945: return EINVAL; edouard@3945: } edouard@3945: } edouard@3945: edouard@3949: // old traces are not valid anymore edouard@3949: PurgeTraceBuffer(); edouard@3949: edouard@3945: // Start debug thread if not already started edouard@3945: if(!m_traceThread.joinable()) edouard@3945: { edouard@3945: m_traceThread = std::thread(&PLCObject::TraceThreadProc, this); edouard@3945: } edouard@3945: edouard@3949: m_PLCSyms.resumeDebug(); edouard@3945: *debugtoken = m_debugToken; edouard@3945: return 0; edouard@3945: } edouard@3945: return res; edouard@3937: } edouard@3937: edouard@3937: uint32_t PLCObject::StartPLC(void) edouard@3937: { edouard@3940: LogMessage(LOG_INFO, "Starting PLC"); edouard@3940: uint32_t res = m_PLCSyms.startPLC(m_argc, m_argv); edouard@3940: if(res != 0) edouard@3940: { edouard@3940: m_status.PLCstatus = Broken; edouard@3940: return res; edouard@3940: } edouard@3940: m_status.PLCstatus = Started; edouard@3945: edouard@3940: return 0; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::StopPLC(bool *success) edouard@3940: { edouard@3940: LogMessage(LOG_INFO, "Stopping PLC"); edouard@3940: uint32_t res = m_PLCSyms.stopPLC(); edouard@3945: if(res == 0) edouard@3945: { edouard@3945: m_status.PLCstatus = Stopped; edouard@3945: } else { edouard@3940: m_status.PLCstatus = Broken; edouard@3945: } edouard@3945: edouard@3945: // Stop debug thread edouard@3945: if(m_traceThread.joinable()) edouard@3945: { edouard@3945: m_traceThread.join(); edouard@3945: } edouard@3945: edouard@3945: return res; edouard@3940: } edouard@3940: edouard@3940: uint32_t PLCObject::LogMessage(uint8_t level, std::string message) edouard@3940: { edouard@3949: // if PLC isn't loaded, log to stdout edouard@3949: if(m_PLCSyms.LogMessage == NULL) edouard@3949: { edouard@3949: std::cout << level << message << std::endl; edouard@3949: return ENOSYS; edouard@3949: } edouard@3949: edouard@3940: // Log std::string message with given level edouard@3940: return m_PLCSyms.LogMessage(level, (char *)message.c_str(), message.size()); edouard@3940: } edouard@3945: edouard@3945: void PLCObject::TraceThreadProc(void) edouard@3945: { edouard@3945: uint32_t err = 0; edouard@3945: edouard@3949: m_PLCSyms.resumeDebug(); edouard@3945: edouard@3945: while(m_status.PLCstatus == Started) edouard@3945: { edouard@3945: unsigned int tick; edouard@3945: unsigned int size; edouard@3945: void * buff; edouard@3945: edouard@3945: // Data allocated here is meant to be freed by eRPC server code edouard@3945: uint8_t* ourData = NULL; edouard@3945: edouard@3945: m_PLClibMutex.lock(); edouard@3945: edouard@3945: int res = m_PLCSyms.GetDebugData(&tick, &size, &buff); edouard@3945: edouard@3945: if(res == 0) edouard@3945: { edouard@3945: ourData = (uint8_t *)malloc(size); edouard@3945: if(ourData != NULL) edouard@3945: { edouard@3945: memcpy(ourData, buff, size); edouard@3945: } edouard@3945: m_PLCSyms.FreeDebugData(); edouard@3945: } edouard@3945: edouard@3945: m_PLClibMutex.unlock(); edouard@3945: edouard@3945: if(ourData == NULL) edouard@3945: { edouard@3945: err = res == 0 ? ENOMEM : res; edouard@3945: break; edouard@3945: edouard@3945: } else { edouard@3945: edouard@3945: m_tracesMutex.lock(); edouard@3945: edouard@3945: m_traces.push_back(trace_sample{tick, binary_t{ourData, size}}); edouard@3945: edouard@3945: m_tracesMutex.unlock(); edouard@3945: } edouard@3945: } edouard@3945: edouard@3949: PurgeTraceBuffer(); edouard@3945: edouard@3945: LogMessage(err ? LOG_CRITICAL : LOG_INFO, edouard@3945: err == ENOMEM ? "Out of memory in TraceThreadProc" : edouard@3945: err ? "TraceThreadProc ended because of error" : edouard@3945: "TraceThreadProc ended normally"); edouard@3945: } edouard@3945: