Update Red Hat Copyright Notices
[nbdkit.git] / filters / ext2 / ext2.c
blobf9858ca40d157edbb485e162130994021d9a5a01
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 #include <config.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <inttypes.h>
38 #include <errno.h>
40 /* Inlining is broken in the ext2fs header file. Disable it by
41 * defining the following:
43 #define NO_INLINE_FUNCS
44 #include <ext2fs.h>
46 #define NBDKIT_API_VERSION 2
48 #include <nbdkit-filter.h>
50 #include "cleanup.h"
51 #include "io.h"
53 /* Filename parameter, or NULL to honor export name. Using the export
54 * name is opt-in (see ext2_config_complete).
56 static const char *file;
58 static void
59 ext2_load (void)
61 initialize_ext2_error_table ();
64 static int
65 ext2_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
66 const char *key, const char *value)
68 if (strcmp (key, "ext2file") == 0) {
69 if (file != NULL) {
70 nbdkit_error ("ext2file parameter specified more than once");
71 return -1;
73 file = value;
74 return 0;
76 else
77 return next (nxdata, key, value);
80 static int
81 ext2_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata)
83 if (file == NULL) {
84 nbdkit_error ("you must supply ext2file=<FILE> parameter "
85 "after the plugin name on the command line");
86 return -1;
89 if (strcmp (file, "exportname") == 0)
90 file = NULL;
91 else if (file[0] != '/') {
92 nbdkit_error ("the file parameter must refer to an absolute path");
93 return -1;
96 return next (nxdata);
99 #define ext2_config_help \
100 "ext2file=<FILENAME> (required) Absolute name of file to serve inside the\n" \
101 " disk image, or 'exportname' for client choice."
103 /* The per-connection handle. */
104 struct handle {
105 const char *exportname; /* Client export name. */
106 ext2_filsys fs; /* Filesystem handle. */
107 ext2_ino_t ino; /* Inode of open file. */
108 ext2_file_t file; /* File handle. */
109 nbdkit_next *next; /* "name" parameter to ext2fs_open. */
112 /* Export list. */
113 static int
114 ext2_list_exports (nbdkit_next_list_exports *next, nbdkit_backend *nxdata,
115 int readonly, int is_tls, struct nbdkit_exports *exports)
117 /* If we are honoring export names, the default export "" won't
118 * work, and we must not leak export names from the underlying
119 * plugin. Advertising all filenames within the ext2 image could be
120 * huge, and even if we wanted to, it would require that we could
121 * open the plugin prior to the client reaching our .open. So leave
122 * the list empty instead.
124 if (!file)
125 return 0;
127 /* If we are serving a specific ext2file, we don't care what export
128 * name the user passes, but the underlying plugin might; there's no
129 * harm in advertising that list.
131 return next (nxdata, readonly, exports);
134 /* Default export. */
135 static const char *
136 ext2_default_export (nbdkit_next_default_export *next, nbdkit_backend *nxdata,
137 int readonly, int is_tls)
139 /* If we are honoring exports, "" will fail (even if we resolve to
140 * the inode of embedded "/", we can't serve directories), and we
141 * don't really have a sane default. XXX picking the largest
142 * embedded file might be in interesting knob to add.
144 if (!file)
145 return NULL;
147 /* Otherwise, we don't care about export name, so keeping things at
148 * "" is fine, regardless of the underlying plugin's default.
150 return "";
153 /* Create the per-connection handle. */
154 static void *
155 ext2_open (nbdkit_next_open *next, nbdkit_context *nxdata,
156 int readonly, const char *exportname, int is_tls)
158 struct handle *h;
160 h = calloc (1, sizeof *h);
161 if (h == NULL) {
162 nbdkit_error ("calloc: %m");
163 return NULL;
166 /* Save the client exportname in the handle. */
167 h->exportname = nbdkit_strdup_intern (exportname);
168 if (h->exportname == NULL) {
169 free (h);
170 return NULL;
173 /* If file == NULL (ie. using exportname) then don't
174 * pass the client exportname to the lower layers.
176 exportname = file ? exportname : "";
178 /* Request write access to the underlying plugin, for journal replay. */
179 if (next (nxdata, 0, exportname) == -1) {
180 free (h);
181 return NULL;
184 return h;
187 static int
188 ext2_prepare (nbdkit_next *next, void *handle, int readonly)
190 struct handle *h = handle;
191 errcode_t err;
192 int fs_flags;
193 int file_flags;
194 struct ext2_inode inode;
195 int64_t r;
196 CLEANUP_FREE char *name = NULL;
197 const char *fname = file ?: h->exportname;
198 CLEANUP_FREE char *absname = NULL;
200 fs_flags = 0;
201 #ifdef EXT2_FLAG_64BITS
202 fs_flags |= EXT2_FLAG_64BITS;
203 #endif
204 r = next->get_size (next);
205 if (r == -1)
206 return -1;
207 r = next->can_write (next);
208 if (r == -1)
209 return -1;
210 if (r == 0)
211 readonly = 1;
213 if (!readonly)
214 fs_flags |= EXT2_FLAG_RW;
216 h->next = next;
217 name = nbdkit_io_encode (next);
218 if (!name) {
219 nbdkit_error ("nbdkit_io_encode: %m");
220 return -1;
223 if (fname[0] != '/') {
224 if (asprintf (&absname, "/%s", fname) < 0) {
225 nbdkit_error ("asprintf: %m");
226 return -1;
228 fname = absname;
231 err = ext2fs_open (name, fs_flags, 0, 0, nbdkit_io_manager, &h->fs);
232 if (err != 0) {
233 nbdkit_error ("open: %s", error_message (err));
234 goto err0;
237 if (strcmp (fname, "/") == 0)
238 /* probably gonna fail, but we'll catch it later */
239 h->ino = EXT2_ROOT_INO;
240 else {
241 err = ext2fs_namei (h->fs, EXT2_ROOT_INO, EXT2_ROOT_INO,
242 &fname[1], &h->ino);
243 if (err != 0) {
244 nbdkit_error ("%s: namei: %s", fname, error_message (err));
245 goto err1;
249 /* Check that fname is a regular file.
250 * XXX This won't follow symlinks, we'd have to do that manually.
252 err = ext2fs_read_inode (h->fs, h->ino, &inode);
253 if (err != 0) {
254 nbdkit_error ("%s: inode: %s", fname, error_message (err));
255 goto err1;
257 if (!LINUX_S_ISREG (inode.i_mode)) {
258 nbdkit_error ("%s: must be a regular file in the disk image", fname);
259 goto err1;
262 file_flags = 0;
263 if (!readonly)
264 file_flags |= EXT2_FILE_WRITE;
265 err = ext2fs_file_open2 (h->fs, h->ino, NULL, file_flags, &h->file);
266 if (err != 0) {
267 nbdkit_error ("%s: open: %s", fname, error_message (err));
268 goto err1;
271 return 0;
273 err1:
274 ext2fs_close (h->fs);
275 h->fs = NULL;
276 err0:
277 return -1;
280 /* Free up the per-connection handle. */
281 static void
282 ext2_close (void *handle)
284 struct handle *h = handle;
286 if (h->fs) {
287 ext2fs_file_close (h->file);
288 ext2fs_close (h->fs);
290 free (h);
293 static int
294 ext2_can_fua (nbdkit_next *next, void *handle)
296 return NBDKIT_FUA_NATIVE;
299 static int
300 ext2_can_cache (nbdkit_next *next, void *handle)
302 /* Let nbdkit call pread to populate the file system cache. */
303 return NBDKIT_CACHE_EMULATE;
306 static int
307 ext2_can_multi_conn (nbdkit_next *next, void *handle)
309 /* Since we do not permit parallel connections, it does not matter
310 * what we advertise here, and we could just as easily inherit the
311 * plugin's .can_multi_conn. But realistically, if we adjust
312 * .thread_model, we cannot advertise support unless .flush is
313 * consistent, and that would require inspecting the ext2 source
314 * code, so for now, we hard-code a safe answer.
316 return 0;
319 static int
320 ext2_can_flush (nbdkit_next *next, void *handle)
322 /* Regardless of the underlying plugin, we handle flush at the level
323 * of the filesystem. However, we also need to cache the underlying
324 * plugin ability, since ext2 wants to flush the filesystem into
325 * permanent storage when possible.
327 if (next->can_flush (next) == -1)
328 return -1;
329 return 1;
332 /* XXX It seems as if we should be able to support trim and zero, if
333 * the ext2fs API were to ever add something like ext2fs_file_fallocate.
335 static int
336 ext2_can_zero (nbdkit_next *next, void *handle)
338 /* For now, tell nbdkit to call .pwrite instead of any optimization.
339 * However, we also want to cache the underlying plugin support - even
340 * though we don't implement .zero, the file system wants to know if
341 * it can use next->zero() during io_zeroout.
343 if (next->can_zero (next) == -1)
344 return -1;
345 return NBDKIT_ZERO_EMULATE;
348 static int
349 ext2_can_trim (nbdkit_next *next, void *handle)
351 /* For now, tell nbdkit to never call .trim. However, we also want
352 * to cache the underlying plugin support - even though we don't
353 * implement .trim, the file system wants to know if it can use
354 * next->trim() during io_discard.
356 if (next->can_trim (next) == -1)
357 return -1;
358 return 0;
361 /* It might be possible to relax this, but it's complicated.
363 * It's desirable for ‘nbdkit -r’ to behave the same way as
364 * ‘mount -o ro’. But we don't know the state of the readonly flag
365 * until ext2_open is called (because the NBD client can also request
366 * a readonly connection). So we could not set the "ro" flag if we
367 * opened the filesystem any earlier (eg in ext2_config).
369 * So out of necessity we have one ext2_filsys handle per connection,
370 * but if we allowed parallel work on those handles then we would get
371 * data corruption, so we need to serialize connections.
373 static int ext2_thread_model (void)
375 return NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS;
378 /* Description. */
379 static const char *
380 ext2_export_description (nbdkit_next *next, void *handle)
382 struct handle *h = handle;
383 const char *fname = file ?: h->exportname;
384 const char *slash = fname[0] == '/' ? "" : "/";
385 const char *base = next->export_description (next);
387 if (!base)
388 return NULL;
389 return nbdkit_printf_intern ("embedded '%s%s' from within ext2 image: %s",
390 slash, fname, base);
393 /* Get the disk size. */
394 static int64_t
395 ext2_get_size (nbdkit_next *next, void *handle)
397 struct handle *h = handle;
398 errcode_t err;
399 uint64_t size;
401 err = ext2fs_file_get_lsize (h->file, (__u64 *) &size);
402 if (err != 0) {
403 nbdkit_error ("%s: lsize: %s", file, error_message (err));
404 return -1;
406 return (int64_t) size;
409 /* Read data. */
410 static int
411 ext2_pread (nbdkit_next *next,
412 void *handle, void *buf, uint32_t count, uint64_t offset,
413 uint32_t flags, int *errp)
415 struct handle *h = handle;
416 errcode_t err;
417 unsigned int got;
419 while (count > 0) {
420 /* Although this function weirdly can return the new offset,
421 * examination of the code shows that it never returns anything
422 * different from what we set, so NULL out that parameter.
424 err = ext2fs_file_llseek (h->file, offset, EXT2_SEEK_SET, NULL);
425 if (err != 0) {
426 nbdkit_error ("%s: llseek: %s", file, error_message (err));
427 *errp = errno;
428 return -1;
431 err = ext2fs_file_read (h->file, buf, (unsigned int) count, &got);
432 if (err != 0) {
433 nbdkit_error ("%s: read: %s", file, error_message (err));
434 *errp = errno;
435 return -1;
438 buf += got;
439 count -= got;
440 offset += got;
443 return 0;
446 /* Write data to the file. */
447 static int
448 ext2_pwrite (nbdkit_next *next,
449 void *handle, const void *buf, uint32_t count, uint64_t offset,
450 uint32_t flags, int *errp)
452 struct handle *h = handle;
453 errcode_t err;
454 unsigned int written;
456 while (count > 0) {
457 err = ext2fs_file_llseek (h->file, offset, EXT2_SEEK_SET, NULL);
458 if (err != 0) {
459 nbdkit_error ("%s: llseek: %s", file, error_message (err));
460 *errp = errno;
461 return -1;
464 err = ext2fs_file_write (h->file, buf, (unsigned int) count, &written);
465 if (err != 0) {
466 nbdkit_error ("%s: write: %s", file, error_message (err));
467 *errp = errno;
468 return -1;
471 buf += written;
472 count -= written;
473 offset += written;
476 if ((flags & NBDKIT_FLAG_FUA) != 0) {
477 err = ext2fs_file_flush (h->file);
478 if (err != 0) {
479 nbdkit_error ("%s: flush: %s", file, error_message (err));
480 *errp = errno;
481 return -1;
485 return 0;
488 static int
489 ext2_flush (nbdkit_next *next,
490 void *handle, uint32_t flags, int *errp)
492 struct handle *h = handle;
493 errcode_t err;
495 err = ext2fs_file_flush (h->file);
496 if (err != 0) {
497 nbdkit_error ("%s: flush: %s", file, error_message (err));
498 *errp = errno;
499 return -1;
502 return 0;
505 static struct nbdkit_filter filter = {
506 .name = "ext2",
507 .longname = "nbdkit ext2 filter",
508 .load = ext2_load,
509 .config = ext2_config,
510 .config_complete = ext2_config_complete,
511 .config_help = ext2_config_help,
512 .thread_model = ext2_thread_model,
513 .list_exports = ext2_list_exports,
514 .default_export = ext2_default_export,
515 .open = ext2_open,
516 .prepare = ext2_prepare,
517 .close = ext2_close,
518 .can_fua = ext2_can_fua,
519 .can_cache = ext2_can_cache,
520 .can_multi_conn = ext2_can_multi_conn,
521 .can_zero = ext2_can_zero,
522 .can_trim = ext2_can_trim,
523 .can_flush = ext2_can_flush,
524 .export_description = ext2_export_description,
525 .get_size = ext2_get_size,
526 .pread = ext2_pread,
527 .pwrite = ext2_pwrite,
528 .flush = ext2_flush,
531 NBDKIT_REGISTER_FILTER (filter)