vndcompress.c revision 1.8 1 1.8 riastrad /* $NetBSD: vndcompress.c,v 1.8 2013/05/03 23:28:15 riastradh Exp $ */
2 1.1 hubertf
3 1.8 riastrad /*-
4 1.8 riastrad * Copyright (c) 2013 The NetBSD Foundation, Inc.
5 1.1 hubertf * All rights reserved.
6 1.1 hubertf *
7 1.8 riastrad * This code is derived from software contributed to The NetBSD Foundation
8 1.8 riastrad * by Taylor R. Campbell.
9 1.8 riastrad *
10 1.1 hubertf * Redistribution and use in source and binary forms, with or without
11 1.1 hubertf * modification, are permitted provided that the following conditions
12 1.1 hubertf * are met:
13 1.1 hubertf * 1. Redistributions of source code must retain the above copyright
14 1.1 hubertf * notice, this list of conditions and the following disclaimer.
15 1.1 hubertf * 2. Redistributions in binary form must reproduce the above copyright
16 1.1 hubertf * notice, this list of conditions and the following disclaimer in the
17 1.1 hubertf * documentation and/or other materials provided with the distribution.
18 1.1 hubertf *
19 1.8 riastrad * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 1.8 riastrad * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 1.1 hubertf * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 1.1 hubertf * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 1.1 hubertf * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 1.1 hubertf * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 1.1 hubertf * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 1.1 hubertf * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 1.1 hubertf * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 1.1 hubertf * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 1.1 hubertf * POSSIBILITY OF SUCH DAMAGE.
30 1.1 hubertf */
31 1.8 riastrad
32 1.8 riastrad #include <sys/cdefs.h>
33 1.8 riastrad __RCSID("$NetBSD: vndcompress.c,v 1.8 2013/05/03 23:28:15 riastradh Exp $");
34 1.8 riastrad
35 1.8 riastrad #include <sys/endian.h>
36 1.8 riastrad
37 1.8 riastrad #include <assert.h>
38 1.1 hubertf #include <err.h>
39 1.8 riastrad #include <errno.h>
40 1.1 hubertf #include <fcntl.h>
41 1.5 lukem #include <inttypes.h>
42 1.8 riastrad #include <limits.h>
43 1.8 riastrad #include <signal.h>
44 1.8 riastrad #include <stdbool.h>
45 1.8 riastrad #include <stdint.h>
46 1.1 hubertf #include <stdio.h>
47 1.1 hubertf #include <stdlib.h>
48 1.1 hubertf #include <string.h>
49 1.1 hubertf #include <unistd.h>
50 1.1 hubertf #include <zlib.h>
51 1.1 hubertf
52 1.8 riastrad /* XXX Seems to be missing from <stdio.h>... */
53 1.8 riastrad int snprintf_ss(char *restrict, size_t, const char *restrict, ...)
54 1.8 riastrad __printflike(3, 4);
55 1.8 riastrad int vsnprintf_ss(char *restrict, size_t, const char *restrict, va_list);
56 1.8 riastrad
57 1.8 riastrad #include "common.h"
58 1.8 riastrad
59 1.8 riastrad /*
60 1.8 riastrad * XXX Switch to control bug-for-bug byte-for-byte compatibility with
61 1.8 riastrad * NetBSD's vndcompress.
62 1.8 riastrad */
63 1.8 riastrad #define VNDCOMPRESS_COMPAT 0
64 1.8 riastrad
65 1.8 riastrad __CTASSERT(sizeof(struct cloop2_header) == CLOOP2_OFFSET_TABLE_OFFSET);
66 1.8 riastrad
67 1.8 riastrad struct compress_state {
68 1.8 riastrad uint64_t size; /* uncompressed size */
69 1.8 riastrad uint64_t offset; /* output byte offset */
70 1.8 riastrad uint32_t blocksize; /* bytes per block */
71 1.8 riastrad uint32_t blkno; /* input block number */
72 1.8 riastrad uint32_t n_full_blocks; /* floor(size/blocksize) */
73 1.8 riastrad uint32_t n_blocks; /* ceiling(size/blocksize) */
74 1.8 riastrad uint32_t n_offsets; /* n_blocks + 1 */
75 1.8 riastrad uint32_t end_block; /* last block to transfer */
76 1.8 riastrad uint32_t checkpoint_blocks; /* blocks before checkpoint */
77 1.8 riastrad int image_fd;
78 1.8 riastrad int cloop2_fd;
79 1.8 riastrad uint64_t *offset_table;
80 1.8 riastrad uint32_t n_checkpointed_blocks;
81 1.8 riastrad volatile sig_atomic_t
82 1.8 riastrad initialized; /* everything above initialized? */
83 1.8 riastrad };
84 1.8 riastrad
85 1.8 riastrad /* Global compression state for SIGINFO handler. */
86 1.8 riastrad static struct compress_state global_state;
87 1.8 riastrad
88 1.8 riastrad struct sigdesc {
89 1.8 riastrad int sd_signo;
90 1.8 riastrad const char *sd_name;
91 1.8 riastrad };
92 1.1 hubertf
93 1.8 riastrad static const struct sigdesc info_signals[] = {
94 1.8 riastrad { SIGINFO, "SIGINFO" },
95 1.8 riastrad { SIGUSR1, "SIGUSR1" },
96 1.1 hubertf };
97 1.1 hubertf
98 1.8 riastrad static const struct sigdesc checkpoint_signals[] = {
99 1.8 riastrad { SIGUSR2, "SIGUSR2" },
100 1.8 riastrad };
101 1.8 riastrad
102 1.8 riastrad static void init_signals(void);
103 1.8 riastrad static void init_signal_handler(int, const struct sigdesc *, size_t,
104 1.8 riastrad void (*)(int));
105 1.8 riastrad static void info_signal_handler(int);
106 1.8 riastrad static void checkpoint_signal_handler(int);
107 1.8 riastrad static void block_signals(sigset_t *);
108 1.8 riastrad static void restore_sigmask(const sigset_t *);
109 1.8 riastrad static void compress_progress(struct compress_state *);
110 1.8 riastrad static void compress_init(int, char **, const struct options *,
111 1.8 riastrad struct compress_state *);
112 1.8 riastrad static bool compress_restart(struct compress_state *);
113 1.8 riastrad static uint32_t compress_block(int, int, uint32_t, uint32_t, uint32_t, void *,
114 1.8 riastrad void *);
115 1.8 riastrad static void compress_maybe_checkpoint(struct compress_state *);
116 1.8 riastrad static void compress_checkpoint(struct compress_state *);
117 1.8 riastrad static void compress_exit(struct compress_state *);
118 1.8 riastrad static ssize_t read_block(int, void *, size_t);
119 1.8 riastrad static void err_ss(int, const char *);
120 1.8 riastrad static void errx_ss(int, const char *, ...) __printflike(2, 3);
121 1.8 riastrad static void warn_ss(const char *);
122 1.8 riastrad static void warnx_ss(const char *, ...) __printflike(1, 2);
123 1.8 riastrad static void vwarnx_ss(const char *, va_list);
124 1.8 riastrad
125 1.1 hubertf /*
126 1.8 riastrad * Compression entry point.
127 1.1 hubertf */
128 1.8 riastrad int
129 1.8 riastrad vndcompress(int argc, char **argv, const struct options *O)
130 1.8 riastrad {
131 1.8 riastrad struct compress_state *const S = &global_state;
132 1.8 riastrad
133 1.8 riastrad /* Paranoia. The other fields either have no sentinel or use zero. */
134 1.8 riastrad S->image_fd = -1;
135 1.8 riastrad S->cloop2_fd = -1;
136 1.8 riastrad
137 1.8 riastrad /* Set up signal handlers so we can handle SIGINFO ASAP. */
138 1.8 riastrad init_signals();
139 1.8 riastrad
140 1.8 riastrad /*
141 1.8 riastrad * Parse the arguments to initialize our state.
142 1.8 riastrad */
143 1.8 riastrad compress_init(argc, argv, O, S);
144 1.8 riastrad assert(MIN_BLOCKSIZE <= S->blocksize);
145 1.8 riastrad assert(S->blocksize <= MAX_BLOCKSIZE);
146 1.8 riastrad assert(S->offset_table != NULL);
147 1.8 riastrad assert(S->n_offsets > 0);
148 1.8 riastrad assert(S->offset_table[0] == htobe64(sizeof(struct cloop2_header) +
149 1.8 riastrad (S->n_offsets * sizeof(uint64_t))));
150 1.8 riastrad
151 1.8 riastrad /*
152 1.8 riastrad * Allocate compression buffers.
153 1.8 riastrad *
154 1.8 riastrad * Compression may actually expand. From an overabundance of
155 1.8 riastrad * caution, assume it can expand by at most double.
156 1.8 riastrad *
157 1.8 riastrad * XXX Check and consider tightening this assumption.
158 1.8 riastrad */
159 1.8 riastrad __CTASSERT(MAX_BLOCKSIZE <= SIZE_MAX);
160 1.8 riastrad void *const uncompbuf = malloc(S->blocksize);
161 1.8 riastrad if (uncompbuf == NULL)
162 1.8 riastrad err(1, "malloc uncompressed buffer");
163 1.8 riastrad
164 1.8 riastrad /* XXX compression ratio bound */
165 1.8 riastrad __CTASSERT(MAX_BLOCKSIZE <= (SIZE_MAX / 2));
166 1.8 riastrad void *const compbuf = malloc(2 * (size_t)S->blocksize);
167 1.8 riastrad if (compbuf == NULL)
168 1.8 riastrad err(1, "malloc compressed buffer");
169 1.8 riastrad
170 1.8 riastrad /*
171 1.8 riastrad * Compress the blocks. S->blkno specifies the input block
172 1.8 riastrad * we're about to transfer. S->offset is the current output
173 1.8 riastrad * offset.
174 1.8 riastrad */
175 1.8 riastrad while (S->blkno < S->n_blocks) {
176 1.8 riastrad /* Report any progress. */
177 1.8 riastrad compress_progress(S);
178 1.8 riastrad
179 1.8 riastrad /* Stop if we've done the requested partial transfer. */
180 1.8 riastrad if ((0 < S->end_block) && (S->end_block <= S->blkno))
181 1.8 riastrad goto out;
182 1.8 riastrad
183 1.8 riastrad /* Checkpoint if appropriate. */
184 1.8 riastrad compress_maybe_checkpoint(S);
185 1.8 riastrad
186 1.8 riastrad /* Choose read size: partial if last block, full if not. */
187 1.8 riastrad const uint32_t readsize = (S->blkno == S->n_full_blocks?
188 1.8 riastrad (S->size % S->blocksize) : S->blocksize);
189 1.8 riastrad assert(readsize > 0);
190 1.8 riastrad assert(readsize <= S->blocksize);
191 1.8 riastrad
192 1.8 riastrad /* Fail noisily if we might be about to overflow. */
193 1.8 riastrad /* XXX compression ratio bound */
194 1.8 riastrad __CTASSERT(MAX_BLOCKSIZE <= (UINTMAX_MAX / 2));
195 1.8 riastrad assert(S->offset <= MIN(UINT64_MAX, OFF_MAX));
196 1.8 riastrad if ((2 * (uintmax_t)readsize) >
197 1.8 riastrad (MIN(UINT64_MAX, OFF_MAX) - S->offset))
198 1.8 riastrad errx(1, "blkno %"PRIu32" may overflow: %ju + 2*%ju",
199 1.8 riastrad S->blkno, (uintmax_t)S->offset,
200 1.8 riastrad (uintmax_t)readsize);
201 1.8 riastrad
202 1.8 riastrad /* Process the block. */
203 1.8 riastrad const uint32_t complen =
204 1.8 riastrad compress_block(S->image_fd, S->cloop2_fd, S->blkno,
205 1.8 riastrad S->blocksize, readsize, uncompbuf, compbuf);
206 1.8 riastrad
207 1.8 riastrad /*
208 1.8 riastrad * Signal-atomically update the state to reflect
209 1.8 riastrad * (a) what block number we are now at,
210 1.8 riastrad * (b) how far we are now in the output file, and
211 1.8 riastrad * (c) where the last block ended.
212 1.8 riastrad */
213 1.8 riastrad assert(S->blkno <= (UINT32_MAX - 1));
214 1.8 riastrad assert(complen <= (MIN(UINT64_MAX, OFF_MAX) - S->offset));
215 1.8 riastrad assert((S->blkno + 1) < S->n_offsets);
216 1.8 riastrad {
217 1.8 riastrad sigset_t old_sigmask;
218 1.8 riastrad block_signals(&old_sigmask);
219 1.8 riastrad S->blkno += 1; /* (a) */
220 1.8 riastrad S->offset += complen; /* (b) */
221 1.8 riastrad S->offset_table[S->blkno] = htobe64(S->offset); /* (c) */
222 1.8 riastrad restore_sigmask(&old_sigmask);
223 1.8 riastrad }
224 1.8 riastrad }
225 1.8 riastrad
226 1.8 riastrad /* Make sure we're all done. */
227 1.8 riastrad assert(S->blkno == S->n_blocks);
228 1.8 riastrad assert((S->blkno + 1) == S->n_offsets);
229 1.8 riastrad
230 1.8 riastrad /* Pad to the disk block size. */
231 1.8 riastrad const uint32_t n_extra = (S->offset % DEV_BSIZE);
232 1.8 riastrad if (n_extra != 0) {
233 1.8 riastrad const uint32_t n_padding = (DEV_BSIZE - n_extra);
234 1.8 riastrad /* Reuse compbuf -- guaranteed to be large enough. */
235 1.8 riastrad (void)memset(compbuf, 0, n_padding);
236 1.8 riastrad const ssize_t n_written = write(S->cloop2_fd, compbuf,
237 1.8 riastrad n_padding);
238 1.8 riastrad if (n_written == -1)
239 1.8 riastrad err(1, "write final padding failed");
240 1.8 riastrad assert(n_written >= 0);
241 1.8 riastrad if ((size_t)n_written != n_padding)
242 1.8 riastrad errx(1, "partial write of final padding bytes"
243 1.8 riastrad ": %zd <= %"PRIu32,
244 1.8 riastrad n_written, n_padding);
245 1.8 riastrad
246 1.8 riastrad /* Account for the extra bytes in the output file. */
247 1.8 riastrad assert(n_padding <= (MIN(UINT64_MAX, OFF_MAX) - S->offset));
248 1.8 riastrad {
249 1.8 riastrad sigset_t old_sigmask;
250 1.8 riastrad block_signals(&old_sigmask);
251 1.8 riastrad S->offset += n_padding;
252 1.8 riastrad restore_sigmask(&old_sigmask);
253 1.8 riastrad }
254 1.8 riastrad }
255 1.8 riastrad
256 1.8 riastrad out:
257 1.8 riastrad /* Commit the offset table. */
258 1.8 riastrad assert(S->offset <= OFF_MAX);
259 1.8 riastrad assert((off_t)S->offset == lseek(S->cloop2_fd, 0, SEEK_CUR));
260 1.8 riastrad compress_checkpoint(S);
261 1.8 riastrad
262 1.8 riastrad /*
263 1.8 riastrad * Free the compression buffers and finalize the compression.
264 1.8 riastrad */
265 1.8 riastrad free(compbuf);
266 1.8 riastrad free(uncompbuf);
267 1.8 riastrad compress_exit(S);
268 1.1 hubertf
269 1.8 riastrad return 0;
270 1.8 riastrad }
271 1.1 hubertf
272 1.1 hubertf /*
273 1.8 riastrad * Signal cruft.
274 1.1 hubertf */
275 1.8 riastrad
276 1.8 riastrad static void
277 1.8 riastrad init_signals(void)
278 1.8 riastrad {
279 1.8 riastrad
280 1.8 riastrad init_signal_handler(SA_RESTART, info_signals,
281 1.8 riastrad __arraycount(info_signals), &info_signal_handler);
282 1.8 riastrad init_signal_handler(SA_RESTART, checkpoint_signals,
283 1.8 riastrad __arraycount(checkpoint_signals), &checkpoint_signal_handler);
284 1.8 riastrad }
285 1.8 riastrad
286 1.8 riastrad static void
287 1.8 riastrad init_signal_handler(int flags, const struct sigdesc *signals, size_t n,
288 1.8 riastrad void (*handler)(int))
289 1.1 hubertf {
290 1.8 riastrad static const struct sigaction zero_sa;
291 1.8 riastrad struct sigaction sa = zero_sa;
292 1.8 riastrad size_t i;
293 1.8 riastrad
294 1.8 riastrad (void)sigemptyset(&sa.sa_mask);
295 1.8 riastrad for (i = 0; i < n; i++)
296 1.8 riastrad (void)sigaddset(&sa.sa_mask, signals[i].sd_signo);
297 1.8 riastrad sa.sa_flags = flags;
298 1.8 riastrad sa.sa_handler = handler;
299 1.8 riastrad for (i = 0; i < n; i++)
300 1.8 riastrad if (sigaction(signals[i].sd_signo, &sa, NULL) == -1)
301 1.8 riastrad err(1, "sigaction(%s)", signals[i].sd_name);
302 1.8 riastrad }
303 1.8 riastrad
304 1.8 riastrad static void
305 1.8 riastrad info_signal_handler(int signo __unused)
306 1.8 riastrad {
307 1.8 riastrad /* Save errno. */
308 1.8 riastrad const int error = errno;
309 1.8 riastrad struct compress_state *const S = &global_state;
310 1.8 riastrad char buf[128];
311 1.8 riastrad
312 1.8 riastrad /* Bail if the state is not yet initialized. */
313 1.8 riastrad if (!S->initialized) {
314 1.8 riastrad warnx_ss("initializing");
315 1.8 riastrad goto out;
316 1.8 riastrad }
317 1.8 riastrad
318 1.8 riastrad /* Carefully calculate our I/O position. */
319 1.8 riastrad assert(S->blocksize > 0);
320 1.8 riastrad __CTASSERT(MAX_N_BLOCKS <= (UINT64_MAX / MAX_BLOCKSIZE));
321 1.8 riastrad const uint64_t nread = ((uint64_t)S->blkno * (uint64_t)S->blocksize);
322 1.8 riastrad
323 1.8 riastrad assert(S->n_blocks > 0);
324 1.8 riastrad __CTASSERT(MAX_N_BLOCKS <= ((UINT64_MAX / sizeof(uint64_t)) -
325 1.8 riastrad CLOOP2_OFFSET_TABLE_OFFSET));
326 1.8 riastrad const uint64_t nwritten = (S->offset <= (CLOOP2_OFFSET_TABLE_OFFSET +
327 1.8 riastrad (S->n_blocks * sizeof(uint64_t)))?
328 1.8 riastrad 0 : S->offset);
329 1.8 riastrad
330 1.8 riastrad /* snprintf_ss can't do floating-point, so do fixed-point instead. */
331 1.8 riastrad const uint64_t ratio_percent =
332 1.8 riastrad (nread > 0?
333 1.8 riastrad ((nwritten >= (UINT64_MAX / 100)) ?
334 1.8 riastrad ((nwritten / nread) * 100) : ((nwritten * 100) / nread))
335 1.8 riastrad : 0);
336 1.8 riastrad
337 1.8 riastrad /* Format the status. */
338 1.8 riastrad assert(S->n_checkpointed_blocks <= (UINT64_MAX / S->blocksize));
339 1.8 riastrad const int n = snprintf_ss(buf, sizeof(buf),
340 1.8 riastrad "vndcompress: read %"PRIu64" bytes, wrote %"PRIu64" bytes, "
341 1.8 riastrad "compression ratio %"PRIu64"%% (checkpointed %"PRIu64" bytes)\n",
342 1.8 riastrad nread, nwritten, ratio_percent,
343 1.8 riastrad ((uint64_t)S->n_checkpointed_blocks * (uint64_t)S->blocksize));
344 1.8 riastrad if (n < 0) {
345 1.8 riastrad const char msg[] = "vndcompress: can't format info\n";
346 1.8 riastrad (void)write(STDERR_FILENO, msg, __arraycount(msg));
347 1.1 hubertf } else {
348 1.8 riastrad __CTASSERT(INT_MAX <= SIZE_MAX);
349 1.8 riastrad (void)write(STDERR_FILENO, buf, (size_t)n);
350 1.8 riastrad }
351 1.8 riastrad
352 1.8 riastrad out:
353 1.8 riastrad /* Restore errno. */
354 1.8 riastrad errno = error;
355 1.8 riastrad }
356 1.8 riastrad
357 1.8 riastrad static void
358 1.8 riastrad checkpoint_signal_handler(int signo __unused)
359 1.8 riastrad {
360 1.8 riastrad /* Save errno. */
361 1.8 riastrad const int error = errno;
362 1.8 riastrad struct compress_state *const S = &global_state;
363 1.8 riastrad
364 1.8 riastrad /* Bail if the state is not yet initialized. */
365 1.8 riastrad if (!S->initialized) {
366 1.8 riastrad warnx_ss("nothing to checkpoint yet");
367 1.8 riastrad goto out;
368 1.1 hubertf }
369 1.8 riastrad
370 1.8 riastrad assert(S->image_fd >= 0);
371 1.8 riastrad assert(S->cloop2_fd >= 0);
372 1.8 riastrad
373 1.8 riastrad /* Take a checkpoint. */
374 1.8 riastrad assert(S->blocksize > 0);
375 1.8 riastrad assert(S->blkno <= (UINT64_MAX / S->blocksize));
376 1.8 riastrad warnx_ss("checkpointing %"PRIu64" bytes",
377 1.8 riastrad ((uint64_t)S->blkno * (uint64_t)S->blocksize));
378 1.8 riastrad compress_checkpoint(S);
379 1.8 riastrad
380 1.8 riastrad out:
381 1.8 riastrad /* Restore errno. */
382 1.8 riastrad errno = error;
383 1.8 riastrad }
384 1.8 riastrad
385 1.8 riastrad static void
386 1.8 riastrad block_signals(sigset_t *old_sigmask)
387 1.8 riastrad {
388 1.8 riastrad sigset_t block;
389 1.8 riastrad
390 1.8 riastrad (void)sigfillset(&block);
391 1.8 riastrad (void)sigprocmask(SIG_BLOCK, &block, old_sigmask);
392 1.8 riastrad }
393 1.8 riastrad
394 1.8 riastrad static void
395 1.8 riastrad restore_sigmask(const sigset_t *sigmask)
396 1.8 riastrad {
397 1.8 riastrad
398 1.8 riastrad (void)sigprocmask(SIG_SETMASK, sigmask, NULL);
399 1.8 riastrad }
400 1.8 riastrad
401 1.8 riastrad /*
402 1.8 riastrad * Report progress.
403 1.8 riastrad *
404 1.8 riastrad * XXX Should do a progress bar here.
405 1.8 riastrad */
406 1.8 riastrad static void
407 1.8 riastrad compress_progress(struct compress_state *S __unused)
408 1.8 riastrad {
409 1.1 hubertf }
410 1.1 hubertf
411 1.1 hubertf /*
412 1.8 riastrad * Parse arguments, open the files, and initialize the state.
413 1.1 hubertf */
414 1.7 joerg static void
415 1.8 riastrad compress_init(int argc, char **argv, const struct options *O,
416 1.8 riastrad struct compress_state *S)
417 1.8 riastrad {
418 1.8 riastrad uint32_t i;
419 1.8 riastrad
420 1.8 riastrad if (!((argc == 2) || (argc == 3)))
421 1.8 riastrad usage();
422 1.8 riastrad
423 1.8 riastrad const char *const image_pathname = argv[0];
424 1.8 riastrad const char *const cloop2_pathname = argv[1];
425 1.8 riastrad
426 1.8 riastrad /* Grab the block size either from `-s' or from the last argument. */
427 1.8 riastrad __CTASSERT(0 < DEV_BSIZE);
428 1.8 riastrad __CTASSERT((MIN_BLOCKSIZE % DEV_BSIZE) == 0);
429 1.8 riastrad __CTASSERT(MIN_BLOCKSIZE <= DEF_BLOCKSIZE);
430 1.8 riastrad __CTASSERT((DEF_BLOCKSIZE % DEV_BSIZE) == 0);
431 1.8 riastrad __CTASSERT(DEF_BLOCKSIZE <= MAX_BLOCKSIZE);
432 1.8 riastrad __CTASSERT((MAX_BLOCKSIZE % DEV_BSIZE) == 0);
433 1.8 riastrad if (ISSET(O->flags, FLAG_s)) {
434 1.8 riastrad if (argc == 3) {
435 1.8 riastrad warnx("use -s or the extra argument, not both");
436 1.8 riastrad usage();
437 1.8 riastrad }
438 1.8 riastrad S->blocksize = O->blocksize;
439 1.8 riastrad } else {
440 1.8 riastrad S->blocksize = (argc == 2? DEF_BLOCKSIZE :
441 1.8 riastrad strsuftoll("block size", argv[2], MIN_BLOCKSIZE,
442 1.8 riastrad MAX_BLOCKSIZE));
443 1.8 riastrad }
444 1.8 riastrad
445 1.8 riastrad /* Sanity-check the blocksize. (strsuftoll guarantees bounds.) */
446 1.8 riastrad __CTASSERT(DEV_BSIZE <= UINT32_MAX);
447 1.8 riastrad if ((S->blocksize % DEV_BSIZE) != 0)
448 1.8 riastrad errx(1, "bad blocksize: %"PRIu32
449 1.8 riastrad " (not a multiple of %"PRIu32")",
450 1.8 riastrad S->blocksize, (uint32_t)DEV_BSIZE);
451 1.8 riastrad assert(MIN_BLOCKSIZE <= S->blocksize);
452 1.8 riastrad assert((S->blocksize % DEV_BSIZE) == 0);
453 1.8 riastrad assert(S->blocksize <= MAX_BLOCKSIZE);
454 1.8 riastrad
455 1.8 riastrad /* Grab the end block number if we have one. */
456 1.8 riastrad S->end_block = (ISSET(O->flags, FLAG_p)? O->end_block : 0);
457 1.8 riastrad
458 1.8 riastrad /* Grab the checkpoint block count, if we have one. */
459 1.8 riastrad S->checkpoint_blocks =
460 1.8 riastrad (ISSET(O->flags, FLAG_k)? O->checkpoint_blocks : 0);
461 1.8 riastrad
462 1.8 riastrad /* Open the input image file and the output cloop2 file. */
463 1.8 riastrad S->image_fd = open(image_pathname, O_RDONLY);
464 1.8 riastrad if (S->image_fd == -1)
465 1.8 riastrad err(1, "open(%s)", image_pathname);
466 1.8 riastrad
467 1.8 riastrad int oflags;
468 1.8 riastrad if (!ISSET(O->flags, FLAG_r))
469 1.8 riastrad oflags = (O_WRONLY | O_TRUNC | O_CREAT); /* XXX O_EXCL? */
470 1.8 riastrad else if (!ISSET(O->flags, FLAG_R))
471 1.8 riastrad oflags = (O_RDWR | O_CREAT);
472 1.8 riastrad else
473 1.8 riastrad oflags = O_RDWR;
474 1.8 riastrad S->cloop2_fd = open(cloop2_pathname, oflags, 0777);
475 1.8 riastrad if (S->cloop2_fd == -1)
476 1.8 riastrad err(1, "open(%s)", cloop2_pathname);
477 1.8 riastrad
478 1.8 riastrad /* Find the size of the input image. */
479 1.8 riastrad if (ISSET(O->flags, FLAG_l)) {
480 1.8 riastrad S->size = O->length;
481 1.8 riastrad } else {
482 1.8 riastrad static const struct stat zero_st;
483 1.8 riastrad struct stat st = zero_st;
484 1.8 riastrad if (fstat(S->image_fd, &st) == -1)
485 1.8 riastrad err(1, "stat(%s)", image_pathname);
486 1.8 riastrad if (st.st_size <= 0)
487 1.8 riastrad errx(1, "unknown image size");
488 1.8 riastrad assert(st.st_size >= 0);
489 1.8 riastrad __CTASSERT(OFF_MAX <= UINT64_MAX);
490 1.8 riastrad assert(__type_fit(uint64_t, st.st_size));
491 1.8 riastrad S->size = st.st_size;
492 1.8 riastrad }
493 1.8 riastrad assert(S->size <= OFF_MAX);
494 1.8 riastrad
495 1.8 riastrad /* Find number of full blocks and whether there's a partial block. */
496 1.8 riastrad S->n_full_blocks = (S->size / S->blocksize);
497 1.8 riastrad assert(S->n_full_blocks <=
498 1.8 riastrad (UINT32_MAX - ((S->size % S->blocksize) > 0)));
499 1.8 riastrad S->n_blocks = (S->n_full_blocks + ((S->size % S->blocksize) > 0));
500 1.8 riastrad assert(S->n_full_blocks <= S->n_blocks);
501 1.8 riastrad
502 1.8 riastrad if (S->n_blocks > MAX_N_BLOCKS)
503 1.8 riastrad errx(1, "image too large for block size %"PRIu32": %"PRIu64,
504 1.8 riastrad S->blocksize, S->size);
505 1.8 riastrad assert(S->n_blocks <= MAX_N_BLOCKS);
506 1.8 riastrad
507 1.8 riastrad /* Allocate an offset table for the blocks; one extra for the end. */
508 1.8 riastrad __CTASSERT(MAX_N_BLOCKS <= (UINT32_MAX - 1));
509 1.8 riastrad S->n_offsets = (S->n_blocks + 1);
510 1.8 riastrad __CTASSERT(MAX_N_OFFSETS == (MAX_N_BLOCKS + 1));
511 1.8 riastrad __CTASSERT(MAX_N_OFFSETS <= (SIZE_MAX / sizeof(uint64_t)));
512 1.8 riastrad S->offset_table = malloc(S->n_offsets * sizeof(uint64_t));
513 1.8 riastrad if (S->offset_table == NULL)
514 1.8 riastrad err(1, "malloc offset table");
515 1.8 riastrad
516 1.8 riastrad /* Attempt to restart a partial transfer if requested. */
517 1.8 riastrad if (ISSET(O->flags, FLAG_r)) {
518 1.8 riastrad if (compress_restart(S)) {
519 1.8 riastrad /*
520 1.8 riastrad * Restart succeeded. Truncate the output
521 1.8 riastrad * here, in case any garbage got appended. We
522 1.8 riastrad * are committed to making progress at this
523 1.8 riastrad * point. If the ftruncate fails, we don't
524 1.8 riastrad * lose anything valuable -- this is the last
525 1.8 riastrad * point at which we can restart anyway.
526 1.8 riastrad */
527 1.8 riastrad if (ftruncate(S->cloop2_fd, S->offset) == -1)
528 1.8 riastrad err(1, "ftruncate failed");
529 1.8 riastrad
530 1.8 riastrad /* All set! No more initialization to do. */
531 1.8 riastrad return;
532 1.8 riastrad } else {
533 1.8 riastrad /* Restart failed. Barf now if requested. */
534 1.8 riastrad if (ISSET(O->flags, FLAG_R))
535 1.8 riastrad errx(1, "restart failed, aborting");
536 1.8 riastrad
537 1.8 riastrad /* Otherwise, truncate and start at the top. */
538 1.8 riastrad if (ftruncate(S->cloop2_fd, 0) == -1)
539 1.8 riastrad err(1, "truncate failed");
540 1.8 riastrad if (lseek(S->cloop2_fd, 0, SEEK_SET) == -1)
541 1.8 riastrad err(1, "lseek to cloop2 beginning failed");
542 1.8 riastrad if (lseek(S->image_fd, 0, SEEK_SET) == -1)
543 1.8 riastrad err(1, "lseek to image beginning failed");
544 1.8 riastrad }
545 1.8 riastrad }
546 1.8 riastrad
547 1.1 hubertf /*
548 1.8 riastrad * Initialize the offset table to all ones (except for the
549 1.8 riastrad * fixed first offset) so that we can easily detect where we
550 1.8 riastrad * were interrupted if we want to restart.
551 1.1 hubertf */
552 1.8 riastrad __CTASSERT(MAX_N_OFFSETS <= UINT32_MAX);
553 1.8 riastrad assert(S->n_offsets > 0);
554 1.8 riastrad S->offset_table[0] = htobe64(sizeof(struct cloop2_header) +
555 1.8 riastrad (S->n_offsets * sizeof(uint64_t)));
556 1.8 riastrad for (i = 1; i < S->n_offsets; i++)
557 1.8 riastrad S->offset_table[i] = ~(uint64_t)0;
558 1.8 riastrad
559 1.8 riastrad /* Write a bogus (zero) header for now, until we checkpoint. */
560 1.8 riastrad static const struct cloop2_header zero_header;
561 1.8 riastrad const ssize_t h_written = write(S->cloop2_fd, &zero_header,
562 1.8 riastrad sizeof(zero_header));
563 1.8 riastrad if (h_written == -1)
564 1.8 riastrad err(1, "write header");
565 1.8 riastrad assert(h_written >= 0);
566 1.8 riastrad if ((size_t)h_written != sizeof(zero_header))
567 1.8 riastrad errx(1, "partial write of header: %zd <= %zu", h_written,
568 1.8 riastrad sizeof(zero_header));
569 1.8 riastrad
570 1.8 riastrad /* Write the initial (empty) offset table. */
571 1.8 riastrad const ssize_t ot_written = write(S->cloop2_fd, S->offset_table,
572 1.8 riastrad (S->n_offsets * sizeof(uint64_t)));
573 1.8 riastrad if (ot_written == -1)
574 1.8 riastrad err(1, "write initial offset table");
575 1.8 riastrad assert(ot_written >= 0);
576 1.8 riastrad if ((size_t)ot_written != (S->n_offsets * sizeof(uint64_t)))
577 1.8 riastrad errx(1, "partial write of initial offset bytes: %zd <= %zu",
578 1.8 riastrad ot_written, (size_t)(S->n_offsets * sizeof(uint64_t)));
579 1.8 riastrad
580 1.8 riastrad /* Start at the beginning of the image. */
581 1.8 riastrad S->blkno = 0;
582 1.8 riastrad S->offset = (sizeof(struct cloop2_header) +
583 1.8 riastrad (S->n_offsets * sizeof(uint64_t)));
584 1.8 riastrad S->n_checkpointed_blocks = 0;
585 1.8 riastrad
586 1.8 riastrad /* Good to go and ready for interruption by a signal. */
587 1.8 riastrad S->initialized = 1;
588 1.8 riastrad }
589 1.8 riastrad
590 1.8 riastrad /*
591 1.8 riastrad * Try to recover state from an existing output file.
592 1.8 riastrad *
593 1.8 riastrad * On success, fill S->offset_table with what's in the file, set
594 1.8 riastrad * S->blkno and S->offset to reflect our position, and seek to the
595 1.8 riastrad * respective positions in the input and output files.
596 1.8 riastrad *
597 1.8 riastrad * On failure, return false. May clobber S->offset_table, S->blkno,
598 1.8 riastrad * S->offset, and the file pointers.
599 1.8 riastrad */
600 1.8 riastrad static bool
601 1.8 riastrad compress_restart(struct compress_state *S)
602 1.8 riastrad {
603 1.8 riastrad
604 1.8 riastrad /* Read in the header. */
605 1.8 riastrad static const struct cloop2_header zero_header;
606 1.8 riastrad struct cloop2_header header = zero_header;
607 1.8 riastrad
608 1.8 riastrad const ssize_t h_read = read_block(S->cloop2_fd, &header,
609 1.8 riastrad sizeof(header));
610 1.8 riastrad if (h_read == -1) {
611 1.8 riastrad warn("failed to read header");
612 1.8 riastrad return false;
613 1.8 riastrad }
614 1.8 riastrad assert(h_read >= 0);
615 1.8 riastrad if ((size_t)h_read != sizeof(header)) {
616 1.8 riastrad warnx("partial read of header");
617 1.8 riastrad return false;
618 1.8 riastrad }
619 1.8 riastrad
620 1.8 riastrad /* Check that the header looks like a header. */
621 1.8 riastrad __CTASSERT(sizeof(cloop2_magic) <= sizeof(header.cl2h_magic));
622 1.8 riastrad if (memcmp(header.cl2h_magic, cloop2_magic, sizeof(cloop2_magic))
623 1.8 riastrad != 0) {
624 1.8 riastrad warnx("bad cloop2 shell script magic");
625 1.8 riastrad return false;
626 1.8 riastrad }
627 1.8 riastrad
628 1.8 riastrad /* Check the header parameters. */
629 1.8 riastrad if (be32toh(header.cl2h_blocksize) != S->blocksize) {
630 1.8 riastrad warnx("mismatched block size: %"PRIu32
631 1.8 riastrad " (expected %"PRIu32")",
632 1.8 riastrad be32toh(header.cl2h_blocksize), S->blocksize);
633 1.8 riastrad return false;
634 1.8 riastrad }
635 1.8 riastrad if (be32toh(header.cl2h_n_blocks) != S->n_blocks) {
636 1.8 riastrad warnx("mismatched number of blocks: %"PRIu32
637 1.8 riastrad " (expected %"PRIu32")",
638 1.8 riastrad be32toh(header.cl2h_n_blocks), S->n_blocks);
639 1.8 riastrad return false;
640 1.8 riastrad }
641 1.8 riastrad
642 1.8 riastrad /* Read in the partial offset table. */
643 1.8 riastrad const ssize_t ot_read = read_block(S->cloop2_fd, S->offset_table,
644 1.8 riastrad (S->n_offsets * sizeof(uint64_t)));
645 1.8 riastrad if (ot_read == -1) {
646 1.8 riastrad warn("failed to read offset table");
647 1.8 riastrad return false;
648 1.8 riastrad }
649 1.8 riastrad assert(ot_read >= 0);
650 1.8 riastrad if ((size_t)ot_read != (S->n_offsets * sizeof(uint64_t))) {
651 1.8 riastrad warnx("partial read of offset table");
652 1.8 riastrad return false;
653 1.8 riastrad }
654 1.8 riastrad
655 1.8 riastrad if (be64toh(S->offset_table[0]) != (sizeof(struct cloop2_header) +
656 1.8 riastrad (S->n_offsets * sizeof(uint64_t)))) {
657 1.8 riastrad warnx("first offset is not %"PRIu64": %"PRIu64,
658 1.8 riastrad ((uint64_t)S->n_offsets * sizeof(uint64_t)),
659 1.8 riastrad be64toh(S->offset_table[0]));
660 1.8 riastrad return false;
661 1.8 riastrad }
662 1.8 riastrad
663 1.8 riastrad /* Find where we left off. */
664 1.8 riastrad __CTASSERT(MAX_N_OFFSETS <= UINT32_MAX);
665 1.8 riastrad uint32_t blkno = 0;
666 1.8 riastrad for (blkno = 0; blkno < S->n_blocks; blkno++) {
667 1.8 riastrad if (S->offset_table[blkno] == ~(uint64_t)0)
668 1.8 riastrad break;
669 1.8 riastrad if (0 < blkno) {
670 1.8 riastrad const uint64_t start =
671 1.8 riastrad be64toh(S->offset_table[blkno - 1]);
672 1.8 riastrad const uint64_t end = be64toh(S->offset_table[blkno]);
673 1.8 riastrad if (end <= start) {
674 1.8 riastrad warnx("bad offset table: 0x%"PRIx64
675 1.8 riastrad ", 0x%"PRIx64, start, end);
676 1.8 riastrad return false;
677 1.8 riastrad }
678 1.8 riastrad /* XXX compression ratio bound */
679 1.8 riastrad __CTASSERT(MAX_BLOCKSIZE <= (SIZE_MAX / 2));
680 1.8 riastrad if ((2 * (size_t)S->blocksize) <= (end - start)) {
681 1.8 riastrad warnx("block %"PRIu32" too large:"
682 1.8 riastrad " %"PRIu64" bytes",
683 1.8 riastrad blkno, (end - start));
684 1.8 riastrad return false;
685 1.8 riastrad }
686 1.8 riastrad }
687 1.8 riastrad }
688 1.8 riastrad
689 1.8 riastrad if (blkno == 0) {
690 1.8 riastrad warnx("no blocks were written; nothing to restart");
691 1.8 riastrad return false;
692 1.8 riastrad }
693 1.8 riastrad
694 1.8 riastrad /* Make sure the rest of the offset table is all ones. */
695 1.8 riastrad if (blkno < S->n_blocks) {
696 1.8 riastrad uint32_t nblkno;
697 1.8 riastrad
698 1.8 riastrad for (nblkno = blkno; nblkno < S->n_blocks; nblkno++) {
699 1.8 riastrad if (S->offset_table[nblkno] != ~(uint64_t)0) {
700 1.8 riastrad warnx("bad partial offset table entry"
701 1.8 riastrad " at %"PRIu32": %"PRIu64,
702 1.8 riastrad nblkno,
703 1.8 riastrad be64toh(S->offset_table[nblkno]));
704 1.8 riastrad return false;
705 1.8 riastrad }
706 1.8 riastrad }
707 1.1 hubertf }
708 1.8 riastrad
709 1.1 hubertf /*
710 1.8 riastrad * XXX Consider decompressing some number of blocks to make
711 1.8 riastrad * sure they match.
712 1.1 hubertf */
713 1.8 riastrad
714 1.8 riastrad /* Back up by one. */
715 1.8 riastrad assert(1 <= blkno);
716 1.8 riastrad blkno -= 1;
717 1.8 riastrad
718 1.8 riastrad /* Seek to the input position. */
719 1.8 riastrad assert(S->size <= OFF_MAX);
720 1.8 riastrad assert(blkno <= (S->size / S->blocksize));
721 1.8 riastrad const off_t restart_position = ((off_t)blkno * (off_t)S->blocksize);
722 1.8 riastrad assert(0 <= restart_position);
723 1.8 riastrad assert(restart_position <= (off_t)S->size);
724 1.8 riastrad if (lseek(S->image_fd, restart_position, SEEK_SET) == -1) {
725 1.8 riastrad if (errno != ESPIPE) {
726 1.8 riastrad warn("lseek input image failed");
727 1.8 riastrad return false;
728 1.8 riastrad }
729 1.8 riastrad
730 1.8 riastrad /* Try read instead of lseek for a pipe/socket/fifo. */
731 1.8 riastrad void *const buffer = malloc(0x10000);
732 1.8 riastrad if (buffer == NULL)
733 1.8 riastrad err(1, "malloc temporary buffer");
734 1.8 riastrad off_t left = restart_position;
735 1.8 riastrad while (left > 0) {
736 1.8 riastrad const size_t size = MIN(0x10000, left);
737 1.8 riastrad const ssize_t n_read = read_block(S->image_fd, buffer,
738 1.8 riastrad size);
739 1.8 riastrad if (n_read == -1) {
740 1.8 riastrad free(buffer);
741 1.8 riastrad warn("read of input image failed");
742 1.8 riastrad return false;
743 1.8 riastrad }
744 1.8 riastrad assert(n_read >= 0);
745 1.8 riastrad if ((size_t)n_read != size) {
746 1.8 riastrad free(buffer);
747 1.8 riastrad warnx("partial read of input image");
748 1.8 riastrad return false;
749 1.1 hubertf }
750 1.8 riastrad assert((off_t)size <= left);
751 1.8 riastrad left -= size;
752 1.1 hubertf }
753 1.8 riastrad free(buffer);
754 1.8 riastrad }
755 1.8 riastrad
756 1.8 riastrad /* Seek to the output position. */
757 1.8 riastrad const uint64_t offset = be64toh(S->offset_table[blkno]);
758 1.8 riastrad assert(offset <= OFF_MAX);
759 1.8 riastrad if (lseek(S->cloop2_fd, offset, SEEK_SET) == -1) {
760 1.8 riastrad warn("lseek output cloop2 to %"PRIx64" failed",
761 1.8 riastrad S->offset);
762 1.8 riastrad return false;
763 1.1 hubertf }
764 1.8 riastrad
765 1.8 riastrad /* Start where we left off. */
766 1.8 riastrad S->blkno = blkno;
767 1.8 riastrad S->offset = offset;
768 1.8 riastrad S->n_checkpointed_blocks = blkno;
769 1.8 riastrad
770 1.8 riastrad /* Good to go and ready for interruption by a signal. */
771 1.8 riastrad S->initialized = 1;
772 1.8 riastrad
773 1.8 riastrad /* Success! */
774 1.8 riastrad return true;
775 1.8 riastrad }
776 1.8 riastrad
777 1.8 riastrad /*
778 1.8 riastrad * Read a single block, compress it, and write the compressed block.
779 1.8 riastrad * Return the size of the compressed block.
780 1.8 riastrad */
781 1.8 riastrad static uint32_t
782 1.8 riastrad compress_block(int in_fd, int out_fd, uint32_t blkno, uint32_t blocksize,
783 1.8 riastrad uint32_t readsize, void *uncompbuf, void *compbuf)
784 1.8 riastrad {
785 1.8 riastrad
786 1.8 riastrad assert(readsize <= blocksize);
787 1.8 riastrad assert(blocksize <= MAX_BLOCKSIZE);
788 1.8 riastrad
789 1.8 riastrad /* Read the uncompressed block. */
790 1.8 riastrad const ssize_t n_read = read_block(in_fd, uncompbuf, readsize);
791 1.8 riastrad if (n_read == -1)
792 1.8 riastrad err(1, "read block %"PRIu32, blkno);
793 1.8 riastrad assert(n_read >= 0);
794 1.8 riastrad assert((uintmax_t)n_read <= (uintmax_t)readsize);
795 1.8 riastrad if (n_read < readsize)
796 1.8 riastrad errx(1, "partial read of block %"PRIu32": %zd <= %"PRIu32,
797 1.8 riastrad blkno, n_read, readsize);
798 1.8 riastrad
799 1.8 riastrad /* Compress the block. */
800 1.8 riastrad /* XXX compression ratio bound */
801 1.8 riastrad __CTASSERT(MAX_BLOCKSIZE <= (ULONG_MAX / 2));
802 1.8 riastrad const unsigned long uncomplen =
803 1.8 riastrad (VNDCOMPRESS_COMPAT? blocksize : readsize); /* XXX */
804 1.8 riastrad unsigned long complen = (uncomplen * 2);
805 1.8 riastrad const int zerror = compress2(compbuf, &complen, uncompbuf, uncomplen,
806 1.8 riastrad Z_BEST_COMPRESSION);
807 1.8 riastrad if (zerror != Z_OK)
808 1.8 riastrad errx(1, "compressed failed at block %"PRIu32" (%d): %s", blkno,
809 1.8 riastrad zerror, zError(zerror));
810 1.8 riastrad assert(complen <= (uncomplen * 2));
811 1.8 riastrad
812 1.8 riastrad /* Write the compressed block. */
813 1.8 riastrad const ssize_t n_written = write(out_fd, compbuf, complen);
814 1.8 riastrad if (n_written == -1)
815 1.8 riastrad err(1, "write block %"PRIu32, blkno);
816 1.8 riastrad assert(n_written >= 0);
817 1.8 riastrad if ((uint32_t)n_written != complen)
818 1.8 riastrad errx(1, "partial write of block %"PRIu32": %zd <= %zu", blkno,
819 1.8 riastrad n_written, complen);
820 1.8 riastrad
821 1.8 riastrad return n_written;
822 1.1 hubertf }
823 1.1 hubertf
824 1.1 hubertf /*
825 1.8 riastrad * Checkpoint if appropriate.
826 1.1 hubertf */
827 1.8 riastrad static void
828 1.8 riastrad compress_maybe_checkpoint(struct compress_state *S)
829 1.1 hubertf {
830 1.1 hubertf
831 1.8 riastrad if ((0 < S->checkpoint_blocks) && (0 < S->blkno) &&
832 1.8 riastrad ((S->blkno % S->checkpoint_blocks) == 0)) {
833 1.8 riastrad assert(S->offset <= OFF_MAX);
834 1.8 riastrad assert((off_t)S->offset == lseek(S->cloop2_fd, 0, SEEK_CUR));
835 1.8 riastrad compress_checkpoint(S);
836 1.1 hubertf }
837 1.1 hubertf }
838 1.1 hubertf
839 1.1 hubertf /*
840 1.8 riastrad * Write the prefix of the offset table that we have filled so far.
841 1.8 riastrad *
842 1.8 riastrad * We fsync the data blocks we have written, and then write the offset
843 1.8 riastrad * table, and then fsync the offset table and file metadata. This
844 1.8 riastrad * should help to avoid offset tables that point at garbage data.
845 1.8 riastrad *
846 1.8 riastrad * This may be called from a signal handler, so it must not use stdio,
847 1.8 riastrad * malloc, &c. -- it may only (a) handle signal-safe state in S, and
848 1.8 riastrad * (b) do file descriptor I/O / fsync.
849 1.8 riastrad *
850 1.8 riastrad * XXX This requires further thought and heavy testing to be sure.
851 1.8 riastrad *
852 1.8 riastrad * XXX Should have an option to suppress fsync.
853 1.8 riastrad *
854 1.8 riastrad * XXX Should have an option to fail on fsync failures.
855 1.8 riastrad *
856 1.8 riastrad * XXX Would be nice if we could just do a barrier rather than an
857 1.8 riastrad * fsync.
858 1.8 riastrad *
859 1.8 riastrad * XXX How might we automatically test the fsyncs?
860 1.1 hubertf */
861 1.7 joerg static void
862 1.8 riastrad compress_checkpoint(struct compress_state *S)
863 1.1 hubertf {
864 1.8 riastrad
865 1.8 riastrad assert(S->blkno < S->n_offsets);
866 1.8 riastrad const uint32_t n_offsets = (S->blkno + 1);
867 1.8 riastrad assert(n_offsets <= S->n_offsets);
868 1.8 riastrad
869 1.8 riastrad assert(S->offset <= OFF_MAX);
870 1.8 riastrad assert((off_t)S->offset <= lseek(S->cloop2_fd, 0, SEEK_CUR));
871 1.8 riastrad
872 1.8 riastrad /* Make sure the data hits the disk before we say it's ready. */
873 1.8 riastrad if (fsync_range(S->cloop2_fd, (FFILESYNC | FDISKSYNC), 0, S->offset)
874 1.8 riastrad == -1)
875 1.8 riastrad warn_ss("fsync of output failed");
876 1.8 riastrad
877 1.8 riastrad /* Say the data blocks are ready. */
878 1.8 riastrad const ssize_t n_written = pwrite(S->cloop2_fd, S->offset_table,
879 1.8 riastrad (n_offsets * sizeof(uint64_t)), CLOOP2_OFFSET_TABLE_OFFSET);
880 1.8 riastrad if (n_written == -1)
881 1.8 riastrad err_ss(1, "write partial offset table");
882 1.8 riastrad assert(n_written >= 0);
883 1.8 riastrad if ((size_t)n_written != (n_offsets * sizeof(uint64_t)))
884 1.8 riastrad errx_ss(1, "partial write of partial offset table: %zd <= %zu",
885 1.8 riastrad n_written, (size_t)(n_offsets * sizeof(uint64_t)));
886 1.8 riastrad
887 1.1 hubertf /*
888 1.8 riastrad * If this is the first checkpoint, initialize the header.
889 1.8 riastrad * Signal handler can race with main code here, but it is
890 1.8 riastrad * harmless -- just an extra fsync and write of the header,
891 1.8 riastrad * which are both idempotent.
892 1.1 hubertf */
893 1.8 riastrad if (S->n_checkpointed_blocks == 0) {
894 1.8 riastrad static const struct cloop2_header zero_header;
895 1.8 riastrad struct cloop2_header header = zero_header;
896 1.8 riastrad
897 1.8 riastrad /* Force the offset table to disk before we set the header. */
898 1.8 riastrad if (fsync_range(S->cloop2_fd, (FFILESYNC | FDISKSYNC),
899 1.8 riastrad 0,
900 1.8 riastrad (CLOOP2_OFFSET_TABLE_OFFSET
901 1.8 riastrad + (n_offsets * (sizeof(uint64_t)))))
902 1.8 riastrad == -1)
903 1.8 riastrad warn_ss("fsync of offset table failed");
904 1.8 riastrad
905 1.8 riastrad /* Subsequent writes will preserve a valid state. */
906 1.8 riastrad
907 1.8 riastrad /* Format the header. */
908 1.8 riastrad __CTASSERT(sizeof(cloop2_magic) <= sizeof(header.cl2h_magic));
909 1.8 riastrad (void)memcpy(header.cl2h_magic, cloop2_magic,
910 1.8 riastrad sizeof(cloop2_magic));
911 1.8 riastrad header.cl2h_blocksize = htobe32(S->blocksize);
912 1.8 riastrad header.cl2h_n_blocks = htobe32(S->n_blocks);
913 1.8 riastrad
914 1.8 riastrad /* Write the header. */
915 1.8 riastrad const ssize_t h_written = pwrite(S->cloop2_fd, &header,
916 1.8 riastrad sizeof(header), 0);
917 1.8 riastrad if (h_written == -1)
918 1.8 riastrad err_ss(1, "write header");
919 1.8 riastrad assert(h_written >= 0);
920 1.8 riastrad if ((size_t)h_written != sizeof(header))
921 1.8 riastrad errx_ss(1, "partial write of header: %zd <= %zu",
922 1.8 riastrad h_written, sizeof(header));
923 1.8 riastrad }
924 1.8 riastrad
925 1.8 riastrad /* Record how many blocks we've checkpointed. */
926 1.8 riastrad {
927 1.8 riastrad sigset_t old_sigmask;
928 1.8 riastrad block_signals(&old_sigmask);
929 1.8 riastrad S->n_checkpointed_blocks = S->blkno;
930 1.8 riastrad restore_sigmask(&old_sigmask);
931 1.8 riastrad }
932 1.8 riastrad }
933 1.8 riastrad
934 1.8 riastrad /*
935 1.8 riastrad * Release everything we allocated in compress_init.
936 1.8 riastrad */
937 1.8 riastrad static void
938 1.8 riastrad compress_exit(struct compress_state *S)
939 1.8 riastrad {
940 1.8 riastrad
941 1.8 riastrad /* Done with the offset table. Free it. */
942 1.8 riastrad free(S->offset_table);
943 1.8 riastrad
944 1.8 riastrad /* Done with the files. Close them. */
945 1.8 riastrad if (close(S->cloop2_fd) == -1)
946 1.8 riastrad warn("close(cloop2 fd)");
947 1.8 riastrad if (close(S->image_fd) == -1)
948 1.8 riastrad warn("close(image fd)");
949 1.1 hubertf }
950 1.1 hubertf
951 1.1 hubertf /*
952 1.8 riastrad * Read, returning partial data only at end of file.
953 1.1 hubertf */
954 1.8 riastrad static ssize_t
955 1.8 riastrad read_block(int fd, void *buffer, size_t n)
956 1.1 hubertf {
957 1.8 riastrad char *p = buffer, *const end __unused = (p + n);
958 1.8 riastrad size_t total_read = 0;
959 1.8 riastrad
960 1.8 riastrad while (n > 0) {
961 1.8 riastrad const ssize_t n_read = read(fd, p, n);
962 1.8 riastrad if (n_read == -1)
963 1.8 riastrad return -1;
964 1.8 riastrad assert(n_read >= 0);
965 1.8 riastrad if (n_read == 0)
966 1.1 hubertf break;
967 1.8 riastrad
968 1.8 riastrad assert((size_t)n_read <= n);
969 1.8 riastrad n -= (size_t)n_read;
970 1.8 riastrad
971 1.8 riastrad assert(p <= end);
972 1.8 riastrad assert(n_read <= (end - p));
973 1.8 riastrad p += (size_t)n_read;
974 1.8 riastrad
975 1.8 riastrad assert((size_t)n_read <= (SIZE_MAX - total_read));
976 1.8 riastrad total_read += (size_t)n_read;
977 1.1 hubertf }
978 1.1 hubertf
979 1.8 riastrad return total_read;
980 1.8 riastrad }
981 1.8 riastrad
982 1.8 riastrad /*
983 1.8 riastrad * Signal-safe err/warn utilities. The errno varieties are limited to
984 1.8 riastrad * having no format arguments for reasons of laziness.
985 1.8 riastrad */
986 1.8 riastrad
987 1.8 riastrad static void
988 1.8 riastrad err_ss(int exit_value, const char *msg)
989 1.8 riastrad {
990 1.8 riastrad warn_ss(msg);
991 1.8 riastrad _Exit(exit_value);
992 1.8 riastrad }
993 1.8 riastrad
994 1.8 riastrad static void
995 1.8 riastrad errx_ss(int exit_value, const char *format, ...)
996 1.8 riastrad {
997 1.8 riastrad va_list va;
998 1.8 riastrad
999 1.8 riastrad va_start(va, format);
1000 1.8 riastrad vwarnx_ss(format, va);
1001 1.8 riastrad va_end(va);
1002 1.8 riastrad _Exit(exit_value);
1003 1.8 riastrad }
1004 1.8 riastrad
1005 1.8 riastrad static void
1006 1.8 riastrad warn_ss(const char *msg)
1007 1.8 riastrad {
1008 1.8 riastrad int error = errno;
1009 1.8 riastrad
1010 1.8 riastrad warnx_ss("%s: %s", msg, strerror(error));
1011 1.8 riastrad
1012 1.8 riastrad errno = error;
1013 1.8 riastrad }
1014 1.8 riastrad
1015 1.8 riastrad static void
1016 1.8 riastrad warnx_ss(const char *format, ...)
1017 1.8 riastrad {
1018 1.8 riastrad va_list va;
1019 1.8 riastrad
1020 1.8 riastrad va_start(va, format);
1021 1.8 riastrad vwarnx_ss(format, va);
1022 1.8 riastrad va_end(va);
1023 1.8 riastrad }
1024 1.8 riastrad
1025 1.8 riastrad static void
1026 1.8 riastrad vwarnx_ss(const char *format, va_list va)
1027 1.8 riastrad {
1028 1.8 riastrad char buf[128];
1029 1.8 riastrad
1030 1.8 riastrad (void)strlcpy(buf, getprogname(), sizeof(buf));
1031 1.8 riastrad (void)strlcat(buf, ": ", sizeof(buf));
1032 1.8 riastrad
1033 1.8 riastrad const int n = vsnprintf_ss(&buf[strlen(buf)], (sizeof(buf) -
1034 1.8 riastrad strlen(buf)), format, va);
1035 1.8 riastrad if (n <= 0) {
1036 1.8 riastrad const char fallback[] =
1037 1.8 riastrad "vndcompress: Help! I'm trapped in a signal handler!\n";
1038 1.8 riastrad (void)write(STDERR_FILENO, fallback, __arraycount(fallback));
1039 1.8 riastrad } else {
1040 1.8 riastrad (void)strlcat(buf, "\n", sizeof(buf));
1041 1.8 riastrad (void)write(STDERR_FILENO, buf, strlen(buf));
1042 1.1 hubertf }
1043 1.1 hubertf }
1044