Update Red Hat Copyright Notices
[nbdkit.git] / filters / tar / tar.c
blobc650a3499c83190d8c16c6354eda26983cf20ae9
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 <stdbool.h>
38 #include <stdint.h>
39 #include <inttypes.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include <assert.h>
43 #include <sys/types.h>
44 #include <sys/stat.h>
46 #include <pthread.h>
48 #include <nbdkit-filter.h>
50 #include "cleanup.h"
51 #include "poll.h"
52 #include "minmax.h"
53 #include "utils.h"
55 static const char *entry; /* File within tar (tar-entry=...) */
56 static const char *tar_program = "tar";
58 /* Offset and size within tarball.
60 * These are calculated once in the first connection that calls
61 * tar_prepare. They are protected by the lock.
63 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
64 static bool initialized = false;
65 static uint64_t tar_offset, tar_size;
67 static int
68 tar_config (nbdkit_next_config *next, nbdkit_backend *nxdata,
69 const char *key, const char *value)
71 if (strcmp (key, "tar-entry") == 0) {
72 if (entry) {
73 nbdkit_error ("only one tar-entry parameter can be given");
74 return -1;
76 entry = value;
77 return 0;
79 else if (strcmp (key, "tar") == 0) {
80 tar_program = value;
81 return 0;
84 return next (nxdata, key, value);
87 static int
88 tar_config_complete (nbdkit_next_config_complete *next,
89 nbdkit_backend *nxdata)
91 if (entry == NULL) {
92 nbdkit_error ("you must supply the tar-entry=<FILENAME> parameter");
93 return -1;
96 return next (nxdata);
99 #define tar_config_help \
100 "tar-entry=<FILENAME> (required) The path inside the tar file to serve."
102 static int
103 tar_thread_model (void)
105 return NBDKIT_THREAD_MODEL_PARALLEL;
108 struct handle {
109 /* These are copied from the globals during tar_prepare, so that we
110 * don't have to keep grabbing the lock on each request.
112 uint64_t offset, size;
115 static void *
116 tar_open (nbdkit_next_open *next, nbdkit_context *nxdata,
117 int readonly, const char *exportname, int is_tls)
119 struct handle *h;
121 if (next (nxdata, readonly, exportname) == -1)
122 return NULL;
124 h = calloc (1, sizeof *h);
125 if (h == NULL) {
126 nbdkit_error ("calloc: %m");
127 return NULL;
129 return h;
132 static void
133 tar_close (void *handle)
135 free (handle);
138 /* Calculate the offset of the entry within the tarball. This is
139 * called with the lock held. The method used is described here:
140 * https://www.redhat.com/archives/libguestfs/2020-July/msg00017.html
142 static int
143 calculate_offset_of_entry (nbdkit_next *next)
145 const size_t bufsize = 65536;
146 char output[] = "/tmp/tarXXXXXX";
147 int fd;
148 FILE *fp;
149 CLEANUP_FREE char *cmd = NULL;
150 size_t cmdlen = 0;
151 CLEANUP_FREE char *buf = NULL;
152 int64_t i, copysize;
153 bool scanned_ok = false;
155 assert (entry);
157 /* Temporary file to capture the output from the tar command. */
158 fd = mkstemp (output);
159 if (fd == -1) {
160 nbdkit_error ("mkstemp: %m");
161 return -1;
163 close (fd);
165 /* Construct the tar command to examine the tar file. */
166 fp = open_memstream (&cmd, &cmdlen);
167 if (fp == NULL) {
168 nbdkit_error ("open_memstream: %m");
169 return -1;
171 /* https://listman.redhat.com/archives/libguestfs/2021-April/msg00072.html */
172 fprintf (fp, "LANG=C ");
173 shell_quote (tar_program, fp);
174 fprintf (fp, " --no-auto-compress -t --block-number -v -f - ");
175 shell_quote (entry, fp);
176 fprintf (fp, " > ");
177 shell_quote (output, fp);
178 /* Unfortunately we have to hide stderr since we are
179 * expecting tar to warn:
180 * tar: Unexpected EOF in archive
181 * tar: Error is not recoverable: exiting now
182 * when we close the connection abruptly.
184 fprintf (fp, " 2>/dev/null");
185 if (fclose (fp) == EOF) {
186 nbdkit_error ("memstream failed: %m");
187 return -1;
190 /* Prepare the copy buffer and copy size. */
191 buf = malloc (bufsize);
192 if (buf == NULL) {
193 nbdkit_error ("malloc: %m");
194 return -1;
196 copysize = next->get_size (next);
197 if (copysize == -1)
198 return -1;
200 /* Run the tar command. */
201 nbdkit_debug ("%s", cmd);
202 fp = popen (cmd, "w");
203 if (fp == NULL) {
204 nbdkit_error ("tar: %m");
205 return -1;
208 /* Now loop, writing data from the plugin (the tar file) until we
209 * detect that tar has written something to the output file or we
210 * run out of plugin. We're making the assumption that the plugin
211 * is not going to be sparse, which is probably true of most tar
212 * files.
214 for (i = 0; i < copysize; i += bufsize) {
215 int err, r;
216 const int64_t count = MIN (bufsize, copysize-i);
217 int64_t j;
218 struct stat statbuf;
220 r = next->pread (next, buf, count, i, 0, &err);
221 if (r == -1) {
222 errno = err;
223 nbdkit_error ("pread: %m");
224 pclose (fp);
225 return -1;
227 for (j = 0; j < count;) {
228 size_t written = fwrite (&buf[j], 1, count-j, fp);
229 if (written == 0) {
230 nbdkit_error ("tar: error writing to subprocess");
231 pclose (fp);
232 return -1;
234 j += written;
237 /* Did we get something in the output file yet? */
238 if (stat (output, &statbuf) == 0 && statbuf.st_size > 0)
239 break;
241 pclose (fp);
243 /* Open the tar output and try to parse it. */
244 fp = fopen (output, "r");
245 if (fp == NULL) {
246 nbdkit_error ("%s: %m", output);
247 return -1;
249 scanned_ok = fscanf (fp, "block %" SCNu64 ": %*s %*s %" SCNu64,
250 &tar_offset, &tar_size) == 2;
251 fclose (fp);
252 unlink (output);
254 if (!scanned_ok) {
255 nbdkit_error ("tar subcommand failed, "
256 "check that the file really exists in the tarball");
257 return -1;
260 /* Adjust the offset: Add 1 for the tar header, then multiply by the
261 * block size.
263 tar_offset = (tar_offset+1) * 512;
265 nbdkit_debug ("tar: %s found at offset %" PRIu64 ", size %" PRIu64,
266 entry, tar_offset, tar_size);
268 /* Check it looks sensible. XXX We ought to check it doesn't exceed
269 * the size of the tar file.
271 if (tar_offset >= INT64_MAX || tar_size >= INT64_MAX) {
272 nbdkit_error ("internal error: calculated offset and size are wrong");
273 return -1;
276 initialized = true;
278 return 0;
281 static int
282 tar_prepare (nbdkit_next *next,
283 void *handle, int readonly)
285 struct handle *h = handle;
286 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock);
288 if (!initialized) {
289 if (calculate_offset_of_entry (next) == -1)
290 return -1;
293 assert (initialized);
294 assert (tar_offset > 0);
295 h->offset = tar_offset;
296 h->size = tar_size;
297 return 0;
300 /* Description. */
301 static const char *
302 tar_export_description (nbdkit_next *next,
303 void *handle)
305 const char *base = next->export_description (next);
307 if (!base)
308 return NULL;
309 return nbdkit_printf_intern ("embedded %s from within tar file: %s",
310 entry, base);
313 /* Get the file size. */
314 static int64_t
315 tar_get_size (nbdkit_next *next,
316 void *handle)
318 struct handle *h = handle;
319 int64_t size;
321 /* We must call underlying get_size even though we don't use the
322 * result, because it caches the plugin size in server/backend.c.
324 size = next->get_size (next);
325 if (size == -1)
326 return -1;
328 return h->size;
331 /* Read data from the file. */
332 static int
333 tar_pread (nbdkit_next *next,
334 void *handle, void *buf, uint32_t count, uint64_t offs,
335 uint32_t flags, int *err)
337 struct handle *h = handle;
338 return next->pread (next, buf, count, offs + h->offset, flags, err);
341 /* Write data to the file. */
342 static int
343 tar_pwrite (nbdkit_next *next,
344 void *handle, const void *buf, uint32_t count, uint64_t offs,
345 uint32_t flags, int *err)
347 struct handle *h = handle;
348 return next->pwrite (next, buf, count, offs + h->offset, flags, err);
351 /* Trim data. */
352 static int
353 tar_trim (nbdkit_next *next,
354 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
355 int *err)
357 struct handle *h = handle;
358 return next->trim (next, count, offs + h->offset, flags, err);
361 /* Zero data. */
362 static int
363 tar_zero (nbdkit_next *next,
364 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
365 int *err)
367 struct handle *h = handle;
368 return next->zero (next, count, offs + h->offset, flags, err);
371 /* Extents. */
372 static int
373 tar_extents (nbdkit_next *next,
374 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
375 struct nbdkit_extents *extents, int *err)
377 struct handle *h = handle;
378 size_t i;
379 CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 = NULL;
380 struct nbdkit_extent e;
382 extents2 = nbdkit_extents_new (offs + h->offset, h->offset + h->size);
383 if (extents2 == NULL) {
384 *err = errno;
385 return -1;
387 if (next->extents (next, count, offs + h->offset, flags, extents2,
388 err) == -1)
389 return -1;
391 for (i = 0; i < nbdkit_extents_count (extents2); ++i) {
392 e = nbdkit_get_extent (extents2, i);
393 e.offset -= h->offset;
394 if (nbdkit_add_extent (extents, e.offset, e.length, e.type) == -1) {
395 *err = errno;
396 return -1;
399 return 0;
402 /* Cache data. */
403 static int
404 tar_cache (nbdkit_next *next,
405 void *handle, uint32_t count, uint64_t offs, uint32_t flags,
406 int *err)
408 struct handle *h = handle;
409 return next->cache (next, count, offs + h->offset, flags, err);
412 static struct nbdkit_filter filter = {
413 .name = "tar",
414 .longname = "nbdkit tar filter",
415 .config = tar_config,
416 .config_complete = tar_config_complete,
417 .config_help = tar_config_help,
418 .thread_model = tar_thread_model,
419 .open = tar_open,
420 .close = tar_close,
421 .prepare = tar_prepare,
422 .export_description = tar_export_description,
423 .get_size = tar_get_size,
424 .pread = tar_pread,
425 .pwrite = tar_pwrite,
426 .trim = tar_trim,
427 .zero = tar_zero,
428 .extents = tar_extents,
429 .cache = tar_cache,
432 NBDKIT_REGISTER_FILTER (filter)