tests/test-file-extents.sh: Make output filenames meaningful
[nbdkit.git] / plugins / file / winfile.c
blob3a34754c59d6a8a617039a236a684cc76a2e343d
1 /* nbdkit
2 * Copyright (C) 2013-2021 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
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
30 * SUCH DAMAGE.
33 /* This is the Windows version of the file plugin. */
35 #ifndef WIN32
36 #error "build error: file.c should be used on Unix-like platforms"
37 #endif
39 #include <config.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <stdbool.h>
44 #include <stdint.h>
45 #include <inttypes.h>
46 #include <string.h>
47 #include <assert.h>
49 #include <ws2tcpip.h>
50 #include <windows.h>
52 #include <pthread.h>
54 #define NBDKIT_API_VERSION 2
55 #include <nbdkit-plugin.h>
57 static char *filename = NULL;
59 static void
60 winfile_unload (void)
62 free (filename);
65 static int
66 winfile_config (const char *key, const char *value)
68 if (strcmp (key, "file") == 0) {
69 free (filename);
70 filename = nbdkit_realpath (value);
71 if (!filename)
72 return -1;
74 else {
75 nbdkit_error ("unknown parameter '%s'", key);
76 return -1;
79 return 0;
82 /* Check the user passed the file parameter. */
83 static int
84 winfile_config_complete (void)
86 if (!filename) {
87 nbdkit_error ("you must supply either [file=]<FILENAME> parameter "
88 "after the plugin name on the command line");
89 return -1;
92 return 0;
95 #define winfile_config_help \
96 "[file=]<FILENAME> The filename to serve."
98 /* Print some extra information about how the plugin was compiled. */
99 static void
100 winfile_dump_plugin (void)
102 printf ("winfile=yes\n");
105 /* Per-connection handle. */
106 struct handle {
107 HANDLE fh;
108 int64_t size;
109 bool is_readonly;
110 bool is_volume;
111 bool is_sparse;
114 static void *
115 winfile_open (int readonly)
117 struct handle *h;
118 HANDLE fh;
119 LARGE_INTEGER size = { 0 };
120 DWORD flags;
121 bool is_volume;
122 BY_HANDLE_FILE_INFORMATION fileinfo;
123 bool is_sparse;
125 flags = GENERIC_READ;
126 if (!readonly) flags |= GENERIC_WRITE;
128 fh = CreateFile (filename, flags, FILE_SHARE_READ|FILE_SHARE_WRITE,
129 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
130 if (fh == INVALID_HANDLE_VALUE && !readonly) {
131 nbdkit_debug ("open for writing failed, falling back to read-only: "
132 "%s: %lu",
133 filename, GetLastError ());
134 flags &= ~GENERIC_WRITE;
135 readonly = true;
136 fh = CreateFile (filename, flags, FILE_SHARE_READ|FILE_SHARE_WRITE,
137 NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
139 if (fh == INVALID_HANDLE_VALUE) {
140 nbdkit_error ("%s: error %lu", filename, GetLastError ());
141 return NULL;
144 /* https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-device-namespaces */
145 is_volume = strncmp (filename, "\\\\.\\", 4) == 0;
147 if (is_volume) {
148 /* Windows volume (block device). Get the size. */
149 GET_LENGTH_INFORMATION li;
150 DWORD lisz = sizeof li;
151 DWORD obsz = 0;
153 if (!DeviceIoControl (fh, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,
154 (LPVOID) &li, lisz, &obsz, NULL)) {
155 nbdkit_error ("%s: DeviceIoControl: %lu", filename, GetLastError ());
156 CloseHandle (fh);
157 return NULL;
159 size.QuadPart = li.Length.QuadPart;
161 else {
162 /* Regular file. Get the size. */
163 if (!GetFileSizeEx (fh, &size)) {
164 nbdkit_error ("%s: GetFileSizeEx: %lu", filename, GetLastError ());
165 CloseHandle (fh);
166 return NULL;
170 /* Sparseness is a file property in Windows. Whoever creates the
171 * file must set the property, we won't modify it. However we must
172 * see if the file is sparse and enable trimming if so.
174 * I couldn't find out how to handle sparse volumes, so if the call
175 * below fails assume non-sparse.
177 * https://docs.microsoft.com/en-us/windows/win32/fileio/sparse-file-operations
178 * http://www.flexhex.com/docs/articles/sparse-files.phtml
180 is_sparse = false;
181 if (GetFileInformationByHandle (fh, &fileinfo))
182 is_sparse = fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE;
184 h = malloc (sizeof *h);
185 if (!h) {
186 nbdkit_error ("malloc: %lu", GetLastError ());
187 CloseHandle (fh);
188 return NULL;
190 h->fh = fh;
191 h->size = size.QuadPart;
192 h->is_readonly = readonly;
193 h->is_volume = is_volume;
194 h->is_sparse = is_sparse;
195 nbdkit_debug ("%s: size=%" PRIi64 " readonly=%s is_volume=%s is_sparse=%s",
196 filename, h->size,
197 readonly ? "true" : "false",
198 is_volume ? "true" : "false",
199 is_sparse ? "true" : "false");
200 return h;
203 static int
204 winfile_can_write (void *handle)
206 struct handle *h = handle;
207 return !h->is_readonly;
210 /* Windows cannot flush on a read-only file. It returns
211 * ERROR_ACCESS_DENIED. Therefore don't advertise flush if the handle
212 * is r/o.
214 static int
215 winfile_can_flush (void *handle)
217 struct handle *h = handle;
218 return !h->is_readonly;
221 static int
222 winfile_can_trim (void *handle)
224 struct handle *h = handle;
225 return h->is_sparse;
228 static int
229 winfile_can_zero (void *handle)
231 return 1;
234 static int
235 winfile_can_extents (void *handle)
237 struct handle *h = handle;
238 return h->is_sparse;
241 static void
242 winfile_close (void *handle)
244 struct handle *h = handle;
245 CloseHandle (h->fh);
248 static int64_t
249 winfile_get_size (void *handle)
251 struct handle *h = handle;
252 return h->size;
255 static int
256 winfile_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
257 uint32_t flags)
259 struct handle *h = handle;
260 DWORD r;
261 OVERLAPPED ovl;
263 memset (&ovl, 0, sizeof ovl);
264 ovl.Offset = offset & 0xffffffff;
265 ovl.OffsetHigh = offset >> 32;
267 /* XXX Will fail weirdly if count is larger than 32 bits. */
268 if (!ReadFile (h->fh, buf, count, &r, &ovl)) {
269 nbdkit_error ("%s: ReadFile: %lu", filename, GetLastError ());
270 return -1;
272 return 0;
275 static int
276 winfile_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset,
277 uint32_t flags)
279 struct handle *h = handle;
280 DWORD r;
281 OVERLAPPED ovl;
283 memset (&ovl, 0, sizeof ovl);
284 ovl.Offset = offset & 0xffffffff;
285 ovl.OffsetHigh = offset >> 32;
287 /* XXX Will fail weirdly if count is larger than 32 bits. */
288 if (!WriteFile (h->fh, buf, count, &r, &ovl)) {
289 nbdkit_error ("%s: WriteFile: %lu", filename, GetLastError ());
290 return -1;
293 if (flags & NBDKIT_FLAG_FUA) {
294 if (!FlushFileBuffers (h->fh)) {
295 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
296 return -1;
300 return 0;
303 static int
304 winfile_flush (void *handle, uint32_t flags)
306 struct handle *h = handle;
308 if (!FlushFileBuffers (h->fh)) {
309 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
310 return -1;
313 return 0;
316 static int
317 winfile_trim (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
319 struct handle *h = handle;
320 FILE_ZERO_DATA_INFORMATION info;
321 DWORD t;
323 assert (h->is_sparse);
325 info.FileOffset.QuadPart = offset;
326 info.BeyondFinalZero.QuadPart = offset + count;
327 if (!DeviceIoControl (h->fh, FSCTL_SET_ZERO_DATA, &info, sizeof info,
328 NULL, 0, &t, NULL)) {
329 nbdkit_error ("%s: DeviceIoControl: FSCTL_SET_ZERO_DATA: %lu",
330 filename, GetLastError ());
331 return -1;
334 if (flags & NBDKIT_FLAG_FUA) {
335 if (!FlushFileBuffers (h->fh)) {
336 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
337 return -1;
341 return 0;
344 static int
345 winfile_zero (void *handle, uint32_t count, uint64_t offset, uint32_t flags)
347 struct handle *h = handle;
348 FILE_ZERO_DATA_INFORMATION info;
349 DWORD t;
351 /* This is documented to work for both non-sparse and sparse files,
352 * but for sparse files it creates a hole. If the file is sparse
353 * and !NBDKIT_FLAG_MAY_TRIM then we should fall back to writing
354 * zeros (by returning errno ENOTSUP). Also I found that Wine does
355 * not support this call, so in that case we also turn the Windows
356 * error ERROR_NOT_SUPPORTED into ENOTSUP.
358 if (h->is_sparse && (flags & NBDKIT_FLAG_MAY_TRIM) == 0) {
359 errno = ENOTSUP;
360 return -1;
362 info.FileOffset.QuadPart = offset;
363 info.BeyondFinalZero.QuadPart = offset + count;
364 if (!DeviceIoControl (h->fh, FSCTL_SET_ZERO_DATA, &info, sizeof info,
365 NULL, 0, &t, NULL)) {
366 if (GetLastError () == ERROR_NOT_SUPPORTED) {
367 errno = ENOTSUP;
368 return -1;
370 nbdkit_error ("%s: DeviceIoControl: FSCTL_SET_ZERO_DATA: %lu",
371 filename, GetLastError ());
372 return -1;
375 if (flags & NBDKIT_FLAG_FUA) {
376 if (!FlushFileBuffers (h->fh)) {
377 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
378 return -1;
382 return 0;
385 static int
386 winfile_extents (void *handle, uint32_t count, uint64_t offset,
387 uint32_t flags, struct nbdkit_extents *extents)
389 struct handle *h = handle;
390 const bool req_one = flags & NBDKIT_FLAG_REQ_ONE;
391 FILE_ALLOCATED_RANGE_BUFFER query;
392 FILE_ALLOCATED_RANGE_BUFFER ranges[16];
393 DWORD nb, n, i, err;
394 BOOL r;
395 uint64_t last_offset = offset, this_offset, this_length;
397 query.FileOffset.QuadPart = offset;
398 query.Length.QuadPart = count;
400 do {
401 r = DeviceIoControl (h->fh, FSCTL_QUERY_ALLOCATED_RANGES,
402 &query, sizeof query, ranges, sizeof ranges,
403 &nb, NULL);
404 err = GetLastError ();
405 /* This can return an error with ERROR_MORE_DATA which is not
406 * really an error, it means there is more data to be fetched
407 * after the set of ranges returned in this call.
409 if (!r && err != ERROR_MORE_DATA) {
410 nbdkit_error ("%s: DeviceIoControl: FSCTL_QUERY_ALLOCATED_RANGES: %lu",
411 filename, err);
412 return -1;
415 /* Number of ranges returned in this call. */
416 n = nb / sizeof ranges[0];
418 for (i = 0; i < n; ++i) {
419 this_offset = ranges[i].FileOffset.QuadPart;
420 this_length = ranges[i].Length.QuadPart;
422 /* The call returns only allocated ranges, so we must insert
423 * holes between them. Holes always read back as zero.
425 if (last_offset < this_offset) {
426 if (nbdkit_add_extent (extents, last_offset, this_offset-last_offset,
427 NBDKIT_EXTENT_HOLE|NBDKIT_EXTENT_ZERO) == -1)
428 return -1;
430 if (nbdkit_add_extent (extents, this_offset, this_length, 0) == -1)
431 return -1;
432 last_offset = this_offset + this_length;
434 if (req_one)
435 return 0;
437 } while (!r /* && err == ERROR_MORE_DATA (implied by error test above) */);
439 return 0;
442 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
444 static struct nbdkit_plugin plugin = {
445 .name = "file",
446 .longname = "nbdkit file plugin (Windows)",
447 .version = PACKAGE_VERSION,
449 .unload = winfile_unload,
451 .config = winfile_config,
452 .config_complete = winfile_config_complete,
453 .config_help = winfile_config_help,
454 .magic_config_key = "file",
455 .dump_plugin = winfile_dump_plugin,
457 .open = winfile_open,
458 .can_write = winfile_can_write,
459 .can_flush = winfile_can_flush,
460 .can_trim = winfile_can_trim,
461 .can_zero = winfile_can_zero,
462 .can_extents = winfile_can_extents,
463 .close = winfile_close,
464 .get_size = winfile_get_size,
465 .pread = winfile_pread,
466 .pwrite = winfile_pwrite,
467 .flush = winfile_flush,
468 .trim = winfile_trim,
469 .zero = winfile_zero,
470 .extents = winfile_extents,
472 .errno_is_preserved = 1, /* XXX ? */
475 NBDKIT_REGISTER_PLUGIN(plugin)