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
40 /* Inlining is broken in the ext2fs header file. Disable it by
41 * defining the following:
43 #define NO_INLINE_FUNCS
46 #define NBDKIT_API_VERSION 2
48 #include <nbdkit-filter.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
;
61 initialize_ext2_error_table ();
65 ext2_config (nbdkit_next_config
*next
, nbdkit_backend
*nxdata
,
66 const char *key
, const char *value
)
68 if (strcmp (key
, "ext2file") == 0) {
70 nbdkit_error ("ext2file parameter specified more than once");
77 return next (nxdata
, key
, value
);
81 ext2_config_complete (nbdkit_next_config_complete
*next
, nbdkit_backend
*nxdata
)
84 nbdkit_error ("you must supply ext2file=<FILE> parameter "
85 "after the plugin name on the command line");
89 if (strcmp (file
, "exportname") == 0)
91 else if (file
[0] != '/') {
92 nbdkit_error ("the file parameter must refer to an absolute path");
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. */
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. */
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.
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. */
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.
147 /* Otherwise, we don't care about export name, so keeping things at
148 * "" is fine, regardless of the underlying plugin's default.
153 /* Create the per-connection handle. */
155 ext2_open (nbdkit_next_open
*next
, nbdkit_context
*nxdata
,
156 int readonly
, const char *exportname
, int is_tls
)
160 h
= calloc (1, sizeof *h
);
162 nbdkit_error ("calloc: %m");
166 /* Save the client exportname in the handle. */
167 h
->exportname
= nbdkit_strdup_intern (exportname
);
168 if (h
->exportname
== 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) {
188 ext2_prepare (nbdkit_next
*next
, void *handle
, int readonly
)
190 struct handle
*h
= handle
;
194 struct ext2_inode inode
;
196 CLEANUP_FREE
char *name
= NULL
;
197 const char *fname
= file
?: h
->exportname
;
198 CLEANUP_FREE
char *absname
= NULL
;
201 #ifdef EXT2_FLAG_64BITS
202 fs_flags
|= EXT2_FLAG_64BITS
;
204 r
= next
->get_size (next
);
207 r
= next
->can_write (next
);
214 fs_flags
|= EXT2_FLAG_RW
;
217 name
= nbdkit_io_encode (next
);
219 nbdkit_error ("nbdkit_io_encode: %m");
223 if (fname
[0] != '/') {
224 if (asprintf (&absname
, "/%s", fname
) < 0) {
225 nbdkit_error ("asprintf: %m");
231 err
= ext2fs_open (name
, fs_flags
, 0, 0, nbdkit_io_manager
, &h
->fs
);
233 nbdkit_error ("open: %s", error_message (err
));
237 if (strcmp (fname
, "/") == 0)
238 /* probably gonna fail, but we'll catch it later */
239 h
->ino
= EXT2_ROOT_INO
;
241 err
= ext2fs_namei (h
->fs
, EXT2_ROOT_INO
, EXT2_ROOT_INO
,
244 nbdkit_error ("%s: namei: %s", fname
, error_message (err
));
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
);
254 nbdkit_error ("%s: inode: %s", fname
, error_message (err
));
257 if (!LINUX_S_ISREG (inode
.i_mode
)) {
258 nbdkit_error ("%s: must be a regular file in the disk image", fname
);
264 file_flags
|= EXT2_FILE_WRITE
;
265 err
= ext2fs_file_open2 (h
->fs
, h
->ino
, NULL
, file_flags
, &h
->file
);
267 nbdkit_error ("%s: open: %s", fname
, error_message (err
));
274 ext2fs_close (h
->fs
);
280 /* Free up the per-connection handle. */
282 ext2_close (void *handle
)
284 struct handle
*h
= handle
;
287 ext2fs_file_close (h
->file
);
288 ext2fs_close (h
->fs
);
294 ext2_can_fua (nbdkit_next
*next
, void *handle
)
296 return NBDKIT_FUA_NATIVE
;
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
;
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.
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)
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.
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)
345 return NBDKIT_ZERO_EMULATE
;
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)
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
;
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
);
389 return nbdkit_printf_intern ("embedded '%s%s' from within ext2 image: %s",
393 /* Get the disk size. */
395 ext2_get_size (nbdkit_next
*next
, void *handle
)
397 struct handle
*h
= handle
;
401 err
= ext2fs_file_get_lsize (h
->file
, (__u64
*) &size
);
403 nbdkit_error ("%s: lsize: %s", file
, error_message (err
));
406 return (int64_t) size
;
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
;
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
);
426 nbdkit_error ("%s: llseek: %s", file
, error_message (err
));
431 err
= ext2fs_file_read (h
->file
, buf
, (unsigned int) count
, &got
);
433 nbdkit_error ("%s: read: %s", file
, error_message (err
));
446 /* Write data to the file. */
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
;
454 unsigned int written
;
457 err
= ext2fs_file_llseek (h
->file
, offset
, EXT2_SEEK_SET
, NULL
);
459 nbdkit_error ("%s: llseek: %s", file
, error_message (err
));
464 err
= ext2fs_file_write (h
->file
, buf
, (unsigned int) count
, &written
);
466 nbdkit_error ("%s: write: %s", file
, error_message (err
));
476 if ((flags
& NBDKIT_FLAG_FUA
) != 0) {
477 err
= ext2fs_file_flush (h
->file
);
479 nbdkit_error ("%s: flush: %s", file
, error_message (err
));
489 ext2_flush (nbdkit_next
*next
,
490 void *handle
, uint32_t flags
, int *errp
)
492 struct handle
*h
= handle
;
495 err
= ext2fs_file_flush (h
->file
);
497 nbdkit_error ("%s: flush: %s", file
, error_message (err
));
505 static struct nbdkit_filter filter
= {
507 .longname
= "nbdkit ext2 filter",
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
,
516 .prepare
= ext2_prepare
,
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
,
527 .pwrite
= ext2_pwrite
,
531 NBDKIT_REGISTER_FILTER (filter
)