Update Red Hat Copyright Notices
[nbdkit.git] / plugins / file / winfile.c
blob8ec164be7173abf18154e07d6f8b85425b320297
1 /* nbdkit
2 * Copyright Red Hat
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 ("file_extents=yes\n");
103 printf ("winfile=yes\n");
106 /* Per-connection handle. */
107 struct handle {
108 HANDLE fh;
109 int64_t size;
110 bool is_readonly;
111 bool is_volume;
112 bool is_sparse;
115 static void *
116 winfile_open (int readonly)
118 struct handle *h;
119 HANDLE fh;
120 LARGE_INTEGER size = { 0 };
121 DWORD flags;
122 bool is_volume;
123 BY_HANDLE_FILE_INFORMATION fileinfo;
124 bool is_sparse;
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: "
133 "%s: %lu",
134 filename, GetLastError ());
135 flags &= ~GENERIC_WRITE;
136 readonly = true;
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 ());
142 return NULL;
145 /* https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#win32-device-namespaces */
146 is_volume = strncmp (filename, "\\\\.\\", 4) == 0;
148 if (is_volume) {
149 /* Windows volume (block device). Get the size. */
150 GET_LENGTH_INFORMATION li;
151 DWORD lisz = sizeof li;
152 DWORD obsz = 0;
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 ());
157 CloseHandle (fh);
158 return NULL;
160 size.QuadPart = li.Length.QuadPart;
162 else {
163 /* Regular file. Get the size. */
164 if (!GetFileSizeEx (fh, &size)) {
165 nbdkit_error ("%s: GetFileSizeEx: %lu", filename, GetLastError ());
166 CloseHandle (fh);
167 return NULL;
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
181 is_sparse = false;
182 if (GetFileInformationByHandle (fh, &fileinfo))
183 is_sparse = fileinfo.dwFileAttributes & FILE_ATTRIBUTE_SPARSE_FILE;
185 h = malloc (sizeof *h);
186 if (!h) {
187 nbdkit_error ("malloc: %lu", GetLastError ());
188 CloseHandle (fh);
189 return NULL;
191 h->fh = fh;
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",
197 filename, h->size,
198 readonly ? "true" : "false",
199 is_volume ? "true" : "false",
200 is_sparse ? "true" : "false");
201 return h;
204 static int
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
213 * is r/o.
215 static int
216 winfile_can_flush (void *handle)
218 struct handle *h = handle;
219 return !h->is_readonly;
222 static int
223 winfile_can_trim (void *handle)
225 struct handle *h = handle;
226 return h->is_sparse;
229 static int
230 winfile_can_zero (void *handle)
232 return 1;
235 static int
236 winfile_can_extents (void *handle)
238 struct handle *h = handle;
239 return h->is_sparse;
242 static void
243 winfile_close (void *handle)
245 struct handle *h = handle;
246 CloseHandle (h->fh);
249 static int64_t
250 winfile_get_size (void *handle)
252 struct handle *h = handle;
253 return h->size;
256 static int
257 winfile_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
258 uint32_t flags)
260 struct handle *h = handle;
261 DWORD r;
262 OVERLAPPED ovl;
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 ());
271 return -1;
273 return 0;
276 static int
277 winfile_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset,
278 uint32_t flags)
280 struct handle *h = handle;
281 DWORD r;
282 OVERLAPPED ovl;
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 ());
291 return -1;
294 if (flags & NBDKIT_FLAG_FUA) {
295 if (!FlushFileBuffers (h->fh)) {
296 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
297 return -1;
301 return 0;
304 static int
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 ());
311 return -1;
314 return 0;
317 static int
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;
322 DWORD t;
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 ());
332 return -1;
335 if (flags & NBDKIT_FLAG_FUA) {
336 if (!FlushFileBuffers (h->fh)) {
337 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
338 return -1;
342 return 0;
345 static int
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;
350 DWORD t;
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) {
360 errno = ENOTSUP;
361 return -1;
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) {
368 errno = ENOTSUP;
369 return -1;
371 nbdkit_error ("%s: DeviceIoControl: FSCTL_SET_ZERO_DATA: %lu",
372 filename, GetLastError ());
373 return -1;
376 if (flags & NBDKIT_FLAG_FUA) {
377 if (!FlushFileBuffers (h->fh)) {
378 nbdkit_error ("%s: FlushFileBuffers: %lu", filename, GetLastError ());
379 return -1;
383 return 0;
386 static int
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];
394 DWORD nb, n, i, err;
395 BOOL r;
396 uint64_t last_offset = offset, this_offset, this_length;
398 query.FileOffset.QuadPart = offset;
399 query.Length.QuadPart = count;
401 do {
402 r = DeviceIoControl (h->fh, FSCTL_QUERY_ALLOCATED_RANGES,
403 &query, sizeof query, ranges, sizeof ranges,
404 &nb, NULL);
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",
412 filename, err);
413 return -1;
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)
429 return -1;
431 if (nbdkit_add_extent (extents, this_offset, this_length, 0) == -1)
432 return -1;
433 last_offset = this_offset + this_length;
435 if (req_one)
436 return 0;
438 } while (!r /* && err == ERROR_MORE_DATA (implied by error test above) */);
440 return 0;
443 #define THREAD_MODEL NBDKIT_THREAD_MODEL_PARALLEL
445 static struct nbdkit_plugin plugin = {
446 .name = "file",
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)