Related
I am currently trying to get the wintun driver to work with my program for simple tunneling (see: https://www.wintun.net/ ).
I successfully find and open the network device, but when it comes to registering the buffer, I get the result ERROR_INVALID_PARAMETER (87). Like I said, opening works just fine and registering is done with SYSTEM privileges (if this is not done, I get ERROR_ACCESS_DENIED (5)).
First attempt was to malloc the ring buffers, but after that did not work I looked at how OpenVPN does it (yes, it added wintun support) and they seem to do with with CreateFileMapping.
First of all, here is my struct:
typedef struct _TUN_RING {
volatile ULONG Head;
volatile ULONG Tail;
volatile LONG Alertable;
UCHAR Data[(1024 * 1024) + 0x10000];
} TUN_RING;
which is according to the docs (https://git.zx2c4.com/wintun/about/ section "Ring Layout). Also its the same as OpenVPN does.
After that I create the file mapping
send_ring_handle_ = CreateFileMapping(INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
sizeof(TUN_RING),
nullptr);
recv_ring_handle_ = CreateFileMapping(INVALID_HANDLE_VALUE,
nullptr,
PAGE_READWRITE,
0,
sizeof(TUN_RING),
nullptr);
Then I create the mappings:
send_ring_ = (TUN_RING *)MapViewOfFile(send_ring_handle_,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof(TUN_RING));
recv_ring_ = (TUN_RING *)MapViewOfFile(recv_ring_handle_,
FILE_MAP_ALL_ACCESS,
0,
0,
sizeof(TUN_RING));
and finally (after impersonating the system user) trying to register it with DeviceIoControl:
TUN_REGISTER_RINGS reg_rings;
memset(®_rings, 0, sizeof(TUN_REGISTER_RINGS));
reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Send.Ring = send_ring_;
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.RingSize = sizeof(TUN_RING);
reg_rings.Receive.Ring = recv_ring_;
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
DWORD len;
if (!DeviceIoControl(tun_fd_,
TUN_IOCTL_REGISTER_RINGS,
®_rings,
sizeof(reg_rings),
nullptr,
0,
&len,
nullptr))
{
printf("Could not register ring buffers (%d).", ::GetLastError());
return false;
}
Can anybody point me to where I am wrong? Like I said, with malloc instead of the file mapping the same error arieses.
I have written a complete example by now using malloc:
#include <windows.h>
#include <winioctl.h>
#include <IPHlpApi.h>
#include <ndisguid.h>
#include <TlHelp32.h>
#include <tchar.h>
#include <securitybaseapi.h>
#include <cfgmgr32.h>
#include <stdint.h>
#include <stdio.h>
#include <string>
#include <assert.h>
#pragma pack(push, 1)
typedef struct _TUN_PACKET_PROTO {
ULONG Size;
UCHAR Data[]; // max packet size as defined by the driver.
} TUN_PACKET_PROTO;
typedef struct _TUN_RING_PROTO {
volatile ULONG Head;
volatile ULONG Tail;
volatile LONG Alertable;
UCHAR Data[];
} TUN_RING_PROTO;
#define TUN_IOCTL_REGISTER_RINGS CTL_CODE(51820U, 0x970U, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)
#define TUN_IOCTL_FORCE_CLOSE_HANDLES CTL_CODE(51820U, 0x971U, METHOD_NEITHER, FILE_READ_DATA | FILE_WRITE_DATA)
#define WINTUN_RING_CAPACITY 0x800000
#define WINTUN_RING_TRAILING_BYTES 0x10000
#define WINTUN_MAX_PACKET_SIZE 0xffff
#define WINTUN_PACKET_ALIGN 4
/* Memory alignment of packets and rings */
#define TUN_ALIGNMENT sizeof(ULONG)
#define TUN_ALIGN(Size) (((ULONG)(Size) + ((ULONG)TUN_ALIGNMENT - 1)) & ~((ULONG)TUN_ALIGNMENT - 1))
#define TUN_IS_ALIGNED(Size) (!((ULONG)(Size) & ((ULONG)TUN_ALIGNMENT - 1)))
/* Maximum IP packet size */
#define TUN_MAX_IP_PACKET_SIZE 0xFFFF
/* Maximum packet size */
#define TUN_MAX_PACKET_SIZE TUN_ALIGN(sizeof(TUN_PACKET_PROTO) + TUN_MAX_IP_PACKET_SIZE)
/* Minimum ring capacity. */
#define TUN_MIN_RING_CAPACITY 0x20000 /* 128kiB */
/* Maximum ring capacity. */
#define TUN_MAX_RING_CAPACITY 0x4000000 /* 64MiB */
/* Calculates ring capacity */
#define TUN_RING_CAPACITY(Size) ((Size) - sizeof(TUN_RING_PROTO) - (TUN_MAX_PACKET_SIZE - TUN_ALIGNMENT))
/* Calculates ring offset modulo capacity */
#define TUN_RING_WRAP(Value, Capacity) ((Value) & (Capacity - 1))
#define IS_POW2(x) ((x) && !((x) & ((x)-1)))
typedef struct _TUN_RING {
volatile ULONG Head;
volatile ULONG Tail;
volatile LONG Alertable;
UCHAR Data[WINTUN_RING_CAPACITY + (TUN_MAX_PACKET_SIZE-TUN_ALIGNMENT)];
} TUN_RING;
typedef struct _TUN_PACKET {
ULONG Size;
UCHAR Data[WINTUN_MAX_PACKET_SIZE]; // max packet size as defined by the driver.
} TUN_PACKET;
typedef struct _TUN_REGISTER_RINGS {
struct {
ULONG RingSize;
TUN_RING *Ring;
HANDLE TailMoved;
} Send, Receive;
} TUN_REGISTER_RINGS;
#pragma pack(pop)
class regkey_t
{
public:
regkey_t(void);
regkey_t(HKEY handle);
~regkey_t(void);
void attach(HKEY handle);
void release(void);
HKEY detach(void);
operator HKEY (void) const;
HKEY &get(void);
HKEY *operator &(void);
private:
regkey_t(const regkey_t &);
regkey_t &operator = (const regkey_t &);
HKEY handle_;
};
regkey_t::regkey_t():
handle_(0)
{
}
regkey_t::regkey_t(HKEY handle):
handle_(handle)
{
}
regkey_t::~regkey_t(void)
{
release();
}
void regkey_t::attach(HKEY handle)
{
release();
handle_ = handle;
}
void regkey_t::release(void)
{
if (handle_)
{
const LONG res (::RegCloseKey(handle_));
if (res != ERROR_SUCCESS)
{
printf("Couldn't close a reg handle (%lu).\n", res);
}
handle_ = 0;
}
}
HKEY regkey_t::detach(void)
{
const HKEY result (handle_);
handle_ = 0;
return result;
}
HKEY ®key_t::get(void)
{
return handle_;
}
HKEY *regkey_t::operator &(void)
{
return &handle_;
}
regkey_t::operator HKEY(void) const
{
return handle_;
}
bool impersonate_as_system()
{
HANDLE thread_token, process_snapshot, winlogon_process, winlogon_token, duplicated_token;
PROCESSENTRY32 entry;
BOOL ret;
DWORD pid = 0;
TOKEN_PRIVILEGES privileges;
::memset(&entry, 0, sizeof(entry));
::memset(&privileges, 0, sizeof(privileges));
entry.dwSize = sizeof(PROCESSENTRY32);
privileges.PrivilegeCount = 1;
privileges.Privileges->Attributes = SE_PRIVILEGE_ENABLED;
if (!LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &privileges.Privileges[0].Luid))
{
return false;
}
if (!ImpersonateSelf(SecurityImpersonation))
{
return false;
}
if (!OpenThreadToken(GetCurrentThread(), TOKEN_ADJUST_PRIVILEGES, FALSE, &thread_token))
{
RevertToSelf();
return false;
}
if (!AdjustTokenPrivileges(thread_token, FALSE, &privileges, sizeof(privileges), NULL, NULL))
{
CloseHandle(thread_token);
RevertToSelf();
return false;
}
CloseHandle(thread_token);
process_snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (process_snapshot == INVALID_HANDLE_VALUE)
{
RevertToSelf();
return false;
}
for (ret = Process32First(process_snapshot, &entry); ret; ret = Process32Next(process_snapshot, &entry))
{
if (::strcmp(entry.szExeFile, "winlogon.exe") == 0)
{
pid = entry.th32ProcessID;
break;
}
}
CloseHandle(process_snapshot);
if (!pid)
{
RevertToSelf();
return false;
}
winlogon_process = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (!winlogon_process)
{
RevertToSelf();
return false;
}
if (!OpenProcessToken(winlogon_process, TOKEN_IMPERSONATE | TOKEN_DUPLICATE, &winlogon_token))
{
CloseHandle(winlogon_process);
RevertToSelf();
return false;
}
CloseHandle(winlogon_process);
if (!DuplicateToken(winlogon_token, SecurityImpersonation, &duplicated_token))
{
CloseHandle(winlogon_token);
RevertToSelf();
return false;
}
CloseHandle(winlogon_token);
if (!SetThreadToken(NULL, duplicated_token))
{
CloseHandle(duplicated_token);
RevertToSelf();
return false;
}
CloseHandle(duplicated_token);
return true;
}
std::string get_instance_id(uint32_t device_index)
{
const std::string key_name("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E972-E325-11CE-BFC1-08002BE10318}");
std::string device_id("");
regkey_t adapters;
DWORD ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, key_name.c_str(), 0, KEY_READ, &adapters);
if (ret != ERROR_SUCCESS)
{
printf("Could not open registry key %s (%d).\n", key_name.c_str(), ret);
return device_id;
}
DWORD sub_keys(0);
ret = ::RegQueryInfoKey(adapters, NULL, NULL, NULL, &sub_keys, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (ret != ERROR_SUCCESS)
{
printf("Could not get info from %s (%d).\n", key_name.c_str(), ret);
return device_id;
}
if (sub_keys <= 0)
{
printf("Wrong registry key %s.\n", key_name.c_str());
return device_id;
}
if (device_index >= sub_keys)
{
return device_id;
}
uint32_t index(0);
for (DWORD i = 0; i < sub_keys; i++)
{
const uint32_t max_key_length = 255;
TCHAR key[max_key_length];
DWORD keylen(max_key_length);
// Get the adapter name
ret = ::RegEnumKeyEx(adapters, i, key, &keylen, NULL, NULL, NULL, NULL);
if (ret != ERROR_SUCCESS)
{
continue;
}
// Append it to NETWORK_ADAPTERS and open it
regkey_t device;
const std::string new_key(key_name + "\\" + std::string(key));
ret = ::RegOpenKeyEx(HKEY_LOCAL_MACHINE, new_key.c_str(), 0, KEY_READ, &device);
if (ret != ERROR_SUCCESS)
{
continue;
}
TCHAR data[256];
DWORD len(sizeof(data));
ret = ::RegQueryValueEx(device, "ComponentId", NULL, NULL, (LPBYTE)data, &len);
if (ret != ERROR_SUCCESS)
{
continue;
}
std::string device_name("wintun");
if (::_tcsnccmp(data, device_name.c_str(), sizeof(TCHAR) * device_name.length()) == 0)
{
if (device_index != index)
{
index++;
continue;
}
DWORD type;
len = sizeof(data);
ret = ::RegQueryValueEx(device, "DeviceInstanceID", NULL, &type, (LPBYTE)data, &len);
if (ret != ERROR_SUCCESS)
{
printf("Could not get info from %s (%d).\n", key_name.c_str(), ret);
}
device_id = data;
break;
}
}
return device_id;
}
bool open_tun_device()
{
HANDLE tun_fd_ = INVALID_HANDLE_VALUE;
std::string device_id;
uint32_t device_index;
{
TCHAR *interface_list = nullptr;
for (device_index = 0; device_index < 256; ++device_index)
{
device_id = get_instance_id(device_index);
if (device_id.empty())
{
continue;
}
CONFIGRET status = CR_SUCCESS;
// This loop is recommended as "robust code" by MSDN. See the Remarks of CM_Get_Device_Interface_list.
do
{
DWORD required_chars(0);
if ((status = ::CM_Get_Device_Interface_List_Size(&required_chars,
(LPGUID)&GUID_DEVINTERFACE_NET,
(char *)device_id.c_str(),
CM_GET_DEVICE_INTERFACE_LIST_PRESENT)) != CR_SUCCESS || !required_chars)
{
break;
}
assert(required_chars > 0);
interface_list = (TCHAR *)::malloc(sizeof(TCHAR) * required_chars);
status = ::CM_Get_Device_Interface_List((LPGUID)&GUID_DEVINTERFACE_NET,
(char *)device_id.c_str(),
interface_list,
required_chars,
CM_GET_DEVICE_INTERFACE_LIST_PRESENT);
if (status == CR_SUCCESS)
{
break;
}
::free(interface_list);
interface_list = nullptr;
} while(status == CR_BUFFER_SMALL);
if (interface_list)
{
break;
}
}
if (!interface_list)
{
printf("Could not find wintun interface.\n");
return false;
}
else
{
tun_fd_ = ::CreateFile(interface_list,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING, 0, nullptr);
}
::free(interface_list);
}
if (!tun_fd_ || tun_fd_ == INVALID_HANDLE_VALUE)
{
printf("Could not open wintun device.\n");
return false;
}
printf("Opened wintun device.\n");
::Sleep(1000);
TUN_RING * send_ring_ = (TUN_RING *)::malloc(sizeof(TUN_RING));
TUN_RING * recv_ring_ = (TUN_RING *)::malloc(sizeof(TUN_RING));
if (!recv_ring_ || !send_ring_)
{
printf("Could not malloc.\n");
return false;
}
::memset(send_ring_, 0, sizeof(*send_ring_));
::memset(recv_ring_, 0, sizeof(*recv_ring_));
recv_ring_->Alertable = TRUE;
recv_ring_->Head = 0;
recv_ring_->Tail = 0;
send_ring_->Alertable = TRUE;
send_ring_->Head = 0;
send_ring_->Tail = 0;
HANDLE send_event = ::CreateEvent(0, FALSE, FALSE, 0);
HANDLE recv_event = ::CreateEvent(0, FALSE, FALSE, 0);
// register the rings
if (impersonate_as_system())
{
TUN_REGISTER_RINGS reg_rings;
::memset(®_rings, 0, sizeof(TUN_REGISTER_RINGS));
reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Send.Ring = send_ring_;
reg_rings.Send.TailMoved = send_event;
reg_rings.Receive.RingSize = sizeof(TUN_RING);
reg_rings.Receive.Ring = recv_ring_;
reg_rings.Receive.TailMoved = recv_event;
int send_capacity = TUN_RING_CAPACITY(reg_rings.Send.RingSize);
if ((send_capacity < TUN_MIN_RING_CAPACITY || send_capacity > TUN_MAX_RING_CAPACITY ||
!IS_POW2(send_capacity) || !reg_rings.Send.TailMoved || !reg_rings.Send.Ring))
{
printf("Fuck this shit I am out...\n");
}
DWORD len;
DWORD fuckyou = 0;
if (!::DeviceIoControl(tun_fd_, TUN_IOCTL_FORCE_CLOSE_HANDLES,
&fuckyou, sizeof(fuckyou), nullptr, 0, &len, nullptr))
{
printf("Error releasing handles (%d).\n", ::GetLastError());
}
if (!::DeviceIoControl(tun_fd_,
TUN_IOCTL_REGISTER_RINGS,
®_rings,
sizeof(reg_rings),
nullptr,
0,
&len,
nullptr))
{
printf("Could not register ring buffers (%d).\n", ::GetLastError());
::Sleep(10000);
RevertToSelf();
return false;
}
::Sleep(10000);
RevertToSelf();
}
else
{
printf("Could not elevate to SYSTEM\n");
return false;
}
return true;
}
int main()
{
if (!open_tun_device())
{
printf("Experiment failed.\n");
}
printf("Size TUNRING: %d (%d)\n", sizeof(TUN_RING), 0x800000 + 0x010000 + 0x0C);
printf("Capacity: %d\n", TUN_RING_CAPACITY(sizeof(TUN_RING)));
if (!IS_POW2(TUN_RING_CAPACITY(sizeof(TUN_RING))))
{
printf("Shit gone wrong...\n");
}
return 0;
}
Please make sure to RUN THIS ELEVATED or you will get error 5 ERROR_ACCESS_DENIED.
I can see a difference in your code when registering rings.
You are doing:
reg_rings.Send.RingSize = sizeof(TUN_RING);
reg_rings.Receive.RingSize = sizeof(TUN_RING);
While the docs says:
Send.RingSize, Receive.RingSize: Sizes of the rings (sizeof(TUN_RING) + capacity + 0x10000, as above)
Your ring is sizeof(TUN_RING) + UCHAR[(1024 * 1024) + 0x10000]
I guess it can't accept a ring that has no data space?
Sorry, I see your TUN_RING includes de data...
May be the events aren't good:
If an event is created from a service or a thread that is impersonating a different user, you can either apply a security descriptor to the event when you create it, or change the default security descriptor for the creating process by changing its default DACL
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
You seem to be using the default DACL.
There may be aligning problems. If malloc isn't returning an aligned address for your buffer (as may be in debug mode, because there are memory management bytes) your Data member for the packet could be not aligned.
You can check the alignment against the address:
template <unsigned int alignment>
struct IsAligned
{
static_assert((alignment & (alignment - 1)) == 0, "Alignment must be a power of 2");
static inline bool Value(const void * ptr)
{
return (((uintptr_t)ptr) & (alignment - 1)) == 0;
}
};
std::cout << IsAligned<32>::Value(ptr + i) << std::endl;
Giving the first packet address &(TUN_RING.Data[0]) (I guess.)
As said in your comment, it is the case, it is unaligned.
You can try two things.
First reserve memory with aligned_alloc which will give you an aligned buffer for TUN_RING.
Second, if TUN_RING is already aligned and the packet alignment is the problem, then you should give the correct offset to the head and tail:
recv_ring_->Head = 0; // <- aligned byte offset
recv_ring_->Tail = 0;
send_ring_->Head = 0;
send_ring_->Tail = 0;
Remember:
Head: Byte offset of the first packet in the ring. Its value must be a multiple of 4 and less than ring capacity.
Tail: Byte offset of the start of free space in the ring. Its value must be multiple of 4 and less than ring capacity.
The byte offset must be a multiple of 4.
You have to increment those skipped bytes to the buffer size. For that you may need to allocate extra space that won't be used, but I think it won't be too much.
In a second view to events, in the docs it says event has to be auto-reset:
Send.TailMoved: A handle to an auto-reset event created by the client that Wintun signals after it moves the Tail member of the send ring.
Receive.TailMoved: A handle to an auto-reset event created by the client that the client will signal when it changes Receive.Ring->Tail and Receive.Ring->Alertable is non-zero.
In your example, the event is auto-reset:
HANDLE send_event = ::CreateEvent(0, FALSE, FALSE, 0);
HANDLE recv_event = ::CreateEvent(0, FALSE, FALSE, 0);
but in the code you show (at top of question) isn't:
reg_rings.Send.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
reg_rings.Receive.TailMoved = CreateEvent(0, TRUE, FALSE, 0);
I don't know if the parameter checking goes so far as to verify event auto-reset setting (not even if that's possible.) Moreover, the openvpn code creates them non auto-reset (although may be there is some code around to signal them before registering.)
Okay, after a lot of trial and error I have translated the whole setup routine from the WireGuard Go code (see here: https://github.com/WireGuard/wireguard-go ) to C++, which seems to make it work. It accepts the rings now just as in the first post and the device is shown as connected afterwards...
They are doing some registry tweaks after installing the device (see https://github.com/WireGuard/wireguard-go/blob/4369db522b3fd7adc28a2a82b89315a6f3edbcc4/tun/wintun/wintun_windows.go#L207 ) which I think takes the cake. Thanks for everyone helping in finding this.
For me the fix to get rid off ERROR_INVALID_PARAMETER (87) was to switch from x86 to x64 architecture in Visual Studio
The issue here is the alignment of the structs. You align your structs to 1 byte [#pragma pack(push, 1)] while the wintun driver does 8(/ZP8 in the solution). This will result in differing struct sizes and thus the size checks will fall through. Furthermore I would like to recommend that you use VirtualAlloc or the Mapping instead of malloc.
Now I get about 3.6GB data per second in memory, and I need to write them on my SSD continuously. I used CrystalDiskMark to test the writing speed of my SSD, it is almost 6GB per second, so I had thought this work should not be that hard.
![my SSD test result][1]:
[1]https://plus.google.com/u/0/photos/photo/106876803948041178149/6649598887699308850?authkey=CNbb5KjF8-jxJQ "test result":
My computer is Windows 10, using Visual Studio 2017 community.
I found this question and tried the highest voted answer. Unfortunately, the writing speed was only about 1s/GB for his option_2, far slower than tested by CrystalDiskMark. And then I tried memory mapping, this time writing becomes faster, about 630ms/GB, but still much slower. Then I tried multi-thread memory mapping, it seems that when the number of threads is 4, the speed was about 350ms/GB, and when I add the threads' number, the writing speed didn't go up anymore.
Code for memory mapping:
#include <fstream>
#include <chrono>
#include <vector>
#include <cstdint>
#include <numeric>
#include <random>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <thread>
#include <windows.h>
#include <sstream>
// Generate random data
std::vector<int> GenerateData(std::size_t bytes) {
assert(bytes % sizeof(int) == 0);
std::vector<int> data(bytes / sizeof(int));
std::iota(data.begin(), data.end(), 0);
std::shuffle(data.begin(), data.end(), std::mt19937{ std::random_device{}() });
return data;
}
// Memory mapping
int map_write(int* data, int size, int id){
char* name = (char*)malloc(100);
sprintf_s(name, 100, "D:\\data_%d.bin",id);
HANDLE hFile = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);//
if (hFile == INVALID_HANDLE_VALUE){
return -1;
}
Sleep(0);
DWORD dwFileSize = size;
char* rname = (char*)malloc(100);
sprintf_s(rname, 100, "data_%d.bin", id);
HANDLE hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, dwFileSize, rname);//create file
if (hFileMap == NULL) {
CloseHandle(hFile);
return -2;
}
PVOID pvFile = MapViewOfFile(hFileMap, FILE_MAP_WRITE, 0, 0, 0);//Acquire the address of file on disk
if (pvFile == NULL) {
CloseHandle(hFileMap);
CloseHandle(hFile);
return -3;
}
PSTR pchAnsi = (PSTR)pvFile;
memcpy(pchAnsi, data, dwFileSize);//memery copy
UnmapViewOfFile(pvFile);
CloseHandle(hFileMap);
CloseHandle(hFile);
return 0;
}
// Multi-thread memory mapping
void Mem2SSD_write(int* data, int size){
int part = size / sizeof(int) / 4;
int index[4];
index[0] = 0;
index[1] = part;
index[2] = part * 2;
index[3] = part * 3;
std::thread ta(map_write, data + index[0], size / 4, 10);
std::thread tb(map_write, data + index[1], size / 4, 11);
std::thread tc(map_write, data + index[2], size / 4, 12);
std::thread td(map_write, data + index[3], size / 4, 13);
ta.join();
tb.join();
tc.join();
td.join();
}
//Test:
int main() {
const std::size_t kB = 1024;
const std::size_t MB = 1024 * kB;
const std::size_t GB = 1024 * MB;
for (int i = 0; i < 10; ++i) {
std::vector<int> data = GenerateData(1 * GB);
auto startTime = std::chrono::high_resolution_clock::now();
Mem2SSD_write(&data[0], 1 * GB);
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime).count();
std::cout << "1G writing cost: " << duration << " ms" << std::endl;
}
system("pause");
return 0;
}
So I'd like to ask, is there any faster writing method for C++ to writing huge files? Or, why can't I write as fast as tested by CrystalDiskMark? How does CrystalDiskMark write?
Any help would be greatly appreciated. Thank you!
first of all this is not c++ question but os related question. for get maximum performance need need use os specific low level api call, which not exist in general c++ libs. from your code clear visible that you use windows api, so search solution for windows how minimum.
from CreateFileW function:
When FILE_FLAG_NO_BUFFERING is combined with FILE_FLAG_OVERLAPPED,
the flags give maximum asynchronous performance, because the I/O does
not rely on the synchronous operations of the memory manager.
so we need use combination of this 2 flags in call CreateFileW or FILE_NO_INTERMEDIATE_BUFFERING in call NtCreateFile
also extend file size and valid data length take some time, so better if final file at begin is known - just set file final size via NtSetInformationFile with FileEndOfFileInformation
or via SetFileInformationByHandle with FileEndOfFileInfo. and then set valid data length with SetFileValidData or via NtSetInformationFile with FileValidDataLengthInformation. set valid data length require SE_MANAGE_VOLUME_NAME privilege enabled when opening a file initially (but not when call SetFileValidData)
also look for file compression - if file compressed (it will be compressed by default if created in compressed folder) this is very slow writting. so need disbale file compression via FSCTL_SET_COMPRESSION
then when we use asynchronous I/O (fastest way) we not need create several dedicated threads. instead we need determine number of I/O requests run in concurrent. if you use CrystalDiskMark it actually run CdmResource\diskspd\diskspd64.exe for test and this is coresponded to it -o<count> parameter (run diskspd64.exe /? > h.txt for look parameters list).
use non Buffering I/O make task more hard, because exist 3 additional requirements:
Any ByteOffset passed to WriteFile must be a multiple of the sector
size.
The Length passed to WriteFile must be an integral of the sector
size
Buffers must be aligned in accordance with the alignment requirement
of the underlying device. To obtain this information, call
NtQueryInformationFile with FileAlignmentInformation
or GetFileInformationByHandleEx with FileAlignmentInfo
in most situations, page-aligned memory will also be sector-aligned,
because the case where the sector size is larger than the page size is
rare.
so almost always buffers allocated with VirtualAlloc function and multiple page size (4,096 bytes ) is ok. in concrete test for smaller code size i use this assumption
struct WriteTest
{
enum { opCompression, opWrite };
struct REQUEST : IO_STATUS_BLOCK
{
WriteTest* pTest;
ULONG opcode;
ULONG offset;
};
LONGLONG _TotalSize, _BytesLeft;
HANDLE _hFile;
ULONG64 _StartTime;
void* _pData;
REQUEST* _pRequests;
ULONG _BlockSize;
ULONG _ConcurrentRequestCount;
ULONG _dwThreadId;
LONG _dwRefCount;
WriteTest(ULONG BlockSize, ULONG ConcurrentRequestCount)
{
if (BlockSize & (BlockSize - 1))
{
__debugbreak();
}
_BlockSize = BlockSize, _ConcurrentRequestCount = ConcurrentRequestCount;
_dwRefCount = 1, _hFile = 0, _pRequests = 0, _pData = 0;
_dwThreadId = GetCurrentThreadId();
}
~WriteTest()
{
if (_pData)
{
VirtualFree(_pData, 0, MEM_RELEASE);
}
if (_pRequests)
{
delete [] _pRequests;
}
if (_hFile)
{
NtClose(_hFile);
}
PostThreadMessageW(_dwThreadId, WM_QUIT, 0, 0);
}
void Release()
{
if (!InterlockedDecrement(&_dwRefCount))
{
delete this;
}
}
void AddRef()
{
InterlockedIncrementNoFence(&_dwRefCount);
}
void StartWrite()
{
IO_STATUS_BLOCK iosb;
FILE_VALID_DATA_LENGTH_INFORMATION fvdl;
fvdl.ValidDataLength.QuadPart = _TotalSize;
NTSTATUS status;
if (0 > (status = NtSetInformationFile(_hFile, &iosb, &_TotalSize, sizeof(_TotalSize), FileEndOfFileInformation)) ||
0 > (status = NtSetInformationFile(_hFile, &iosb, &fvdl, sizeof(fvdl), FileValidDataLengthInformation)))
{
DbgPrint("FileValidDataLength=%x\n", status);
}
ULONG offset = 0;
ULONG dwNumberOfBytesTransfered = _BlockSize;
_BytesLeft = _TotalSize + dwNumberOfBytesTransfered;
ULONG ConcurrentRequestCount = _ConcurrentRequestCount;
REQUEST* irp = _pRequests;
_StartTime = GetTickCount64();
do
{
irp->opcode = opWrite;
irp->pTest = this;
irp->offset = offset;
offset += dwNumberOfBytesTransfered;
DoWrite(irp++);
} while (--ConcurrentRequestCount);
}
void FillBuffer(PULONGLONG pu, LONGLONG ByteOffset)
{
ULONG n = _BlockSize / sizeof(ULONGLONG);
do
{
*pu++ = ByteOffset, ByteOffset += sizeof(ULONGLONG);
} while (--n);
}
void DoWrite(REQUEST* irp)
{
LONG BlockSize = _BlockSize;
LONGLONG BytesLeft = InterlockedExchangeAddNoFence64(&_BytesLeft, -BlockSize) - BlockSize;
if (0 < BytesLeft)
{
LARGE_INTEGER ByteOffset;
ByteOffset.QuadPart = _TotalSize - BytesLeft;
PVOID Buffer = RtlOffsetToPointer(_pData, irp->offset);
FillBuffer((PULONGLONG)Buffer, ByteOffset.QuadPart);
AddRef();
NTSTATUS status = NtWriteFile(_hFile, 0, 0, irp, irp, Buffer, BlockSize, &ByteOffset, 0);
if (0 > status)
{
OnComplete(status, 0, irp);
}
}
else if (!BytesLeft)
{
// write end
ULONG64 time = GetTickCount64() - _StartTime;
WCHAR sz[64];
StrFormatByteSizeW((_TotalSize * 1000) / time, sz, RTL_NUMBER_OF(sz));
DbgPrint("end:%S\n", sz);
}
}
static VOID NTAPI _OnComplete(
_In_ NTSTATUS status,
_In_ ULONG_PTR dwNumberOfBytesTransfered,
_Inout_ PVOID Ctx
)
{
reinterpret_cast<REQUEST*>(Ctx)->pTest->OnComplete(status, dwNumberOfBytesTransfered, reinterpret_cast<REQUEST*>(Ctx));
}
VOID OnComplete(NTSTATUS status, ULONG_PTR dwNumberOfBytesTransfered, REQUEST* irp)
{
if (0 > status)
{
DbgPrint("OnComplete[%x]: %x\n", irp->opcode, status);
}
else
switch (irp->opcode)
{
default:
__debugbreak();
case opCompression:
StartWrite();
break;
case opWrite:
if (dwNumberOfBytesTransfered == _BlockSize)
{
DoWrite(irp);
}
else
{
DbgPrint(":%I64x != %x\n", dwNumberOfBytesTransfered, _BlockSize);
}
}
Release();
}
NTSTATUS Create(POBJECT_ATTRIBUTES poa, ULONGLONG size)
{
if (!(_pRequests = new REQUEST[_ConcurrentRequestCount]) ||
!(_pData = VirtualAlloc(0, _BlockSize * _ConcurrentRequestCount, MEM_COMMIT, PAGE_READWRITE)))
{
return STATUS_INSUFFICIENT_RESOURCES;
}
ULONGLONG sws = _BlockSize - 1;
LARGE_INTEGER as;
_TotalSize = as.QuadPart = (size + sws) & ~sws;
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = NtCreateFile(&hFile,
DELETE|FILE_GENERIC_READ|FILE_GENERIC_WRITE&~FILE_APPEND_DATA,
poa, &iosb, &as, 0, 0, FILE_OVERWRITE_IF,
FILE_NON_DIRECTORY_FILE|FILE_NO_INTERMEDIATE_BUFFERING, 0, 0);
if (0 > status)
{
return status;
}
_hFile = hFile;
if (0 > (status = RtlSetIoCompletionCallback(hFile, _OnComplete, 0)))
{
return status;
}
static USHORT cmp = COMPRESSION_FORMAT_NONE;
REQUEST* irp = _pRequests;
irp->pTest = this;
irp->opcode = opCompression;
AddRef();
status = NtFsControlFile(hFile, 0, 0, irp, irp, FSCTL_SET_COMPRESSION, &cmp, sizeof(cmp), 0, 0);
if (0 > status)
{
OnComplete(status, 0, irp);
}
return status;
}
};
void WriteSpeed(POBJECT_ATTRIBUTES poa, ULONGLONG size, ULONG BlockSize, ULONG ConcurrentRequestCount)
{
BOOLEAN b;
NTSTATUS status = RtlAdjustPrivilege(SE_MANAGE_VOLUME_PRIVILEGE, TRUE, FALSE, &b);
if (0 <= status)
{
status = STATUS_INSUFFICIENT_RESOURCES;
if (WriteTest * pTest = new WriteTest(BlockSize, ConcurrentRequestCount))
{
status = pTest->Create(poa, size);
pTest->Release();
if (0 <= status)
{
MessageBoxW(0, 0, L"Test...", MB_OK|MB_ICONINFORMATION);
}
}
}
}
These are the suggestions that come to my mind:
stop all running processes that are using the disk, in particular
disable Windows Defender realtime protection (or other anti virus/malware)
disable pagefile
use Windows Resource Monitor to find processes reading or writing to your disk
make sure you write continuous sectors on disk
don't take into account file opening and closing times
do not use multithreading (your disk is using DMA so the CPU won't matter)
write data that is in RAM (obviously)
be sure to disable all debugging features when building (build a release)
if using M.2 PCIe disk (seems to be your case) make sure other PCIe
devices aren't stealing PCIe lanes to your disk (the CPU has a
limited number AND mobo too)
don't run the test from your IDE
disable Windows file indexing
Finally, you can find good hints on how to code fast writes in C/C++ in this question's thread: Writing a binary file in C++ very fast
One area that might give you improvement is to have your threads running constantly and each reading from a queue.
At the moment every time you go to write you spawn 4 threads (which is slow) and then they're deconstructed at the end of the function. You'll see a speedup of at least the cpu time of your function if you spawn the threads at the start and have them all reading from separate queue's in an infinite loop.
They'll simply check after a SMALL delay if there's anything in their queue, if their is they'll write it all. Your only issue then is making sure order of data is maintained.
From an unprivileged user context, how can I query the size of the NTFS special files?
The size is the most important piece of metadata for me, but if I could get everything that is typically found in WIN32_FIND_DATA I would not mind it.
The NTFS special files I mean are (among others): $Mft, $MftMirr, $LogFile, $BadClus et cetera.
In order to open the MFT, I'd have to acquire certain privileges, open the volume and then parse the MFT. So that's out.
Also it seems to be impossible to open these files by name (for the majority of them), which precludes NtQueryInformationFile() and GetFileInformationByHandle(). Or maybe there is a combination of flags I haven't tried and it is possible to open them somehow for querying the file information?
Last but not least I don't get these files returned when using the respective Win32 APIs (FindFirstFile() et. al.), nor with NtQueryDirectoryFile() nor by using IRP_MN_QUERY_DIRECTORY directly.
Yes, I understand that I can effectively get the size of the MFT using FSCTL_GET_NTFS_VOLUME_DATA, but that's just one of these special files.
on ntfs volume we can enumerate all file records with FSCTL_GET_NTFS_FILE_RECORD. unfortunatelly format of FileRecordBuffer is undocumented/undeclared in windows headers. but this is common ntfs structs. buffer begin with NTFS_RECORD_HEADER (base class) after which will be several NTFS_ATTRIBUTE records. partial and custom definitions:
union NTFS_FILE_ID
{
LONGLONG IndexNumber;
struct
{
LONGLONG MftRecordIndex : 48;
LONGLONG SequenceNumber : 16;
};
};
struct NTFS_RECORD_HEADER
{
enum {
FILE = 'ELIF',
INDX = 'XDNI',
BAAD = 'DAAB',
HOLE = 'ELOH',
CHKD = 'DKHC'
} Type;
USHORT UsaOffset;
USHORT UsaCount;
USN Usn;
};
struct NTFS_FILE_RECORD_HEADER : public NTFS_RECORD_HEADER
{
USHORT SequenceNumber;
USHORT LinkCount;
USHORT AttributesOffset;
USHORT Flags;
ULONG BytesInUse;
ULONG BytesAllocated;
ULONGLONG BaseFileRecord;
USHORT NextAttributeNumber;
enum{
flgInUse = 1, flgDirectory = 2
};
};
struct NTFS_ATTRIBUTE
{
enum ATTRIBUTE_TYPE {
StandardInformation = 0x10,
AttributeList = 0x20,
FileName = 0x30,
ObjectId = 0x40,
SecurityDescriptor = 0x50,
VolumeName = 0x60,
VolumeInformation = 0x70,
Data = 0x80,
IndexRoot = 0x90,
IndexAllocation = 0xa0,
Bitmap = 0xb0,
ReparsePoint = 0xc0,
EAInformation = 0xd0,
EA = 0xe0,
PropertySet = 0xf0,
LoggedUtilityStream = 0x100,
StopTag = MAXDWORD
} Type;
ULONG Length;
BOOLEAN Nonresident;
UCHAR NameLength;
USHORT NameOffset;
USHORT Flags;// 1 = Compresed
USHORT AttributeNumber;
};
struct NTFS_RESIDENT_ATTRIBUTE : public NTFS_ATTRIBUTE
{
ULONG ValueLength;
USHORT ValueOffset;
USHORT Flags;
};
struct NTFS_NONRESIDENT_ATTRIBUTE : public NTFS_ATTRIBUTE
{
LONGLONG LowVcn;
LONGLONG HighVcn;
USHORT RunArrayOffset;
UCHAR CompressionUnit;
UCHAR Unknown[5];
LONGLONG AllocationSize;
LONGLONG DataSize;
LONGLONG InitializedSize;
LONGLONG CompressedSize;
};
struct NTFS_ATTRIBUTE_LIST
{
NTFS_ATTRIBUTE::ATTRIBUTE_TYPE Type;
USHORT Length;
UCHAR NameLength;
UCHAR NameOffset;
LONGLONG LowVcn;
LONGLONG FileReferenceNumber : 48;
LONGLONG FileReferenceNumber2 : 16;
USHORT AttributeNumber;
USHORT Unknown[3];
};
struct NTFS_STANDARD_ATTRIBUTE
{
LONGLONG CreationTime;
LONGLONG ChangeTime;
LONGLONG LastWriteTime;
LONGLONG LastAccessTime;
ULONG FileAttributes;
ULONG Unknown[3];
ULONG QuotaId;
ULONG SecurityId;
ULONGLONG QuotaChange;
USN Usn;
};
struct NTFS_FILENAME_ATTRIBUTE
{
NTFS_FILE_ID DirectoryId;
LONGLONG CreationTime;
LONGLONG ChangeTime;
LONGLONG LastWriteTime;
LONGLONG LastAccessTime;
LONGLONG AllocationSize;
LONGLONG DataSize;
ULONG FileAttributes;
ULONG EaSize;
UCHAR FileNameLength;// in symbols !!
UCHAR NameType;
WCHAR FileName[];
enum {
systemName , longName, shortName, systemName2
};
};
the code of enumeration all files can look like:
inline ULONG BOOL_TO_ERROR(BOOL f)
{
return f ? NOERROR : GetLastError();
}
ULONG QFMD(PCWSTR szVolumeName)
{
HANDLE hVolume = CreateFile(szVolumeName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hVolume == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
ULONG cb, BytesReturned;
NTFS_VOLUME_DATA_BUFFER nvdb;
ULONG err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_VOLUME_DATA, 0, 0, &nvdb, sizeof(nvdb), &BytesReturned, 0));
if (err == NOERROR)
{
NTFS_FILE_RECORD_INPUT_BUFFER nfrib;
cb = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer[nvdb.BytesPerFileRecordSegment]);
PNTFS_FILE_RECORD_OUTPUT_BUFFER pnfrob = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)alloca(cb);
// search for maximum valid FileReferenceNumber
LONG a = 0, b = MAXLONG, o;
do
{
nfrib.FileReferenceNumber.QuadPart = o = (a + b) >> 1;
err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD,
&nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0));
err ? b = o : a = o + 1;
} while(a < b);
nfrib.FileReferenceNumber.QuadPart--;
DbgPrint("MftRecordCount=%u\n", nfrib.FileReferenceNumber.LowPart);
union {
PVOID FileRecordBuffer;
PBYTE pb;
NTFS_RECORD_HEADER* pnrh;
NTFS_FILE_RECORD_HEADER* pnfrh;
NTFS_ATTRIBUTE* pna;
NTFS_RESIDENT_ATTRIBUTE* pnra;
NTFS_NONRESIDENT_ATTRIBUTE* pnaa;
};
NTFS_FILE_ID nfi;
UNICODE_STRING us = { sizeof (nfi), sizeof (nfi), (PWSTR)&nfi };
OBJECT_ATTRIBUTES oa = { sizeof(oa), hVolume, &us };
do
{
FileRecordBuffer = pnfrob->FileRecordBuffer;
if (err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD,
&nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0)))
{
break;
}
// are really file
if (
pnrh->Type != NTFS_RECORD_HEADER::FILE ||
!(pnfrh->Flags & NTFS_FILE_RECORD_HEADER::flgInUse) ||
pnfrh->BaseFileRecord
)
{
continue;
}
ULONG FileAttributes = INVALID_FILE_ATTRIBUTES;
ULONGLONG FileSize = 0;
nfi.MftRecordIndex = pnfrob->FileReferenceNumber.QuadPart;
nfi.SequenceNumber = pnfrh->SequenceNumber;
pb += pnfrh->AttributesOffset;
for( ; ; )
{
NTFS_FILENAME_ATTRIBUTE* pnfa;
NTFS_STANDARD_ATTRIBUTE* pnsa;
switch (pna->Type)
{
case NTFS_ATTRIBUTE::StopTag:
goto __end;
case NTFS_ATTRIBUTE::FileName:
pnfa = (NTFS_FILENAME_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
if (pnfa->NameType == NTFS_FILENAME_ATTRIBUTE::longName)
{
//DbgPrint("<< %.*S\n", pnfa->FileNameLength, pnfa->FileName);
}
break;
case NTFS_ATTRIBUTE::StandardInformation:
pnsa = (NTFS_STANDARD_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
FileAttributes = pnsa->FileAttributes;
break;
case NTFS_ATTRIBUTE::Data:
FileSize += pna->Nonresident ? pnaa->DataSize : pnra->ValueLength;
break;
}
pb += pna->Length;
}
__end:;
//HANDLE hFile;
//IO_STATUS_BLOCK iosb;
//NTSTATUS status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
// FILE_OPEN_REPARSE_POINT| FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT);
//if (0 <= status)
//{
// NtClose(hFile);
//}
} while (0 <= (nfrib.FileReferenceNumber.QuadPart = pnfrob->FileReferenceNumber.QuadPart - 1));
}
CloseHandle(hVolume);
return err;
}
some NTFS System Files, but this list already old, exist more system files. if want concrete system file query need assign it number to NTFS_FILE_RECORD_INPUT_BUFFER. little changed code for query sys files only:
ULONG QFMD(PCWSTR szVolumeName)
{
HANDLE hVolume = CreateFile(szVolumeName, FILE_GENERIC_READ, FILE_SHARE_VALID_FLAGS, 0, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, 0);
if (hVolume == INVALID_HANDLE_VALUE)
{
return GetLastError();
}
ULONG cb, BytesReturned;
NTFS_VOLUME_DATA_BUFFER nvdb;
ULONG err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_VOLUME_DATA, 0, 0, &nvdb, sizeof(nvdb), &BytesReturned, 0));
if (err == NOERROR)
{
NTFS_FILE_RECORD_INPUT_BUFFER nfrib;
nfrib.FileReferenceNumber.QuadPart = 0x30;
cb = FIELD_OFFSET(NTFS_FILE_RECORD_OUTPUT_BUFFER, FileRecordBuffer[nvdb.BytesPerFileRecordSegment]);
PNTFS_FILE_RECORD_OUTPUT_BUFFER pnfrob = (PNTFS_FILE_RECORD_OUTPUT_BUFFER)alloca(cb);
union {
PVOID FileRecordBuffer;
PBYTE pb;
NTFS_RECORD_HEADER* pnrh;
NTFS_FILE_RECORD_HEADER* pnfrh;
NTFS_ATTRIBUTE* pna;
NTFS_RESIDENT_ATTRIBUTE* pnra;
NTFS_NONRESIDENT_ATTRIBUTE* pnaa;
};
NTFS_FILE_ID nfi;
UNICODE_STRING us = { sizeof (nfi), sizeof (nfi), (PWSTR)&nfi };
OBJECT_ATTRIBUTES oa = { sizeof(oa), hVolume, &us };
do
{
FileRecordBuffer = pnfrob->FileRecordBuffer;
if (err = BOOL_TO_ERROR(DeviceIoControl(hVolume, FSCTL_GET_NTFS_FILE_RECORD,
&nfrib, sizeof nfrib, pnfrob, cb, &BytesReturned, 0)))
{
break;
}
// are really file
if (
pnrh->Type != NTFS_RECORD_HEADER::FILE ||
!(pnfrh->Flags & NTFS_FILE_RECORD_HEADER::flgInUse) ||
pnfrh->BaseFileRecord
)
{
continue;
}
ULONG FileAttributes = INVALID_FILE_ATTRIBUTES;
ULONGLONG FileSize = 0;
PCWSTR ShortName = 0, LongName = 0, SystemName = 0;
UCHAR ShortNameLength = 0, LongNameLength = 0, SystemNameLength = 0;
nfi.MftRecordIndex = pnfrob->FileReferenceNumber.QuadPart;
nfi.SequenceNumber = pnfrh->SequenceNumber;
pb += pnfrh->AttributesOffset;
BOOL bSysFile = FALSE;
for( ; ; )
{
union {
NTFS_FILENAME_ATTRIBUTE* pnfa;
NTFS_STANDARD_ATTRIBUTE* pnsa;
};
switch (pna->Type)
{
case NTFS_ATTRIBUTE::StopTag:
goto __end;
case NTFS_ATTRIBUTE::FileName:
pnfa = (NTFS_FILENAME_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
switch (pnfa->NameType)
{
case NTFS_FILENAME_ATTRIBUTE::systemName:
case NTFS_FILENAME_ATTRIBUTE::systemName2:
bSysFile = TRUE;
SystemName = pnfa->FileName, SystemNameLength = pnfa->FileNameLength;
break;
case NTFS_FILENAME_ATTRIBUTE::longName:
LongName = pnfa->FileName, LongNameLength = pnfa->FileNameLength;
break;
case NTFS_FILENAME_ATTRIBUTE::shortName:
ShortName = pnfa->FileName, ShortNameLength = pnfa->FileNameLength;
break;
}
break;
case NTFS_ATTRIBUTE::StandardInformation:
pnsa = (NTFS_STANDARD_ATTRIBUTE*)RtlOffsetToPointer(pnra, pnra->ValueOffset);
FileAttributes = pnsa->FileAttributes;
break;
case NTFS_ATTRIBUTE::Data:
FileSize += pna->Nonresident ? pnaa->DataSize : pnra->ValueLength;
break;
}
pb += pna->Length;
}
__end:;
if (bSysFile)
{
HANDLE hFile;
IO_STATUS_BLOCK iosb;
NTSTATUS status = NtOpenFile(&hFile, FILE_READ_ATTRIBUTES, &oa, &iosb, FILE_SHARE_VALID_FLAGS,
FILE_OPEN_REPARSE_POINT| FILE_OPEN_BY_FILE_ID | FILE_OPEN_FOR_BACKUP_INTENT);
if (0 <= status)
{
NtClose(hFile);
}
char sz[32];
StrFormatByteSize64A(FileSize, sz, RTL_NUMBER_OF(sz));
DbgPrint("%I64u: %08x %s [%x] %.*S\n", pnfrob->FileReferenceNumber.QuadPart,
FileAttributes, sz, status, SystemNameLength, SystemName);
}
} while (0 <= (nfrib.FileReferenceNumber.QuadPart = pnfrob->FileReferenceNumber.QuadPart - 1));
}
CloseHandle(hVolume);
return err;
}
with it i got next result:
38: 10000006 0 bytes [0] $Deleted
34: 00000020 10.0 MB [0] $TxfLogContainer00000000000000000002
33: 00000020 10.0 MB [0] $TxfLogContainer00000000000000000001
32: 00000020 64.0 KB [0] $TxfLog.blf
31: 00000026 1.00 MB [0] $Tops
30: 80000006 0 bytes [0] $Txf
29: 00000006 0 bytes [0] $TxfLog
28: 00000026 27.0 MB [0] $Repair
27: 00000006 0 bytes [0] $RmMetadata
26: 20000026 0 bytes [c0000034] $Reparse
25: 20000026 0 bytes [c0000034] $ObjId
24: 20000026 0 bytes [c0000034] $Quota
11: 00000006 0 bytes [0] $Extend
10: 00000006 128 KB [0] $UpCase
9: 20000006 0 bytes [c0000034] $Secure
8: 00000006 237 GB [c0000022] $BadClus
7: 00000006 8.00 KB [c0000022] $Boot
6: 00000006 7.42 MB [c0000022] $Bitmap
5: 00000806 0 bytes [0] .
4: 00000006 2.50 KB [0] $AttrDef
3: 00000006 0 bytes [0] $Volume
2: 00000006 64.0 MB [c0000022] $LogFile
1: 00000006 4.00 KB [0] $MFTMirr
0: 00000006 212 MB [0] $MFT
Yes, it is possible to use DeviceIoControl / FSCTL_GET_NTFS_FILE_RECORD to read the $MFT without elevation. With the help of this page, I have worked out the minimum settings. Note the last few paragraphs at the bottom of that page.
in Group Policy gpedit.msc, add the (non-elevated) user account you'll be running under to the following to the following policies:
Windows Settings/Security Settings/Local Policies/User Rights Assignment/...Perform Volume Maintenance tasks (definitely needed)
Windows Settings/Security Settings/Local Policies/User Rights Assignment/...Back up files and directories (not sure if this is essential)
So far, I haven't needed the following, but make a note of it in case you need to come back to it:
Windows Settings/Security Settings/Local Policies/User Rights Assignment/...Restore files and directories
Run gpupdate.exe from a Windows command prompt, or wait about 15 minutes for the group policy changes to take effect.
Those changes allow your user account to acquire the privileges. As a one-time step every time your app starts, you'll have to explicitly adjust your token. Here's a standalone version of the Win32 API AdjustTokenPrivileges:
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.ComponentModel;
[SuppressUnmanagedCodeSecurity]
public static class AdjPriv
{
[DllImport("kernel32.dll")]
static extern IntPtr GetCurrentProcess();
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool OpenProcessToken(IntPtr h, int acc, out IntPtr phtok);
[DllImport("advapi32.dll", SetLastError = true, ExactSpelling = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool LookupPrivilegeValueW(IntPtr host, [MarshalAs(UnmanagedType.LPWStr)] String name, out long pluid);
[DllImport("advapi32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool AdjustTokenPrivileges(IntPtr htok, bool disall, in TokPriv1Luid newst, int len, IntPtr prev, IntPtr relen);
const int
SE_PRIVILEGE_ENABLED /**/ = 0x00000002,
TOKEN_QUERY /**/ = 0x00000008,
TOKEN_ADJUST_PRIVILEGES /**/ = 0x00000020,
ERROR_NOT_ALL_ASSIGNED /**/ = 0x00000514;
[StructLayout(LayoutKind.Sequential, Pack = 4)]
struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
};
public static bool SetPrivilege(String szSe)
{
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out IntPtr htok))
goto _error;
var tp = new TokPriv1Luid { Count = 1, Attr = SE_PRIVILEGE_ENABLED };
if (!LookupPrivilegeValueW(IntPtr.Zero, szSe, out tp.Luid))
goto _error;
if (!AdjustTokenPrivileges(htok, false, in tp, 0, IntPtr.Zero, IntPtr.Zero))
goto _error;
return Marshal.GetLastWin32Error() != ERROR_NOT_ALL_ASSIGNED;
_error:
throw new Win32Exception();
}
};
Give the current user account the "SeManageVolumePrivilege" privilege when the app starts up by calling the AdjPriv.SetPrivilege utility function shown above. Call it once for each additional privilege you may also want to add.
static MyProgram()
{
if (!AdjPriv.SetPrivilege("SeManageVolumePrivilege"))
throw new SecurityException();
/// etc...
}
And now for the code. I won't go into detail with the p/Invoke since everyone has their own way of doing it. I'll just show the exact flags and constants values passed into the two critical two API calls in order to work without prompting for elevation.
IntPtr h = CreateFileW(#"\\?\Volume{c2655473-adc2-4fe3-99a0-77d5bb1b809f}\",
FILE_ACCESS_READ_CONTROL, // 0x00020000
FILE_SHARE_ANY, // 7
IntPtr.Zero,
CREATE_MODE_OPEN_EXISTING, // 3
FILE_FLAG_BACKUP_SEMANTICS, // 0x02000000
IntPtr.Zero);
And then finally...
/// <summary>
/// Given a 48-bit MFT index 'frn', recover the current "sequence number" of the file, which
/// can be used as the upper 16-bits to complete a usable FILE_REFERENCE (NTFS FileId):
/// </summary>
var frn = (FILE_REFERENCE)0x_0000_000000000218;
// ^^^^----- ???
// lookup proceeds downwards, so set the sought-after upper 16 bits to max. seq. value
frn.Seq = 0xFFFF;
if (!DeviceIoControl(h, FSCTL_GET_NTFS_FILE_RECORD, in frn, out FILE_RECORD_OUTPUT_BUFFER rec) ||
frn.Index != rec.file_ref.Index)
throw new Win32Exception();
frn.Seq = rec.frh.SequenceNumber;
// vvvv--- !!!
Console.WriteLine($"0x{frn:X16}"); // 0x_0092_000000000218
Notes:
It works! Well at least for me on Windows 10, Version 10.0.18362.387. Please notice the backslash ...\ at the end of the Volume Guid path in CreateFileW. The code will not work without it.
I'm creating file deletion tool, so working with raw disk access. Have made some functions to read data.
A bit sorry because posting so much code, but not sure where is the real problem.
struct Extent
{
LONGLONG ClustersCount;
LARGE_INTEGER Lcn; //lcn - logical cluster number - the offset of a cluster from some arbitary point within volume
Extent() : ClustersCount(), Lcn()
{}
Extent(LONGLONG clustersCount, LARGE_INTEGER lcn) : ClustersCount(clustersCount), Lcn(lcn)
{}
};
typedef std::vector<Extent> ExtentsVector;
bool GetFileExtentPoints(const std::wstring& filePath, ExtentsVector& output)
{
output.clear();
DWORD err = ERROR_SUCCESS;
HANDLE file = CreateFile(filePath.c_str(), FILE_READ_ATTRIBUTES, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), NULL, OPEN_EXISTING, 0, 0);
if (file != INVALID_HANDLE_VALUE)
{
STARTING_VCN_INPUT_BUFFER vcnStartBuffer = {};
RETRIEVAL_POINTERS_BUFFER pointsBuffer = {};
DWORD deviceIoControlDataritten = 0;
do
{
if (DeviceIoControl(file, FSCTL_GET_RETRIEVAL_POINTERS, &vcnStartBuffer, sizeof(vcnStartBuffer), &pointsBuffer, sizeof(pointsBuffer), &deviceIoControlDataritten, NULL))
{
Extent extent(pointsBuffer.Extents->NextVcn.QuadPart - pointsBuffer.StartingVcn.QuadPart, pointsBuffer.Extents[0].Lcn);
output.push_back(extent);
CloseHandle(file);
return true;
}
if (pointsBuffer.ExtentCount == 0) //small files could be stroed in master file table, so this part shouldn't always return false
{
CloseHandle(file);
return false;
}
Extent extent(pointsBuffer.Extents->NextVcn.QuadPart - pointsBuffer.StartingVcn.QuadPart, pointsBuffer.Extents[0].Lcn);
output.push_back(extent);
vcnStartBuffer.StartingVcn = pointsBuffer.Extents->NextVcn;
}
while (ERROR_MORE_DATA == GetLastError());
CloseHandle(file);
}
return false;
}
bool PermanentDeleteFile/*for now just read...*/(const std::wstring& filePath)
{
ExtentsVector extents;
if (!GetFileExtentPoints(filePath, extents))
return false;
DWORD sectorsPerCluster = 0;
DWORD bytesPerSector = 0;
LARGE_INTEGER fileSize = {};
HANDLE file = CreateFile(filePath.c_str(), FILE_READ_ATTRIBUTES, (FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE), NULL, OPEN_EXISTING, 0, 0);
if (file != INVALID_HANDLE_VALUE)
{
TCHAR drive[] = L"?:\\";
drive[0] = filePath[0];
if (!GetDiskFreeSpace(drive, §orsPerCluster, &bytesPerSector, NULL, NULL))
{
CloseHandle(file);
return false;
}
if (!GetFileSizeEx(file, &fileSize))
{
CloseHandle(file);
return false;
}
CloseHandle(file);
}
LONGLONG clusterSize = sectorsPerCluster * bytesPerSector;
LONGLONG clustersCount = fileSize.QuadPart / clusterSize + ((fileSize.QuadPart % clusterSize == 0) ? 0 : 1);
TCHAR rawDrive[] = L"\\\\.\\?:";
rawDrive[4] = filePath[0];
HANDLE driveHandle = CreateFile(rawDrive, /*GENERIC_WRITE*/GENERIC_READ/**/, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
if (driveHandle != INVALID_HANDLE_VALUE)
{
for (ExtentsVector::iterator it = extents.begin(); it != extents.end(); ++it)
{
LARGE_INTEGER distance = {};
distance.QuadPart = it->Lcn.QuadPart * clusterSize;
BOOL b = SetFilePointerEx(driveHandle, distance, NULL, FILE_BEGIN);
if (b)
{
std::string buffer;
buffer.resize(clusterSize * it->ClustersCount);
DWORD read = 0;
BOOL B = ReadFile(driveHandle, &buffer[0], clusterSize * it->ClustersCount, &read, NULL);
B = FALSE;//here I have breakpoint and for FAT type drives buffer contains invalid data
}
else
{
CloseHandle(driveHandle);
return false;
}
}
}
return false;
}
GetFileExtentPoints should collect clusters entries and cluster chain size for latter actions according to given file.
The place where the problem is seen is in PermanentDeleteFile I have marked that place with comment.
I have tried to read all my FAT32 drive and found that data I'm looking for is in drive but in other place so, as I understand FAT uses some kind of data offset. After that read this. As I think the offset is described by first 3 bytes in drive, but don't understand how to use it. In my case it is 0xEB 0x58 0x90. Want to ask, how to decode file system header into data offset.
BTW. I'm coding with C++ and using WinAPI.
EDIT: I have change my line into distance.QuadPart = it->Lcn.QuadPart * clusterSize + 0x01000000;, this works for FAT, however I don't want to have hardcoded constants, so can someone tell, if it is always this value, or it is calculated some way.
I would like to know whether i can get the drive information using the
SP_DEVICE_INTERFACE_DETAIL_DATA's DevicePath
my device path looks like below
"\?\usb#vid_04f2&pid_0111#5&39fe81e&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}"
also please tell me in the winapi they say "To determine whether a drive is a USB-type drive, call SetupDiGetDeviceRegistryProperty and specify the SPDRP_REMOVAL_POLICY property."
i too use SetupDiGetDeviceRegistryProperty like below
while ( !SetupDiGetDeviceRegistryProperty( hDevInfo,&DeviceInfoData,
SPDRP_REMOVAL_POLICY,&DataT,( PBYTE )buffer,buffersize,&buffersize ))
but i dont know how can i get the drive type using the above..
Please help me up
I've created a GetMountedVolumes method with optional mask to specify what type of volumes should be included in the search (readable, writeable, removeable, hotplugable, eraseable).
I abstained from Setup API methods at all and only used null access volume handle for DeviceIoControl calls (no administrative privileges are required). I will share my code, may be this will help others to implement methods to determine drive (volume) type without using Setup API.
enum VolumesFlags {
VolumeReadable = 1,
VolumeWriteable = 2,
VolumeEraseable = 4,
VolumeRemoveable = 8,
VolumeHotplugable = 16,
VolumeMounted = 128
};
bool GetMountedVolumes(std::vector<std::wstring> &Volumes,
unsigned int Flags = VolumeReadable | VolumeWriteable,
unsigned int Mask = VolumeReadable | VolumeWriteable) {
wchar_t Volume[MAX_PATH] = {0};
wchar_t* VolumeEndPtr = Volume;
Flags |= VolumeMounted;
Mask |= VolumeMounted;
Flags &= Mask;
HANDLE hFind = FindFirstVolume(Volume, sizeof(Volume) / sizeof(wchar_t));
if (hFind != INVALID_HANDLE_VALUE) {
do {
bool IsMatching = false;
VolumeEndPtr = &Volume[wcslen(Volume) - 1];
*VolumeEndPtr = L'\0';
HANDLE hDevice = CreateFile(Volume, 0, 0, 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
if (hDevice != INVALID_HANDLE_VALUE) {
unsigned int CurrentFlags = 0;
DWORD ReturnedSize;
STORAGE_HOTPLUG_INFO Info = {0};
if (DeviceIoControl(hDevice, IOCTL_STORAGE_GET_HOTPLUG_INFO, 0, 0, &Info, sizeof(Info), &ReturnedSize, NULL)) {
if (Info.MediaRemovable) {
CurrentFlags |= VolumeRemoveable;
}
if (Info.DeviceHotplug) {
CurrentFlags |= VolumeHotplugable;
}
}
DWORD MediaTypeSize = sizeof(GET_MEDIA_TYPES);
GET_MEDIA_TYPES* MediaType = (GET_MEDIA_TYPES*) new char[MediaTypeSize];
while (DeviceIoControl(hDevice, IOCTL_STORAGE_GET_MEDIA_TYPES_EX, 0, 0, MediaType, MediaTypeSize, &ReturnedSize, NULL) == FALSE && GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
delete [] (char*) MediaType;
MediaTypeSize *= 2;
MediaType = (GET_MEDIA_TYPES*) new char[MediaTypeSize];
}
if (MediaType->MediaInfoCount > 0) {
DWORD Characteristics = 0;
// Supports: Disk, CD, DVD
if (MediaType->DeviceType == FILE_DEVICE_DISK || MediaType->DeviceType == FILE_DEVICE_CD_ROM || MediaType->DeviceType == FILE_DEVICE_DVD) {
if (Info.MediaRemovable) {
Characteristics = MediaType->MediaInfo[0].DeviceSpecific.RemovableDiskInfo.MediaCharacteristics;
} else {
Characteristics = MediaType->MediaInfo[0].DeviceSpecific.DiskInfo.MediaCharacteristics;
}
if (Characteristics & MEDIA_CURRENTLY_MOUNTED) {
CurrentFlags |= VolumeMounted;
}
if (Characteristics & (MEDIA_READ_ONLY | MEDIA_READ_WRITE)) {
CurrentFlags |= VolumeReadable;
}
if (((Characteristics & MEDIA_READ_WRITE) != 0 || (Characteristics & MEDIA_WRITE_ONCE) != 0) && (Characteristics & MEDIA_WRITE_PROTECTED) == 0 && (Characteristics & MEDIA_READ_ONLY) == 0) {
CurrentFlags |= VolumeWriteable;
}
if (Characteristics & MEDIA_ERASEABLE) {
CurrentFlags |= VolumeEraseable;
}
}
}
delete [] (char*) MediaType;
CloseHandle(hDevice);
CurrentFlags &= Mask;
if (CurrentFlags == Flags) {
*VolumeEndPtr = L'\\';
wchar_t VolumePaths[MAX_PATH] = {0};
if (GetVolumePathNamesForVolumeName(Volume, VolumePaths, MAX_PATH, &ReturnedSize)) {
if (*VolumePaths) {
Volumes.push_back(VolumePaths);
}
}
}
}
} while (FindNextVolume(hFind, Volume, sizeof(Volume) / sizeof(wchar_t)));
FindVolumeClose(hFind);
}
return Volumes.size() > 0;
}
Probably what you are looking for you will be find here http://support.microsoft.com/kb/264203/en. Another link http://support.microsoft.com/kb/305184/en can be also interesting for you.
UPDATED: Example from http://support.microsoft.com/kb/264203/en shows you how to use to determine whether USB-Drive is removable. You can also use SetupDiGetDeviceRegistryProperty with SPDRP_REMOVAL_POLICY on the device instance (use SetupDiEnumDeviceInfo, SetupDiGetDeviceInstanceId and then SetupDiGetDeviceRegistryProperty). If returned DWORD has CM_REMOVAL_POLICY_EXPECT_SURPRISE_REMOVAL or CM_REMOVAL_POLICY_EXPECT_ORDERLY_REMOVAL as value, the drive is removable.
Moreover the code example show how to open device handle which you can use with DeviceIoControl function to retrieve a lot of useful information which you can need. IOCTL_STORAGE_QUERY_PROPERTY (see http://msdn.microsoft.com/en-us/library/ff566997%28v=VS.85%29.aspx) with different QueryType and PropertyId only one example. You can use IOCTL_STORAGE_GET_DEVICE_NUMBER for example to receive storage volumes and their disk number.
If you will have full STORAGE_DEVICE_NUMBER information about your USB device we will be able to find all other information about it with different ways. One of the easiest is: just enumerate all drive letters with QueryDosDevice and query STORAGE_DEVICE_NUMBER for every drive. If you will find full match in STORAGE_DEVICE_NUMBER you will find the drive letter.
Given your Storage Device Path:
Open the device using CreateFile
use DeviceIOControl to issue IOCTL_STORAGE_QUERY_PROPERTY
this populates STORAGE_DEVICE_DESCRIPTOR structure
which has a STORAGE_BUS_TYPE enumeration:
STORAGE_DEVICE_DESCRIPTOR {
DWORD Version;
DWORD Size;
BYTE DeviceType;
BYTE DeviceTypeModifier;
BOOLEAN RemovableMedia;
BOOLEAN CommandQueueing;
DWORD VendorIdOffset;
DWORD ProductIdOffset;
DWORD ProductRevisionOffset;
DWORD SerialNumberOffset;
STORAGE_BUS_TYPE BusType; //<---------------
DWORD RawPropertiesLength;
BYTE RawDeviceProperties[1];
}
The different storage bus types are
BusTypeScsi: SCSI
BusTypeAtapi: ATAPI
BusTypeAta: ATA
BusType1394: IEEE-1394
BusTypeSsa: SSA
BusTypeFibre: Fiber Channel
BusTypeUsb: USB
BusTypeRAID: RAID
BusTypeiSCSI: iSCSI
BusTypeSas: Serial Attached SCSI (SAS)
BusTypeSata: SATA
So example psueudo-code
STORAGE_BUS_TYPE GetStorageDeviceBusType(String StorageDevicePath)
{
/*
Given a storage device path of
\?\usb#vid_04f2&pid_0111#5&39fe81e&0&2#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
return its StorageBusType, e.g.:
BusTypeUsb
*/
//Open the disk for reading (must be an administrator)
HANDLE diskHandle = CreateFile(StorageDevicePath,
GENERIC_READ, //desired access
FILE_SHARE_READ | FILE_SHARE_WRITE, //share mode
null, //security attributes
OPEN_EXISTING, //creation disposition
FILE_ATTRIBUTE_NORMAL, //flags and attributes
0);
if (diskHandle == INVALID_HANDLE_VALUE)
RaiseLastWin32Error();
try
{
BOOL res;
DWORD bytesReturned;
//Set up what we want to query from the drive
STORAGE_PROPERTY_QUERY query= {};
query.QueryType = PropertyStandardQuery;
query.PropertyID = StorageDeviceProperty;
DWORD bufferSize;
// Query for the header to get the required buffer size
STORAGE_DESCRIPTOR_HEADER header;
res = DeviceIoControl(diskHandle, IOCTL_STORAGE_QUERY_PROPERTY,
ref query, sizeof(STORAGE_PROPERTY_QUERY),
ref header, sizeof(STORAGE_DESCRIPTOR_HEADER),
out bytesReturned, null);
if (!res) RaiseLastWin32Error();
bufferSize = header.Size;
//Allocate the buffer and query for the full property
STORAGE_DEVICE_DESCRIPTOR *deviceDescriptor = GetMem(bufferSize);
try
{
//Issue IOCTL_STORAGE_QUERY_PROPERTY to get STORAGE_DEVICE_DESCRIPTOR
res = DeviceIoControl(diskHandle, IOCTL_STORAGE_QUERY_PROPERTY,
#query, sizeof(STORAGE_PROPERTY_QUERY),
deviceDescriptor, bufferSize,
out bytesReturned, null));
if (!res)
RaiseLastWin32Error();
return deviceDescriptor.BusType;
}
finally
{
FreeMem(deviceDescriptor);
}
}
finally
{
CloseHandle(diskHandle);
}
}