/* * Output stream write function. */ static int string_buf_output_write(void *arg, const char *data, int len) { struct output_buf *const sbuf = arg; int esave; /* Was there a previous error? */ if (sbuf->error) { errno = sbuf->error; return (-1); } /* Expand buffer */ if (sbuf->length + len + 1 > sbuf->alloc) { const size_t new_size = ROUNDUP2(sbuf->length + len + 1, 256); void *mem; if ((mem = REALLOC(sbuf->mtype, sbuf->buf, new_size)) == NULL) { esave = errno; FREE(sbuf->mtype, sbuf->buf); sbuf->buf = NULL; sbuf->length = 0; sbuf->alloc = 0; sbuf->error = errno = esave; return (-1); } sbuf->buf = mem; sbuf->alloc = new_size; } /* Done */ memcpy(sbuf->buf + sbuf->length, data, len); sbuf->length += len; sbuf->buf[sbuf->length] = '\0'; return (len); }
/* * Resize (and compress) an existing cache file. Upon successful return, priv->fd is closed * and the cache file must be re-opened. */ static int cb_dcache_resize_file(struct cb_dcache *priv, const struct file_header *old_header) { const u_int old_max_blocks = old_header->max_blocks; const u_int new_max_blocks = priv->max_blocks; struct file_header new_header; off_t old_data_base; off_t new_data_base; u_int base_old_dslot; u_int new_dslot = 0; u_int num_entries; u_char *block_buf = NULL; char *tempfile = NULL; int new_fd = -1; int r; /* Create new temporary cache file */ if (asprintf(&tempfile, "%s.new", priv->filename) == -1) { r = errno; tempfile = NULL; (*priv->log)(LOG_ERR, "can't allocate string: %s", strerror(r)); goto fail; } if ((r = cb_dcache_create_file(priv, &new_fd, tempfile, new_max_blocks, &new_header)) != 0) goto fail; /* Allocate block data buffer */ if ((block_buf = malloc(priv->block_size)) == NULL) { r = errno; (*priv->log)(LOG_ERR, "can't allocate buffer: %s", strerror(r)); goto fail; } /* Copy non-empty cache entries from old file to new file */ old_data_base = ROUNDUP2(DIR_OFFSET(old_max_blocks), old_header->data_align); new_data_base = ROUNDUP2(DIR_OFFSET(new_max_blocks), new_header.data_align); for (base_old_dslot = 0; base_old_dslot < old_max_blocks; base_old_dslot += num_entries) { struct dir_entry entries[DIRECTORY_READ_CHUNK]; int i; /* Read in the next chunk of old directory entries */ num_entries = old_max_blocks - base_old_dslot; if (num_entries > DIRECTORY_READ_CHUNK) num_entries = DIRECTORY_READ_CHUNK; if ((r = cb_dcache_read(priv, DIR_OFFSET(base_old_dslot), entries, num_entries * sizeof(*entries))) != 0) { (*priv->log)(LOG_ERR, "error reading cache file `%s' directory: %s", priv->filename, strerror(r)); goto fail; } /* For each dslot: if not free, copy it to the next slot in the new file */ for (i = 0; i < num_entries; i++) { const struct dir_entry *const entry = &entries[i]; const u_int old_dslot = base_old_dslot + i; off_t old_data; off_t new_data; /* Is this entry non-empty? */ if (memcmp(entry, &zero_entry, sizeof(*entry)) == 0) continue; /* Any more space? */ if (new_dslot == new_max_blocks) { (*priv->log)(LOG_INFO, "cache file `%s' contains more than %u blocks; some will be discarded", priv->filename, new_max_blocks); goto done; } /* Copy the directory entry */ if ((r = cb_dcache_write2(priv, new_fd, tempfile, DIR_OFFSET(new_dslot), entry, sizeof(*entry))) != 0) goto fail; /* Copy the data block */ old_data = old_data_base + (off_t)old_dslot * priv->block_size; new_data = new_data_base + (off_t)new_dslot * priv->block_size; if ((r = cb_dcache_read(priv, old_data, block_buf, priv->block_size)) != 0) goto fail; if ((r = cb_dcache_write2(priv, new_fd, tempfile, new_data, block_buf, priv->block_size)) != 0) goto fail; /* Advance to the next slot */ new_dslot++; } } done: /* Close the new file */ if (close(new_fd) == -1) { (*priv->log)(LOG_ERR, "error closing temporary cache file `%s': %s", tempfile, strerror(r)); goto fail; } new_fd = -1; /* Replace old cache file with new cache file */ if (rename(tempfile, priv->filename) == -1) { r = errno; (*priv->log)(LOG_ERR, "error renaming `%s' to `%s': %s", tempfile, priv->filename, strerror(r)); goto fail; } free(tempfile); tempfile = NULL; /* Close old file to release it and we're done */ close(priv->fd); priv->fd = -1; r = 0; fail: /* Clean up */ if (block_buf != NULL) free(block_buf); if (new_fd != -1) (void)close(new_fd); if (tempfile != NULL) { (void)unlink(tempfile); free(tempfile); } return r; }
int cb_dcache_open(struct cb_dcache **dcachep, log_func_t *log, const char *filename, u_int block_size, u_int max_blocks, cb_dcache_visit_t *visitor, void *arg) { struct file_header header; struct cb_dcache *priv; struct stat sb; int r; /* Sanity check */ if (max_blocks == 0) return EINVAL; /* Initialize private structure */ if ((priv = malloc(sizeof(*priv))) == NULL) return errno; memset(priv, 0, sizeof(*priv)); priv->fd = -1; priv->log = log; priv->block_size = block_size; priv->max_blocks = max_blocks; if ((priv->filename = strdup(filename)) == NULL) { r = errno; goto fail1; } if ((priv->zero_block = calloc(1, block_size)) == NULL) { r = errno; goto fail2; } /* Create cache file if it doesn't already exist */ if (stat(priv->filename, &sb) == -1 && errno == ENOENT) { (*priv->log)(LOG_NOTICE, "creating new cache file `%s' with capacity %u blocks", priv->filename, priv->max_blocks); if ((r = cb_dcache_create_file(priv, &priv->fd, priv->filename, priv->max_blocks, NULL)) != 0) goto fail3; (void)close(priv->fd); priv->fd = -1; } retry: /* Open cache file */ assert(priv->fd == -1); if ((priv->fd = open(priv->filename, O_RDWR, 0)) == -1) { r = errno; (*priv->log)(LOG_ERR, "can't open cache file `%s': %s", priv->filename, strerror(r)); goto fail3; } /* Get file info */ if (fstat(priv->fd, &sb) == -1) { r = errno; goto fail4; } /* Read in header */ if (sb.st_size < sizeof(header)) { (*priv->log)(LOG_ERR, "invalid cache file `%s': file is truncated (size %ju < %u)", priv->filename, (uintmax_t)sb.st_size, (u_int)sizeof(header)); r = EINVAL; goto fail4; } if ((r = cb_dcache_read(priv, (off_t)0, &header, sizeof(header))) != 0) { (*priv->log)(LOG_ERR, "can't read cache file `%s' header: %s", priv->filename, strerror(r)); goto fail4; } /* Verify header - all but number of blocks */ r = EINVAL; if (header.signature != DCACHE_SIGNATURE) { (*priv->log)(LOG_ERR, "invalid cache file `%s': wrong signature %08x != %08x", priv->filename, header.signature, DCACHE_SIGNATURE); goto fail4; } if (header.header_size != sizeof(header)) { (*priv->log)(LOG_ERR, "invalid cache file `%s': %s", priv->filename, "unrecognized format"); goto fail4; } if (header.u_int_size != sizeof(u_int)) { (*priv->log)(LOG_ERR, "invalid cache file `%s': created with sizeof(u_int) %u != %u", priv->filename, header.u_int_size, (u_int)sizeof(u_int)); goto fail4; } if (header.cb_block_t_size != sizeof(cb_block_t)) { (*priv->log)(LOG_ERR, "invalid cache file `%s': created with sizeof(cb_block_t) %u != %u", priv->filename, header.cb_block_t_size, (u_int)sizeof(cb_block_t)); goto fail4; } if (header.block_size != priv->block_size) { (*priv->log)(LOG_ERR, "invalid cache file `%s': created with block size %u != %u", priv->filename, header.block_size, priv->block_size); goto fail4; } if (header.data_align != getpagesize()) { (*priv->log)(LOG_ERR, "invalid cache file `%s': created with alignment %u != %u", priv->filename, header.data_align, getpagesize()); goto fail4; } if (header.zero != 0) { (*priv->log)(LOG_ERR, "invalid cache file `%s': %s", priv->filename, "unrecognized field"); goto fail4; } /* Check number of blocks, shrinking or expanding if necessary */ if (header.max_blocks != priv->max_blocks) { (*priv->log)(LOG_NOTICE, "cache file `%s' was created with capacity %u != %u blocks, automatically %s", priv->filename, header.max_blocks, priv->max_blocks, header.max_blocks < priv->max_blocks ? "expanding" : "shrinking"); if ((r = cb_dcache_resize_file(priv, &header)) != 0) goto fail4; (*priv->log)(LOG_INFO, "successfully resized cache file `%s' from %u to %u blocks", priv->filename, header.max_blocks, priv->max_blocks); goto retry; } /* Verify file's directory is not truncated */ if (sb.st_size < DIR_OFFSET(priv->max_blocks)) { (*priv->log)(LOG_ERR, "invalid cache file `%s': file is truncated (size %ju < %ju)", priv->filename, (uintmax_t)sb.st_size, (uintmax_t)DIR_OFFSET(priv->max_blocks)); goto fail4; } /* Compute offset of first data block */ priv->data = ROUNDUP2(DIR_OFFSET(priv->max_blocks), header.data_align); /* Read the directory to build the free list and visit allocated blocks */ if ((r = cb_dcache_init_free_list(priv, visitor, arg)) != 0) goto fail4; /* Done */ *dcachep = priv; return 0; fail4: close(priv->fd); fail3: free(priv->zero_block); fail2: free(priv->filename); fail1: free(priv->free_list); free(priv); return r; }