#5 MiniFilter - FilterGetMessage 비동기I/O
Table of Contents
Abstract
FilterGetMessage 비동기 I/O 관련 검색어로 내 블로그에 들어온 기록이 있길래 생각난 김에 정리했다. 본 글에서는 OVERLAPPED를 이용해 FilterGetMessage를 비동기식으로 받는 방법을 알아본다.
Port
유저 모드 어플리케이션에서 미니 필터로 포트 연결은 앞선 글과 깃허브 예제를 참고하길 바란다.
- #3 MiniFilter - Communication Port (user → minifilter)
https://geun-yeong.tistory.com/53
- #4 MiniFilter - Communication Port (minifilter → user)
https://geun-yeong.tistory.com/54
Create an Overlapped
일반 File I/O에서 사용하는 Overlapped과 사용법이 동일하다. 본 예제에서는 이벤트 이름을 할당했지만 필요 없다면 nullptr 값을 할당해도 된다.
OVERLAPPED overlapped; ZeroMemory(&overlapped, sizeof(overlapped));
overlapped.hEvent = CreateEvent(NULL,
FALSE,
FALSE,
L"Local\\FilterGetMessage_IsSuccess");
if (!overlapped.hEvent) {
fprintf(stderr, "CreateEvent failed (error = %u)\n", GetLastError());
return 1;
}
Call a FilterGetMessage with Overlapped
Overlapped에 이벤트 핸들을 할당했으면 FilterGetMessage에 Overlapped 구조체를 넘겨주면서 호출한다. Overlapped를 이용한 FilterGetMessage 호출이 성공하면 HRESULT 값으로 0x800730E5가 반환된다. I/O 작업이 진행중이라는 에러 메시지인데, IS_ERROR로 검사하면 에러라고 내뱉으니 그냥 이 값을 따로 검사하는 게 좋다.
// get message from minifilter with async io
h_result = FilterGetMessage(port_handle, &recv.hdr, sizeof(recv), &overlapped);
if (h_result != 0x800703e5 && IS_ERROR(h_result)) {
sprintf_s(exception_msg,
128,
"FilterGetMessage failed (HRESULT = 0x%x)",
h_result);
throw exception(exception_msg);
}
Wait an I/O operation to finish
이제 I/O 작업이 완료될 때 까지 다른 작업을 진행하면 된다. 본 예제에서는 특별히 할 일이 없으므로 WaitForSingleObject로 1초마다 I/O 작업 완료 여부를 검사하고 기다린 횟수를 출력하도록 작성했다.
UINT wait_count = 0;
while (WaitForSingleObject(overlapped.hEvent, 1000 /* 1 second */) == WAIT_TIMEOUT) {
printf("wait for %u times...\n", ++wait_count);
}
Do it
비동기 I/O 작업이 완료되면 WaitForSingleObject는 WAIT_OBJECT_0 값을 반환한다. 본 예제에서는 WAIT_TIMEOUT이 아니면 반복문을 끝내도록 했기 때문에 정상적으로 I/O 작업이 끝났다면 반복문을 탈출할 것이다. 비동기 I/O 작업이 완료되면 하고싶은 작업을 하면 된다.
DWORD transferred = 0;
if (GetOverlappedResult(port_handle, &overlapped, &transferred, FALSE)) {
printf("FilterGetMessage was finished!!! (%ws)\n", recv.data.path);
// ...(후략)...
Result
본 예제에서는 미니 필터에서 파일을 감시하다가 파일 명이 test.txt면 파일 전체 경로를 유저 모드 어플리케이션으로 전송하도록 했다. FsRtlIsNameInExpression은 와일드카드 문자를 사용한 문자열 비교를 할 수 있는 함수다.
RtlInitUnicodeString(&test_txt_pattern, L"*TEST.TXT");
RtlInitUnicodeString(&file_path, name_info->Name.Buffer);
if (!FsRtlIsNameInExpression(&test_txt_pattern, &file_path, TRUE, NULL)) {
return FLT_POSTOP_FINISHED_PROCESSING;
}
FLT_TO_USER sent; RtlZeroMemory(&sent, sizeof(sent));
FLT_TO_USER_REPLY reply; RtlZeroMemory(&reply, sizeof(reply));
ULONG returned_bytes = 0;
// send the file path to client
// if client reply to block accessing the file,
// the minifilter return STATUS_ACCESS_DENIED to filter manager
wcscpy_s(sent.path, ARRAYSIZE(sent.path), name_info->Name.Buffer);
status = MinifltPortSendMessage(&sent, sizeof(sent), &reply, sizeof(reply), &returned_bytes);
if (NT_SUCCESS(status) && returned_bytes > 0 && reply.block) {
callback_data->IoStatus.Status = STATUS_ACCESS_DENIED;
}
아래 사진은 본 예제 코드를 실행한 모습이다. 유저 모드 어플리케이션은 1초마다 wait for ~ 메시지를 출력하며 비동기 I/O 작업을 기다리고, 미니 필터는 test.txt 파일에 대한 접근 요청이 발생하면 이를 유저 모드 어플리케이션에 전달한다. 그 결과 유저 모드 어플리케이션은 비동기 I/O 작업을 정상적으로 완료하고 파일의 전체 경로를 올바르게 출력하는 걸 볼 수 있다.
Close
이상으로 비동기 Filter I/O 사용 방법에 대해서 살펴봤다. 사실 일반적인 File I/O와 차이가 없어 사용하는 데 큰 불편이나 어색함은 없을 것이다.
Github
https://github.com/geun-yeong/minifilter-example