1 1.29 riastrad /* $NetBSD: vndcompress.c,v 1.29 2017/07/29 21:04:07 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.29 riastrad __RCSID("$NetBSD: vndcompress.c,v 1.29 2017/07/29 21:04:07 riastradh Exp $"); 34 1.8 riastrad 35 1.8 riastrad #include <sys/endian.h> 36 1.26 christos #include <sys/stat.h> 37 1.8 riastrad 38 1.8 riastrad #include <assert.h> 39 1.1 hubertf #include <err.h> 40 1.8 riastrad #include <errno.h> 41 1.1 hubertf #include <fcntl.h> 42 1.5 lukem #include <inttypes.h> 43 1.8 riastrad #include <limits.h> 44 1.8 riastrad #include <signal.h> 45 1.8 riastrad #include <stdbool.h> 46 1.8 riastrad #include <stdint.h> 47 1.1 hubertf #include <stdio.h> 48 1.1 hubertf #include <stdlib.h> 49 1.1 hubertf #include <string.h> 50 1.1 hubertf #include <unistd.h> 51 1.1 hubertf #include <zlib.h> 52 1.1 hubertf 53 1.8 riastrad #include "common.h" 54 1.15 riastrad #include "offtab.h" 55 1.14 riastrad #include "utils.h" 56 1.8 riastrad 57 1.8 riastrad /* 58 1.8 riastrad * XXX Switch to control bug-for-bug byte-for-byte compatibility with 59 1.8 riastrad * NetBSD's vndcompress. 60 1.8 riastrad */ 61 1.8 riastrad #define VNDCOMPRESS_COMPAT 0 62 1.8 riastrad 63 1.8 riastrad __CTASSERT(sizeof(struct cloop2_header) == CLOOP2_OFFSET_TABLE_OFFSET); 64 1.8 riastrad 65 1.8 riastrad struct compress_state { 66 1.8 riastrad uint64_t size; /* uncompressed size */ 67 1.8 riastrad uint64_t offset; /* output byte offset */ 68 1.8 riastrad uint32_t blocksize; /* bytes per block */ 69 1.8 riastrad uint32_t blkno; /* input block number */ 70 1.8 riastrad uint32_t n_full_blocks; /* floor(size/blocksize) */ 71 1.8 riastrad uint32_t n_blocks; /* ceiling(size/blocksize) */ 72 1.8 riastrad uint32_t n_offsets; /* n_blocks + 1 */ 73 1.8 riastrad uint32_t end_block; /* last block to transfer */ 74 1.8 riastrad uint32_t checkpoint_blocks; /* blocks before checkpoint */ 75 1.8 riastrad int image_fd; 76 1.8 riastrad int cloop2_fd; 77 1.15 riastrad struct offtab offtab; 78 1.8 riastrad uint32_t n_checkpointed_blocks; 79 1.8 riastrad volatile sig_atomic_t 80 1.8 riastrad initialized; /* everything above initialized? */ 81 1.8 riastrad }; 82 1.8 riastrad 83 1.8 riastrad /* Global compression state for SIGINFO handler. */ 84 1.8 riastrad static struct compress_state global_state; 85 1.8 riastrad 86 1.8 riastrad struct sigdesc { 87 1.8 riastrad int sd_signo; 88 1.8 riastrad const char *sd_name; 89 1.8 riastrad }; 90 1.1 hubertf 91 1.8 riastrad static const struct sigdesc info_signals[] = { 92 1.8 riastrad { SIGINFO, "SIGINFO" }, 93 1.8 riastrad { SIGUSR1, "SIGUSR1" }, 94 1.1 hubertf }; 95 1.1 hubertf 96 1.8 riastrad static const struct sigdesc checkpoint_signals[] = { 97 1.8 riastrad { SIGUSR2, "SIGUSR2" }, 98 1.8 riastrad }; 99 1.8 riastrad 100 1.8 riastrad static void init_signals(void); 101 1.8 riastrad static void init_signal_handler(int, const struct sigdesc *, size_t, 102 1.8 riastrad void (*)(int)); 103 1.8 riastrad static void info_signal_handler(int); 104 1.8 riastrad static void checkpoint_signal_handler(int); 105 1.8 riastrad static void compress_progress(struct compress_state *); 106 1.8 riastrad static void compress_init(int, char **, const struct options *, 107 1.8 riastrad struct compress_state *); 108 1.8 riastrad static bool compress_restart(struct compress_state *); 109 1.8 riastrad static uint32_t compress_block(int, int, uint32_t, uint32_t, uint32_t, void *, 110 1.8 riastrad void *); 111 1.8 riastrad static void compress_maybe_checkpoint(struct compress_state *); 112 1.8 riastrad static void compress_checkpoint(struct compress_state *); 113 1.8 riastrad static void compress_exit(struct compress_state *); 114 1.8 riastrad 115 1.1 hubertf /* 116 1.8 riastrad * Compression entry point. 117 1.1 hubertf */ 118 1.8 riastrad int 119 1.8 riastrad vndcompress(int argc, char **argv, const struct options *O) 120 1.8 riastrad { 121 1.8 riastrad struct compress_state *const S = &global_state; 122 1.8 riastrad 123 1.8 riastrad /* Paranoia. The other fields either have no sentinel or use zero. */ 124 1.8 riastrad S->image_fd = -1; 125 1.8 riastrad S->cloop2_fd = -1; 126 1.8 riastrad 127 1.8 riastrad /* Set up signal handlers so we can handle SIGINFO ASAP. */ 128 1.8 riastrad init_signals(); 129 1.8 riastrad 130 1.8 riastrad /* 131 1.8 riastrad * Parse the arguments to initialize our state. 132 1.8 riastrad */ 133 1.8 riastrad compress_init(argc, argv, O, S); 134 1.8 riastrad assert(MIN_BLOCKSIZE <= S->blocksize); 135 1.8 riastrad assert(S->blocksize <= MAX_BLOCKSIZE); 136 1.8 riastrad 137 1.8 riastrad /* 138 1.8 riastrad * Allocate compression buffers. 139 1.8 riastrad * 140 1.8 riastrad * Compression may actually expand. From an overabundance of 141 1.8 riastrad * caution, assume it can expand by at most double. 142 1.8 riastrad * 143 1.8 riastrad * XXX Check and consider tightening this assumption. 144 1.8 riastrad */ 145 1.8 riastrad __CTASSERT(MAX_BLOCKSIZE <= SIZE_MAX); 146 1.8 riastrad void *const uncompbuf = malloc(S->blocksize); 147 1.8 riastrad if (uncompbuf == NULL) 148 1.8 riastrad err(1, "malloc uncompressed buffer"); 149 1.8 riastrad 150 1.8 riastrad /* XXX compression ratio bound */ 151 1.29 riastrad __CTASSERT(MUL_OK(size_t, 2, MAX_BLOCKSIZE)); 152 1.8 riastrad void *const compbuf = malloc(2 * (size_t)S->blocksize); 153 1.8 riastrad if (compbuf == NULL) 154 1.8 riastrad err(1, "malloc compressed buffer"); 155 1.8 riastrad 156 1.8 riastrad /* 157 1.8 riastrad * Compress the blocks. S->blkno specifies the input block 158 1.8 riastrad * we're about to transfer. S->offset is the current output 159 1.8 riastrad * offset. 160 1.8 riastrad */ 161 1.8 riastrad while (S->blkno < S->n_blocks) { 162 1.8 riastrad /* Report any progress. */ 163 1.8 riastrad compress_progress(S); 164 1.8 riastrad 165 1.8 riastrad /* Stop if we've done the requested partial transfer. */ 166 1.8 riastrad if ((0 < S->end_block) && (S->end_block <= S->blkno)) 167 1.8 riastrad goto out; 168 1.8 riastrad 169 1.8 riastrad /* Checkpoint if appropriate. */ 170 1.8 riastrad compress_maybe_checkpoint(S); 171 1.15 riastrad offtab_prepare_put(&S->offtab, (S->blkno + 1)); 172 1.8 riastrad 173 1.8 riastrad /* Choose read size: partial if last block, full if not. */ 174 1.8 riastrad const uint32_t readsize = (S->blkno == S->n_full_blocks? 175 1.8 riastrad (S->size % S->blocksize) : S->blocksize); 176 1.8 riastrad assert(readsize > 0); 177 1.8 riastrad assert(readsize <= S->blocksize); 178 1.8 riastrad 179 1.8 riastrad /* Fail noisily if we might be about to overflow. */ 180 1.8 riastrad /* XXX compression ratio bound */ 181 1.29 riastrad __CTASSERT(MUL_OK(uint64_t, 2, MAX_BLOCKSIZE)); 182 1.29 riastrad __CTASSERT(MUL_OK(off_t, 2, MAX_BLOCKSIZE)); 183 1.8 riastrad assert(S->offset <= MIN(UINT64_MAX, OFF_MAX)); 184 1.29 riastrad if (!ADD_OK(uint64_t, S->offset, 2*(uintmax_t)readsize) || 185 1.29 riastrad !ADD_OK(off_t, S->offset, 2*(uintmax_t)readsize)) 186 1.8 riastrad errx(1, "blkno %"PRIu32" may overflow: %ju + 2*%ju", 187 1.8 riastrad S->blkno, (uintmax_t)S->offset, 188 1.8 riastrad (uintmax_t)readsize); 189 1.8 riastrad 190 1.8 riastrad /* Process the block. */ 191 1.8 riastrad const uint32_t complen = 192 1.8 riastrad compress_block(S->image_fd, S->cloop2_fd, S->blkno, 193 1.8 riastrad S->blocksize, readsize, uncompbuf, compbuf); 194 1.8 riastrad 195 1.8 riastrad /* 196 1.8 riastrad * Signal-atomically update the state to reflect 197 1.8 riastrad * (a) what block number we are now at, 198 1.8 riastrad * (b) how far we are now in the output file, and 199 1.8 riastrad * (c) where the last block ended. 200 1.8 riastrad */ 201 1.29 riastrad assert(ADD_OK(uint32_t, S->blkno, 1)); 202 1.29 riastrad assert(ADD_OK(uint64_t, S->offset, complen)); 203 1.29 riastrad assert(ADD_OK(off_t, (off_t)S->offset, (off_t)complen)); 204 1.8 riastrad assert((S->blkno + 1) < S->n_offsets); 205 1.8 riastrad { 206 1.8 riastrad sigset_t old_sigmask; 207 1.8 riastrad block_signals(&old_sigmask); 208 1.8 riastrad S->blkno += 1; /* (a) */ 209 1.8 riastrad S->offset += complen; /* (b) */ 210 1.15 riastrad offtab_put(&S->offtab, S->blkno, S->offset); /* (c) */ 211 1.8 riastrad restore_sigmask(&old_sigmask); 212 1.8 riastrad } 213 1.8 riastrad } 214 1.8 riastrad 215 1.8 riastrad /* Make sure we're all done. */ 216 1.8 riastrad assert(S->blkno == S->n_blocks); 217 1.8 riastrad assert((S->blkno + 1) == S->n_offsets); 218 1.8 riastrad 219 1.8 riastrad /* Pad to the disk block size. */ 220 1.8 riastrad const uint32_t n_extra = (S->offset % DEV_BSIZE); 221 1.8 riastrad if (n_extra != 0) { 222 1.8 riastrad const uint32_t n_padding = (DEV_BSIZE - n_extra); 223 1.8 riastrad /* Reuse compbuf -- guaranteed to be large enough. */ 224 1.8 riastrad (void)memset(compbuf, 0, n_padding); 225 1.8 riastrad const ssize_t n_written = write(S->cloop2_fd, compbuf, 226 1.8 riastrad n_padding); 227 1.8 riastrad if (n_written == -1) 228 1.8 riastrad err(1, "write final padding failed"); 229 1.8 riastrad assert(n_written >= 0); 230 1.8 riastrad if ((size_t)n_written != n_padding) 231 1.8 riastrad errx(1, "partial write of final padding bytes" 232 1.13 riastrad ": %zu != %"PRIu32, 233 1.13 riastrad (size_t)n_written, n_padding); 234 1.8 riastrad 235 1.8 riastrad /* Account for the extra bytes in the output file. */ 236 1.29 riastrad assert(ADD_OK(uint64_t, S->offset, n_padding)); 237 1.29 riastrad assert(ADD_OK(off_t, (off_t)S->offset, (off_t)n_padding)); 238 1.8 riastrad { 239 1.8 riastrad sigset_t old_sigmask; 240 1.8 riastrad block_signals(&old_sigmask); 241 1.8 riastrad S->offset += n_padding; 242 1.8 riastrad restore_sigmask(&old_sigmask); 243 1.8 riastrad } 244 1.8 riastrad } 245 1.8 riastrad 246 1.8 riastrad out: 247 1.15 riastrad /* One last checkpoint to commit the offset table. */ 248 1.8 riastrad assert(S->offset <= OFF_MAX); 249 1.8 riastrad assert((off_t)S->offset == lseek(S->cloop2_fd, 0, SEEK_CUR)); 250 1.8 riastrad compress_checkpoint(S); 251 1.8 riastrad 252 1.8 riastrad /* 253 1.8 riastrad * Free the compression buffers and finalize the compression. 254 1.8 riastrad */ 255 1.8 riastrad free(compbuf); 256 1.8 riastrad free(uncompbuf); 257 1.8 riastrad compress_exit(S); 258 1.1 hubertf 259 1.8 riastrad return 0; 260 1.8 riastrad } 261 1.1 hubertf 262 1.1 hubertf /* 263 1.8 riastrad * Signal cruft. 264 1.1 hubertf */ 265 1.8 riastrad 266 1.8 riastrad static void 267 1.8 riastrad init_signals(void) 268 1.8 riastrad { 269 1.8 riastrad 270 1.8 riastrad init_signal_handler(SA_RESTART, info_signals, 271 1.8 riastrad __arraycount(info_signals), &info_signal_handler); 272 1.8 riastrad init_signal_handler(SA_RESTART, checkpoint_signals, 273 1.8 riastrad __arraycount(checkpoint_signals), &checkpoint_signal_handler); 274 1.8 riastrad } 275 1.8 riastrad 276 1.8 riastrad static void 277 1.8 riastrad init_signal_handler(int flags, const struct sigdesc *signals, size_t n, 278 1.8 riastrad void (*handler)(int)) 279 1.1 hubertf { 280 1.8 riastrad static const struct sigaction zero_sa; 281 1.8 riastrad struct sigaction sa = zero_sa; 282 1.8 riastrad size_t i; 283 1.8 riastrad 284 1.8 riastrad (void)sigemptyset(&sa.sa_mask); 285 1.8 riastrad for (i = 0; i < n; i++) 286 1.8 riastrad (void)sigaddset(&sa.sa_mask, signals[i].sd_signo); 287 1.8 riastrad sa.sa_flags = flags; 288 1.8 riastrad sa.sa_handler = handler; 289 1.8 riastrad for (i = 0; i < n; i++) 290 1.8 riastrad if (sigaction(signals[i].sd_signo, &sa, NULL) == -1) 291 1.8 riastrad err(1, "sigaction(%s)", signals[i].sd_name); 292 1.8 riastrad } 293 1.8 riastrad 294 1.8 riastrad static void 295 1.8 riastrad info_signal_handler(int signo __unused) 296 1.8 riastrad { 297 1.8 riastrad /* Save errno. */ 298 1.8 riastrad const int error = errno; 299 1.8 riastrad struct compress_state *const S = &global_state; 300 1.8 riastrad char buf[128]; 301 1.8 riastrad 302 1.8 riastrad /* Bail if the state is not yet initialized. */ 303 1.8 riastrad if (!S->initialized) { 304 1.8 riastrad warnx_ss("initializing"); 305 1.8 riastrad goto out; 306 1.8 riastrad } 307 1.8 riastrad 308 1.8 riastrad /* Carefully calculate our I/O position. */ 309 1.8 riastrad assert(S->blocksize > 0); 310 1.29 riastrad __CTASSERT(MUL_OK(uint64_t, MAX_N_BLOCKS, MAX_BLOCKSIZE)); 311 1.8 riastrad const uint64_t nread = ((uint64_t)S->blkno * (uint64_t)S->blocksize); 312 1.8 riastrad 313 1.8 riastrad assert(S->n_blocks > 0); 314 1.29 riastrad __CTASSERT(MUL_OK(uint64_t, MAX_N_BLOCKS, sizeof(uint64_t))); 315 1.29 riastrad __CTASSERT(ADD_OK(uint64_t, CLOOP2_OFFSET_TABLE_OFFSET, 316 1.29 riastrad MAX_N_BLOCKS*sizeof(uint64_t))); 317 1.8 riastrad const uint64_t nwritten = (S->offset <= (CLOOP2_OFFSET_TABLE_OFFSET + 318 1.24 riastrad ((uint64_t)S->n_blocks * sizeof(uint64_t)))? 319 1.8 riastrad 0 : S->offset); 320 1.8 riastrad 321 1.8 riastrad /* snprintf_ss can't do floating-point, so do fixed-point instead. */ 322 1.8 riastrad const uint64_t ratio_percent = 323 1.8 riastrad (nread > 0? 324 1.8 riastrad ((nwritten >= (UINT64_MAX / 100)) ? 325 1.8 riastrad ((nwritten / nread) * 100) : ((nwritten * 100) / nread)) 326 1.8 riastrad : 0); 327 1.8 riastrad 328 1.8 riastrad /* Format the status. */ 329 1.29 riastrad assert(S->n_checkpointed_blocks <= MAX_N_BLOCKS); 330 1.29 riastrad assert(S->blocksize <= MAX_BLOCKSIZE); 331 1.29 riastrad __CTASSERT(MUL_OK(uint64_t, MAX_N_BLOCKS, MAX_BLOCKSIZE)); 332 1.8 riastrad const int n = snprintf_ss(buf, sizeof(buf), 333 1.8 riastrad "vndcompress: read %"PRIu64" bytes, wrote %"PRIu64" bytes, " 334 1.8 riastrad "compression ratio %"PRIu64"%% (checkpointed %"PRIu64" bytes)\n", 335 1.8 riastrad nread, nwritten, ratio_percent, 336 1.8 riastrad ((uint64_t)S->n_checkpointed_blocks * (uint64_t)S->blocksize)); 337 1.8 riastrad if (n < 0) { 338 1.8 riastrad const char msg[] = "vndcompress: can't format info\n"; 339 1.8 riastrad (void)write(STDERR_FILENO, msg, __arraycount(msg)); 340 1.1 hubertf } else { 341 1.8 riastrad __CTASSERT(INT_MAX <= SIZE_MAX); 342 1.8 riastrad (void)write(STDERR_FILENO, buf, (size_t)n); 343 1.8 riastrad } 344 1.8 riastrad 345 1.8 riastrad out: 346 1.8 riastrad /* Restore errno. */ 347 1.8 riastrad errno = error; 348 1.8 riastrad } 349 1.8 riastrad 350 1.8 riastrad static void 351 1.8 riastrad checkpoint_signal_handler(int signo __unused) 352 1.8 riastrad { 353 1.8 riastrad /* Save errno. */ 354 1.8 riastrad const int error = errno; 355 1.8 riastrad struct compress_state *const S = &global_state; 356 1.8 riastrad 357 1.8 riastrad /* Bail if the state is not yet initialized. */ 358 1.8 riastrad if (!S->initialized) { 359 1.8 riastrad warnx_ss("nothing to checkpoint yet"); 360 1.8 riastrad goto out; 361 1.1 hubertf } 362 1.8 riastrad 363 1.8 riastrad assert(S->image_fd >= 0); 364 1.8 riastrad assert(S->cloop2_fd >= 0); 365 1.8 riastrad 366 1.8 riastrad /* Take a checkpoint. */ 367 1.29 riastrad assert(S->blkno <= MAX_N_BLOCKS); 368 1.29 riastrad assert(S->blocksize <= MAX_BLOCKSIZE); 369 1.29 riastrad __CTASSERT(MUL_OK(uint64_t, MAX_N_BLOCKS, MAX_BLOCKSIZE)); 370 1.8 riastrad warnx_ss("checkpointing %"PRIu64" bytes", 371 1.8 riastrad ((uint64_t)S->blkno * (uint64_t)S->blocksize)); 372 1.8 riastrad compress_checkpoint(S); 373 1.8 riastrad 374 1.8 riastrad out: 375 1.8 riastrad /* Restore errno. */ 376 1.8 riastrad errno = error; 377 1.8 riastrad } 378 1.8 riastrad 379 1.8 riastrad /* 380 1.8 riastrad * Report progress. 381 1.8 riastrad * 382 1.8 riastrad * XXX Should do a progress bar here. 383 1.8 riastrad */ 384 1.8 riastrad static void 385 1.8 riastrad compress_progress(struct compress_state *S __unused) 386 1.8 riastrad { 387 1.1 hubertf } 388 1.1 hubertf 389 1.1 hubertf /* 390 1.8 riastrad * Parse arguments, open the files, and initialize the state. 391 1.1 hubertf */ 392 1.7 joerg static void 393 1.8 riastrad compress_init(int argc, char **argv, const struct options *O, 394 1.8 riastrad struct compress_state *S) 395 1.8 riastrad { 396 1.8 riastrad 397 1.8 riastrad if (!((argc == 2) || (argc == 3))) 398 1.8 riastrad usage(); 399 1.8 riastrad 400 1.8 riastrad const char *const image_pathname = argv[0]; 401 1.8 riastrad const char *const cloop2_pathname = argv[1]; 402 1.8 riastrad 403 1.21 riastrad /* Grab the block size either from `-b' or from the last argument. */ 404 1.8 riastrad __CTASSERT(0 < DEV_BSIZE); 405 1.8 riastrad __CTASSERT((MIN_BLOCKSIZE % DEV_BSIZE) == 0); 406 1.8 riastrad __CTASSERT(MIN_BLOCKSIZE <= DEF_BLOCKSIZE); 407 1.8 riastrad __CTASSERT((DEF_BLOCKSIZE % DEV_BSIZE) == 0); 408 1.8 riastrad __CTASSERT(DEF_BLOCKSIZE <= MAX_BLOCKSIZE); 409 1.8 riastrad __CTASSERT((MAX_BLOCKSIZE % DEV_BSIZE) == 0); 410 1.21 riastrad if (ISSET(O->flags, FLAG_b)) { 411 1.8 riastrad if (argc == 3) { 412 1.21 riastrad warnx("use -b or the extra argument, not both"); 413 1.8 riastrad usage(); 414 1.8 riastrad } 415 1.8 riastrad S->blocksize = O->blocksize; 416 1.8 riastrad } else { 417 1.8 riastrad S->blocksize = (argc == 2? DEF_BLOCKSIZE : 418 1.8 riastrad strsuftoll("block size", argv[2], MIN_BLOCKSIZE, 419 1.8 riastrad MAX_BLOCKSIZE)); 420 1.8 riastrad } 421 1.8 riastrad 422 1.8 riastrad /* Sanity-check the blocksize. (strsuftoll guarantees bounds.) */ 423 1.8 riastrad __CTASSERT(DEV_BSIZE <= UINT32_MAX); 424 1.8 riastrad if ((S->blocksize % DEV_BSIZE) != 0) 425 1.8 riastrad errx(1, "bad blocksize: %"PRIu32 426 1.8 riastrad " (not a multiple of %"PRIu32")", 427 1.8 riastrad S->blocksize, (uint32_t)DEV_BSIZE); 428 1.8 riastrad assert(MIN_BLOCKSIZE <= S->blocksize); 429 1.8 riastrad assert((S->blocksize % DEV_BSIZE) == 0); 430 1.8 riastrad assert(S->blocksize <= MAX_BLOCKSIZE); 431 1.8 riastrad 432 1.8 riastrad /* Grab the end block number if we have one. */ 433 1.8 riastrad S->end_block = (ISSET(O->flags, FLAG_p)? O->end_block : 0); 434 1.8 riastrad 435 1.8 riastrad /* Grab the checkpoint block count, if we have one. */ 436 1.8 riastrad S->checkpoint_blocks = 437 1.8 riastrad (ISSET(O->flags, FLAG_k)? O->checkpoint_blocks : 0); 438 1.8 riastrad 439 1.8 riastrad /* Open the input image file and the output cloop2 file. */ 440 1.8 riastrad S->image_fd = open(image_pathname, O_RDONLY); 441 1.8 riastrad if (S->image_fd == -1) 442 1.8 riastrad err(1, "open(%s)", image_pathname); 443 1.8 riastrad 444 1.8 riastrad int oflags; 445 1.8 riastrad if (!ISSET(O->flags, FLAG_r)) 446 1.28 riastrad oflags = (O_WRONLY | O_TRUNC | O_CREAT); 447 1.8 riastrad else if (!ISSET(O->flags, FLAG_R)) 448 1.8 riastrad oflags = (O_RDWR | O_CREAT); 449 1.8 riastrad else 450 1.8 riastrad oflags = O_RDWR; 451 1.8 riastrad S->cloop2_fd = open(cloop2_pathname, oflags, 0777); 452 1.8 riastrad if (S->cloop2_fd == -1) 453 1.8 riastrad err(1, "open(%s)", cloop2_pathname); 454 1.8 riastrad 455 1.8 riastrad /* Find the size of the input image. */ 456 1.8 riastrad if (ISSET(O->flags, FLAG_l)) { 457 1.8 riastrad S->size = O->length; 458 1.8 riastrad } else { 459 1.8 riastrad static const struct stat zero_st; 460 1.8 riastrad struct stat st = zero_st; 461 1.8 riastrad if (fstat(S->image_fd, &st) == -1) 462 1.8 riastrad err(1, "stat(%s)", image_pathname); 463 1.8 riastrad if (st.st_size <= 0) 464 1.8 riastrad errx(1, "unknown image size"); 465 1.8 riastrad assert(st.st_size >= 0); 466 1.8 riastrad __CTASSERT(OFF_MAX <= UINT64_MAX); 467 1.8 riastrad assert(__type_fit(uint64_t, st.st_size)); 468 1.8 riastrad S->size = st.st_size; 469 1.8 riastrad } 470 1.8 riastrad assert(S->size <= OFF_MAX); 471 1.8 riastrad 472 1.8 riastrad /* Find number of full blocks and whether there's a partial block. */ 473 1.29 riastrad __CTASSERT(0 < MIN_BLOCKSIZE); 474 1.29 riastrad assert(0 < S->blocksize); 475 1.29 riastrad if (TOOMANY(off_t, (off_t)S->size, (off_t)S->blocksize, 476 1.29 riastrad (off_t)MAX_N_BLOCKS)) 477 1.8 riastrad errx(1, "image too large for block size %"PRIu32": %"PRIu64, 478 1.8 riastrad S->blocksize, S->size); 479 1.29 riastrad __CTASSERT(MAX_N_BLOCKS <= UINT32_MAX); 480 1.29 riastrad S->n_full_blocks = S->size/S->blocksize; 481 1.29 riastrad S->n_blocks = HOWMANY(S->size, S->blocksize); 482 1.29 riastrad assert(S->n_full_blocks <= S->n_blocks); 483 1.8 riastrad assert(S->n_blocks <= MAX_N_BLOCKS); 484 1.8 riastrad 485 1.22 riastrad /* Choose a window size. */ 486 1.22 riastrad const uint32_t window_size = (ISSET(O->flags, FLAG_w)? O->window_size : 487 1.22 riastrad DEF_WINDOW_SIZE); 488 1.22 riastrad 489 1.15 riastrad /* Create an offset table for the blocks; one extra for the end. */ 490 1.29 riastrad __CTASSERT(ADD_OK(uint32_t, MAX_N_BLOCKS, 1)); 491 1.8 riastrad S->n_offsets = (S->n_blocks + 1); 492 1.8 riastrad __CTASSERT(MAX_N_OFFSETS == (MAX_N_BLOCKS + 1)); 493 1.29 riastrad __CTASSERT(MUL_OK(size_t, MAX_N_OFFSETS, sizeof(uint64_t))); 494 1.27 riastrad __CTASSERT(CLOOP2_OFFSET_TABLE_OFFSET <= OFFTAB_MAX_FDPOS); 495 1.22 riastrad offtab_init(&S->offtab, S->n_offsets, window_size, S->cloop2_fd, 496 1.15 riastrad CLOOP2_OFFSET_TABLE_OFFSET); 497 1.8 riastrad 498 1.8 riastrad /* Attempt to restart a partial transfer if requested. */ 499 1.8 riastrad if (ISSET(O->flags, FLAG_r)) { 500 1.8 riastrad if (compress_restart(S)) { 501 1.8 riastrad /* 502 1.8 riastrad * Restart succeeded. Truncate the output 503 1.8 riastrad * here, in case any garbage got appended. We 504 1.8 riastrad * are committed to making progress at this 505 1.8 riastrad * point. If the ftruncate fails, we don't 506 1.8 riastrad * lose anything valuable -- this is the last 507 1.8 riastrad * point at which we can restart anyway. 508 1.8 riastrad */ 509 1.8 riastrad if (ftruncate(S->cloop2_fd, S->offset) == -1) 510 1.8 riastrad err(1, "ftruncate failed"); 511 1.8 riastrad 512 1.8 riastrad /* All set! No more initialization to do. */ 513 1.8 riastrad return; 514 1.8 riastrad } else { 515 1.8 riastrad /* Restart failed. Barf now if requested. */ 516 1.8 riastrad if (ISSET(O->flags, FLAG_R)) 517 1.8 riastrad errx(1, "restart failed, aborting"); 518 1.8 riastrad 519 1.8 riastrad /* Otherwise, truncate and start at the top. */ 520 1.8 riastrad if (ftruncate(S->cloop2_fd, 0) == -1) 521 1.8 riastrad err(1, "truncate failed"); 522 1.8 riastrad if (lseek(S->cloop2_fd, 0, SEEK_SET) == -1) 523 1.8 riastrad err(1, "lseek to cloop2 beginning failed"); 524 1.25 riastrad 525 1.25 riastrad /* If we seeked in the input, rewind. */ 526 1.25 riastrad if (S->blkno != 0) { 527 1.25 riastrad if (lseek(S->image_fd, 0, SEEK_SET) == -1) 528 1.25 riastrad err(1, 529 1.25 riastrad "lseek to image beginning failed"); 530 1.25 riastrad } 531 1.8 riastrad } 532 1.8 riastrad } 533 1.8 riastrad 534 1.8 riastrad /* Write a bogus (zero) header for now, until we checkpoint. */ 535 1.8 riastrad static const struct cloop2_header zero_header; 536 1.8 riastrad const ssize_t h_written = write(S->cloop2_fd, &zero_header, 537 1.8 riastrad sizeof(zero_header)); 538 1.8 riastrad if (h_written == -1) 539 1.8 riastrad err(1, "write header"); 540 1.8 riastrad assert(h_written >= 0); 541 1.8 riastrad if ((size_t)h_written != sizeof(zero_header)) 542 1.13 riastrad errx(1, "partial write of header: %zu != %zu", 543 1.13 riastrad (size_t)h_written, sizeof(zero_header)); 544 1.8 riastrad 545 1.15 riastrad /* Reset the offset table to be empty and write it. */ 546 1.15 riastrad offtab_reset_write(&S->offtab); 547 1.8 riastrad 548 1.8 riastrad /* Start at the beginning of the image. */ 549 1.8 riastrad S->blkno = 0; 550 1.8 riastrad S->offset = (sizeof(struct cloop2_header) + 551 1.24 riastrad ((uint64_t)S->n_offsets * sizeof(uint64_t))); 552 1.8 riastrad S->n_checkpointed_blocks = 0; 553 1.8 riastrad 554 1.8 riastrad /* Good to go and ready for interruption by a signal. */ 555 1.8 riastrad S->initialized = 1; 556 1.8 riastrad } 557 1.8 riastrad 558 1.8 riastrad /* 559 1.8 riastrad * Try to recover state from an existing output file. 560 1.8 riastrad * 561 1.15 riastrad * On success, fill the offset table with what's in the file, set 562 1.8 riastrad * S->blkno and S->offset to reflect our position, and seek to the 563 1.8 riastrad * respective positions in the input and output files. 564 1.8 riastrad * 565 1.15 riastrad * On failure, return false. May clobber the offset table, S->blkno, 566 1.8 riastrad * S->offset, and the file pointers. 567 1.8 riastrad */ 568 1.8 riastrad static bool 569 1.8 riastrad compress_restart(struct compress_state *S) 570 1.8 riastrad { 571 1.8 riastrad 572 1.8 riastrad /* Read in the header. */ 573 1.8 riastrad static const struct cloop2_header zero_header; 574 1.8 riastrad struct cloop2_header header = zero_header; 575 1.8 riastrad 576 1.8 riastrad const ssize_t h_read = read_block(S->cloop2_fd, &header, 577 1.8 riastrad sizeof(header)); 578 1.8 riastrad if (h_read == -1) { 579 1.8 riastrad warn("failed to read header"); 580 1.8 riastrad return false; 581 1.8 riastrad } 582 1.8 riastrad assert(h_read >= 0); 583 1.8 riastrad if ((size_t)h_read != sizeof(header)) { 584 1.8 riastrad warnx("partial read of header"); 585 1.8 riastrad return false; 586 1.8 riastrad } 587 1.8 riastrad 588 1.8 riastrad /* Check that the header looks like a header. */ 589 1.8 riastrad __CTASSERT(sizeof(cloop2_magic) <= sizeof(header.cl2h_magic)); 590 1.8 riastrad if (memcmp(header.cl2h_magic, cloop2_magic, sizeof(cloop2_magic)) 591 1.8 riastrad != 0) { 592 1.8 riastrad warnx("bad cloop2 shell script magic"); 593 1.8 riastrad return false; 594 1.8 riastrad } 595 1.8 riastrad 596 1.8 riastrad /* Check the header parameters. */ 597 1.8 riastrad if (be32toh(header.cl2h_blocksize) != S->blocksize) { 598 1.8 riastrad warnx("mismatched block size: %"PRIu32 599 1.8 riastrad " (expected %"PRIu32")", 600 1.8 riastrad be32toh(header.cl2h_blocksize), S->blocksize); 601 1.8 riastrad return false; 602 1.8 riastrad } 603 1.8 riastrad if (be32toh(header.cl2h_n_blocks) != S->n_blocks) { 604 1.8 riastrad warnx("mismatched number of blocks: %"PRIu32 605 1.8 riastrad " (expected %"PRIu32")", 606 1.8 riastrad be32toh(header.cl2h_n_blocks), S->n_blocks); 607 1.8 riastrad return false; 608 1.8 riastrad } 609 1.8 riastrad 610 1.8 riastrad /* Read in the partial offset table. */ 611 1.15 riastrad if (!offtab_reset_read(&S->offtab, &warn, &warnx)) 612 1.8 riastrad return false; 613 1.15 riastrad if (!offtab_prepare_get(&S->offtab, 0)) 614 1.8 riastrad return false; 615 1.15 riastrad const uint64_t first_offset = offtab_get(&S->offtab, 0); 616 1.29 riastrad __CTASSERT(MUL_OK(uint64_t, MAX_N_OFFSETS, sizeof(uint64_t))); 617 1.29 riastrad __CTASSERT(ADD_OK(uint64_t, sizeof(struct cloop2_header), 618 1.29 riastrad MAX_N_OFFSETS*sizeof(uint64_t))); 619 1.29 riastrad const uint64_t expected = sizeof(struct cloop2_header) + 620 1.23 christos ((uint64_t)S->n_offsets * sizeof(uint64_t)); 621 1.23 christos if (first_offset != expected) { 622 1.17 riastrad warnx("first offset is not 0x%"PRIx64": 0x%"PRIx64, 623 1.23 christos expected, first_offset); 624 1.8 riastrad return false; 625 1.8 riastrad } 626 1.8 riastrad 627 1.8 riastrad /* Find where we left off. */ 628 1.8 riastrad __CTASSERT(MAX_N_OFFSETS <= UINT32_MAX); 629 1.8 riastrad uint32_t blkno = 0; 630 1.15 riastrad uint64_t last_offset = first_offset; 631 1.8 riastrad for (blkno = 0; blkno < S->n_blocks; blkno++) { 632 1.15 riastrad if (!offtab_prepare_get(&S->offtab, blkno)) 633 1.15 riastrad return false; 634 1.15 riastrad const uint64_t offset = offtab_get(&S->offtab, blkno); 635 1.15 riastrad if (offset == ~(uint64_t)0) 636 1.8 riastrad break; 637 1.15 riastrad 638 1.8 riastrad if (0 < blkno) { 639 1.15 riastrad const uint64_t start = last_offset; 640 1.15 riastrad const uint64_t end = offset; 641 1.8 riastrad if (end <= start) { 642 1.8 riastrad warnx("bad offset table: 0x%"PRIx64 643 1.8 riastrad ", 0x%"PRIx64, start, end); 644 1.8 riastrad return false; 645 1.8 riastrad } 646 1.8 riastrad /* XXX compression ratio bound */ 647 1.29 riastrad __CTASSERT(MUL_OK(size_t, 2, MAX_BLOCKSIZE)); 648 1.8 riastrad if ((2 * (size_t)S->blocksize) <= (end - start)) { 649 1.8 riastrad warnx("block %"PRIu32" too large:" 650 1.17 riastrad " %"PRIu64" bytes" 651 1.17 riastrad " from 0x%"PRIx64" to 0x%"PRIx64, 652 1.17 riastrad blkno, (end - start), start, end); 653 1.8 riastrad return false; 654 1.8 riastrad } 655 1.8 riastrad } 656 1.15 riastrad 657 1.15 riastrad last_offset = offset; 658 1.8 riastrad } 659 1.8 riastrad 660 1.8 riastrad if (blkno == 0) { 661 1.8 riastrad warnx("no blocks were written; nothing to restart"); 662 1.8 riastrad return false; 663 1.8 riastrad } 664 1.8 riastrad 665 1.8 riastrad /* Make sure the rest of the offset table is all ones. */ 666 1.8 riastrad if (blkno < S->n_blocks) { 667 1.8 riastrad uint32_t nblkno; 668 1.8 riastrad 669 1.8 riastrad for (nblkno = blkno; nblkno < S->n_blocks; nblkno++) { 670 1.15 riastrad if (!offtab_prepare_get(&S->offtab, nblkno)) 671 1.15 riastrad return false; 672 1.15 riastrad const uint64_t offset = offtab_get(&S->offtab, nblkno); 673 1.15 riastrad if (offset != ~(uint64_t)0) { 674 1.8 riastrad warnx("bad partial offset table entry" 675 1.17 riastrad " at %"PRIu32": 0x%"PRIx64, 676 1.15 riastrad nblkno, offset); 677 1.8 riastrad return false; 678 1.8 riastrad } 679 1.8 riastrad } 680 1.1 hubertf } 681 1.8 riastrad 682 1.1 hubertf /* 683 1.8 riastrad * XXX Consider decompressing some number of blocks to make 684 1.8 riastrad * sure they match. 685 1.1 hubertf */ 686 1.8 riastrad 687 1.8 riastrad /* Back up by one. */ 688 1.8 riastrad assert(1 <= blkno); 689 1.8 riastrad blkno -= 1; 690 1.8 riastrad 691 1.25 riastrad /* Seek to the output position. */ 692 1.25 riastrad assert(last_offset <= OFF_MAX); 693 1.25 riastrad if (lseek(S->cloop2_fd, last_offset, SEEK_SET) == -1) { 694 1.25 riastrad warn("lseek output cloop2 to %"PRIx64" failed", last_offset); 695 1.25 riastrad return false; 696 1.25 riastrad } 697 1.25 riastrad 698 1.25 riastrad /* Switch from reading to writing the offset table. */ 699 1.25 riastrad if (!offtab_transmogrify_read_to_write(&S->offtab, blkno)) 700 1.25 riastrad return false; 701 1.25 riastrad 702 1.25 riastrad /* 703 1.25 riastrad * Seek to the input position last, after all other possible 704 1.25 riastrad * failures, because if the input is a pipe, we can't change 705 1.25 riastrad * our mind, rewind, and start at the beginning instead of 706 1.25 riastrad * restarting. 707 1.25 riastrad */ 708 1.8 riastrad assert(S->size <= OFF_MAX); 709 1.8 riastrad assert(blkno <= (S->size / S->blocksize)); 710 1.8 riastrad const off_t restart_position = ((off_t)blkno * (off_t)S->blocksize); 711 1.8 riastrad assert(0 <= restart_position); 712 1.8 riastrad assert(restart_position <= (off_t)S->size); 713 1.8 riastrad if (lseek(S->image_fd, restart_position, SEEK_SET) == -1) { 714 1.8 riastrad if (errno != ESPIPE) { 715 1.8 riastrad warn("lseek input image failed"); 716 1.8 riastrad return false; 717 1.8 riastrad } 718 1.8 riastrad 719 1.8 riastrad /* Try read instead of lseek for a pipe/socket/fifo. */ 720 1.8 riastrad void *const buffer = malloc(0x10000); 721 1.8 riastrad if (buffer == NULL) 722 1.8 riastrad err(1, "malloc temporary buffer"); 723 1.8 riastrad off_t left = restart_position; 724 1.8 riastrad while (left > 0) { 725 1.8 riastrad const size_t size = MIN(0x10000, left); 726 1.8 riastrad const ssize_t n_read = read_block(S->image_fd, buffer, 727 1.8 riastrad size); 728 1.8 riastrad if (n_read == -1) { 729 1.8 riastrad free(buffer); 730 1.8 riastrad warn("read of input image failed"); 731 1.8 riastrad return false; 732 1.8 riastrad } 733 1.8 riastrad assert(n_read >= 0); 734 1.8 riastrad if ((size_t)n_read != size) { 735 1.8 riastrad free(buffer); 736 1.8 riastrad warnx("partial read of input image"); 737 1.8 riastrad return false; 738 1.1 hubertf } 739 1.8 riastrad assert((off_t)size <= left); 740 1.8 riastrad left -= size; 741 1.1 hubertf } 742 1.8 riastrad free(buffer); 743 1.8 riastrad } 744 1.8 riastrad 745 1.8 riastrad /* Start where we left off. */ 746 1.8 riastrad S->blkno = blkno; 747 1.15 riastrad S->offset = last_offset; 748 1.8 riastrad S->n_checkpointed_blocks = blkno; 749 1.8 riastrad 750 1.8 riastrad /* Good to go and ready for interruption by a signal. */ 751 1.8 riastrad S->initialized = 1; 752 1.8 riastrad 753 1.8 riastrad /* Success! */ 754 1.8 riastrad return true; 755 1.8 riastrad } 756 1.8 riastrad 757 1.8 riastrad /* 758 1.8 riastrad * Read a single block, compress it, and write the compressed block. 759 1.8 riastrad * Return the size of the compressed block. 760 1.8 riastrad */ 761 1.8 riastrad static uint32_t 762 1.8 riastrad compress_block(int in_fd, int out_fd, uint32_t blkno, uint32_t blocksize, 763 1.8 riastrad uint32_t readsize, void *uncompbuf, void *compbuf) 764 1.8 riastrad { 765 1.8 riastrad 766 1.8 riastrad assert(readsize <= blocksize); 767 1.8 riastrad assert(blocksize <= MAX_BLOCKSIZE); 768 1.8 riastrad 769 1.8 riastrad /* Read the uncompressed block. */ 770 1.8 riastrad const ssize_t n_read = read_block(in_fd, uncompbuf, readsize); 771 1.8 riastrad if (n_read == -1) 772 1.8 riastrad err(1, "read block %"PRIu32, blkno); 773 1.8 riastrad assert(n_read >= 0); 774 1.13 riastrad if ((size_t)n_read != readsize) 775 1.13 riastrad errx(1, "partial read of block %"PRIu32": %zu != %"PRIu32, 776 1.13 riastrad blkno, (size_t)n_read, readsize); 777 1.8 riastrad 778 1.8 riastrad /* Compress the block. */ 779 1.8 riastrad /* XXX compression ratio bound */ 780 1.29 riastrad __CTASSERT(MUL_OK(unsigned long, 2, MAX_BLOCKSIZE)); 781 1.8 riastrad const unsigned long uncomplen = 782 1.8 riastrad (VNDCOMPRESS_COMPAT? blocksize : readsize); /* XXX */ 783 1.8 riastrad unsigned long complen = (uncomplen * 2); 784 1.8 riastrad const int zerror = compress2(compbuf, &complen, uncompbuf, uncomplen, 785 1.8 riastrad Z_BEST_COMPRESSION); 786 1.8 riastrad if (zerror != Z_OK) 787 1.8 riastrad errx(1, "compressed failed at block %"PRIu32" (%d): %s", blkno, 788 1.8 riastrad zerror, zError(zerror)); 789 1.8 riastrad assert(complen <= (uncomplen * 2)); 790 1.8 riastrad 791 1.8 riastrad /* Write the compressed block. */ 792 1.8 riastrad const ssize_t n_written = write(out_fd, compbuf, complen); 793 1.8 riastrad if (n_written == -1) 794 1.8 riastrad err(1, "write block %"PRIu32, blkno); 795 1.8 riastrad assert(n_written >= 0); 796 1.13 riastrad if ((size_t)n_written != complen) 797 1.13 riastrad errx(1, "partial write of block %"PRIu32": %zu != %lu", 798 1.13 riastrad blkno, (size_t)n_written, complen); 799 1.8 riastrad 800 1.13 riastrad return (size_t)n_written; 801 1.1 hubertf } 802 1.1 hubertf 803 1.1 hubertf /* 804 1.8 riastrad * Checkpoint if appropriate. 805 1.1 hubertf */ 806 1.8 riastrad static void 807 1.8 riastrad compress_maybe_checkpoint(struct compress_state *S) 808 1.1 hubertf { 809 1.1 hubertf 810 1.8 riastrad if ((0 < S->checkpoint_blocks) && (0 < S->blkno) && 811 1.8 riastrad ((S->blkno % S->checkpoint_blocks) == 0)) { 812 1.8 riastrad assert(S->offset <= OFF_MAX); 813 1.8 riastrad assert((off_t)S->offset == lseek(S->cloop2_fd, 0, SEEK_CUR)); 814 1.8 riastrad compress_checkpoint(S); 815 1.1 hubertf } 816 1.1 hubertf } 817 1.1 hubertf 818 1.1 hubertf /* 819 1.8 riastrad * Write the prefix of the offset table that we have filled so far. 820 1.8 riastrad * 821 1.8 riastrad * We fsync the data blocks we have written, and then write the offset 822 1.8 riastrad * table, and then fsync the offset table and file metadata. This 823 1.8 riastrad * should help to avoid offset tables that point at garbage data. 824 1.8 riastrad * 825 1.8 riastrad * This may be called from a signal handler, so it must not use stdio, 826 1.8 riastrad * malloc, &c. -- it may only (a) handle signal-safe state in S, and 827 1.8 riastrad * (b) do file descriptor I/O / fsync. 828 1.8 riastrad * 829 1.8 riastrad * XXX This requires further thought and heavy testing to be sure. 830 1.8 riastrad * 831 1.8 riastrad * XXX Should have an option to suppress fsync. 832 1.8 riastrad * 833 1.8 riastrad * XXX Should have an option to fail on fsync failures. 834 1.8 riastrad * 835 1.8 riastrad * XXX Would be nice if we could just do a barrier rather than an 836 1.8 riastrad * fsync. 837 1.8 riastrad * 838 1.8 riastrad * XXX How might we automatically test the fsyncs? 839 1.1 hubertf */ 840 1.7 joerg static void 841 1.8 riastrad compress_checkpoint(struct compress_state *S) 842 1.1 hubertf { 843 1.8 riastrad 844 1.8 riastrad assert(S->blkno < S->n_offsets); 845 1.8 riastrad const uint32_t n_offsets = (S->blkno + 1); 846 1.8 riastrad assert(n_offsets <= S->n_offsets); 847 1.8 riastrad 848 1.8 riastrad assert(S->offset <= OFF_MAX); 849 1.8 riastrad assert((off_t)S->offset <= lseek(S->cloop2_fd, 0, SEEK_CUR)); 850 1.8 riastrad 851 1.8 riastrad /* Make sure the data hits the disk before we say it's ready. */ 852 1.8 riastrad if (fsync_range(S->cloop2_fd, (FFILESYNC | FDISKSYNC), 0, S->offset) 853 1.8 riastrad == -1) 854 1.8 riastrad warn_ss("fsync of output failed"); 855 1.8 riastrad 856 1.8 riastrad /* Say the data blocks are ready. */ 857 1.15 riastrad offtab_checkpoint(&S->offtab, n_offsets, 858 1.15 riastrad (S->n_checkpointed_blocks == 0? OFFTAB_CHECKPOINT_SYNC : 0)); 859 1.8 riastrad 860 1.1 hubertf /* 861 1.8 riastrad * If this is the first checkpoint, initialize the header. 862 1.8 riastrad * Signal handler can race with main code here, but it is 863 1.8 riastrad * harmless -- just an extra fsync and write of the header, 864 1.8 riastrad * which are both idempotent. 865 1.15 riastrad * 866 1.15 riastrad * Once we have synchronously checkpointed the offset table, 867 1.15 riastrad * subsequent writes will preserve a valid state. 868 1.1 hubertf */ 869 1.8 riastrad if (S->n_checkpointed_blocks == 0) { 870 1.8 riastrad static const struct cloop2_header zero_header; 871 1.8 riastrad struct cloop2_header header = zero_header; 872 1.8 riastrad 873 1.8 riastrad /* Format the header. */ 874 1.8 riastrad __CTASSERT(sizeof(cloop2_magic) <= sizeof(header.cl2h_magic)); 875 1.8 riastrad (void)memcpy(header.cl2h_magic, cloop2_magic, 876 1.8 riastrad sizeof(cloop2_magic)); 877 1.8 riastrad header.cl2h_blocksize = htobe32(S->blocksize); 878 1.8 riastrad header.cl2h_n_blocks = htobe32(S->n_blocks); 879 1.8 riastrad 880 1.8 riastrad /* Write the header. */ 881 1.8 riastrad const ssize_t h_written = pwrite(S->cloop2_fd, &header, 882 1.8 riastrad sizeof(header), 0); 883 1.8 riastrad if (h_written == -1) 884 1.8 riastrad err_ss(1, "write header"); 885 1.8 riastrad assert(h_written >= 0); 886 1.8 riastrad if ((size_t)h_written != sizeof(header)) 887 1.13 riastrad errx_ss(1, "partial write of header: %zu != %zu", 888 1.13 riastrad (size_t)h_written, sizeof(header)); 889 1.8 riastrad } 890 1.8 riastrad 891 1.8 riastrad /* Record how many blocks we've checkpointed. */ 892 1.8 riastrad { 893 1.8 riastrad sigset_t old_sigmask; 894 1.8 riastrad block_signals(&old_sigmask); 895 1.8 riastrad S->n_checkpointed_blocks = S->blkno; 896 1.8 riastrad restore_sigmask(&old_sigmask); 897 1.8 riastrad } 898 1.8 riastrad } 899 1.8 riastrad 900 1.8 riastrad /* 901 1.8 riastrad * Release everything we allocated in compress_init. 902 1.8 riastrad */ 903 1.8 riastrad static void 904 1.8 riastrad compress_exit(struct compress_state *S) 905 1.8 riastrad { 906 1.8 riastrad 907 1.15 riastrad /* Done with the offset table. Destroy it. */ 908 1.15 riastrad offtab_destroy(&S->offtab); 909 1.8 riastrad 910 1.8 riastrad /* Done with the files. Close them. */ 911 1.8 riastrad if (close(S->cloop2_fd) == -1) 912 1.8 riastrad warn("close(cloop2 fd)"); 913 1.8 riastrad if (close(S->image_fd) == -1) 914 1.8 riastrad warn("close(image fd)"); 915 1.1 hubertf } 916