Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,4 @@ changelog
*.orig
*.tmp
*.log
contrib/windows/OpenZFSOnWindows-debug-2.4.1*
3 changes: 3 additions & 0 deletions cmd/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ add_subdirectory(os/windows/zfs_tray)
add_subdirectory(raidz_test)
add_subdirectory(zinject)
add_subdirectory(zed)
if(WIN32)
add_subdirectory(zfs_remoted)
endif()
32 changes: 32 additions & 0 deletions cmd/zfs_remoted/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# CMakeLists.txt for zfs_remoted - ZFS Remote Block Device Daemon
#
# This is a standalone Windows user-mode application. It does NOT link to
# any ZFS libraries and does NOT include ZFS kernel headers.
#
# Source files:
# zfs_remoted.c - main: arg parsing, network, RPC dispatch
# backend_file.c - file backend (serves a raw image file)
# backend_disk.c - disk backend (serves a physical Windows disk)

add_executable(zfs_remoted
zfs_remoted.c
backend_file.c
backend_disk.c
)

# The parent cmake scope adds ZFS kernel headers globally via
# include_directories(), which shadows MSVC's system <string.h> (ZFS's
# wrapper uses #include_next, unsupported by MSVC). We reset this target's
# includes to only the Windows SDK user-mode paths.
set_target_properties(zfs_remoted PROPERTIES INCLUDE_DIRECTORIES "")

# Re-add Windows SDK UM paths so winsock2.h, winioctl.h etc. are found.
# WDK_VERSION and WDK_ROOT are already set by the parent build system.
target_include_directories(zfs_remoted PRIVATE
"${WDK_ROOT}/Include/${WDK_VERSION}/um"
"${WDK_ROOT}/Include/${WDK_VERSION}/shared"
"${WDK_ROOT}/Include/${WDK_VERSION}/ucrt"
"${CMAKE_CURRENT_SOURCE_DIR}" # for "zfs_remoted.h"
)

install(TARGETS zfs_remoted RUNTIME DESTINATION ${CMAKE_INSTALL_SBINDIR})
181 changes: 181 additions & 0 deletions cmd/zfs_remoted/backend_disk.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
/*
* CDDL HEADER START ... (see LICENSE)
* CDDL HEADER END
*/
/*
* Copyright (c) 2026, OpenZFS Remote VDEV contributors.
*
* Disk backend -- serves a physical disk (e.g. \\.\PhysicalDrive0).
*
* Disk specifier resolution:
* "0" -> \\.\PhysicalDrive0
* "physicaldrive2" -> \\.\PhysicalDrive2 (case-insensitive)
* "\\.\PhysicalDrive3" -> literal NT path
*/

#include "zfs_remoted.h"
#include <winioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

typedef struct {
HANDLE dh;
} disk_priv_t;

static int
resolve_path(const char *spec, char *out, size_t outlen)
{
/* Already a full NT path? */
if (spec[0] == '\\' && spec[1] == '\\' && spec[2] == '.') {
strncpy(out, spec, outlen - 1);
out[outlen - 1] = '\0';
return 0;
}

/* "physicaldriveN" (case-insensitive) */
if (_strnicmp(spec, "physicaldrive", 13) == 0) {
snprintf(out, outlen, "\\\\.\\%s", spec);
return 0;
}

/* Plain number "N" -> PhysicalDriveN */
int all_digits = 1;
for (const char *p = spec; *p; p++) {
if (!isdigit((unsigned char)*p)) { all_digits = 0; break; }
}
if (all_digits && spec[0] != '\0') {
snprintf(out, outlen, "\\\\.\\PhysicalDrive%s", spec);
return 0;
}

return -1;
}

static int
disk_open(block_backend_t *bb, const char *spec)
{
char path[256];
HANDLE h;

if (resolve_path(spec, path, sizeof(path)) != 0) {
fprintf(stderr, "disk: cannot resolve '%s'\n", spec);
return -1;
}

h = CreateFileA(path,
GENERIC_READ | GENERIC_WRITE,
0, /* exclusive */
NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (h == INVALID_HANDLE_VALUE) {
fprintf(stderr, "disk: CreateFile(%s) failed: %lu\n",
path, GetLastError());
return -1;
}

/* --- disk size --- */
GET_LENGTH_INFORMATION gli = { 0 };
DWORD junk;
if (DeviceIoControl(h, IOCTL_DISK_GET_LENGTH_INFO,
NULL, 0, &gli, sizeof(gli), &junk, NULL)) {
bb->bb_dev_size = gli.Length.QuadPart;
} else {
DISK_GEOMETRY_EX dg = { 0 };
if (DeviceIoControl(h, IOCTL_DISK_GET_DRIVE_GEOMETRY_EX,
NULL, 0, &dg, sizeof(dg), &junk, NULL)) {
bb->bb_dev_size = dg.DiskSize.QuadPart;
} else {
bb->bb_dev_size = 0;
}
}

/* --- sector sizes --- */
STORAGE_PROPERTY_QUERY spq = { 0 };
spq.PropertyId = StorageAccessAlignmentProperty;
spq.QueryType = PropertyStandardQuery;

STORAGE_ACCESS_ALIGNMENT_DESCRIPTOR sa = { 0 };
if (DeviceIoControl(h, IOCTL_STORAGE_QUERY_PROPERTY,
&spq, sizeof(spq), &sa, sizeof(sa), &junk, NULL)) {
bb->bb_lbasize = sa.BytesPerLogicalSector;
bb->bb_pbasize = sa.BytesPerPhysicalSector;
} else {
bb->bb_lbasize = 512;
bb->bb_pbasize = 4096;
}

disk_priv_t *dp = (disk_priv_t *)calloc(1, sizeof(*dp));
if (!dp) { CloseHandle(h); return -1; }
dp->dh = h;
bb->bb_priv = dp;

fprintf(stderr, "disk: opened %s (%llu MiB, %u/%u byte sectors)\n",
path,
(unsigned long long)(bb->bb_dev_size / (1024 * 1024)),
bb->bb_lbasize, bb->bb_pbasize);
return 0;
}

static void
disk_close(block_backend_t *bb)
{
disk_priv_t *dp = (disk_priv_t *)bb->bb_priv;
if (dp) {
if (dp->dh != INVALID_HANDLE_VALUE) CloseHandle(dp->dh);
free(dp);
bb->bb_priv = NULL;
}
}

/* Helper: overlapped I/O with GetOverlappedResult wait */
static int
disk_io(HANDLE dh, void *buf, uint32_t size, uint64_t offset,
int is_write)
{
OVERLAPPED ov = { 0 };
DWORD bytes = 0;

ov.Offset = (DWORD)(offset & 0xFFFFFFFF);
ov.OffsetHigh = (DWORD)(offset >> 32);

BOOL ok = is_write
? WriteFile(dh, buf, size, &bytes, &ov)
: ReadFile(dh, buf, size, &bytes, &ov);

if (!ok) {
if (GetLastError() != ERROR_IO_PENDING ||
!GetOverlappedResult(dh, &ov, &bytes, TRUE))
return -1;
}
return (bytes == size) ? 0 : -1;
}

static int
disk_read(block_backend_t *bb, void *buf, uint32_t size, uint64_t offset)
{
return disk_io(((disk_priv_t *)bb->bb_priv)->dh,
buf, size, offset, 0);
}

static int
disk_write(block_backend_t *bb, const void *buf, uint32_t size,
uint64_t offset)
{
return disk_io(((disk_priv_t *)bb->bb_priv)->dh,
(void *)buf, size, offset, 1);
}

static int
disk_flush(block_backend_t *bb)
{
return FlushFileBuffers(((disk_priv_t *)bb->bb_priv)->dh) ? 0 : -1;
}

const block_backend_t disk_backend = {
"disk",
disk_open, disk_close, disk_read, disk_write, disk_flush,
0, 512, 4096, NULL
};
96 changes: 96 additions & 0 deletions cmd/zfs_remoted/backend_file.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* CDDL HEADER START ... (see LICENSE)
* CDDL HEADER END
*/
/*
* Copyright (c) 2026, OpenZFS Remote VDEV contributors.
*
* File backend -- serves a raw image file.
*/
#include "zfs_remoted.h"
#include <stdio.h>
#include <stdlib.h>

typedef struct {
HANDLE fh;
} file_priv_t;

static int
file_open(block_backend_t *bb, const char *path)
{
fprintf(stderr, "file_open: '%s'\n", path);
fflush(stderr);

file_priv_t *fp = (file_priv_t *)calloc(1, sizeof(*fp));
if (!fp) { fprintf(stderr, "file_open: calloc failed\n"); return -1; }

fp->fh = CreateFileA(
path,
GENERIC_READ | GENERIC_WRITE,
0, /* exclusive */
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (fp->fh == INVALID_HANDLE_VALUE) { free(fp); return -1; }

LARGE_INTEGER sz;
bb->bb_dev_size = (GetFileSizeEx(fp->fh, &sz))
? (uint64_t)sz.QuadPart : 0;
bb->bb_lbasize = 512;
bb->bb_pbasize = 4096;
bb->bb_priv = fp;
return 0;
}

static void
file_close(block_backend_t *bb)
{
file_priv_t *fp = (file_priv_t *)bb->bb_priv;
if (fp) {
if (fp->fh != INVALID_HANDLE_VALUE) CloseHandle(fp->fh);
free(fp);
bb->bb_priv = NULL;
}
}

static int
file_read(block_backend_t *bb, void *buf, uint32_t size, uint64_t offset)
{
file_priv_t *fp = (file_priv_t *)bb->bb_priv;
LARGE_INTEGER li;
DWORD bytes = 0;

li.QuadPart = (LONGLONG)offset;
if (!SetFilePointerEx(fp->fh, li, NULL, FILE_BEGIN)) return -1;
if (!ReadFile(fp->fh, buf, size, &bytes, NULL)) return -1;
return (bytes == size) ? 0 : -1;
}

static int
file_write(block_backend_t *bb, const void *buf, uint32_t size,
uint64_t offset)
{
file_priv_t *fp = (file_priv_t *)bb->bb_priv;
LARGE_INTEGER li;
DWORD bytes = 0;

li.QuadPart = (LONGLONG)offset;
if (!SetFilePointerEx(fp->fh, li, NULL, FILE_BEGIN)) return -1;
if (!WriteFile(fp->fh, buf, size, &bytes, NULL)) return -1;
return (bytes == size) ? 0 : -1;
}

static int
file_flush(block_backend_t *bb)
{
file_priv_t *fp = (file_priv_t *)bb->bb_priv;
return FlushFileBuffers(fp->fh) ? 0 : -1;
}

const block_backend_t file_backend = {
"file",
file_open, file_close, file_read, file_write, file_flush,
0, 512, 4096, NULL
};
Loading