1 /* nbdkit
2 * Copyright (C) 2017-2020 Red Hat Inc.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 *
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.
14 *
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.
18 *
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.
31 */
32
33 #include <config.h>
34
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <inttypes.h>
38
39 /* Inlining is broken in the ext2fs header file. Disable it by
40 * defining the following:
41 */
42 #define NO_INLINE_FUNCS
43 #include <ext2fs.h>
44
45 #define NBDKIT_API_VERSION 2
46
47 #include <nbdkit-plugin.h>
48
49 /* Disk image and filename parameters. */
50 static char *disk;
51 static char *file;
52
53 static void
ext2_load(void)54 ext2_load (void)
55 {
56 initialize_ext2_error_table ();
57 }
58
59 static void
ext2_unload(void)60 ext2_unload (void)
61 {
62 free (disk);
63 free (file);
64 }
65
66 static int
ext2_config(const char * key,const char * value)67 ext2_config (const char *key, const char *value)
68 {
69 if (strcmp (key, "disk") == 0) {
70 if (disk != NULL) {
71 nbdkit_error ("disk parameter specified more than once");
72 return -1;
73 }
74 disk = nbdkit_absolute_path (value);
75 if (disk == NULL)
76 return -1;
77 }
78 else if (strcmp (key, "file") == 0) {
79 if (file != NULL) {
80 nbdkit_error ("file parameter specified more than once");
81 return -1;
82 }
83 file = strdup (value);
84 if (file == NULL) {
85 nbdkit_error ("strdup: %m");
86 return -1;
87 }
88 }
89 else {
90 nbdkit_error ("unknown parameter '%s'", key);
91 return -1;
92 }
93
94 return 0;
95 }
96
97 static int
ext2_config_complete(void)98 ext2_config_complete (void)
99 {
100 if (disk == NULL || file == NULL) {
101 nbdkit_error ("you must supply disk=<DISK> and file=<FILE> parameters "
102 "after the plugin name on the command line");
103 return -1;
104 }
105
106 if (file[0] != '/') {
107 nbdkit_error ("the file parameter must refer to an absolute path");
108 return -1;
109 }
110
111 return 0;
112 }
113
114 #define ext2_config_help \
115 "disk=<FILENAME> (required) Raw ext2, ext3 or ext4 filesystem.\n" \
116 "file=<FILENAME> (required) File to serve inside the disk image."
117
118 /* The per-connection handle. */
119 struct handle {
120 ext2_filsys fs; /* Filesystem handle. */
121 ext2_ino_t ino; /* Inode of open file. */
122 ext2_file_t file; /* File handle. */
123 };
124
125 /* Create the per-connection handle. */
126 static void *
ext2_open(int readonly)127 ext2_open (int readonly)
128 {
129 struct handle *h;
130 errcode_t err;
131 int fs_flags;
132 int file_flags;
133 struct ext2_inode inode;
134
135 h = malloc (sizeof *h);
136 if (h == NULL) {
137 nbdkit_error ("malloc: %m");
138 return NULL;
139 }
140
141 fs_flags = 0;
142 #ifdef EXT2_FLAG_64BITS
143 fs_flags |= EXT2_FLAG_64BITS;
144 #endif
145 if (!readonly)
146 fs_flags |= EXT2_FLAG_RW;
147
148 err = ext2fs_open (disk, fs_flags, 0, 0, unix_io_manager, &h->fs);
149 if (err != 0) {
150 nbdkit_error ("%s: open: %s", disk, error_message (err));
151 goto err0;
152 }
153
154 if (strcmp (file, "/") == 0)
155 /* probably gonna fail, but we'll catch it later */
156 h->ino = EXT2_ROOT_INO;
157 else {
158 err = ext2fs_namei (h->fs, EXT2_ROOT_INO, EXT2_ROOT_INO,
159 &file[1], &h->ino);
160 if (err != 0) {
161 nbdkit_error ("%s: %s: namei: %s", disk, file, error_message (err));
162 goto err1;
163 }
164 }
165
166 /* Check the file is a regular file.
167 * XXX This won't follow symlinks, we'd have to do that manually.
168 */
169 err = ext2fs_read_inode (h->fs, h->ino, &inode);
170 if (err != 0) {
171 nbdkit_error ("%s: %s: inode: %s", disk, file, error_message (err));
172 goto err1;
173 }
174 if (!LINUX_S_ISREG (inode.i_mode)) {
175 nbdkit_error ("%s: %s: must be a regular file in the disk image",
176 disk, file);
177 goto err1;
178 }
179
180 file_flags = 0;
181 if (!readonly)
182 file_flags |= EXT2_FILE_WRITE;
183 err = ext2fs_file_open2 (h->fs, h->ino, NULL, file_flags, &h->file);
184 if (err != 0) {
185 nbdkit_error ("%s: %s: open: %s", disk, file, error_message (err));
186 goto err1;
187 }
188
189 return h;
190
191 err1:
192 ext2fs_close (h->fs);
193 err0:
194 free (h);
195 return NULL;
196 }
197
198 /* Free up the per-connection handle. */
199 static void
ext2_close(void * handle)200 ext2_close (void *handle)
201 {
202 struct handle *h = handle;
203
204 ext2fs_file_close (h->file);
205 ext2fs_close (h->fs);
206 free (h);
207 }
208
209 static int
ext2_can_fua(void * handle)210 ext2_can_fua (void *handle)
211 {
212 return NBDKIT_FUA_NATIVE;
213 }
214
215 static int
ext2_can_cache(void * handle)216 ext2_can_cache (void *handle)
217 {
218 /* Let nbdkit call pread to populate the file system cache. */
219 return NBDKIT_CACHE_EMULATE;
220 }
221
222 /* It might be possible to relax this, but it's complicated.
223 *
224 * It's desirable for ‘nbdkit -r’ to behave the same way as
225 * ‘mount -o ro’. But we don't know the state of the readonly flag
226 * until ext2_open is called (because the NBD client can also request
227 * a readonly connection). So we could not set the "ro" flag if we
228 * opened the filesystem any earlier (eg in ext2_config).
229 *
230 * So out of necessity we have one ext2_filsys handle per connection,
231 * but if we allowed parallel work on those handles then we would get
232 * data corruption, so we need to serialize connections.
233 */
234 #define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS
235
236 /* Get the disk size. */
237 static int64_t
ext2_get_size(void * handle)238 ext2_get_size (void *handle)
239 {
240 struct handle *h = handle;
241 errcode_t err;
242 uint64_t size;
243
244 err = ext2fs_file_get_lsize (h->file, (__u64 *) &size);
245 if (err != 0) {
246 nbdkit_error ("%s: %s: lsize: %s", disk, file, error_message (err));
247 return -1;
248 }
249 return (int64_t) size;
250 }
251
252 /* Read data. */
253 static int
ext2_pread(void * handle,void * buf,uint32_t count,uint64_t offset,uint32_t flags)254 ext2_pread (void *handle, void *buf, uint32_t count, uint64_t offset,
255 uint32_t flags)
256 {
257 struct handle *h = handle;
258 errcode_t err;
259 unsigned int got;
260
261 while (count > 0) {
262 /* Although this function weirdly can return the new offset,
263 * examination of the code shows that it never returns anything
264 * different from what we set, so NULL out that parameter.
265 */
266 err = ext2fs_file_llseek (h->file, offset, EXT2_SEEK_SET, NULL);
267 if (err != 0) {
268 nbdkit_error ("%s: %s: llseek: %s", disk, file, error_message (err));
269 return -1;
270 }
271
272 err = ext2fs_file_read (h->file, buf, (unsigned int) count, &got);
273 if (err != 0) {
274 nbdkit_error ("%s: %s: read: %s", disk, file, error_message (err));
275 return -1;
276 }
277
278 buf += got;
279 count -= got;
280 offset += got;
281 }
282
283 return 0;
284 }
285
286 /* Write data to the file. */
287 static int
ext2_pwrite(void * handle,const void * buf,uint32_t count,uint64_t offset,uint32_t flags)288 ext2_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset,
289 uint32_t flags)
290 {
291 struct handle *h = handle;
292 errcode_t err;
293 unsigned int written;
294
295 while (count > 0) {
296 err = ext2fs_file_llseek (h->file, offset, EXT2_SEEK_SET, NULL);
297 if (err != 0) {
298 nbdkit_error ("%s: %s: llseek: %s", disk, file, error_message (err));
299 return -1;
300 }
301
302 err = ext2fs_file_write (h->file, buf, (unsigned int) count, &written);
303 if (err != 0) {
304 nbdkit_error ("%s: %s: write: %s", disk, file, error_message (err));
305 return -1;
306 }
307
308 buf += written;
309 count -= written;
310 offset += written;
311 }
312
313 if ((flags & NBDKIT_FLAG_FUA) != 0) {
314 err = ext2fs_file_flush (h->file);
315 if (err != 0) {
316 nbdkit_error ("%s: %s: flush: %s", disk, file, error_message (err));
317 return -1;
318 }
319 }
320
321 return 0;
322 }
323
324 static int
ext2_flush(void * handle,uint32_t flags)325 ext2_flush (void *handle, uint32_t flags)
326 {
327 struct handle *h = handle;
328 errcode_t err;
329
330 err = ext2fs_file_flush (h->file);
331 if (err != 0) {
332 nbdkit_error ("%s: %s: flush: %s", disk, file, error_message (err));
333 return -1;
334 }
335
336 return 0;
337 }
338
339 /* XXX It seems as if we should be able to support trim and zero, if
340 * we could work out how those are implemented in the ext2fs API which
341 * is very obscure.
342 */
343
344 static struct nbdkit_plugin plugin = {
345 .name = "ext2",
346 .version = PACKAGE_VERSION,
347 .load = ext2_load,
348 .unload = ext2_unload,
349 .config = ext2_config,
350 .config_complete = ext2_config_complete,
351 .config_help = ext2_config_help,
352 .open = ext2_open,
353 .close = ext2_close,
354 .can_fua = ext2_can_fua,
355 .can_cache = ext2_can_cache,
356 .get_size = ext2_get_size,
357 .pread = ext2_pread,
358 .pwrite = ext2_pwrite,
359 .flush = ext2_flush,
360 .errno_is_preserved = 1,
361 };
362
363 NBDKIT_REGISTER_PLUGIN(plugin)
364