4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 /* This is the Windows version of the file plugin. */
36 #error "build error: file.c should be used on Unix-like platforms"
54 #define NBDKIT_API_VERSION 2
55 #include <nbdkit-plugin.h>
57 static char *filename
= NULL
;
66 winfile_config (const char *key
, const char *value
)
68 if (strcmp (key
, "file") == 0) {
70 filename
= nbdkit_realpath (value
);
75 nbdkit_error ("unknown parameter '%s'", key
);
82 /* Check the user passed the file parameter. */
84 winfile_config_complete (void)
87 nbdkit_error ("you must supply either [file=]<FILENAME> parameter "
88 "after the plugin name on the command line");
95 #define winfile_config_help \
96 "[file=]<FILENAME> The filename to serve."
98 /* Print some extra information about how the plugin was compiled. */
100 winfile_dump_plugin (void)
102 printf ("file_extents=yes\n");
103 printf ("winfile=yes\n");
106 /* Per-connection handle. */
116 winfile_open (int readonly
)
120 LARGE_INTEGER size
= { 0 };
123 BY_HANDLE_FILE_INFORMATION fileinfo
;
126 flags
= GENERIC_READ
;
127 if (!readonly
) flags
|= GENERIC_WRITE
;
129 fh
= CreateFile (filename
, flags
, FILE_SHARE_READ
|FILE_SHARE_WRITE
,
130 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
131 if (fh
== INVALID_HANDLE_VALUE
&& !readonly
) {
132 nbdkit_debug ("open for writing failed, falling back to read-only: "
134 filename
, GetLastError ());
135 flags
&= ~GENERIC_WRITE
;
137 fh
= CreateFile (filename
, flags
, FILE_SHARE_READ
|FILE_SHARE_WRITE
,
138 NULL
, OPEN_EXISTING
, FILE_ATTRIBUTE_NORMAL
, NULL
);
140 if (fh
== INVALID_HANDLE_VALUE
) {
141 nbdkit_error ("%s: error %lu", filename
, GetLastError ());
145 /* https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-device-namespaces */
146 is_volume
= strncmp (filename
, "\\\\.\\", 4) == 0;
149 /* Windows volume (block device). Get the size. */
150 GET_LENGTH_INFORMATION li
;
151 DWORD lisz
= sizeof li
;
154 if (!DeviceIoControl (fh
, IOCTL_DISK_GET_LENGTH_INFO
, NULL
, 0,
155 (LPVOID
) &li
, lisz
, &obsz
, NULL
)) {
156 nbdkit_error ("%s: DeviceIoControl: %lu", filename
, GetLastError ());
160 size
.QuadPart
= li
.Length
.QuadPart
;
163 /* Regular file. Get the size. */
164 if (!GetFileSizeEx (fh
, &size
)) {
165 nbdkit_error ("%s: GetFileSizeEx: %lu", filename
, GetLastError ());
171 /* Sparseness is a file property in Windows. Whoever creates the
172 * file must set the property, we won't modify it. However we must
173 * see if the file is sparse and enable trimming if so.
175 * I couldn't find out how to handle sparse volumes, so if the call
176 * below fails assume non-sparse.
178 * https://docs.microsoft.com/en-us/windows/win32/fileio/sparse-file-operations
179 * http://www.flexhex.com/docs/articles/sparse-files.phtml
182 if (GetFileInformationByHandle (fh
, &fileinfo
))
183 is_sparse
= fileinfo
.dwFileAttributes
& FILE_ATTRIBUTE_SPARSE_FILE
;
185 h
= malloc (sizeof *h
);
187 nbdkit_error ("malloc: %lu", GetLastError ());
192 h
->size
= size
.QuadPart
;
193 h
->is_readonly
= readonly
;
194 h
->is_volume
= is_volume
;
195 h
->is_sparse
= is_sparse
;
196 nbdkit_debug ("%s: size=%" PRIi64
" readonly=%s is_volume=%s is_sparse=%s",
198 readonly
? "true" : "false",
199 is_volume
? "true" : "false",
200 is_sparse
? "true" : "false");
205 winfile_can_write (void *handle
)
207 struct handle
*h
= handle
;
208 return !h
->is_readonly
;
211 /* Windows cannot flush on a read-only file. It returns
212 * ERROR_ACCESS_DENIED. Therefore don't advertise flush if the handle
216 winfile_can_flush (void *handle
)
218 struct handle
*h
= handle
;
219 return !h
->is_readonly
;
223 winfile_can_trim (void *handle
)
225 struct handle
*h
= handle
;
230 winfile_can_zero (void *handle
)
236 winfile_can_extents (void *handle
)
238 struct handle
*h
= handle
;
243 winfile_close (void *handle
)
245 struct handle
*h
= handle
;
250 winfile_get_size (void *handle
)
252 struct handle
*h
= handle
;
257 winfile_pread (void *handle
, void *buf
, uint32_t count
, uint64_t offset
,
260 struct handle
*h
= handle
;
264 memset (&ovl
, 0, sizeof ovl
);
265 ovl
.Offset
= offset
& 0xffffffff;
266 ovl
.OffsetHigh
= offset
>> 32;
268 /* XXX Will fail weirdly if count is larger than 32 bits. */
269 if (!ReadFile (h
->fh
, buf
, count
, &r
, &ovl
)) {
270 nbdkit_error ("%s: ReadFile: %lu", filename
, GetLastError ());
277 winfile_pwrite (void *handle
, const void *buf
, uint32_t count
, uint64_t offset
,
280 struct handle
*h
= handle
;
284 memset (&ovl
, 0, sizeof ovl
);
285 ovl
.Offset
= offset
& 0xffffffff;
286 ovl
.OffsetHigh
= offset
>> 32;
288 /* XXX Will fail weirdly if count is larger than 32 bits. */
289 if (!WriteFile (h
->fh
, buf
, count
, &r
, &ovl
)) {
290 nbdkit_error ("%s: WriteFile: %lu", filename
, GetLastError ());
294 if (flags
& NBDKIT_FLAG_FUA
) {
295 if (!FlushFileBuffers (h
->fh
)) {
296 nbdkit_error ("%s: FlushFileBuffers: %lu", filename
, GetLastError ());
305 winfile_flush (void *handle
, uint32_t flags
)
307 struct handle
*h
= handle
;
309 if (!FlushFileBuffers (h
->fh
)) {
310 nbdkit_error ("%s: FlushFileBuffers: %lu", filename
, GetLastError ());
318 winfile_trim (void *handle
, uint32_t count
, uint64_t offset
, uint32_t flags
)
320 struct handle
*h
= handle
;
321 FILE_ZERO_DATA_INFORMATION info
;
324 assert (h
->is_sparse
);
326 info
.FileOffset
.QuadPart
= offset
;
327 info
.BeyondFinalZero
.QuadPart
= offset
+ count
;
328 if (!DeviceIoControl (h
->fh
, FSCTL_SET_ZERO_DATA
, &info
, sizeof info
,
329 NULL
, 0, &t
, NULL
)) {
330 nbdkit_error ("%s: DeviceIoControl: FSCTL_SET_ZERO_DATA: %lu",
331 filename
, GetLastError ());
335 if (flags
& NBDKIT_FLAG_FUA
) {
336 if (!FlushFileBuffers (h
->fh
)) {
337 nbdkit_error ("%s: FlushFileBuffers: %lu", filename
, GetLastError ());
346 winfile_zero (void *handle
, uint32_t count
, uint64_t offset
, uint32_t flags
)
348 struct handle
*h
= handle
;
349 FILE_ZERO_DATA_INFORMATION info
;
352 /* This is documented to work for both non-sparse and sparse files,
353 * but for sparse files it creates a hole. If the file is sparse
354 * and !NBDKIT_FLAG_MAY_TRIM then we should fall back to writing
355 * zeros (by returning errno ENOTSUP). Also I found that Wine does
356 * not support this call, so in that case we also turn the Windows
357 * error ERROR_NOT_SUPPORTED into ENOTSUP.
359 if (h
->is_sparse
&& (flags
& NBDKIT_FLAG_MAY_TRIM
) == 0) {
363 info
.FileOffset
.QuadPart
= offset
;
364 info
.BeyondFinalZero
.QuadPart
= offset
+ count
;
365 if (!DeviceIoControl (h
->fh
, FSCTL_SET_ZERO_DATA
, &info
, sizeof info
,
366 NULL
, 0, &t
, NULL
)) {
367 if (GetLastError () == ERROR_NOT_SUPPORTED
) {
371 nbdkit_error ("%s: DeviceIoControl: FSCTL_SET_ZERO_DATA: %lu",
372 filename
, GetLastError ());
376 if (flags
& NBDKIT_FLAG_FUA
) {
377 if (!FlushFileBuffers (h
->fh
)) {
378 nbdkit_error ("%s: FlushFileBuffers: %lu", filename
, GetLastError ());
387 winfile_extents (void *handle
, uint32_t count
, uint64_t offset
,
388 uint32_t flags
, struct nbdkit_extents
*extents
)
390 struct handle
*h
= handle
;
391 const bool req_one
= flags
& NBDKIT_FLAG_REQ_ONE
;
392 FILE_ALLOCATED_RANGE_BUFFER query
;
393 FILE_ALLOCATED_RANGE_BUFFER ranges
[16];
396 uint64_t last_offset
= offset
, this_offset
, this_length
;
398 query
.FileOffset
.QuadPart
= offset
;
399 query
.Length
.QuadPart
= count
;
402 r
= DeviceIoControl (h
->fh
, FSCTL_QUERY_ALLOCATED_RANGES
,
403 &query
, sizeof query
, ranges
, sizeof ranges
,
405 err
= GetLastError ();
406 /* This can return an error with ERROR_MORE_DATA which is not
407 * really an error, it means there is more data to be fetched
408 * after the set of ranges returned in this call.
410 if (!r
&& err
!= ERROR_MORE_DATA
) {
411 nbdkit_error ("%s: DeviceIoControl: FSCTL_QUERY_ALLOCATED_RANGES: %lu",
416 /* Number of ranges returned in this call. */
417 n
= nb
/ sizeof ranges
[0];
419 for (i
= 0; i
< n
; ++i
) {
420 this_offset
= ranges
[i
].FileOffset
.QuadPart
;
421 this_length
= ranges
[i
].Length
.QuadPart
;
423 /* The call returns only allocated ranges, so we must insert
424 * holes between them. Holes always read back as zero.
426 if (last_offset
< this_offset
) {
427 if (nbdkit_add_extent (extents
, last_offset
, this_offset
-last_offset
,
428 NBDKIT_EXTENT_HOLE
|NBDKIT_EXTENT_ZERO
) == -1)
431 if (nbdkit_add_extent (extents
, this_offset
, this_length
, 0) == -1)
433 last_offset
= this_offset
+ this_length
;
438 } while (!r
/* && err == ERROR_MORE_DATA (implied by error test above) */);
443 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
445 static struct nbdkit_plugin plugin
= {
447 .longname
= "nbdkit file plugin (Windows)",
448 .version
= PACKAGE_VERSION
,
450 .unload
= winfile_unload
,
452 .config
= winfile_config
,
453 .config_complete
= winfile_config_complete
,
454 .config_help
= winfile_config_help
,
455 .magic_config_key
= "file",
456 .dump_plugin
= winfile_dump_plugin
,
458 .open
= winfile_open
,
459 .can_write
= winfile_can_write
,
460 .can_flush
= winfile_can_flush
,
461 .can_trim
= winfile_can_trim
,
462 .can_zero
= winfile_can_zero
,
463 .can_extents
= winfile_can_extents
,
464 .close
= winfile_close
,
465 .get_size
= winfile_get_size
,
466 .pread
= winfile_pread
,
467 .pwrite
= winfile_pwrite
,
468 .flush
= winfile_flush
,
469 .trim
= winfile_trim
,
470 .zero
= winfile_zero
,
471 .extents
= winfile_extents
,
473 .errno_is_preserved
= 1, /* XXX ? */
476 NBDKIT_REGISTER_PLUGIN (plugin
)