I got a problem when I developed the WBDI and EngineAdapter with my fingerprint module on Win10 OS.
The problem is why the EngineAdapter doesn't go into below callback functions, after I finished Step 1 and Step 2 below?
Because the document of Microsoft mentioned that below functions will be called when enrollment workflow goes through.
(https://learn.microsoft.com/zh-tw/windows/win32/secbiomet/adapter-workflow)
Below are the callback functions not be called.
(1) "EngineAdapterCreateEnrollment"
(2) "EngineAdapterSetEnrollmentParameters"
Does anyone know how to solve it?
Many thanks.
Step1
Step2
Adapter Workflow
EngineAdapter log
DllMain, Wed Mar 17 14:22:37 2021
WbioQueryEngineInterface, Wed Mar 17 14:22:37 2021
EngineAdapterAttach, Wed Mar 17 14:22:37 2021
EngineAdapterSelectCalibrationFormat, Wed Mar 17 14:22:37 2021
EngineAdapterPipelineInit, Wed Mar 17 14:22:37 2021
DllMain, Wed Mar 17 14:22:42 2021
EngineAdapterActivate, Wed Mar 17 14:22:42 2021
EngineAdapterQueryExtendedInfo, Wed Mar 17 14:22:42 2021
EngineAdapterSetAccountPolicy, Wed Mar 17 14:22:42 2021
DllMain, Wed Mar 17 14:22:43 2021
EngineAdapterClearContext, Wed Mar 17 14:22:45 2021
EngineAdapterQueryPreferredFormat, Wed Mar 17 14:22:45 2021
EngineAdapterCreateKey, Wed Mar 17 14:22:46 2021
EngineAdapterCreateKey, Wed Mar 17 14:22:46 2021
EngineAdapterAcceptSampleData, Wed Mar 17 14:22:46 2021
EngineAdapterIdentifyFeatureSet, Wed Mar 17 14:22:46 2021
EngineAdapterClearContext, Wed Mar 17 14:22:46 2021
EngineAdapterClearContext, Wed Mar 17 14:22:46 2021
WBDI log
IOCTL_BIOMETRIC_GET_ATTRIBUTES, Wed Mar 17 14:22:37 2021
IOCTL_BIOMETRIC_GET_SENSOR_STATUS, Wed Mar 17 14:22:45 2021
IOCTL_BIOMETRIC_GET_ATTRIBUTES, Wed Mar 17 14:22:45 2021
IOCTL_BIOMETRIC_CAPTURE_DATA, Wed Mar 17 14:22:45 2021
IOCTL_BIOMETRIC_CAPTURE_DATA, Wed Mar 17 14:22:45 2021
IOCTL_BIOMETRIC_GET_SENSOR_STATUS, Wed Mar 17 14:22:46 2021
EngineAdapter code (EngineAdapter.cpp)
static WINBIO_ENGINE_INTERFACE g_EngineInterface = {
#if (NTDDI_VERSION >= NTDDI_WIN10_RS4)
WINBIO_ENGINE_INTERFACE_VERSION_6,
#elif(NTDDI_VERSION >= NTDDI_WIN10_RS3)
WINBIO_ENGINE_INTERFACE_VERSION_5,
#elif(NTDDI_VERSION >= NTDDI_WIN10_RS1)
WINBIO_ENGINE_INTERFACE_VERSION_4,
#elif(NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
WINBIO_ENGINE_INTERFACE_VERSION_3,
#elif(NTDDI_VERSION >= NTDDI_WIN8)
WINBIO_ENGINE_INTERFACE_VERSION_2,
#elif
WINBIO_ENGINE_INTERFACE_VERSION_1,
#endif
WINBIO_ADAPTER_TYPE_ENGINE,
sizeof(WINBIO_ENGINE_INTERFACE),
{0xb876fdc8, 0x34e7, 0x471a, {0x82, 0xc8, 0x9c, 0xba, 0x6a, 0x35, 0x38, 0xec}},
EngineAdapterAttach,
EngineAdapterDetach,
EngineAdapterClearContext,
EngineAdapterQueryPreferredFormat,
EngineAdapterQueryIndexVectorSize,
EngineAdapterQueryHashAlgorithms,
EngineAdapterSetHashAlgorithm,
EngineAdapterAcceptSampleHint,
EngineAdapterAcceptSampleData,
EngineAdapterExportEngineData,
EngineAdapterVerifyFeatureSet,
EngineAdapterIdentifyFeatureSet,
EngineAdapterCreateEnrollment,
EngineAdapterUpdateEnrollment,
EngineAdapterGetEnrollmentStatus,
EngineAdapterGetEnrollmentHash,
EngineAdapterCheckForDuplicate,
EngineAdapterCommitEnrollment,
EngineAdapterDiscardEnrollment,
EngineAdapterControlUnit,
EngineAdapterControlUnitPrivileged,
#if(NTDDI_VERSION >= NTDDI_WIN8)
EngineAdapterNotifyPowerChange,
EngineAdapter_RESERVED,
//EngineAdapterReserved_1;
#endif//(NTDDI_VERSION >= NTDDI_WIN8)
#if(NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
//
// V3.0 methods begin here...
//
EngineAdapterPipelineInit,
EngineAdapterPipelineCleanup,
EngineAdapterActivate,
EngineAdapterDeactivate,
EngineAdapterQueryExtendedInfo,
EngineAdapterIdentifyAll,
EngineAdapterSetEnrollmentSelector,
EngineAdapterSetEnrollmentParameters,
EngineAdapterQueryExtendedEnrollmentStatus,
EngineAdapterRefreshCache,
EngineAdapterSelectCalibrationFormat,
EngineAdapterQueryCalibrationData,
EngineAdapterSetAccountPolicy,
#endif //(NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
#if(NTDDI_VERSION >= NTDDI_WIN10_RS1)
//
// V4.0 methods begin here...
//
EngineAdapterCreateKey,
EngineAdapterIdentifyFeatureSetSecure,
#endif//(NTDDI_VERSION >= NTDDI_WIN10_RS1)
#if(NTDDI_VERSION >= NTDDI_WIN10_RS3)
//
// V5.0 methods begin here...
//
EngineAdapterAcceptPrivateSensorTypeInfo,
#endif(NTDDI_VERSION >= NTDDI_WIN10_RS3)
#if(NTDDI_VERSION >= NTDDI_WIN10_RS4)
//
// V6.0 methods begin here...
//
EngineAdapterCreateEnrollmentAuthenticated,
EngineAdapterIdentifyFeatureSetAuthenticated
#endif//(NTDDI_VERSION >= NTDDI_WIN10_RS4)
};
WBDI code
switch (ControlCode) {
//
// Mandatory IOCTLs
//
case IOCTL_BIOMETRIC_GET_ATTRIBUTES:
m_BiometricDevice->OnGetAttributes(FxRequest);
break;
case IOCTL_BIOMETRIC_RESET:
m_BiometricDevice->OnReset(FxRequest);
break;
case IOCTL_BIOMETRIC_CALIBRATE:
m_BiometricDevice->OnCalibrate(FxRequest);
break;
case IOCTL_BIOMETRIC_GET_SENSOR_STATUS:
m_BiometricDevice->OnGetSensorStatus(FxRequest);
break;
case IOCTL_BIOMETRIC_CAPTURE_DATA:
m_BiometricDevice->OnCaptureData(FxRequest);
break;
}
void
CBiometricDevice::OnGetAttributes(
_Inout_ IWDFIoRequest *FxRequest
)
{
CRequestHelper MyRequest(FxRequest); // RAII helper class
ULONG controlCode = 0;
PUCHAR inputBuffer= NULL;
SIZE_T inputBufferSize = 0;
PWINBIO_SENSOR_ATTRIBUTES sensorAttributes = NULL;
SIZE_T outputBufferSize;
//
// Get the request parameters
//
GetIoRequestParams(FxRequest,
&controlCode,
&inputBuffer,
&inputBufferSize,
(PUCHAR *)&sensorAttributes,
&outputBufferSize);
//
// Make sure we have an output buffer big enough
//
if (sensorAttributes == NULL || outputBufferSize < sizeof(DWORD))
{
// We cannot return size information.
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Output buffer NULL or too small to return size information.");
MyRequest.SetCompletionHr(E_INVALIDARG);
return;
}
// We only have one supported format, so sizeof (WINBIO_SENSOR_ATTRIBUTES) is sufficient.
if (outputBufferSize < sizeof(WINBIO_SENSOR_ATTRIBUTES))
{
// Buffer too small.
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Buffer too small - return size necessary in PayloadSize - 0x%x.", sizeof(WINBIO_SENSOR_ATTRIBUTES));
sensorAttributes->PayloadSize = (DWORD) sizeof(WINBIO_SENSOR_ATTRIBUTES);
MyRequest.SetInformation(sizeof(DWORD));
MyRequest.SetCompletionHr(S_OK);
return;
}
//
// Fill in the attribute payload structure
//
RtlZeroMemory(sensorAttributes, outputBufferSize);
sensorAttributes->PayloadSize = (DWORD) sizeof(WINBIO_SENSOR_ATTRIBUTES);
sensorAttributes->WinBioHresult = S_OK;
sensorAttributes->WinBioVersion.MajorVersion = WINBIO_WBDI_MAJOR_VERSION;
sensorAttributes->WinBioVersion.MinorVersion = WINBIO_WBDI_MINOR_VERSION;
sensorAttributes->SensorType = WINBIO_TYPE_FINGERPRINT;
sensorAttributes->SensorSubType = WINBIO_FP_SENSOR_SUBTYPE_TOUCH;// WINBIO_FP_SENSOR_SUBTYPE_SWIPE;//WINBIO_FP_SENSOR_SUBTYPE_TOUCH;
sensorAttributes->Capabilities = WINBIO_CAPABILITY_SENSOR | WINBIO_CAPABILITY_MATCHING | WINBIO_CAPABILITY_DATABASE;// WINBIO_CAPABILITY_SENSOR | WINBIO_CAPABILITY_MATCHING | WINBIO_CAPABILITY_DATABASE | WINBIO_CAPABILITY_PROCESSING;// WINBIO_CAPABILITY_SENSOR;
sensorAttributes->SupportedFormatEntries = 1;
sensorAttributes->SupportedFormat[0].Owner = WINBIO_ANSI_381_FORMAT_OWNER;
sensorAttributes->SupportedFormat[0].Type= WINBIO_ANSI_381_FORMAT_TYPE;
RtlCopyMemory(sensorAttributes->ManufacturerName, SAMPLE_MANUFACTURER_NAME, (wcslen(SAMPLE_MANUFACTURER_NAME)+1)*sizeof(WCHAR));
RtlCopyMemory(sensorAttributes->ModelName, SAMPLE_MODEL_NAME, (wcslen(SAMPLE_MODEL_NAME)+1)*sizeof(WCHAR));
RtlCopyMemory(sensorAttributes->SerialNumber, SAMPLE_SERIAL_NUMBER, (wcslen(SAMPLE_SERIAL_NUMBER)+1)*sizeof(WCHAR));
sensorAttributes->FirmwareVersion.MajorVersion = 1;
sensorAttributes->FirmwareVersion.MinorVersion = 0;
MyRequest.SetInformation(sensorAttributes->PayloadSize);
MyRequest.SetCompletionHr(S_OK);
}
void
CBiometricDevice::OnGetSensorStatus(
_Inout_ IWDFIoRequest *FxRequest
)
{
CRequestHelper MyRequest(FxRequest); // RAII helper class
ULONG controlCode = 0;
PUCHAR inputBuffer= NULL;
SIZE_T inputBufferSize = 0;
PWINBIO_DIAGNOSTICS diagnostics = NULL;
SIZE_T outputBufferSize;
//
// Get the request parameters
//
GetIoRequestParams(FxRequest,
&controlCode,
&inputBuffer,
&inputBufferSize,
(PUCHAR *)&diagnostics,
&outputBufferSize);
//
// Make sure we have an output buffer big enough
//
if (diagnostics == NULL || outputBufferSize < sizeof(DWORD))
{
// We cannot return size information.
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Output buffer NULL or too small to return size information.");
MyRequest.SetCompletionHr(E_INVALIDARG);
return;
}
if (outputBufferSize < sizeof(WINBIO_DIAGNOSTICS))
{
// Buffer too small.
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Buffer too small - return size necessary in PayloadSize - 0x%x.", sizeof(WINBIO_DIAGNOSTICS));
diagnostics->PayloadSize = (DWORD)sizeof(WINBIO_DIAGNOSTICS);
MyRequest.SetInformation(sizeof(DWORD));
MyRequest.SetCompletionHr(S_OK);
return;
}
//
// Fill in the OUT payload structure
//
RtlZeroMemory(diagnostics, outputBufferSize);
diagnostics->PayloadSize = (DWORD) sizeof(WINBIO_DIAGNOSTICS);
diagnostics->WinBioHresult = S_OK;
//diagnostics->SensorStatus = WINBIO_SENSOR_READY;
if (SensorStatusFlag)
{
SensorStatusFlag = 0;
diagnostics->SensorStatus = WINBIO_SENSOR_ACCEPT;
}
else
{
diagnostics->SensorStatus = WINBIO_SENSOR_READY;
}
MyRequest.SetInformation(diagnostics->PayloadSize);
MyRequest.SetCompletionHr(S_OK);
}
void
CBiometricDevice::OnCaptureData(
_Inout_ IWDFIoRequest *FxRequest
)
{
ULONG controlCode = 0;
PWINBIO_CAPTURE_PARAMETERS captureParams = NULL;
SIZE_T inputBufferSize = 0;
PWINBIO_CAPTURE_DATA captureData = NULL;
SIZE_T outputBufferSize = 0;
//
// We can only have one outstanding data capture request at a time.
// Check to see if we have a request pending.
//
bool requestPending = false;
EnterCriticalSection(&m_RequestLock);
if (m_PendingRequest == NULL)
{
//
// See if we have an active sleep thread.
// If so, tell it to exit.
// Wait for it to exit.
//
if (m_SleepThread != INVALID_HANDLE_VALUE)
{
LeaveCriticalSection(&m_RequestLock);
// TODO: Add code to signal thread to exit.
// NOTE: Sleeping for INFINITE time is dangerous. A real driver
// should be able to handle the case where the thread does
// not exit.
WaitForSingleObject(m_SleepThread, INFINITE);
CloseHandle(m_SleepThread);
m_SleepThread = INVALID_HANDLE_VALUE;
EnterCriticalSection(&m_RequestLock);
}
//
// We might have had to leave the CS to wait for the sleep thread.
// Double check that the pending request is still NULL.
//
if (m_PendingRequest == NULL)
{
// Save the request.
m_PendingRequest = FxRequest;
// Mark the request as cancellable.
m_PendingRequest->MarkCancelable(this);
}
else
{
requestPending = true;
}
}
else
{
requestPending = true;
}
LeaveCriticalSection(&m_RequestLock);
if (requestPending)
{
// Complete the request to tell the app that there is already
// a pending data collection request.
FxRequest->Complete(WINBIO_E_DATA_COLLECTION_IN_PROGRESS);
return;
}
//
// Get the request parameters
//
GetIoRequestParams(FxRequest,
&controlCode,
(PUCHAR *)&captureParams,
&inputBufferSize,
(PUCHAR *)&captureData,
&outputBufferSize);
//
// Check input parameters.
//
if (inputBufferSize < sizeof (WINBIO_CAPTURE_PARAMETERS))
{
// Invalid arguments
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Invalid argument(s).");
CompletePendingRequest(E_INVALIDARG, 0);
return;
}
//
// Make sure we have an output buffer big enough
//
if (outputBufferSize < sizeof(DWORD))
{
// We cannot return size information.
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Output buffer NULL or too small to return size information.");
CompletePendingRequest(E_INVALIDARG, 0);
return;
}
//
// Check output buffer size.
//
if (outputBufferSize < sizeof (WINBIO_CAPTURE_DATA))
{
// Buffer too small.
TraceEvents(TRACE_LEVEL_ERROR,
BIOMETRIC_TRACE_DEVICE,
"%!FUNC!Buffer too small - must be at least 0x%x.", sizeof (WINBIO_CAPTURE_DATA));
//
// NOTE: The output buffer size necessary for this sample is sizeof(WINBIO_CAPTURE_DATA).
// Real devices will need additional space to handle a typical capture.
// The value that should be returned here is sizeof(WINBIO_CAPTURE_DATA) + CaptureBufferSize.
//
captureData->PayloadSize = (DWORD)sizeof(WINBIO_CAPTURE_DATA)+12;
CompletePendingRequest(S_OK, sizeof(DWORD));
return;
}
//
// NOTE: This call always fails in this sample since it is not
// written for a real device.
//
//
// Set default values in output buffer.
//
SensorStatusFlag = 1;
RtlZeroMemory(captureData, outputBufferSize);
captureData->PayloadSize = (DWORD)outputBufferSize;
captureData->WinBioHresult = S_OK;
captureData->SensorStatus = WINBIO_SENSOR_ACCEPT;
captureData->RejectDetail = 0;
captureData->CaptureData.Size = (DWORD)12;
UCHAR szBuffer[] = { 0x01, 0x02, 0x03, 0x04 ,0x05, 0x06, 0x07, 0x08, 0x01, 0x02, 0x03, 0x04 };
RtlCopyMemory(captureData->CaptureData.Data, szBuffer,12);
//
// Check purpose, format and type.
//
if (captureParams->Purpose == WINBIO_NO_PURPOSE_AVAILABLE)
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_PURPOSE;
}
else if ((captureParams->Format.Type != WINBIO_ANSI_381_FORMAT_TYPE) ||
(captureParams->Format.Owner != WINBIO_ANSI_381_FORMAT_OWNER))
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_DATA_FORMAT;
}
else if (captureParams->Flags != WINBIO_DATA_FLAG_RAW)
{
captureData->WinBioHresult = WINBIO_E_UNSUPPORTED_DATA_TYPE;
}
//
// Create thread to sleep 1 seconds before completing the request.
//
m_SleepParams.SleepValue = 1;
m_SleepParams.Hr = S_OK;
m_SleepParams.Information = captureData->PayloadSize;
m_SleepThread = CreateThread(NULL, // default security attributes
0, // use default stack size
CaptureSleepThread, // thread function name
this, // argument to thread function
0, // use default creation flags
NULL); // returns the thread identifier
}
DWORD WINAPI
CaptureSleepThread(
LPVOID lpParam
)
{
CBiometricDevice *device = (CBiometricDevice *) lpParam;
PCAPTURE_SLEEP_PARAMS sleepParams = device->GetCaptureSleepParams();
//
// Make sure it is less than or equal to 1 minute.
//
if (sleepParams->SleepValue > 60)
{
sleepParams->SleepValue = 60;
}
Sleep(sleepParams->SleepValue * 1000);
device->CompletePendingRequest(sleepParams->Hr, sleepParams->Information);
return 0;
}
Related
I am trying to send a signal from the kernel space to the user space.
I have the below code and I am seeing a kernel panic.
[ 5230.132362] Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: prog_irq_handler+0x1d4/0x2cc [prog_mon]
[ 5230.146795] ---[ end Kernel panic - not syncing: stack-protector: Kernel stack is corrupted in: prog_irq_handler+0x1d4/0x2cc
Upon debugging some more, I found that the source of the panic is from the below function:
t = pid_task(find_pid_ns(id, &init_pid_ns), PIDTYPE_PID);
Referencing the value of "t" seems to cause an exception, resulting in a kernel panic.
Is there any known issue with the kernel 5.4 wrt the pid_task().
Any help will be appreciated.
My kernel comes from yocto, and it is:
branch ti-linux-5.4.y
commit 6f3bf13d53820fc12432d7052744be2ee046fc92 (HEAD -> ti-linux-5.4.y)
Merge: d2f658ed506d d5ef1ab82339
Author: LCPD Auto Merger lcpd_integration#list.ti.com
Date: Fri Apr 3 10:50:48 2020 -0500
https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/
Full code below:
send_signal(int val, int id, int sig)
{
struct kernel_siginfo info;
struct task_struct *t;
int ret;
ret = 0;
if ((id > 0) && (sig > 0)) {
memset(&info, 0, sizeof(struct siginfo));
info.si_signo = sig;
/* Using SI_KERNEL here results in real_time data not getting delivered to the user space signal handler */
info.si_code = SI_QUEUE;
/* Real time signals may have 32 bits of data */
info.si_int = val;
info._sifields._rt._sigval.sival_int = val;
info.si_errno = 0;
rcu_read_lock();
t = pid_task(find_pid_ns(id, &init_pid_ns), PIDTYPE_PID);
if(t == NULL) {
printk(KERN_ERR "%s: Invalid user handler PID %d\n", module_name, id);
rcu_read_unlock();
return -ENODEV;
}
ret = send_sig_info(sig, &info, t);
rcu_read_unlock();
if (ret < 0)
printk(KERN_INFO "%s: Failed to signal with data %d to user space\n", module_name, val);
}
return ret
}
Description:
I'm following the tutorial on hypervisor development. After day 4 of the series I cannot make my code run. Right after __vmx_vmlaunch instruction gets executed successfully, the virtual machine I'm testing the hypervisor on, reboots. I believe this is caused by some incorrect settings of VMCS Host State Area (Chapter 24.5). There is no BSOD, crash nor error message in WinDbg.
I see two ways to approach this problem. One is to somehow extract more information from WinDbg. And the second one would require someone to spot what stupid thing I'm doing or missing in the vmcs initialization.
Unfortunately the code in tutorial is incomplete. I've spend some fair time to make sure that everything that tutorial covers is the same in my code. I will highlight parts which where added by me.
I'm really sorry for such an amount of code. Again let me emphasise that I believe the problem lies within the HOST fields (as the crash inside guest vm shouldn't crash the host).
int init_vmcs(struct __vcpu_t* vcpu)
{
log_entry("init_vmcs()\n");
// Determinate exact size which is implementation specific.
// We expect it to be 4KB, but it is not guaranteed.
union __vmx_basic_msr_t vmx_basic_msr = { 0 };
vmx_basic_msr.control = __readmsr(IA32_VMX_BASIC);
if (vmx_basic_msr.bits.vmxon_region_size != sizeof(struct __vmcs_t)) {
log_error("Non standard vmcs region size: %llx. Support not yet implemented.\n",
vmx_basic_msr.bits.vmxon_region_size);
log_exit("init_vmcs()\n");
return -1;
}
PHYSICAL_ADDRESS physical_max;
physical_max.QuadPart = MAXULONG64;
vcpu->vmcs = MmAllocateContiguousMemory(sizeof(struct __vmcs_t), physical_max);
if (!vcpu->vmcs) {
log_error("Failed to allocate vcpu->vmcs(MmAllocateContiguousMemory failed).\n");
log_exit("init_vmcs()\n");
return -1;
}
RtlSecureZeroMemory(vcpu->vmcs, sizeof(struct __vmcs_t));
vcpu->vmcs_physical = MmGetPhysicalAddress(vcpu->vmcs).QuadPart;
// Discover VMCS revision identifier that a processor uses by reading the
// VMX capability MSR IA32_VMX_BASIC.
vcpu->vmcs->header.bits.revision_identifier = (unsigned int)vmx_basic_msr.bits.vmcs_revision_identifier;
vcpu->vmcs->header.bits.shadow_vmcs_indicator = 0;
// Before loading vmcs we invoke vmclear to flush data which might be cached by processor.
if (__vmx_vmclear(&vcpu->vmcs_physical) || __vmx_vmptrld(&vcpu->vmcs_physical)) {
log_error("Failed to flush data or load vmcs. (__vmx_vmclear or __vmx_vmptrld failed).\n");
log_exit("init_vmcs()\n");
return -1;
}
// Initialize VMCS Guest State Area.
if (__vmx_vmwrite(GUEST_CR0, __readcr0()) ||
__vmx_vmwrite(GUEST_CR3, __readcr3()) ||
__vmx_vmwrite(GUEST_CR4, __readcr4()) ||
__vmx_vmwrite(GUEST_DR7, __readdr(7)) ||
__vmx_vmwrite(GUEST_RSP, vcpu->guest_rsp) ||
__vmx_vmwrite(GUEST_RIP, vcpu->guest_rip) ||
__vmx_vmwrite(GUEST_RFLAGS, __readeflags()) ||
__vmx_vmwrite(GUEST_DEBUG_CONTROL, __readmsr(IA32_DEBUGCTL)) ||
__vmx_vmwrite(GUEST_SYSENTER_ESP, __readmsr(IA32_SYSENTER_ESP)) ||
__vmx_vmwrite(GUEST_SYSENTER_EIP, __readmsr(IA32_SYSENTER_EIP)) ||
__vmx_vmwrite(GUEST_SYSENTER_CS, __readmsr(IA32_SYSENTER_CS)) ||
__vmx_vmwrite(GUEST_VMCS_LINK_POINTER, ~0ULL) ||
__vmx_vmwrite(GUEST_FS_BASE, __readmsr(IA32_FS_BASE)) ||
__vmx_vmwrite(GUEST_GS_BASE, __readmsr(IA32_GS_BASE))
) {
log_error("Failed to set guest state. (__vmx_vmwrite failed).\n");
log_exit("init_vmcs()\n");
return -1;
}
if (__vmx_vmwrite(CR0_READ_SHADOW, __readcr0()) ||
__vmx_vmwrite(CR4_READ_SHADOW, __readcr4())
) {
log_error("Failed to set cr0_read_shadow or cr4_read_shadow. (__vmx_vmwrite failed).\n");
log_exit("init_vmcs()\n");
return -1;
}
union __vmx_entry_control_t entry_controls = { 0 };
entry_controls.bits.ia32e_mode_guest = 1;
vmx_adjust_entry_controls(&entry_controls);
__vmx_vmwrite(VM_ENTRY_CONTROLS, entry_controls.control);
union __vmx_exit_control_t exit_controls = { 0 };
exit_controls.bits.host_address_space_size = 1;
vmx_adjust_exit_controls(&exit_controls);
__vmx_vmwrite(VM_EXIT_CONTROLS, exit_controls.control);
union __vmx_pinbased_control_msr_t pinbased_controls = { 0 };
vmx_adjust_pinbased_controls(&pinbased_controls);
__vmx_vmwrite(PIN_BASED_VM_EXECUTION_CONTROLS, pinbased_controls.control);
union __vmx_primary_processor_based_control_t primary_controls = { 0 };
primary_controls.bits.use_msr_bitmaps = 1;
primary_controls.bits.active_secondary_controls = 1;
vmx_adjust_primary_processor_based_controls(&primary_controls);
__vmx_vmwrite(PRIMARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, primary_controls.control);
union __vmx_secondary_processor_based_control_t secondary_controls = { 0 };
secondary_controls.bits.enable_rdtscp = 1;
secondary_controls.bits.enable_xsave_xrstor = 1;
secondary_controls.bits.enable_invpcid = 1;
vmx_adjust_secondary_processor_based_controls(&secondary_controls);
__vmx_vmwrite(SECONDARY_PROCESSOR_BASED_VM_EXECUTION_CONTROLS, secondary_controls.control);
__vmx_vmwrite(GUEST_CS_SELECTOR, __read_cs());
__vmx_vmwrite(GUEST_SS_SELECTOR, __read_ss());
__vmx_vmwrite(GUEST_DS_SELECTOR, __read_ds());
__vmx_vmwrite(GUEST_ES_SELECTOR, __read_es());
__vmx_vmwrite(GUEST_FS_SELECTOR, __read_fs());
__vmx_vmwrite(GUEST_GS_SELECTOR, __read_gs());
__vmx_vmwrite(GUEST_LDTR_SELECTOR, __read_ldtr());
__vmx_vmwrite(GUEST_TR_SELECTOR, __read_tr());
__vmx_vmwrite(GUEST_CS_LIMIT, __segmentlimit(__read_cs()));
__vmx_vmwrite(GUEST_SS_LIMIT, __segmentlimit(__read_ss()));
__vmx_vmwrite(GUEST_DS_LIMIT, __segmentlimit(__read_ds()));
__vmx_vmwrite(GUEST_ES_LIMIT, __segmentlimit(__read_es()));
__vmx_vmwrite(GUEST_FS_LIMIT, __segmentlimit(__read_fs()));
__vmx_vmwrite(GUEST_GS_LIMIT, __segmentlimit(__read_gs()));
__vmx_vmwrite(GUEST_LDTR_LIMIT, __segmentlimit(__read_ldtr()));
__vmx_vmwrite(GUEST_TR_LIMIT, __segmentlimit(__read_tr()));
struct __pseudo_descriptor_64_t gdtr;
struct __pseudo_descriptor_64_t idtr;
_sgdt(&gdtr);
__sidt(&idtr);
__vmx_vmwrite(GUEST_GDTR_BASE, gdtr.base_address);
__vmx_vmwrite(GUEST_GDTR_LIMIT, gdtr.limit);
__vmx_vmwrite(GUEST_IDTR_BASE, idtr.base_address);
__vmx_vmwrite(GUEST_IDTR_LIMIT, idtr.limit);
__vmx_vmwrite(GUEST_CS_BASE, get_segment_base(gdtr.base_address, __read_cs()));
__vmx_vmwrite(GUEST_DS_BASE, get_segment_base(gdtr.base_address, __read_ds()));
__vmx_vmwrite(GUEST_SS_BASE, get_segment_base(gdtr.base_address, __read_ss()));
__vmx_vmwrite(GUEST_ES_BASE, get_segment_base(gdtr.base_address, __read_es()));
__vmx_vmwrite(GUEST_CS_ACCESS_RIGHTS, read_segment_access_rights(__read_cs()));
__vmx_vmwrite(GUEST_SS_ACCESS_RIGHTS, read_segment_access_rights(__read_ss()));
__vmx_vmwrite(GUEST_DS_ACCESS_RIGHTS, read_segment_access_rights(__read_ds()));
__vmx_vmwrite(GUEST_ES_ACCESS_RIGHTS, read_segment_access_rights(__read_es()));
__vmx_vmwrite(GUEST_FS_ACCESS_RIGHTS, read_segment_access_rights(__read_fs()));
__vmx_vmwrite(GUEST_GS_ACCESS_RIGHTS, read_segment_access_rights(__read_gs()));
__vmx_vmwrite(GUEST_LDTR_ACCESS_RIGHTS, read_segment_access_rights(__read_ldtr()));
__vmx_vmwrite(GUEST_TR_ACCESS_RIGHTS, read_segment_access_rights(__read_tr()));
__vmx_vmwrite(GUEST_LDTR_BASE, get_segment_base(gdtr.base_address, __read_ldtr()));
__vmx_vmwrite(GUEST_TR_BASE, get_segment_base(gdtr.base_address, __read_tr()));
// Initialize VMCS Host State Area.
__vmx_vmwrite(HOST_CR0, __readcr0()); // Added by me
__vmx_vmwrite(HOST_CR3, __readcr3()); // Added by me
__vmx_vmwrite(HOST_CR4, __readcr4()); // Added by me
// Fields RPL and TI in host selector fields must be cleared.
unsigned short host_selector_mask = 7;
__vmx_vmwrite(HOST_CS_SELECTOR, __read_cs() & ~host_selector_mask);
__vmx_vmwrite(HOST_SS_SELECTOR, __read_ss() & ~host_selector_mask);
__vmx_vmwrite(HOST_DS_SELECTOR, __read_ds() & ~host_selector_mask);
__vmx_vmwrite(HOST_ES_SELECTOR, __read_es() & ~host_selector_mask);
__vmx_vmwrite(HOST_FS_SELECTOR, __read_fs() & ~host_selector_mask);
__vmx_vmwrite(HOST_GS_SELECTOR, __read_gs() & ~host_selector_mask);
__vmx_vmwrite(HOST_TR_SELECTOR, __read_tr() & ~host_selector_mask);
__vmx_vmwrite(HOST_TR_BASE, get_segment_base(gdtr.base_address, __read_tr()));
__vmx_vmwrite(HOST_GDTR_BASE, gdtr.base_address);
__vmx_vmwrite(HOST_IDTR_BASE, idtr.base_address);
unsigned __int64 vmm_stack = (unsigned __int64)vcpu->vmm_context->stack + VMM_STACK_SIZE;
if (__vmx_vmwrite(HOST_RSP, vmm_stack) ||
__vmx_vmwrite(HOST_RIP, vmm_entrypoint)
) {
log_error("Failed to set host_rsp, host_rip. (__vmx_vmwrite failed).\n");
log_exit("init_vmcs()\n");
return -1;
}
log_exit("init_vmcs()\n");
return 0;
}
void init_logical_processor(struct __vmm_context_t *vmm_context, void *guest_rsp)
{
log_entry("init_logical_processor()\n");
unsigned long cur_processor_number = KeGetCurrentProcessorNumber();
struct __vcpu_t* vcpu = vmm_context->vcpu_table[cur_processor_number];
log_debug("vcpu: %llx, guest_rsp: %llx\n", cur_processor_number, guest_rsp);
vcpu->guest_rsp = guest_rsp;
vcpu->guest_rip = (void*) guest_entry_stub;
adjust_control_registers();
if (enable_vmx_operation() != 0) {
log_error("Failed to enable_vmx_operation.\n");
goto _end;
}
if (!vm_has_cpuid_support()) {
log_error("VMX operation is not supported by the processor.\n");
goto _end;
}
log_success("VMX operation is supported by the processor.\n");
if (init_vmxon(vcpu)) {
log_error("Failed to initialize vmxon region.\n");
goto _end;
}
log_success("Initialized vmxon region.\n");
unsigned char vmxon_res = __vmx_on(&vcpu->vmxon_physical);
if (vmxon_res != 0) {
log_error("Failed to put vcpu into VMX operation. Error code: %d\n", vmxon_res);
goto _end;
}
log_success("vmx_on succeeded.\n");
if (init_vmcs(vcpu)) {
log_error("Failed to initialize vmcs.\n");
goto _end;
}
log_success("Initialized vmcs.\n");
unsigned char vmlaunch_res = vmxlaunch(); // just a wrapper over __vmx_vmlaunch
if (vmlaunch_res != 0) {
goto _end;
}
_end:
log_exit("init_logical_processor()\n");
}
vmm_entrypoint proc
int 3 ; addded by me
vmm_entrypoint endp
guest_entry_stub proc
mov rax, 1337h
hlt
guest_entry_stub endp
Update
I've read again the Intel manual section regarding VM-entry checks and found out that my init_vmcs function wasn't setting HOST_FS_BASE, HOST_GS_BASE. After adding these fields it finally worked and trapped inside vmm_entrypoint.
However I would love to hear some solution on how to debug the unexpected shutdowns.
In a bash script I am using a many-producer single-consumer pattern. Producers are background processes writing lines into a fifo (via GNU Parallel). The consumer reads all lines from the fifo, then sorts, filters, and prints the formatted result to stdout.
However, it could take a long time until the full result is available. Producers are usually fast on the first few results but then would slow down. Here I am more interested to see chunks of data every few seconds, each sorted and filtered individually.
mkfifo fifo
parallel ... >"$fifo" &
while chunk=$(read with timeout 5s and at most 10s <"$fifo"); do
process "$chunk"
done
The loop would run until all producers are done and all input is read. Each chunk is read until there has been no new data for 5s, or until 10s have passed since the chunk was started. A chunk may also be empty if there was no new data for 10s.
I tried to make it work like this:
output=$(mktemp)
while true; do
wasTimeout=0 interruptAt=$(( $(date '+%s') + 10 ))
while true; do
IFS= read -r -t5 <>"${fifo}"
rc="$?"
if [[ "${rc}" -gt 0 ]]; then
[[ "${rc}" -gt 128 ]] && wasTimeout=1
break
fi
echo "$REPLY" >>"${output}"
if [[ $(date '+%s') -ge "${interruptAt}" ]]; then
wasTimeout=1
break
fi
done
echo '---' >>"${output}"
[[ "${wasTimeout}" -eq 0 ]] && break
done
Tried some variations of this. In the form above it reads the first chunk but then loops forever. If I use <"${fifo}" (no read/write as above) it blocks after the first chunk. Maybe all of this could be simplified with buffer and/or stdbuf? But both of them define blocks by size, not by time.
This is not a trivial problem to resolve. As I hinted, a C program (or a program in some programming language other than the shell) is probably the best solution. Some of the complicating factors are:
Reading with timeouts.
If data arrives soon enough, the timeout changes.
Different systems have different sets of interval timing functions:
alarm() is likely available everywhere, but has only 1-second resolution which is liable to accumulated rounding errors. (Compile this version with make UFLAGS=-DUSE_ALARM; on macOS, use make UFLAGS=-DUSE_ALARM LDLIB2=.)
setitimer()
uses microsecond timing and the struct timeval type. (Compile this version with make UFLAGS=-DUSE_SETITIMER; on macOS, compile with make UFLAGS=-DUSE_SETITIMER LDLIB2=.)
timer_create() and
timer_settime() etc use the modern nanosecond type struct timespec. This is available on Linux; it is not available on macOS 10.14.5 Mojave or earlier. (Compile this version with make; it won't work on macOS.)
The program usage message is:
$ chunker79 -h
Usage: chunker79 [-hvV][-c chunk][-d delay][-f file]
-c chunk Maximum time to wait for data in a chunk (default 10)
-d delay Maximum delay after line read (default: 5)
-f file Read from file instead of standard input
-h Print this help message and exit
-v Verbose mode: print timing information to stderr
-V Print version information and exit
$
This code is available in my SOQ (Stack Overflow Questions) repository on GitHub as file chunker79.c in the src/so-5631-4784 sub-directory. You will need some of the support code from the src/libsoq directory too.
/*
#(#)File: chunker79.c
#(#)Purpose: Chunk Reader for SO 5631-4784
#(#)Author: J Leffler
#(#)Copyright: (C) JLSS 2019
*/
/*TABSTOP=4*/
/*
** Problem specification from the Stack Overflow question
**
** In a bash script I am using a many-producer single-consumer pattern.
** Producers are background processes writing lines into a fifo (via GNU
** Parallel). The consumer reads all lines from the fifo, then sorts,
** filters, and prints the formatted result to stdout.
**
** However, it could take a long time until the full result is
** available. Producers are usually fast on the first few results but
** then would slow down. Here I am more interested to see chunks of
** data every few seconds, each sorted and filtered individually.
**
** mkfifo fifo
** parallel ... >"$fifo" &
** while chunk=$(read with timeout 5s and at most 10s <"$fifo"); do
** process "$chunk"
** done
**
** The loop would run until all producers are done and all input is
** read. Each chunk is read until there has been no new data for 5s, or
** until 10s have passed since the chunk was started. A chunk may also
** be empty if there was no new data for 10s.
*/
/*
** Analysis
**
** 1. If no data arrives at all for 10 seconds, then the program should
** terminate producing no output. This timeout is controlled by the
** value of time_chunk in the code.
** 2. If data arrives more or less consistently, then the collection
** should continue for 10s and then finish. This timeout is also
** controlled by the value of time_chunk in the code.
** 3. If a line of data arrives before 5 seconds have elapsed, and no
** more arrives for 5 seconds, then the collection should finish.
** (If the first line arrives after 5 seconds and no more arrives
** for more than 5 seconds, then the 10 second timeout cuts in.)
** This timeout is controlled by the value of time_delay in the code.
** 4. This means that we want two separate timers at work:
** - Chunk timer (started when the program starts).
** - Delay timer (started each time a line is read).
**
** It doesn't matter which timer goes off, but further timer signals
** should be ignored. External signals will confuse things; tough!
**
** -- Using alarm(2) is tricky because it provides only one time, not two.
** -- Using getitimer(2), setitimer(2) uses obsolescent POSIX functions,
** but these are available on macOS.
** -- Using timer_create(2), timer_destroy(2), timer_settime(2),
** timer_gettime(2) uses current POSIX function but is not available
** on macOS.
*/
#include "posixver.h"
#include "stderr.h"
#include "timespec_io.h"
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/uio.h>
#include <time.h>
#include <unistd.h>
#ifdef USE_SETITIMER
#include "timeval_math.h"
#include "timeval_io.h"
#include <sys/time.h>
#endif /* USE_SETITIMER */
static const char optstr[] = "hvVc:d:f:";
static const char usestr[] = "[-hvV][-c chunk][-d delay][-f file]";
static const char hlpstr[] =
" -c chunk Maximum time to wait for data in a chunk (default 10)\n"
" -d delay Maximum delay after line read (default: 5)\n"
" -f file Read from file instead of standard input\n"
" -h Print this help message and exit\n"
" -v Verbose mode: print timing information to stderr\n"
" -V Print version information and exit\n"
;
static struct timespec time_delay = { .tv_sec = 5, .tv_nsec = 0 };
static struct timespec time_chunk = { .tv_sec = 10, .tv_nsec = 0 };
static struct timespec time_start;
static bool verbose = false;
static void set_chunk_timeout(void);
static void set_delay_timeout(void);
static void cancel_timeout(void);
static void alarm_handler(int signum);
// Using signal() manages to set SA_RESTART on a Mac.
// This is allowed by standard C and POSIX, sadly.
// signal(SIGALRM, alarm_handler);
#if defined(USE_ALARM)
static void set_chunk_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
alarm(time_chunk.tv_sec);
struct sigaction sa;
sa.sa_handler = alarm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
static void set_delay_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
unsigned time_left = alarm(0);
if (time_left > time_delay.tv_sec)
alarm(time_delay.tv_sec);
else
alarm(time_left);
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
static void cancel_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
alarm(0);
signal(SIGALRM, SIG_IGN);
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
#elif defined(USE_SETITIMER)
static inline struct timeval cvt_timespec_to_timeval(struct timespec ts)
{
return (struct timeval){ .tv_sec = ts.tv_sec, .tv_usec = ts.tv_nsec / 1000 };
}
static void set_chunk_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
struct itimerval tv_new = { { 0, 0 }, { 0, 0 } };
tv_new.it_value = cvt_timespec_to_timeval(time_chunk);
struct itimerval tv_old;
if (setitimer(ITIMER_REAL, &tv_new, &tv_old) != 0)
err_syserr("failed to set interval timer: ");
struct sigaction sa;
sa.sa_handler = alarm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
static void set_delay_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
struct itimerval tv_until;
if (getitimer(ITIMER_REAL, &tv_until) != 0)
err_syserr("failed to set interval timer: ");
struct timeval tv_delay = cvt_timespec_to_timeval(time_delay);
if (verbose)
{
char buff1[32];
fmt_timeval(&tv_delay, 6, buff1, sizeof(buff1));
char buff2[32];
fmt_timeval(&tv_until.it_value, 6, buff2, sizeof(buff2));
err_remark("---- %s(): delay %s, left %s\n", __func__, buff1, buff2);
}
if (cmp_timeval(tv_until.it_value, tv_delay) <= 0)
{
if (verbose)
err_remark("---- %s(): no need for delay timer\n", __func__);
}
else
{
struct itimerval tv_new = { { 0, 0 }, { 0, 0 } };
tv_new.it_value = cvt_timespec_to_timeval(time_delay);
struct itimerval tv_old;
if (setitimer(ITIMER_REAL, &tv_new, &tv_old) != 0)
err_syserr("failed to set interval timer: ");
if (verbose)
err_remark("---- %s(): set delay timer\n", __func__);
}
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
static void cancel_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
struct itimerval tv_new =
{
.it_value = { .tv_sec = 0, .tv_usec = 0 },
.it_interval = { .tv_sec = 0, .tv_usec = 0 },
};
struct itimerval tv_old;
if (setitimer(ITIMER_REAL, &tv_new, &tv_old) != 0)
err_syserr("failed to set interval timer: ");
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
#else /* USE_TIMER_GETTIME */
#include "timespec_math.h"
static timer_t t0 = { 0 };
static void set_chunk_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
struct sigevent ev =
{
.sigev_notify = SIGEV_SIGNAL,
.sigev_signo = SIGALRM,
.sigev_value.sival_int = 0,
.sigev_notify_function = 0,
.sigev_notify_attributes = 0,
};
if (timer_create(CLOCK_REALTIME, &ev, &t0) < 0)
err_syserr("failed to create a timer: ");
struct itimerspec it =
{
.it_interval = { .tv_sec = 0, .tv_nsec = 0 },
.it_value = time_chunk,
};
struct itimerspec ot;
if (timer_settime(t0, 0, &it, &ot) != 0)
err_syserr("failed to activate timer: ");
struct sigaction sa;
sa.sa_handler = alarm_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(SIGALRM, &sa, NULL);
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
static void set_delay_timeout(void)
{
if (verbose)
err_remark("-->> %s()\n", __func__);
struct itimerspec time_until;
if (timer_gettime(t0, &time_until) != 0)
err_syserr("failed to set per-process timer: ");
char buff1[32];
fmt_timespec(&time_delay, 6, buff1, sizeof(buff1));
char buff2[32];
fmt_timespec(&time_until.it_value, 6, buff2, sizeof(buff2));
err_remark("---- %s(): delay %s, left %s\n", __func__, buff1, buff2);
if (cmp_timespec(time_until.it_value, time_delay) <= 0)
{
if (verbose)
err_remark("---- %s(): no need for delay timer\n", __func__);
}
else
{
struct itimerspec time_new =
{
.it_interval = { .tv_sec = 0, .tv_nsec = 0 },
.it_value = time_delay,
};
struct itimerspec time_old;
if (timer_settime(t0, 0, &time_new, &time_old) != 0)
err_syserr("failed to set per-process timer: ");
if (verbose)
err_remark("---- %s(): set delay timer\n", __func__);
}
if (verbose)
err_remark("<<-- %s()\n", __func__);
}
static void cancel_timeout(void)
{
if (timer_delete(t0) != 0)
err_syserr("failed to delete timer: ");
}
#endif /* Timing mode */
/* Writing to stderr via err_remark() is not officially supported */
static void alarm_handler(int signum)
{
assert(signum == SIGALRM);
if (verbose)
err_remark("---- %s(): signal %d\n", __func__, signum);
}
static void read_chunks(FILE *fp)
{
size_t num_data = 0;
size_t max_data = 0;
struct iovec *data = 0;
size_t buflen = 0;
char *buffer = 0;
ssize_t length;
size_t chunk_len = 0;
clock_gettime(CLOCK_REALTIME, &time_start);
set_chunk_timeout();
while ((length = getline(&buffer, &buflen, fp)) != -1)
{
if (num_data >= max_data)
{
size_t new_size = (num_data * 2) + 2;
void *newspace = realloc(data, new_size * sizeof(data[0]));
if (newspace == 0)
err_syserr("failed to allocate %zu bytes data: ", new_size * sizeof(data[0]));
data = newspace;
max_data = new_size;
}
data[num_data].iov_base = buffer;
data[num_data].iov_len = length;
num_data++;
if (verbose)
err_remark("Received line %zu\n", num_data);
chunk_len += length;
buffer = 0;
buflen = 0;
set_delay_timeout();
}
cancel_timeout();
if (chunk_len > 0)
{
if ((length = writev(STDOUT_FILENO, data, num_data)) < 0)
err_syserr("failed to write %zu bytes to standard output: ", chunk_len);
else if ((size_t)length != chunk_len)
err_error("failed to write %zu bytes to standard output "
"(short write of %zu bytes)\n", chunk_len, (size_t)length);
}
if (verbose)
err_remark("---- %s(): data written (%zu bytes)\n", __func__, length);
for (size_t i = 0; i < num_data; i++)
free(data[i].iov_base);
free(data);
free(buffer);
}
int main(int argc, char **argv)
{
const char *name = "(standard input)";
FILE *fp = stdin;
err_setarg0(argv[0]);
err_setlogopts(ERR_MICRO);
int opt;
while ((opt = getopt(argc, argv, optstr)) != -1)
{
switch (opt)
{
case 'c':
if (scn_timespec(optarg, &time_chunk) != 0)
err_error("Failed to convert '%s' into a time value\n", optarg);
break;
case 'd':
if (scn_timespec(optarg, &time_delay) != 0)
err_error("Failed to convert '%s' into a time value\n", optarg);
break;
case 'f':
if ((fp = fopen(optarg, "r")) == 0)
err_syserr("Failed to open file '%s' for reading: ", optarg);
name = optarg;
break;
case 'h':
err_help(usestr, hlpstr);
/*NOTREACHED*/
case 'v':
verbose = true;
break;
case 'V':
err_version("CHUNKER79", &"#(#)$Revision$ ($Date$)"[4]);
/*NOTREACHED*/
default:
err_usage(usestr);
/*NOTREACHED*/
}
}
if (optind != argc)
err_usage(usestr);
if (verbose)
{
err_remark("chunk: %3lld.%09ld\n", (long long)time_chunk.tv_sec, time_chunk.tv_nsec);
err_remark("delay: %3lld.%09ld\n", (long long)time_delay.tv_sec, time_delay.tv_nsec);
err_remark("file: %s\n", name);
}
read_chunks(fp);
return 0;
}
My SOQ repository also has a script gen-data.sh which makes use of some custom programs to generate a data stream such as this (the seed value is written to standard error, not standard output):
$ gen-data.sh
# Seed: 1313715286
2019-06-03 23:04:16.653: Zunmieoprri Rdviqymcho 5878 2017-03-29 03:59:15 Udransnadioiaeamprirteo
2019-06-03 23:04:18.525: Rndflseoevhgs Etlaevieripeoetrnwkn 9500 2015-12-18 10:49:15 Ebyrcoebeezatiagpleieoefyc
2019-06-03 23:04:20.526: Nrzsuiakrooab Nbvliinfqidbujoops 1974 2020-05-13 08:05:14 Lgithearril
2019-06-03 23:04:21.777: Eeagop Aieneose 6533 2016-11-06 22:51:58 Aoejlwebbssroncmeovtuuueigraa
2019-06-03 23:04:23.876: Izirdoeektau Atesltiybysaclee 4557 2020-09-13 02:24:46 Igrooiaauiwtna
2019-06-03 23:04:26.145: Yhioit Eamrexuabagsaraiw 9703 2014-09-13 07:44:12 Dyiiienglolqopnrbneerltnmsdn
^C
$
When fed into chunker79 with default options, I get output like:
$ gen-data.sh | chunker79
# Seed: 722907235
2019-06-03 23:06:20.570: Aluaezkgiebeewal Oyvahee 1022 2015-08-12 07:45:54 Weuababeeduklleym
2019-06-03 23:06:24.100: Gmujvoyevihvoilc Negeiiuvleem 8196 2015-08-29 21:15:15 Nztkrvsadeoeagjgoyotvertavedi
$
If you analyze the time intervals (look at the first two fields in the output lines), that output meets the specification. A still more detailed analysis is shown by:
$ timecmd -mr -- gen-data.sh | timecmd -mr -- chunker79
2019-06-03 23:09:14.246 [PID 57159] gen-data.sh
2019-06-03 23:09:14.246 [PID 57160] chunker79
# Seed: -1077610201
2019-06-03 23:09:14.269: Woreio Rdtpimvoscttbyhxim 7893 2017-03-12 12:46:57 Uywaietirkekes
2019-06-03 23:09:16.939: Uigaba Nzoxdeuisofai 3630 2017-11-16 09:28:59 Jnsncgoesycsevdscugoathusaoq
2019-06-03 23:09:17.845: Sscreua Aloaoonnsuur 5163 2016-08-13 19:47:15 Injhsiifqovbnyeooiimitaaoir
2019-06-03 23:09:19.272 [PID 57160; status 0x0000] - 5.026s - chunker79
2019-06-03 23:09:22.084 [PID 57159; status 0x8D00] - 7.838s - gen-data.sh
$
There is a noticeable pause in this setup between when the output from chunker79 appears and when gen-data.sh completes. That's due to Bash waiting on all processes in the pipeline to complete, and gen-data.sh doesn't complete until the next time it writes to the pipe after the message that finishes chunker79. This is an artefact of this test setup; it wouldn't be a factor in the shell script outlined in the question.
I would consider writing a safe multi-threaded program with queues.
I know Java better, but there might be more modern suitable languages like Go and Kotlin.
Something like this:
#!/usr/bin/perl
$timeout = 3;
while(<STDIN>) {
# Make sure there is some input
push #out,$_;
eval {
local $SIG{ALRM} = sub { die };
alarm $timeout;
while(<STDIN>) {
alarm $timeout;
push #out,$_;
}
alarm 0;
};
system "echo","process",#out;
}
GNU Parallel 20200122 introduced --blocktimeout (--bt):
find ~ | parallel -j3 --bt 2s --pipe wc
This works like normal GNU Parallel except if it takes > 2 seconds to fill a block. In that case the block read so far is simply passed to wc (unless it is empty).
It has a slightly odd startup behaviour: You have to wait 3*2s (jobslots*timeout) before the output stabilizes, and you get an output at least every 2s.
Summary
I'm creating a pseudo filesystem for Linux kernel 4.13, but my directories can't be listed with ls. I keep getting the message:
ls: cannot access 'mountedfs/data': No such file or directory.
Details
Creation and registration of the super_block and root dentry structures goes well. I can mount the file system without trouble, but I can't list the contents of my filesystem. When I try I get the error "No such file or directory".
After creating the super_block and root dentry, I call womfs_create_files() to populate the tree. Here is the full source code of the module. As you can see, I haven't even bothered with the file operations yet. I'm still stuck on inode operations.
#include <linux/kernel.h>
#include <linux/fs.h> /* libfs and most file-related headers. */
#include <linux/dcache.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/pagemap.h> /* PAGE_SIZE */
#include <linux/atomic.h>
#include <linux/time.h>
#include <linux/string.h>
#include <linux/sched.h>
#include <linux/parser.h>
#include <linux/magic.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "wombat.h"
/*
* Wombat directories are all basic. They just contain stuff, you can't link to them,
* you can't delete them, and you can't modify them. Anyone can list them, and
* they're owned by root.
*
* The file inodes are more specialized: they have to be linked to information about the
* keys they represent and the operations that can be performed on those keys.
* The same key data will be used by several inodes, but each inode needs to understand
* its particular purpose. (eg: "<key>/pub_key" lets you retrieve the public key;
* "<key>/sign" lets you sign data with the key.)
*
* Open file nodes need state information for processing reads, writes, ioctl,
* etc..
*/
/*
* Boilerplate stuff.
*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("CJ Holmes");
#define WOMFS_NAME "womfs"
#define WOMFS_MAGIC 0x00ff0019 // Lear Red
/*
* Operations keygen file that uses the machine-specific KEK to create
* a shrouded key for data encryption.
*/
int womfs_keygen_open(struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t womfs_keygen_read(struct file *filp, char __user *buf,
size_t count, loff_t *offset)
{
return 0;
}
int womfs_keygen_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations keygen_ops = {
.open = womfs_keygen_open,
.release = womfs_keygen_release,
.read = womfs_keygen_read,
};
/*
* Operations for encryption endpoints. The key and semantics depend on
* the key info in i_private and calls to ioctl.
*/
int womfs_encrypt_open(struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t womfs_encrypt_read(struct file *filp, char *buf,
size_t count, loff_t *offset)
{
return 0;
}
ssize_t womfs_encrypt_write(struct file *filp, const char __user * buf, size_t len, loff_t *pos) {
return 0;
}
int womfs_encrypt_release(struct inode *inode, struct file *filp)
{
return 0;
}
long womfs_encrypt_ioctl(struct file *filp, unsigned int cmd, unsigned long data)
{
return 0;
}
static struct file_operations encrypt_ops = {
.open = womfs_encrypt_open,
.release = womfs_encrypt_release,
.read = womfs_encrypt_read,
.write = womfs_encrypt_write,
.unlocked_ioctl = womfs_encrypt_ioctl,
};
/*
* Stuff for building our FS structure.
*/
// Implementation borrowed from fs/stat.c:vsf_getattr_nosec()
int womfs_getattr(const struct path *path, struct kstat *stat, u32 request_mask,
unsigned int query_flags) {
struct inode *inode;
inode = d_inode(path->dentry);
printk(KERN_WARNING WOMFS_NAME " womfs_getattr(%pd4) --> %p\n", path->dentry, inode);
memset(stat, 0, sizeof(*stat));
stat->result_mask |= STATX_BASIC_STATS;
if(inode != NULL)
generic_fillattr(inode, stat); // fs/stat.c
return 0;
}
const struct inode_operations womfs_dir_inode_operations = {
.lookup = simple_lookup,
.getattr = womfs_getattr,
};
const struct inode_operations womfs_inode_operations = {
.getattr = womfs_getattr,
};
static struct inode *womfs_make_inode(struct super_block *sb, struct wombat_key_info *key,
kgid_t group, char *domain, int mode, struct file_operations *fops)
{
struct inode *ret = new_inode(sb);
struct timespec now = current_kernel_time();
if (ret) {
ret->i_mode = mode;
ret->i_uid = KUIDT_INIT(0);
ret->i_gid = group;
ret->i_blocks = 0;
ret->i_atime = now;
ret->i_mtime = now;
ret->i_ctime = now;
if (mode & S_IFDIR) {
ret->i_op = &womfs_dir_inode_operations;
ret->i_fop = (fops == NULL) ? &simple_dir_operations : fops ;
inc_nlink(ret);
} else {
ret->i_op = &womfs_inode_operations;
ret->i_fop = fops;
}
ret->i_private = key;
}
return ret;
}
struct dentry *womfs_add_node_to_dir(struct dentry *parent, struct inode *node, const char *name)
{
struct dentry *child;
struct qstr qname;
qname.name = name;
qname.len = strlen(name);
qname.hash = full_name_hash(NULL, name, qname.len);
child = d_alloc(parent, &qname);
if (child != NULL)
{
// d_instantiate(child, node);
d_add(child, node);
inode_inc_link_count(node);
}
return child;
}
static void womfs_create_files (struct super_block *sb, struct dentry *root)
{
struct dentry *subdir;
struct dentry *fentry;
struct inode *node;
kgid_t group;
/* This will eventually be a loop through all of the slots provided by the SNVS.
For now, we can just add the KEK entry. */
struct wombat_key_info *ki = kzalloc(sizeof(struct wombat_key_info), GFP_KERNEL);
ki->type = wombat_key_kek;
ki->slot = 0;
strcpy(ki->name, "data");
// leave group and domain blank, pending further implementation.
group = KGIDT_INIT(0);
/* This can be cleaned up, perhaps by combining womfs_make_inode()
and womfs_add_node_to_dir()
*/
node = womfs_make_inode(sb, ki, group, NULL, S_IFDIR | 0555, NULL);
if(node != NULL) {
subdir = womfs_add_node_to_dir(root, node, ki->name);
if (subdir != NULL) {
printk(KERN_WARNING WOMFS_NAME " %pd4", subdir);
switch(ki->type) {
case wombat_key_kek:
/* create the data/keygen file */
node = womfs_make_inode(sb, ki, group, NULL, S_IFREG | 0444, &keygen_ops);
if(node != NULL) {
fentry = womfs_add_node_to_dir(subdir, node, "keygen");
if(fentry != NULL)
printk(KERN_WARNING WOMFS_NAME " %pd4", fentry);
else
iput(node);
}
/* create the data/encrypt file */
node = womfs_make_inode(sb, ki, group, NULL, S_IFREG | 0666, &encrypt_ops);
if( node != NULL) {
fentry = womfs_add_node_to_dir(subdir, node, "encrypt");
if(fentry != NULL)
printk(KERN_WARNING WOMFS_NAME " %pd4", fentry);
else
iput(node);
}
break;
default:
/* Show some error here. */
break;
}
} else {
iput(node);
}
}
}
/*
* Superblock stuff. This is all boilerplate to give the vfs something
* that looks like a filesystem to work with.
*/
/*
* Our superblock operations, both of which are generic kernel ops
* that we don't have to write ourselves.
*/
static struct super_operations womfs_s_ops = {
.statfs = simple_statfs,
.drop_inode = generic_delete_inode,
};
/*
* "Fill" a superblock with mundane stuff.
*/
static int womfs_fill_super (struct super_block *sb, void *data, int silent)
{
int retval = 0;
struct inode *root = NULL;
struct dentry *root_dentry = NULL;
kgid_t gid = KGIDT_INIT(0);
/*
* Basic parameters.
*/
sb->s_blocksize = PAGE_SIZE;
sb->s_blocksize_bits = PAGE_SHIFT;
sb->s_magic = WOMFS_MAGIC;
sb->s_op = &womfs_s_ops;
sb->s_time_gran = 1;
/*
* We need to conjure up an inode to represent the root directory
* of this filesystem. Its operations all come from libfs, so we
* don't have to mess with actually *doing* things inside this
* directory.
*/
root = womfs_make_inode(sb, NULL, gid, NULL, S_IFDIR | 0555, NULL);
if (root != NULL) {
// make the root directory entry.
root_dentry = d_make_root(root);
if (root_dentry != NULL) {
sb->s_root = root_dentry;
womfs_create_files (sb, root_dentry);
printk(KERN_WARNING WOMFS_NAME " setup complete\n");
} else {
retval = -ENOMEM;
}
} else {
retval = -ENOMEM;
}
if (retval != 0) {
// clean up our inode and dirent
if (root != NULL) {
iput(root);
}
if (root_dentry != NULL) {
dput(root_dentry);
}
}
return retval;
}
/*
* Stuff to pass in when registering the filesystem.
*/
struct dentry *womfs_mount(struct file_system_type *fst,
int flags, const char *devname, void *data)
{
return mount_nodev(fst, flags, data, womfs_fill_super);
}
static struct file_system_type womfs_type = {
.owner = THIS_MODULE,
.name = WOMFS_NAME,
.mount = womfs_mount,
.kill_sb = kill_litter_super,
};
/*
* Get things set up.
*/
static int __init womfs_init(void)
{
return register_filesystem(&womfs_type);
}
static void __exit womfs_exit(void)
{
unregister_filesystem(&womfs_type);
}
module_init(womfs_init);
module_exit(womfs_exit);
When I load the module and mount my filesystem, I see the following lines in my syslog:
Apr 17 11:43:59 felix kernel: [ 7024.360872] womfs data
Apr 17 11:43:59 felix kernel: [ 7024.360873] womfs data/keygen
Apr 17 11:43:59 felix kernel: [ 7024.360874] womfs data/encrypt
Apr 17 11:43:59 felix kernel: [ 7024.360875] womfs setup complete
The ls command shows my mounted file system:
cholmes#felix:~/leardev/womfs$ ls -l
total 668
-rw-rw-r-- 1 cholmes cholmes 317 Apr 12 10:38 Makefile
-rw-rw-r-- 1 cholmes cholmes 46 Apr 17 11:28 modules.order
-rw-rw-r-- 1 cholmes cholmes 0 Apr 16 15:50 Module.symvers
-rw-rw-r-- 1 cholmes cholmes 6860 Apr 12 11:39 README.md
... etc ...
dr-xr-xr-x 2 root root 0 Apr 17 11:28 mountedfs
But listing the contents of mountedfs is a disaster:
cholmes#felix:~/leardev/wombat$ ls -l mountedfs
ls: cannot access 'mountedfs/data': No such file or directory
total 0
d????????? ? ? ? ? ? data
It seems obvious I'm forgetting something pretty simple. It just isn't obvious to me yet.
Update 1
The data directory uses simple_dir_inode_operations and simple_dir_operations pointers from libfs.
The two regular files have very minimal ops structures, but I'll post them after I remove all the truly interesting bits ;-)
An strace ls -l mountedfs command displays:
... all the usual linking to run ls ...
open("mountedfs/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
getdents(3, /* 3 entries */, 32768) = 72
lstat("mountedfs/data", 0xaa01a0) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
... the rest is all about printing the error message ...
Update 2
The missing getattr() hook doesn't seem to be the problem. VFS provides a default implementation that copies attribute information from the inode, so you don't really need to implement getattr() unless you have to deal with syncing to disk or similar issues.
What I discovered was that my getattr() methods aren't even being called for subdirectories. Here's the new bits of my implementation:
// Implementation borrowed from fs/stat.c:vsf_getattr_nosec()
int womfs_getattr(const struct path *path, struct kstat *stat, u32 request_mask,
unsigned int query_flags) {
struct inode *inode;
inode = d_inode(path->dentry);
printk(KERN_WARNING WOMFS_NAME " womfs_getattr(%pd4) --> %p\n", path->dentry, inode);
memset(stat, 0, sizeof(*stat));
stat->result_mask |= STATX_BASIC_STATS;
if(inode != NULL)
generic_fillattr(inode, stat); // fs/stat.c
return 0;
}
const struct inode_operations womfs_dir_inode_operations = {
.lookup = simple_lookup,
.getattr = womfs_getattr,
};
const struct inode_operations womfs_inode_operations = {
.getattr = womfs_getattr,
};
I add these ops to the inodes in the expected way. When I mount my file system I get printk() messages as expected:
Apr 18 14:19:21 felix kernel: [20777.116214] womfs /data
Apr 18 14:19:21 felix kernel: [20777.116216] womfs /data/keygen
Apr 18 14:19:21 felix kernel: [20777.116217] womfs /data/encrypt
Apr 18 14:19:21 felix kernel: [20777.116218] womfs setup complete
And when I do an ls on the filesystem I get the same error message as before, plus some syslog messages telling me that womfs_getattr() was invoked.
Apr 18 14:19:27 felix kernel: [20782.880473] womfs womfs_getattr(/) --> ffff9e957c902960
Apr 18 14:19:27 felix kernel: [20782.880696] womfs womfs_getattr(/) --> ffff9e957c902960
So only the root of the filesystem is being stat'ed. It seems like my inode isn't being properly added to the dentry.
After reading more examples, I switched out my call to d_add() with d_instantiate() instead. Now the error messsage is gone, but the file system shows as being empty:
cholmes#felix:~/leardev/wombat$ ls -l mountedfs/
total 0
And now my syslog shows 5 accesses:
Apr 18 14:45:06 felix kernel: [22321.799115] womfs womfs_getattr(/) --> ffff9e95f95a04b0
Apr 18 14:45:06 felix kernel: [22321.800553] womfs womfs_getattr(/) --> ffff9e95f95a04b0
Apr 18 14:45:06 felix kernel: [22321.800554] womfs womfs_getattr(/) --> ffff9e95f95a04b0
Apr 18 14:45:06 felix kernel: [22322.406112] womfs womfs_getattr(/) --> ffff9e95f95a04b0
Apr 18 14:45:06 felix kernel: [22322.406341] womfs womfs_getattr(/) --> ffff9e95f95a04b0
And strace says:
open("mountedfs/", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFDIR|0555, st_size=0, ...}) = 0
getdents(3, /* 2 entries */, 32768) = 48
getdents(3, /* 0 entries */, 32768) = 0
close(3) = 0
At this point I'm completely confused and just trying semi-random things to see what will happen.
wombat$ ls -l mountedfs
ls: cannot access 'mountedfs/data': No such file or directory
total 0
d????????? ? ? ? ? ? data
ls output like this shows subdirectory name but not shows file mode, uid, gid or date/time. This can be when there was some correct syscall to return directory name; and where was another syscall to get additional information about inode, which had failed.
You should to check strace ls -l output to get exact syscall names and return values, but I think there was readdir/getdents to read data and there was failed stat (stat64, fstat, lstat, ...)
In your code you set fops for the inode (of directory type) in this fragment:
if (mode & S_IFDIR) {
ret->i_op = &simple_dir_inode_operations;
ret->i_fop = &simple_dir_operations;
But simple file/inode operations have not any method defined to provide stat implementation:
https://elixir.bootlin.com/linux/v4.4/source/fs/libfs.c#L189
const struct file_operations simple_dir_operations = {
.open = dcache_dir_open,
.release = dcache_dir_close,
.llseek = dcache_dir_lseek,
.read = generic_read_dir,
.iterate = dcache_readdir,
.fsync = noop_fsync,
};
const struct inode_operations simple_dir_inode_operations = {
.lookup = simple_lookup,
};
I think, getattr field of struct inode_operations is for implementing stat. Without implementation (with simple_inode_operations) stat syscall can't fill any useful data into struct kstat.
Some examples of getattr: fuse_getattr in fs/fuse/dir.c, http://pages.cpsc.ucalgary.ca/~crwth/programming/VFS/inodes.php, https://www.win.tue.nl/~aeb/linux/lk/lk-8.html ...
Documentation: https://www.kernel.org/doc/Documentation/filesystems/vfs.txt
struct inode_operations
int (*getattr) (const struct path *, struct kstat *, u32, unsigned int);
getattr: called by the VFS to get attributes of a file. This method
is called by stat(2) and related system calls.
ls sources https://github.com/coreutils/coreutils/blob/master/src/ls.c to prove that invalid return code of stat leads to ls: cannot access message
/* If true, the file listing format requires that stat be called on
each file. */
...
format_needs_stat = ... || format == long_format || ...
...
dereference = ... DEREF_NEVER;
...
gobble_file (char const *name, enum filetype type, ino_t inode,
...
if ( ... || format_needs_stat || ...) {
...
default: /* DEREF_NEVER */
err = lstat (full_name, &f->stat);
do_deref = false;
break;
}
if (err != 0)
{
/* Failure to stat a command line argument leads to
an exit status of 2. For other files, stat failure
provokes an exit status of 1. */
file_failure (command_line_arg,
_("cannot access %s"), full_name);
I try to make a C++ TLS client with OpenSSL which use non-blocking socket on Windows.
I want to work with SSL_read()/SSL_write() and select() functions but I don't find the algorithme which work well and the net not provide good and simple exemple. There is allready a timeout return by select() after the last block of data recved.
I don't understand OpenSSL api, SSL_pending() return already 0 and select a time out??
Select cause a criticale delay at last bloc of data.
My algorithme for recv_buffer() is this:
I have function which check if a socket is readable or writeable (work well):
int CSocket::socket_RWable(int rw_flag, const int time_out)
{
fd_set rwfs;
int error = 0;
struct timeval timeout;
try
{
memset(&timeout, 0, sizeof(struct timeval));
timeout.tv_sec = time_out;
while( 1 ) // boucle de surveillance
{
FD_ZERO(&rwfs);
FD_SET(m_socket, &rwfs);
// surveiller la socket en lecture ou ecriture
if(rw_flag == R_MODE)
error = select(m_socket+1, &rwfs, NULL, NULL, &timeout);
else if(rw_flag == W_MODE)
error = select(m_socket+1, NULL, &rwfs, NULL, &timeout);
if(error < 0) // echec de select
throw 1;
else if(error == 0) // fin du time out
throw 2;
// Une opération d' entree/sortie sur la socket est disponible
if(FD_ISSET(m_socket, &rwfs) != 0)
{
FD_CLR(m_socket, &rwfs );
return 0;
}
}
}
catch(int ret)
{
FD_CLR(m_socket, &rwfs );
if(ret == 1) throw CErreur("[-] CSocket : select : ", CWinUtil::Win_sys_error(NET_ERROR));
else if(ret == 2) return -1;
}
return -1;
}
UPDATE:
and this function recve the data into a buffer and cause a time out after the las block of data:
int CTLSClient::recv_buffer(char *buffer, const int buffer_size, const int time_out)
{
int selectErr = 0;
int sslErr = 0;
int retRead = 0;
int recvData = 0;
selectErr = m_socket->socket_RWable(R_MODE, time_out);
while(selectErr == 0)
{
retRead = SSL_read(m_ssl, buffer+recvData, buffer_size-recvData);
sslErr = SSL_get_error(m_ssl, retRead);
if(sslErr == SSL_ERROR_NONE)
{
cout<<"DEBUG 2 SSL_ERROR_NONE recv data="<<retRead<<endl;
recvData += retRead;
}
else if(sslErr == SSL_ERROR_WANT_READ)
{
cout<<"DEBUG 3 SSL_ERROR_WANT_READ select()"<<endl;
selectErr = m_socket->socket_RWable(R_MODE, time_out);
}
else if(sslErr == SSL_ERROR_WANT_WRITE)
{
cout<<"DEBUG 4 SSL_ERROR_WANT_WRITE select()"<<endl;
selectErr = m_socket->socket_RWable(W_MODE, time_out);
}
else if(sslErr == SSL_ERROR_ZERO_RETURN)
{
return -1;
}
else
return -1;
}
return recvData;
}
this is a output with connection to a POP3 server:
DEBUG 2 SSL_ERROR_NONE recv data=35
DEBUG 3 SSL_ERROR_WANT_READ select()
[S]+OK BLU0-POP617 POP3 server ready
total data -> 35
DEBUG 2 SSL_ERROR_NONE recv data=23
DEBUG 3 SSL_ERROR_WANT_READ select()
[S]+OK password required
total data -> 23
DEBUG 2 SSL_ERROR_NONE recv data=30
DEBUG 3 SSL_ERROR_WANT_READ select()
[S]+OK mailbox has 180 messages
total data -> 30
DEBUG 2 SSL_ERROR_NONE recv data=18
DEBUG 3 SSL_ERROR_WANT_READ select()
[S]+OK 180 12374432
total data -> 18
DEBUG 2 SSL_ERROR_NONE recv data=13
DEBUG 3 SSL_ERROR_WANT_READ select()
[S]+OK 1 23899
total data -> 13
DEBUG 2 SSL_ERROR_NONE recv data=5
DEBUG 3 SSL_ERROR_WANT_READ select()
DEBUG 2 SSL_ERROR_NONE recv data=8192
DEBUG 2 SSL_ERROR_NONE recv data=8192
DEBUG 3 SSL_ERROR_WANT_READ select()
DEBUG 3 SSL_ERROR_WANT_READ select()
DEBUG 2 SSL_ERROR_NONE recv data=7521
DEBUG 3 SSL_ERROR_WANT_READ select()
[S]total data -> 23910
Assuming you have already read the headers, for some reason SSL_read() hangs after reading the email message and returns SSL_WANT_READ. I solved this problem by looping through the message body one line at a time until I find the ending period. When I reach this line, I call SSL_pending(). Although there is no pending data, it prevents an endless loop where SSL_read() returns SSL_WANT_READ. However, I am looking for a better solution.
for(;;)
{
char *line = ReadLine(ssl, buf, sizeof(buf));
if(line != NULL)
{
if(*line == '.')
{
int pending = SSL_pending(ssl);
if(pending > 0)
{
int read = SSL_read(ssl,buf,pending);
}
}
}
}
This function reads one character at a time until it reaches an end of line character and returns the line.
char *ReadLine(SSL *ssl, char *buf, int size)
{
int i = 0;
char *ptr = NULL;
for (ptr = str; size > 1; size--, ptr++)
{
i = SSL_read(out, ptr, 1);
switch (SSL_get_error(out, i)){
case SSL_ERROR_NONE:
break;
case SSL_ERROR_ZERO_RETURN:
break;
case SSL_ERROR_WANT_READ:
break;
case SSL_ERROR_WANT_WRITE:
break;
default:
TRACE("SSL problem\r\n");
}
if (*ptr == '\n')
break;
if (*ptr == '\r'){
ptr--;
}
}
*ptr = '\0';
return(str);
}