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
43 #include <sys/types.h>
48 #include <nbdkit-filter.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
;
68 tar_config (nbdkit_next_config
*next
, nbdkit_backend
*nxdata
,
69 const char *key
, const char *value
)
71 if (strcmp (key
, "tar-entry") == 0) {
73 nbdkit_error ("only one tar-entry parameter can be given");
79 else if (strcmp (key
, "tar") == 0) {
84 return next (nxdata
, key
, value
);
88 tar_config_complete (nbdkit_next_config_complete
*next
,
89 nbdkit_backend
*nxdata
)
92 nbdkit_error ("you must supply the tar-entry=<FILENAME> parameter");
99 #define tar_config_help \
100 "tar-entry=<FILENAME> (required) The path inside the tar file to serve."
103 tar_thread_model (void)
105 return NBDKIT_THREAD_MODEL_PARALLEL
;
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
;
116 tar_open (nbdkit_next_open
*next
, nbdkit_context
*nxdata
,
117 int readonly
, const char *exportname
, int is_tls
)
121 if (next (nxdata
, readonly
, exportname
) == -1)
124 h
= calloc (1, sizeof *h
);
126 nbdkit_error ("calloc: %m");
133 tar_close (void *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
143 calculate_offset_of_entry (nbdkit_next
*next
)
145 const size_t bufsize
= 65536;
146 char output
[] = "/tmp/tarXXXXXX";
149 CLEANUP_FREE
char *cmd
= NULL
;
151 CLEANUP_FREE
char *buf
= NULL
;
153 bool scanned_ok
= false;
157 /* Temporary file to capture the output from the tar command. */
158 fd
= mkstemp (output
);
160 nbdkit_error ("mkstemp: %m");
165 /* Construct the tar command to examine the tar file. */
166 fp
= open_memstream (&cmd
, &cmdlen
);
168 nbdkit_error ("open_memstream: %m");
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
);
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");
190 /* Prepare the copy buffer and copy size. */
191 buf
= malloc (bufsize
);
193 nbdkit_error ("malloc: %m");
196 copysize
= next
->get_size (next
);
200 /* Run the tar command. */
201 nbdkit_debug ("%s", cmd
);
202 fp
= popen (cmd
, "w");
204 nbdkit_error ("tar: %m");
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
214 for (i
= 0; i
< copysize
; i
+= bufsize
) {
216 const int64_t count
= MIN (bufsize
, copysize
-i
);
220 r
= next
->pread (next
, buf
, count
, i
, 0, &err
);
223 nbdkit_error ("pread: %m");
227 for (j
= 0; j
< count
;) {
228 size_t written
= fwrite (&buf
[j
], 1, count
-j
, fp
);
230 nbdkit_error ("tar: error writing to subprocess");
237 /* Did we get something in the output file yet? */
238 if (stat (output
, &statbuf
) == 0 && statbuf
.st_size
> 0)
243 /* Open the tar output and try to parse it. */
244 fp
= fopen (output
, "r");
246 nbdkit_error ("%s: %m", output
);
249 scanned_ok
= fscanf (fp
, "block %" SCNu64
": %*s %*s %" SCNu64
,
250 &tar_offset
, &tar_size
) == 2;
255 nbdkit_error ("tar subcommand failed, "
256 "check that the file really exists in the tarball");
260 /* Adjust the offset: Add 1 for the tar header, then multiply by the
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");
282 tar_prepare (nbdkit_next
*next
,
283 void *handle
, int readonly
)
285 struct handle
*h
= handle
;
286 ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock
);
289 if (calculate_offset_of_entry (next
) == -1)
293 assert (initialized
);
294 assert (tar_offset
> 0);
295 h
->offset
= tar_offset
;
302 tar_export_description (nbdkit_next
*next
,
305 const char *base
= next
->export_description (next
);
309 return nbdkit_printf_intern ("embedded %s from within tar file: %s",
313 /* Get the file size. */
315 tar_get_size (nbdkit_next
*next
,
318 struct handle
*h
= handle
;
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
);
331 /* Read data from the file. */
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. */
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
);
353 tar_trim (nbdkit_next
*next
,
354 void *handle
, uint32_t count
, uint64_t offs
, uint32_t flags
,
357 struct handle
*h
= handle
;
358 return next
->trim (next
, count
, offs
+ h
->offset
, flags
, err
);
363 tar_zero (nbdkit_next
*next
,
364 void *handle
, uint32_t count
, uint64_t offs
, uint32_t flags
,
367 struct handle
*h
= handle
;
368 return next
->zero (next
, count
, offs
+ h
->offset
, flags
, err
);
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
;
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
) {
387 if (next
->extents (next
, count
, offs
+ h
->offset
, flags
, extents2
,
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) {
404 tar_cache (nbdkit_next
*next
,
405 void *handle
, uint32_t count
, uint64_t offs
, uint32_t flags
,
408 struct handle
*h
= handle
;
409 return next
->cache (next
, count
, offs
+ h
->offset
, flags
, err
);
412 static struct nbdkit_filter filter
= {
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
,
421 .prepare
= tar_prepare
,
422 .export_description
= tar_export_description
,
423 .get_size
= tar_get_size
,
425 .pwrite
= tar_pwrite
,
428 .extents
= tar_extents
,
432 NBDKIT_REGISTER_FILTER (filter
)