/* gdbmopen.c - Open the dbm file and initialize data structures for use. */ /* This file is part of GDBM, the GNU data base manager. Copyright (C) 1990-2021 Free Software Foundation, Inc. GDBM is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GDBM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GDBM. If not, see . */ /* Include system configuration before all else. */ #include "autoconf.h" #include "gdbmdefs.h" #include static void compute_directory_size (blksize_t block_size, int *ret_dir_size, int *ret_dir_bits) { /* Create the initial hash table directory. */ int dir_size = 8 * sizeof (off_t); int dir_bits = 3; if (block_size > INT_MAX / 2) block_size = INT_MAX / 2; while (dir_size < block_size && dir_bits < GDBM_HASH_BITS - 3) { dir_size <<= 1; dir_bits++; } *ret_dir_size = dir_size; *ret_dir_bits = dir_bits; } static inline int bucket_element_count (size_t bucket_size) { return (bucket_size - sizeof (hash_bucket)) / sizeof (bucket_element) + 1; } static void gdbm_header_avail (gdbm_file_header *hdr, avail_block **avail_ptr, size_t *avail_size, gdbm_ext_header **exhdr) { switch (hdr->header_magic) { case GDBM_OMAGIC: case GDBM_MAGIC: *exhdr = NULL; *avail_ptr = &((gdbm_file_standard_header*)hdr)->avail; *avail_size = (hdr->block_size - offsetof (gdbm_file_standard_header, avail)); break; case GDBM_NUMSYNC_MAGIC: *exhdr = &((gdbm_file_extended_header*)hdr)->ext; *avail_ptr = &((gdbm_file_extended_header*)hdr)->avail; *avail_size = (hdr->block_size - offsetof (gdbm_file_extended_header, avail)); break; } } static int validate_header_std (gdbm_file_header const *hdr, struct stat const *st) { int result = GDBM_NO_ERROR; int dir_size, dir_bits; if (!(hdr->block_size > 0 && hdr->block_size > sizeof (gdbm_file_header) && hdr->block_size - sizeof (gdbm_file_header) >= sizeof(avail_block))) { return GDBM_BLOCK_SIZE_ERROR; } /* Technically speaking, the condition below should read hdr->next_block != st->st_size However, gdbm versions prior to commit 4e819c98 could leave hdr->next_block pointing beyond current end of file. To ensure backward compatibility with these versions, the condition has been slackened to this: */ if (hdr->next_block < st->st_size) result = GDBM_NEED_RECOVERY; /* Make sure dir and dir + dir_size fall within the file boundary */ if (!(hdr->dir > 0 && hdr->dir < st->st_size && hdr->dir_size > 0 && hdr->dir + hdr->dir_size < st->st_size)) return GDBM_BAD_HEADER; compute_directory_size (hdr->block_size, &dir_size, &dir_bits); if (!(hdr->dir_size >= dir_size)) return GDBM_BAD_HEADER; compute_directory_size (hdr->dir_size, &dir_size, &dir_bits); if (hdr->dir_bits != dir_bits) return GDBM_BAD_HEADER; if (!(hdr->bucket_size > 0 && hdr->bucket_size > sizeof (hash_bucket))) return GDBM_BAD_HEADER; if (hdr->bucket_elems != bucket_element_count (hdr->bucket_size)) return GDBM_BAD_HEADER; return result; } static int validate_header_numsync (gdbm_file_header const *hdr, struct stat const *st) { int result = GDBM_NO_ERROR; int dir_size, dir_bits; if (!(hdr->block_size > 0 && hdr->block_size > (sizeof (gdbm_file_header) + sizeof (gdbm_ext_header)) && hdr->block_size - (sizeof (gdbm_file_header) + sizeof (gdbm_ext_header)) >= sizeof(avail_block))) { return GDBM_BLOCK_SIZE_ERROR; } /* Technically speaking, the condition below should read hdr->next_block != st->st_size However, gdbm versions prior to commit 4e819c98 could leave hdr->next_block pointing beyond current end of file. To ensure backward compatibility with these versions, the condition has been slackened to this: */ if (hdr->next_block < st->st_size) result = GDBM_NEED_RECOVERY; /* Make sure dir and dir + dir_size fall within the file boundary */ if (!(hdr->dir > 0 && hdr->dir < st->st_size && hdr->dir_size > 0 && hdr->dir + hdr->dir_size < st->st_size)) return GDBM_BAD_HEADER; compute_directory_size (hdr->block_size, &dir_size, &dir_bits); if (!(hdr->dir_size >= dir_size)) return GDBM_BAD_HEADER; compute_directory_size (hdr->dir_size, &dir_size, &dir_bits); if (hdr->dir_bits != dir_bits) return GDBM_BAD_HEADER; if (!(hdr->bucket_size > 0 && hdr->bucket_size > sizeof (hash_bucket))) return GDBM_BAD_HEADER; if (hdr->bucket_elems != bucket_element_count (hdr->bucket_size)) return GDBM_BAD_HEADER; return result; } static int validate_header (gdbm_file_header const *hdr, struct stat const *st) { /* Is the magic number good? */ switch (hdr->header_magic) { case GDBM_OMAGIC: case GDBM_MAGIC: return validate_header_std (hdr, st); case GDBM_NUMSYNC_MAGIC: return validate_header_numsync (hdr, st); default: switch (hdr->header_magic) { case GDBM_OMAGIC_SWAP: case GDBM_MAGIC32_SWAP: case GDBM_MAGIC64_SWAP: case GDBM_NUMSYNC_MAGIC32_SWAP: case GDBM_NUMSYNC_MAGIC64_SWAP: return GDBM_BYTE_SWAPPED; case GDBM_MAGIC32: case GDBM_MAGIC64: case GDBM_NUMSYNC_MAGIC32: case GDBM_NUMSYNC_MAGIC64: return GDBM_BAD_FILE_OFFSET; default: return GDBM_BAD_MAGIC_NUMBER; } } } int _gdbm_validate_header (GDBM_FILE dbf) { struct stat file_stat; int rc; if (fstat (dbf->desc, &file_stat)) return GDBM_FILE_STAT_ERROR; rc = validate_header (dbf->header, &file_stat); if (rc == 0) { if (gdbm_avail_block_validate (dbf, dbf->avail, dbf->avail_size)) rc = GDBM_BAD_AVAIL; } return rc; } /* Do we have ftruncate? */ static inline int _gdbm_ftruncate (GDBM_FILE dbf) { #if HAVE_FTRUNCATE return ftruncate (dbf->desc, 0); #else int fd; fd = open (dbf->name, O_RDWR|O_TRUNC, mode); if (fd == -1) return -1; return close (fd); #endif } GDBM_FILE gdbm_fd_open (int fd, const char *file_name, int block_size, int flags, void (*fatal_func) (const char *)) { GDBM_FILE dbf; /* The record to return. */ struct stat file_stat; /* Space for the stat information. */ off_t file_pos; /* Used with seeks. */ int index; /* Used as a loop index. */ /* Initialize the gdbm_errno variable. */ gdbm_set_errno (NULL, GDBM_NO_ERROR, FALSE); /* Get the status of the file. */ if (fstat (fd, &file_stat)) { if (flags & GDBM_CLOERROR) SAVE_ERRNO (close (fd)); GDBM_SET_ERRNO2 (NULL, GDBM_FILE_STAT_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } /* Allocate new info structure. */ dbf = calloc (1, sizeof (*dbf)); if (dbf == NULL) { if (flags & GDBM_CLOERROR) SAVE_ERRNO (close (fd)); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } dbf->desc = fd; /* Initialize some fields for known values. This is done so gdbm_close will work if called before allocating some structures. */ dbf->dir = NULL; dbf->bucket = NULL; dbf->header = NULL; dbf->file_size = -1; dbf->memory_mapping = FALSE; dbf->mapped_size_max = SIZE_T_MAX; dbf->mapped_region = NULL; dbf->mapped_size = 0; dbf->mapped_pos = 0; dbf->mapped_off = 0; /* Save name of file. */ dbf->name = strdup (file_name); if (dbf->name == NULL) { if (flags & GDBM_CLOERROR) close (fd); _gdbm_cache_free (dbf); free (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } /* Initialize the fatal error routine. */ dbf->fatal_err = fatal_func; dbf->fast_write = TRUE; /* Default to setting fast_write. */ dbf->file_locking = TRUE; /* Default to doing file locking. */ dbf->central_free = FALSE; /* Default to not using central_free. */ dbf->coalesce_blocks = FALSE; /* Default to not coalesce blocks. */ dbf->need_recovery = FALSE; dbf->last_error = GDBM_NO_ERROR; dbf->last_syserror = 0; dbf->last_errstr = NULL; _gdbmsync_init (dbf); /* GDBM_FAST used to determine whether or not we set fast_write. */ if (flags & GDBM_SYNC) { /* If GDBM_SYNC has been requested, don't do fast_write. */ dbf->fast_write = FALSE; } if (flags & GDBM_NOLOCK) { dbf->file_locking = FALSE; } dbf->cloexec = !!(flags & GDBM_CLOEXEC); /* Zero-length file can't be a reader... */ if (((flags & GDBM_OPENMASK) == GDBM_READER) && (file_stat.st_size == 0)) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_EMPTY_DATABASE, FALSE, GDBM_DEBUG_OPEN); return NULL; } /* Record the kind of user. */ dbf->read_write = (flags & GDBM_OPENMASK); /* Lock the file in the appropriate way. */ if (dbf->file_locking) { if (_gdbm_lock_file (dbf) == -1) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); GDBM_SET_ERRNO2 (NULL, (flags & GDBM_OPENMASK) == GDBM_READER ? GDBM_CANT_BE_READER : GDBM_CANT_BE_WRITER, FALSE, GDBM_DEBUG_OPEN); return NULL; } } /* If we do have a write lock and it was a GDBM_NEWDB, it is now time to truncate the file. */ if ((flags & GDBM_OPENMASK) == GDBM_NEWDB && file_stat.st_size != 0) { if (_gdbm_ftruncate (dbf)) { GDBM_SET_ERRNO2 (dbf, GDBM_FILE_TRUNCATE_ERROR, FALSE, GDBM_DEBUG_OPEN); } else if (fstat (dbf->desc, &file_stat)) { GDBM_SET_ERRNO2 (dbf, GDBM_FILE_STAT_ERROR, FALSE, GDBM_DEBUG_OPEN); } if (gdbm_last_errno (dbf)) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } } /* Decide if this is a new file or an old file. */ if (file_stat.st_size == 0) { /* This is a new file. Create an empty database. */ int dir_size, dir_bits; /* Start with the blocksize. */ if (block_size < GDBM_MIN_BLOCK_SIZE) { block_size = STATBLKSIZE (file_stat); flags &= ~GDBM_BSEXACT; } compute_directory_size (block_size, &dir_size, &dir_bits); GDBM_DEBUG (GDBM_DEBUG_OPEN, "%s: computed dir_size=%d, dir_bits=%d", dbf->name, dir_size, dir_bits); /* Check for correct block_size. */ if (dir_size != block_size) { if (flags & GDBM_BSEXACT) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_BLOCK_SIZE_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } else block_size = dir_size; } GDBM_DEBUG (GDBM_DEBUG_OPEN, "%s: block_size=%d", dbf->name, block_size); /* Get space for the file header. It will be written to disk, so make sure there's no garbage in it. */ dbf->header = calloc (1, block_size); if (dbf->header == NULL) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } /* Set the magic number and the block_size. */ if (flags & GDBM_NUMSYNC) dbf->header->header_magic = GDBM_NUMSYNC_MAGIC; else dbf->header->header_magic = GDBM_MAGIC; /* * Set block size. It must be initialized for gdbm_header_avail to work. */ dbf->header->block_size = block_size; gdbm_header_avail (dbf->header, &dbf->avail, &dbf->avail_size, &dbf->xheader); dbf->header->dir_size = dir_size; dbf->header->dir_bits = dir_bits; /* Allocate the space for the directory. */ dbf->dir = (off_t *) malloc (dbf->header->dir_size); if (dbf->dir == NULL) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } dbf->header->dir = dbf->header->block_size; /* Create the first and only hash bucket. */ dbf->header->bucket_elems = bucket_element_count (dbf->header->block_size); dbf->header->bucket_size = dbf->header->block_size; dbf->bucket = calloc (1, dbf->header->bucket_size); if (dbf->bucket == NULL) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } _gdbm_new_bucket (dbf, dbf->bucket, 0); dbf->bucket->av_count = 1; dbf->bucket->bucket_avail[0].av_adr = 3*dbf->header->block_size; dbf->bucket->bucket_avail[0].av_size = dbf->header->block_size; /* Set table entries to point to hash buckets. */ for (index = 0; index < GDBM_DIR_COUNT (dbf); index++) dbf->dir[index] = 2*dbf->header->block_size; /* Initialize the active avail block. */ dbf->avail->size = (dbf->avail_size - offsetof(avail_block, av_table)) / sizeof (avail_elem); dbf->avail->count = 0; dbf->avail->next_block = 0; dbf->header->next_block = 4*dbf->header->block_size; /* Write initial configuration to the file. */ /* Block 0 is the file header and active avail block. */ if (_gdbm_full_write (dbf, dbf->header, dbf->header->block_size)) { GDBM_DEBUG (GDBM_DEBUG_OPEN|GDBM_DEBUG_ERR, "%s: error writing header: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } /* Block 1 is the initial bucket directory. */ if (_gdbm_full_write (dbf, dbf->dir, dbf->header->dir_size)) { GDBM_DEBUG (GDBM_DEBUG_OPEN|GDBM_DEBUG_ERR, "%s: error writing directory: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } /* Block 2 is the only bucket. */ if (_gdbm_full_write (dbf, dbf->bucket, dbf->header->bucket_size)) { GDBM_DEBUG (GDBM_DEBUG_OPEN|GDBM_DEBUG_ERR, "%s: error writing bucket: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } if (_gdbm_file_extend (dbf, dbf->header->next_block)) { GDBM_DEBUG (GDBM_DEBUG_OPEN|GDBM_DEBUG_ERR, "%s: error extending file: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } /* Wait for initial configuration to be written to disk. */ gdbm_file_sync (dbf); free (dbf->bucket); } else { /* This is an old database. Read in the information from the file header and initialize the hash directory. */ gdbm_file_header partial_header; /* For the first part of it. */ int rc; /* Read the partial file header. */ if (_gdbm_full_read (dbf, &partial_header, sizeof (partial_header))) { GDBM_DEBUG (GDBM_DEBUG_ERR|GDBM_DEBUG_OPEN, "%s: error reading partial header: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } /* Is the header valid? */ rc = validate_header (&partial_header, &file_stat); if (rc == GDBM_NEED_RECOVERY) { dbf->need_recovery = 1; } else if (rc != GDBM_NO_ERROR) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, rc, FALSE, GDBM_DEBUG_OPEN); return NULL; } /* It is a good database, read the entire header. */ dbf->header = malloc (partial_header.block_size); if (dbf->header == NULL) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } memcpy (dbf->header, &partial_header, sizeof (partial_header)); if (_gdbm_full_read (dbf, dbf->header + 1, dbf->header->block_size - sizeof (gdbm_file_header))) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } gdbm_header_avail (dbf->header, &dbf->avail, &dbf->avail_size, &dbf->xheader); if (((dbf->header->block_size - (GDBM_HEADER_AVAIL_OFFSET (dbf) + sizeof (avail_block))) / sizeof (avail_elem) + 1) != dbf->avail->size) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_BAD_HEADER, FALSE, GDBM_DEBUG_OPEN); return NULL; } if (gdbm_avail_block_validate (dbf, dbf->avail, dbf->avail_size)) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } /* Allocate space for the hash table directory. */ dbf->dir = malloc (dbf->header->dir_size); if (dbf->dir == NULL) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; gdbm_close (dbf); GDBM_SET_ERRNO2 (NULL, GDBM_MALLOC_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } /* Read the hash table directory. */ file_pos = gdbm_file_seek (dbf, dbf->header->dir, SEEK_SET); if (file_pos != dbf->header->dir) { if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); GDBM_SET_ERRNO2 (NULL, GDBM_FILE_SEEK_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } if (_gdbm_full_read (dbf, dbf->dir, dbf->header->dir_size)) { GDBM_DEBUG (GDBM_DEBUG_ERR|GDBM_DEBUG_OPEN, "%s: error reading dir: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } } if (_gdbm_cache_init (dbf, DEFAULT_CACHESIZE)) { GDBM_DEBUG (GDBM_DEBUG_ERR|GDBM_DEBUG_OPEN, "%s: error initializing cache: %s", dbf->name, gdbm_db_strerror (dbf)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } #if HAVE_MMAP if (!(flags & GDBM_NOMMAP)) { dbf->mmap_preread = (flags & GDBM_PREREAD) != 0; if (_gdbm_mapped_init (dbf) == 0) dbf->memory_mapping = TRUE; else { /* gdbm_errno should already be set. */ GDBM_DEBUG (GDBM_DEBUG_ERR|GDBM_DEBUG_OPEN, "%s: _gdbm_mapped_init failed: %s", dbf->name, strerror (errno)); if (!(flags & GDBM_CLOERROR)) dbf->desc = -1; SAVE_ERRNO (gdbm_close (dbf)); return NULL; } } #endif /* Finish initializing dbf. */ dbf->bucket = NULL; dbf->bucket_dir = 0; dbf->header_changed = FALSE; dbf->directory_changed = FALSE; dbf->bucket_changed = FALSE; dbf->second_changed = FALSE; if (flags & GDBM_XVERIFY) { gdbm_avail_verify (dbf); } GDBM_DEBUG (GDBM_DEBUG_ALL, "%s: opened %s", dbf->name, dbf->need_recovery ? "for recovery" : "successfully"); /* Everything is fine, return the pointer to the file information structure. */ return dbf; } /* Initialize dbm system. FILE is a pointer to the file name. If the file has a size of zero bytes, a file initialization procedure is performed, setting up the initial structure in the file. BLOCK_SIZE is used during initialization to determine the size of various constructs. If the value is less than GDBM_MIN_BLOCK_SIZE, the file system blocksize is used, otherwise the value of BLOCK_SIZE is used. BLOCK_SIZE is ignored if the file has previously initialized. If FLAGS is set to GDBM_READ the user wants to just read the database and any call to dbm_store or dbm_delete will fail. Many readers can access the database at the same time. If FLAGS is set to GDBM_WRITE, the user wants both read and write access to the database and requires exclusive access. If FLAGS is GDBM_WRCREAT, the user wants both read and write access to the database and if the database does not exist, create a new one. If FLAGS is GDBM_NEWDB, the user want a new database created, regardless of whether one existed, and wants read and write access to the new database. Any error detected will cause a return value of null and an approprate value will be in gdbm_errno. If no errors occur, a pointer to the "gdbm file descriptor" will be returned. */ GDBM_FILE gdbm_open (const char *file, int block_size, int flags, int mode, void (*fatal_func) (const char *)) { int fd; /* additional bits for open(2) flags */ int fbits = 0; switch (flags & GDBM_OPENMASK) { case GDBM_READER: fbits = O_RDONLY; break; case GDBM_WRITER: fbits = O_RDWR; break; case GDBM_WRCREAT: case GDBM_NEWDB: fbits = O_RDWR|O_CREAT; break; default: errno = EINVAL; GDBM_SET_ERRNO2 (NULL, GDBM_FILE_OPEN_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } if (flags & GDBM_CLOEXEC) fbits |= O_CLOEXEC; fd = open (file, fbits, mode); if (fd < 0) { GDBM_SET_ERRNO2 (NULL, GDBM_FILE_OPEN_ERROR, FALSE, GDBM_DEBUG_OPEN); return NULL; } return gdbm_fd_open (fd, file, block_size, flags | GDBM_CLOERROR, fatal_func); } int _gdbm_file_size (GDBM_FILE dbf, off_t *psize) { if (dbf->file_size == -1) { struct stat sb; if (fstat (dbf->desc, &sb)) { GDBM_SET_ERRNO (dbf, GDBM_FILE_STAT_ERROR, FALSE); return -1; } dbf->file_size = sb.st_size; } *psize = dbf->file_size; return 0; } /* * Convert from numsync to the standard GDBM format. This is pretty * straightforward: the avail block gets expanded by * sizeof(gdbm_ext_header), so we only need to shift the avail block * up this number of bytes, change the avail and avail_size pointers * and update the magic number. */ static int _gdbm_convert_from_numsync (GDBM_FILE dbf) { avail_block *old_avail = dbf->avail; /* Change the magic number */ dbf->header->header_magic = GDBM_MAGIC; /* Update avail pointer and size */ gdbm_header_avail (dbf->header, &dbf->avail, &dbf->avail_size, &dbf->xheader); /* Move data up */ memmove (dbf->avail, old_avail, dbf->avail_size - sizeof (gdbm_ext_header)); /* Fix up the avail table size */ dbf->avail->size = (dbf->avail_size - offsetof (avail_block, av_table)) / sizeof (avail_elem); dbf->header_changed = TRUE; return 0; } /* * Convert the database from the standard to extended (numsync) format. * The avail block shrinks by sizeof(gdbm_ext_header) bytes. The av_table * entries that don't fit into the new size need to be returned to the * avail pool using the _gdbm_free call. */ static int _gdbm_convert_to_numsync (GDBM_FILE dbf) { avail_block *old_avail = dbf->avail; size_t old_avail_size = dbf->avail->size; size_t n; /* Number of elements to return to the pool */ int rc; avail_elem *av = NULL; /* Change the magic number */ dbf->header->header_magic = GDBM_NUMSYNC_MAGIC; /* Update avail pointer and size */ gdbm_header_avail (dbf->header, &dbf->avail, &dbf->avail_size, &dbf->xheader); /* * Compute new av_table size. * NOTE: Don't try to modify dbf->avail until the final move, otherwise * the available block would end up clobbered. All modifications are * applied to old_avail. */ old_avail->size = (dbf->avail_size - offsetof (avail_block, av_table)) / sizeof (avail_elem); /* Compute the number of avail elements that don't fit in the new table. */ n = old_avail_size - old_avail->size; if (n > 0) { /* Stash them away */ av = calloc (n, sizeof (av[0])); if (!av) { GDBM_SET_ERRNO (dbf, GDBM_MALLOC_ERROR, FALSE); return -1; } n = 0; while (old_avail->count > old_avail->size) { old_avail->count--; av[n++] = old_avail->av_table[old_avail->count]; } } /* * Move the modified avail block into its new place. From now on, * old_avail may not be used. The database header is in consistent * state and all modifications should be applied to it directly. */ memmove (dbf->avail, old_avail, dbf->avail_size); /* Initialize the extended header */ memset (dbf->xheader, 0, sizeof (dbf->xheader[0])); rc = 0; /* Assume success */ if (av) { /* Return stashed av_table elements to the available pool. */ /* _gdbm_free needs a non-NULL bucket, so get one: */ if (!dbf->bucket) rc = _gdbm_get_bucket (dbf, 0); if (rc == 0) { size_t i; for (i = 0; i < n; i++) { rc = _gdbm_free (dbf, av[i].av_adr, av[i].av_size); if (rc) break; } } free (av); } dbf->header_changed = TRUE; return rc; } int gdbm_convert (GDBM_FILE dbf, int flag) { int rc; /* Return immediately if the database needs recovery */ GDBM_ASSERT_CONSISTENCY (dbf, -1); /* First check to make sure this guy is a writer. */ if (dbf->read_write == GDBM_READER) { GDBM_SET_ERRNO2 (dbf, GDBM_READER_CANT_STORE, FALSE, GDBM_DEBUG_STORE); return -1; } switch (flag) { case 0: case GDBM_NUMSYNC: break; default: GDBM_SET_ERRNO2 (dbf, GDBM_MALFORMED_DATA, FALSE, GDBM_DEBUG_STORE); return -1; } rc = 0; switch (dbf->header->header_magic) { case GDBM_OMAGIC: case GDBM_MAGIC: if (flag == GDBM_NUMSYNC) rc = _gdbm_convert_to_numsync (dbf); break; case GDBM_NUMSYNC_MAGIC: if (flag == 0) rc = _gdbm_convert_from_numsync (dbf); } if (rc == 0) rc = _gdbm_end_update (dbf); return 0; }