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