/* Check that an unused record is valid. */ static bool tdb_check_free_record(struct tdb_context *tdb, tdb_off_t off, const struct tdb_record *rec, unsigned char **hashes) { if (!tdb_check_record(tdb, off, rec)) return false; /* Mark this offset as a known value for the free list. */ record_offset(hashes[0], off); /* And similarly if the next pointer is valid. */ if (rec->next) record_offset(hashes[0], rec->next); return true; }
static int zip_load_next(gp_container *self, gp_pixmap **img, gp_storage *storage, gp_progress_cb *callback) { struct zip_priv *priv = GP_CONTAINER_PRIV(self); int err; GP_DEBUG(1, "Trying to load next image from ZIP container"); *img = NULL; do { err = zip_next_file(priv, img, storage, callback); } while (!*img && errno == 0); if (err) return 1; record_offset(priv, gp_io_tell(priv->io)); priv->cur_pos++; self->cur_img = priv->cur_pos; return 0; }
static int load_next_offset(struct zip_priv *priv) { struct zip_local_header header = {.file_name = NULL}; int ret; long offset = gp_io_tell(priv->io); if ((ret = zip_load_header(priv->io, &header))) return ret; //TODO: Match image extension and signature record_offset(priv, offset); /* Seek to the next local header */ seek_bytes(priv->io, (uint32_t)header.fname_len + (uint32_t)header.extf_len); seek_bytes(priv->io, header.comp_size); return 0; }
_PUBLIC_ int tdb_check(struct tdb_context *tdb, int (*check)(TDB_DATA key, TDB_DATA data, void *private_data), void *private_data) { unsigned int h; unsigned char **hashes; tdb_off_t off, recovery_start; struct tdb_record rec; bool found_recovery = false; tdb_len_t dead; bool locked; /* Read-only databases use no locking at all: it's best-effort. * We may have a write lock already, so skip that case too. */ if (tdb->read_only || tdb->allrecord_lock.count != 0) { locked = false; } else { if (tdb_lockall_read(tdb) == -1) return -1; locked = true; } /* Make sure we know true size of the underlying file. */ tdb->methods->tdb_oob(tdb, tdb->map_size, 1, 1); /* Header must be OK: also gets us the recovery ptr, if any. */ if (!tdb_check_header(tdb, &recovery_start)) goto unlock; /* We should have the whole header, too. */ if (tdb->map_size < TDB_DATA_START(tdb->hash_size)) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "File too short for hashes\n")); goto unlock; } /* One big malloc: pointers then bit arrays. */ hashes = (unsigned char **)calloc( 1, sizeof(hashes[0]) * (1+tdb->hash_size) + BITMAP_BITS / CHAR_BIT * (1+tdb->hash_size)); if (!hashes) { tdb->ecode = TDB_ERR_OOM; goto unlock; } /* Initialize pointers */ hashes[0] = (unsigned char *)(&hashes[1+tdb->hash_size]); for (h = 1; h < 1+tdb->hash_size; h++) hashes[h] = hashes[h-1] + BITMAP_BITS / CHAR_BIT; /* Freelist and hash headers are all in a row: read them. */ for (h = 0; h < 1+tdb->hash_size; h++) { if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t), &off) == -1) goto free; if (off) record_offset(hashes[h], off); } /* For each record, read it in and check it's ok. */ for (off = TDB_DATA_START(tdb->hash_size); off < tdb->map_size; off += sizeof(rec) + rec.rec_len) { if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), DOCONV()) == -1) goto free; switch (rec.magic) { case TDB_MAGIC: case TDB_DEAD_MAGIC: if (!tdb_check_used_record(tdb, off, &rec, hashes, check, private_data)) goto free; break; case TDB_FREE_MAGIC: if (!tdb_check_free_record(tdb, off, &rec, hashes)) goto free; break; /* If we crash after ftruncate, we can get zeroes or fill. */ case TDB_RECOVERY_INVALID_MAGIC: case 0x42424242: if (recovery_start == off) { found_recovery = true; break; } dead = tdb_dead_space(tdb, off); if (dead < sizeof(rec)) goto corrupt; TDB_LOG((tdb, TDB_DEBUG_ERROR, "Dead space at %d-%d (of %u)\n", off, off + dead, tdb->map_size)); rec.rec_len = dead - sizeof(rec); break; case TDB_RECOVERY_MAGIC: if (recovery_start != off) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Unexpected recovery record at offset %d\n", off)); goto free; } found_recovery = true; break; default: ; corrupt: tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "Bad magic 0x%x at offset %d\n", rec.magic, off)); goto free; } } /* Now, hashes should all be empty: each record exists and is referred * to by one other. */ for (h = 0; h < 1+tdb->hash_size; h++) { unsigned int i; for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) { if (hashes[h][i] != 0) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "Hashes do not match records\n")); goto free; } } } /* We must have found recovery area if there was one. */ if (recovery_start != 0 && !found_recovery) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Expected a recovery area at %u\n", recovery_start)); goto free; } free(hashes); if (locked) { tdb_unlockall_read(tdb); } return 0; free: free(hashes); unlock: if (locked) { tdb_unlockall_read(tdb); } return -1; }
int tdb_check(struct tdb_context *tdb, int (*check)(TDB_DATA key, TDB_DATA data, void *private_data), void *private_data) { unsigned int h; unsigned char **hashes; tdb_off_t off, recovery_start; struct tdb_record rec; bool found_recovery = false; if (tdb_lockall(tdb) == -1) return -1; /* Make sure we know true size of the underlying file. */ tdb->methods->tdb_oob(tdb, tdb->map_size + 1, 1); /* Header must be OK: also gets us the recovery ptr, if any. */ if (!tdb_check_header(tdb, &recovery_start)) goto unlock; /* We should have the whole header, too. */ if (tdb->map_size < TDB_DATA_START(tdb->header.hash_size)) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "File too short for hashes\n")); goto unlock; } /* One big malloc: pointers then bit arrays. */ hashes = (unsigned char **)calloc( 1, sizeof(hashes[0]) * (1+tdb->header.hash_size) + BITMAP_BITS / CHAR_BIT * (1+tdb->header.hash_size)); if (!hashes) { tdb->ecode = TDB_ERR_OOM; goto unlock; } /* Initialize pointers */ hashes[0] = (unsigned char *)(&hashes[1+tdb->header.hash_size]); for (h = 1; h < 1+tdb->header.hash_size; h++) hashes[h] = hashes[h-1] + BITMAP_BITS / CHAR_BIT; /* Freelist and hash headers are all in a row: read them. */ for (h = 0; h < 1+tdb->header.hash_size; h++) { if (tdb_ofs_read(tdb, FREELIST_TOP + h*sizeof(tdb_off_t), &off) == -1) goto free; if (off) record_offset(hashes[h], off); } /* For each record, read it in and check it's ok. */ for (off = TDB_DATA_START(tdb->header.hash_size); off < tdb->map_size; off += sizeof(rec) + rec.rec_len) { if (tdb->methods->tdb_read(tdb, off, &rec, sizeof(rec), DOCONV()) == -1) goto free; switch (rec.magic) { case TDB_MAGIC: case TDB_DEAD_MAGIC: if (!tdb_check_used_record(tdb, off, &rec, hashes, check, private_data)) goto free; break; case TDB_FREE_MAGIC: if (!tdb_check_free_record(tdb, off, &rec, hashes)) goto free; break; case TDB_RECOVERY_MAGIC: case 0: /* Used for invalid (or in-progress) recovery area. */ if (recovery_start != off) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Unexpected recovery record at offset %d\n", off)); goto free; } found_recovery = true; break; default: tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "Bad magic 0x%x at offset %d\n", rec.magic, off)); goto free; } } /* Now, hashes should all be empty: each record exists and is referred * to by one other. */ for (h = 0; h < 1+tdb->header.hash_size; h++) { unsigned int i; for (i = 0; i < BITMAP_BITS / CHAR_BIT; i++) { if (hashes[h][i] != 0) { tdb->ecode = TDB_ERR_CORRUPT; TDB_LOG((tdb, TDB_DEBUG_ERROR, "Hashes do not match records\n")); goto free; } } } /* We must have found recovery area if there was one. */ if (recovery_start != 0 && !found_recovery) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Expected %s recovery area, got %s\n", recovery_start ? "a" : "no", found_recovery ? "one" : "none")); goto free; } free(hashes); tdb_unlockall(tdb); return 0; free: free(hashes); unlock: tdb_unlockall(tdb); return -1; }
/* Check that an in-use record is valid. */ static bool tdb_check_used_record(struct tdb_context *tdb, tdb_off_t off, const struct tdb_record *rec, unsigned char **hashes, int (*check)(TDB_DATA, TDB_DATA, void *), void *private_data) { TDB_DATA key, data; tdb_len_t len; if (!tdb_check_record(tdb, off, rec)) return false; /* key + data + tailer must fit in record */ len = rec->key_len; len += rec->data_len; if (len < rec->data_len) { /* overflow */ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record lengths overflow\n")); return false; } len += sizeof(tdb_off_t); if (len < sizeof(tdb_off_t)) { /* overflow */ TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record lengths overflow\n")); return false; } if (len > rec->rec_len) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record offset %u too short for contents\n", off)); return false; } key = get_bytes(tdb, off + sizeof(*rec), rec->key_len); if (!key.dptr) return false; if (tdb->hash_fn(&key) != rec->full_hash) { TDB_LOG((tdb, TDB_DEBUG_ERROR, "Record offset %u has incorrect hash\n", off)); goto fail_put_key; } /* Mark this offset as a known value for this hash bucket. */ record_offset(hashes[BUCKET(rec->full_hash)+1], off); /* And similarly if the next pointer is valid. */ if (rec->next) record_offset(hashes[BUCKET(rec->full_hash)+1], rec->next); /* If they supply a check function and this record isn't dead, get data and feed it. */ if (check && rec->magic != TDB_DEAD_MAGIC) { data = get_bytes(tdb, off + sizeof(*rec) + rec->key_len, rec->data_len); if (!data.dptr) goto fail_put_key; if (check(key, data, private_data) == -1) goto fail_put_data; put_bytes(tdb, data); } put_bytes(tdb, key); return true; fail_put_data: put_bytes(tdb, data); fail_put_key: put_bytes(tdb, key); return false; }