5 kx /* gdbmopen.c - Open the dbm file and initialize data structures for use. */
5 kx
5 kx /* This file is part of GDBM, the GNU data base manager.
5 kx Copyright (C) 1990, 1991, 1993, 2007, 2011, 2013 Free Software Foundation,
5 kx Inc.
5 kx
5 kx GDBM is free software; you can redistribute it and/or modify
5 kx it under the terms of the GNU General Public License as published by
5 kx the Free Software Foundation; either version 3, or (at your option)
5 kx any later version.
5 kx
5 kx GDBM is distributed in the hope that it will be useful,
5 kx but WITHOUT ANY WARRANTY; without even the implied warranty of
5 kx MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5 kx GNU General Public License for more details.
5 kx
5 kx You should have received a copy of the GNU General Public License
5 kx along with GDBM. If not, see <http://www.gnu.org/licenses/>. */
5 kx
5 kx /* Include system configuration before all else. */
5 kx #include "autoconf.h"
5 kx
5 kx #include "gdbmdefs.h"
5 kx
5 kx /* Determine our native magic number and bail if we can't. */
5 kx #if SIZEOF_OFF_T == 4
5 kx # define GDBM_MAGIC GDBM_MAGIC32
5 kx #elif SIZEOF_OFF_T == 8
5 kx # define GDBM_MAGIC GDBM_MAGIC64
5 kx #else
5 kx # error "Unsupported off_t size, contact GDBM maintainer. What crazy system is this?!?"
5 kx #endif
5 kx
5 kx /* Initialize dbm system. FILE is a pointer to the file name. If the file
5 kx has a size of zero bytes, a file initialization procedure is performed,
5 kx setting up the initial structure in the file. BLOCK_SIZE is used during
5 kx initialization to determine the size of various constructs. If the value
5 kx is less than 512, the file system blocksize is used, otherwise the value
5 kx of BLOCK_SIZE is used. BLOCK_SIZE is ignored if the file has previously
5 kx initialized. If FLAGS is set to GDBM_READ the user wants to just
5 kx read the database and any call to dbm_store or dbm_delete will fail. Many
5 kx readers can access the database at the same time. If FLAGS is set to
5 kx GDBM_WRITE, the user wants both read and write access to the database and
5 kx requires exclusive access. If FLAGS is GDBM_WRCREAT, the user wants
5 kx both read and write access to the database and if the database does not
5 kx exist, create a new one. If FLAGS is GDBM_NEWDB, the user want a
5 kx new database created, regardless of whether one existed, and wants read
5 kx and write access to the new database. Any error detected will cause a
5 kx return value of null and an approprate value will be in gdbm_errno. If
5 kx no errors occur, a pointer to the "gdbm file descriptor" will be
5 kx returned. */
5 kx
5 kx
5 kx GDBM_FILE
5 kx gdbm_open (const char *file, int block_size, int flags, int mode,
5 kx void (*fatal_func) (const char *))
5 kx {
5 kx GDBM_FILE dbf; /* The record to return. */
5 kx struct stat file_stat; /* Space for the stat information. */
5 kx int len; /* Length of the file name. */
5 kx off_t file_pos; /* Used with seeks. */
5 kx int file_block_size; /* Block size to use for a new file. */
5 kx int index; /* Used as a loop index. */
5 kx char need_trunc; /* Used with GDBM_NEWDB and locking to avoid
5 kx truncating a file from under a reader. */
5 kx int rc; /* temporary error code */
5 kx int fbits = 0; /* additional bits for open(2) flags */
5 kx
5 kx /* Initialize the gdbm_errno variable. */
5 kx gdbm_errno = GDBM_NO_ERROR;
5 kx
5 kx /* Allocate new info structure. */
5 kx dbf = (GDBM_FILE) malloc (sizeof (*dbf));
5 kx if (dbf == NULL)
5 kx {
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Initialize some fields for known values. This is done so gdbm_close
5 kx will work if called before allocating some structures. */
5 kx dbf->dir = NULL;
5 kx dbf->bucket = NULL;
5 kx dbf->header = NULL;
5 kx dbf->bucket_cache = NULL;
5 kx dbf->cache_size = 0;
5 kx
5 kx dbf->memory_mapping = FALSE;
5 kx dbf->mapped_size_max = SIZE_T_MAX;
5 kx dbf->mapped_region = NULL;
5 kx dbf->mapped_size = 0;
5 kx dbf->mapped_pos = 0;
5 kx dbf->mapped_off = 0;
5 kx
5 kx /* Save name of file. */
5 kx len = strlen (file);
5 kx dbf->name = (char *) malloc (len + 1);
5 kx if (dbf->name == NULL)
5 kx {
5 kx free (dbf);
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx strcpy (dbf->name, file);
5 kx
5 kx /* Initialize the fatal error routine. */
5 kx dbf->fatal_err = fatal_func;
5 kx
5 kx dbf->fast_write = TRUE; /* Default to setting fast_write. */
5 kx dbf->file_locking = TRUE; /* Default to doing file locking. */
5 kx dbf->central_free = FALSE; /* Default to not using central_free. */
5 kx dbf->coalesce_blocks = FALSE; /* Default to not coalescing blocks. */
5 kx
5 kx /* GDBM_FAST used to determine whether or not we set fast_write. */
5 kx if (flags & GDBM_SYNC)
5 kx {
5 kx /* If GDBM_SYNC has been requested, don't do fast_write. */
5 kx dbf->fast_write = FALSE;
5 kx }
5 kx if (flags & GDBM_NOLOCK)
5 kx {
5 kx dbf->file_locking = FALSE;
5 kx }
5 kx if (flags & GDBM_CLOEXEC)
5 kx {
5 kx fbits = O_CLOEXEC;
5 kx dbf->cloexec = TRUE;
5 kx }
5 kx else
5 kx dbf->cloexec = FALSE;
5 kx
5 kx /* Open the file. */
5 kx need_trunc = FALSE;
5 kx switch (flags & GDBM_OPENMASK)
5 kx {
5 kx case GDBM_READER:
5 kx dbf->desc = open (dbf->name, O_RDONLY|fbits, 0);
5 kx break;
5 kx
5 kx case GDBM_WRITER:
5 kx dbf->desc = open (dbf->name, O_RDWR|fbits, 0);
5 kx break;
5 kx
5 kx case GDBM_NEWDB:
5 kx dbf->desc = open (dbf->name, O_RDWR|O_CREAT|fbits, mode);
5 kx need_trunc = TRUE;
5 kx break;
5 kx
5 kx default:
5 kx dbf->desc = open (dbf->name, O_RDWR|O_CREAT|fbits, mode);
5 kx break;
5 kx
5 kx }
5 kx if (dbf->desc < 0)
5 kx {
5 kx SAVE_ERRNO (free (dbf->name);
5 kx free (dbf));
5 kx gdbm_errno = GDBM_FILE_OPEN_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Get the status of the file. */
5 kx if (fstat (dbf->desc, &file_stat))
5 kx {
5 kx SAVE_ERRNO (close (dbf->desc);
5 kx free (dbf->name);
5 kx free (dbf));
5 kx gdbm_errno = GDBM_FILE_STAT_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Zero-length file can't be a reader... */
5 kx if (((flags & GDBM_OPENMASK) == GDBM_READER) && (file_stat.st_size == 0))
5 kx {
5 kx close (dbf->desc);
5 kx free (dbf->name);
5 kx free (dbf);
5 kx gdbm_errno = GDBM_EMPTY_DATABASE;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Record the kind of user. */
5 kx dbf->read_write = (flags & GDBM_OPENMASK);
5 kx
5 kx /* Lock the file in the appropriate way. */
5 kx if (dbf->file_locking)
5 kx {
5 kx if (_gdbm_lock_file (dbf) == -1)
5 kx {
5 kx close (dbf->desc);
5 kx free (dbf->name);
5 kx free (dbf);
5 kx if ((flags & GDBM_OPENMASK) == GDBM_READER)
5 kx gdbm_errno = GDBM_CANT_BE_READER;
5 kx else
5 kx gdbm_errno = GDBM_CANT_BE_WRITER;
5 kx return NULL;
5 kx }
5 kx }
5 kx
5 kx /* If we do have a write lock and it was a GDBM_NEWDB, it is
5 kx now time to truncate the file. */
5 kx if (need_trunc && file_stat.st_size != 0)
5 kx {
5 kx TRUNCATE (dbf);
5 kx fstat (dbf->desc, &file_stat);
5 kx }
5 kx
5 kx /* Decide if this is a new file or an old file. */
5 kx if (file_stat.st_size == 0)
5 kx {
5 kx
5 kx /* This is a new file. Create an empty database. */
5 kx
5 kx /* Start with the blocksize. */
5 kx if (block_size < 512)
5 kx file_block_size = STATBLKSIZE;
5 kx else
5 kx file_block_size = block_size;
5 kx
5 kx /* Get space for the file header. It will be written to disk, so
5 kx make sure there's no garbage in it. */
5 kx dbf->header = (gdbm_file_header *) calloc (1, file_block_size);
5 kx if (dbf->header == NULL)
5 kx {
5 kx gdbm_close (dbf);
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Set the magic number and the block_size. */
5 kx dbf->header->header_magic = GDBM_MAGIC;
5 kx dbf->header->block_size = file_block_size;
5 kx
5 kx /* Create the initial hash table directory. */
5 kx dbf->header->dir_size = 8 * sizeof (off_t);
5 kx dbf->header->dir_bits = 3;
5 kx while (dbf->header->dir_size < dbf->header->block_size)
5 kx {
5 kx dbf->header->dir_size <<= 1;
5 kx dbf->header->dir_bits += 1;
5 kx }
5 kx
5 kx /* Check for correct block_size. */
5 kx if (dbf->header->dir_size != dbf->header->block_size)
5 kx {
5 kx gdbm_close (dbf);
5 kx gdbm_errno = GDBM_BLOCK_SIZE_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Allocate the space for the directory. */
5 kx dbf->dir = (off_t *) malloc (dbf->header->dir_size);
5 kx if (dbf->dir == NULL)
5 kx {
5 kx gdbm_close (dbf);
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx dbf->header->dir = dbf->header->block_size;
5 kx
5 kx /* Create the first and only hash bucket. */
5 kx dbf->header->bucket_elems =
5 kx (dbf->header->block_size - sizeof (hash_bucket))
5 kx / sizeof (bucket_element) + 1;
5 kx dbf->header->bucket_size = dbf->header->block_size;
5 kx dbf->bucket = (hash_bucket *) calloc (1, dbf->header->bucket_size);
5 kx if (dbf->bucket == NULL)
5 kx {
5 kx gdbm_close (dbf);
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx _gdbm_new_bucket (dbf, dbf->bucket, 0);
5 kx dbf->bucket->av_count = 1;
5 kx dbf->bucket->bucket_avail[0].av_adr = 3*dbf->header->block_size;
5 kx dbf->bucket->bucket_avail[0].av_size = dbf->header->block_size;
5 kx
5 kx /* Set table entries to point to hash buckets. */
5 kx for (index = 0; index < GDBM_DIR_COUNT (dbf); index++)
5 kx dbf->dir[index] = 2*dbf->header->block_size;
5 kx
5 kx /* Initialize the active avail block. */
5 kx dbf->header->avail.size
5 kx = ( (dbf->header->block_size - sizeof (gdbm_file_header))
5 kx / sizeof (avail_elem)) + 1;
5 kx dbf->header->avail.count = 0;
5 kx dbf->header->avail.next_block = 0;
5 kx dbf->header->next_block = 4*dbf->header->block_size;
5 kx
5 kx /* Write initial configuration to the file. */
5 kx /* Block 0 is the file header and active avail block. */
5 kx rc = _gdbm_full_write (dbf, dbf->header, dbf->header->block_size);
5 kx if (rc)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = rc;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Block 1 is the initial bucket directory. */
5 kx rc = _gdbm_full_write (dbf, dbf->dir, dbf->header->dir_size);
5 kx if (rc)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = rc;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Block 2 is the only bucket. */
5 kx rc = _gdbm_full_write (dbf, dbf->bucket, dbf->header->bucket_size);
5 kx if (rc)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = rc;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Wait for initial configuration to be written to disk. */
5 kx __fsync (dbf);
5 kx
5 kx free (dbf->bucket);
5 kx }
5 kx else
5 kx {
5 kx /* This is an old database. Read in the information from the file
5 kx header and initialize the hash directory. */
5 kx
5 kx gdbm_file_header partial_header; /* For the first part of it. */
5 kx
5 kx /* Read the partial file header. */
5 kx rc = _gdbm_full_read (dbf, &partial_header, sizeof (gdbm_file_header));
5 kx if (rc)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = rc;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Is the magic number good? */
5 kx if (partial_header.header_magic != GDBM_MAGIC
5 kx && partial_header.header_magic != GDBM_OMAGIC)
5 kx {
5 kx gdbm_close (dbf);
5 kx switch (partial_header.header_magic)
5 kx {
5 kx case GDBM_OMAGIC_SWAP:
5 kx case GDBM_MAGIC32_SWAP:
5 kx case GDBM_MAGIC64_SWAP:
5 kx gdbm_errno = GDBM_BYTE_SWAPPED;
5 kx break;
5 kx case GDBM_MAGIC32:
5 kx case GDBM_MAGIC64:
5 kx gdbm_errno = GDBM_BAD_FILE_OFFSET;
5 kx break;
5 kx default:
5 kx gdbm_errno = GDBM_BAD_MAGIC_NUMBER;
5 kx }
5 kx return NULL;
5 kx }
5 kx
5 kx /* It is a good database, read the entire header. */
5 kx dbf->header = (gdbm_file_header *) malloc (partial_header.block_size);
5 kx if (dbf->header == NULL)
5 kx {
5 kx gdbm_close (dbf);
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx memcpy (dbf->header, &partial_header, sizeof (gdbm_file_header));
5 kx rc = _gdbm_full_read (dbf, &dbf->header->avail.av_table[1],
5 kx dbf->header->block_size-sizeof (gdbm_file_header));
5 kx if (rc)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = rc;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Allocate space for the hash table directory. */
5 kx dbf->dir = (off_t *) malloc (dbf->header->dir_size);
5 kx if (dbf->dir == NULL)
5 kx {
5 kx gdbm_close (dbf);
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx /* Read the hash table directory. */
5 kx file_pos = __lseek (dbf, dbf->header->dir, SEEK_SET);
5 kx if (file_pos != dbf->header->dir)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = GDBM_FILE_SEEK_ERROR;
5 kx return NULL;
5 kx }
5 kx
5 kx rc = _gdbm_full_read (dbf, dbf->dir, dbf->header->dir_size);
5 kx if (rc)
5 kx {
5 kx SAVE_ERRNO (gdbm_close (dbf));
5 kx gdbm_errno = rc;
5 kx return NULL;
5 kx }
5 kx
5 kx }
5 kx
5 kx #if HAVE_MMAP
5 kx if (!(flags & GDBM_NOMMAP))
5 kx {
5 kx if (_gdbm_mapped_init (dbf) == 0)
5 kx dbf->memory_mapping = TRUE;
5 kx else
5 kx {
5 kx /* gdbm_errno should already be set. */
5 kx close (dbf->desc);
5 kx free (dbf->name);
5 kx free (dbf);
5 kx return NULL;
5 kx }
5 kx }
5 kx #endif
5 kx
5 kx /* Finish initializing dbf. */
5 kx dbf->last_read = -1;
5 kx dbf->bucket = NULL;
5 kx dbf->bucket_dir = 0;
5 kx dbf->cache_entry = NULL;
5 kx dbf->header_changed = FALSE;
5 kx dbf->directory_changed = FALSE;
5 kx dbf->bucket_changed = FALSE;
5 kx dbf->second_changed = FALSE;
5 kx
5 kx /* Everything is fine, return the pointer to the file
5 kx information structure. */
5 kx return dbf;
5 kx
5 kx }
5 kx
5 kx /* Initialize the bucket cache. */
5 kx int
5 kx _gdbm_init_cache(GDBM_FILE dbf, size_t size)
5 kx {
5 kx int index;
5 kx
5 kx if (dbf->bucket_cache == NULL)
5 kx {
5 kx dbf->bucket_cache = (cache_elem *) malloc(sizeof(cache_elem) * size);
5 kx if(dbf->bucket_cache == NULL)
5 kx {
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return -1;
5 kx }
5 kx dbf->cache_size = size;
5 kx
5 kx for(index = 0; index < size; index++)
5 kx {
5 kx (dbf->bucket_cache[index]).ca_bucket
5 kx = (hash_bucket *) calloc (1, dbf->header->bucket_size);
5 kx if ((dbf->bucket_cache[index]).ca_bucket == NULL)
5 kx {
5 kx gdbm_errno = GDBM_MALLOC_ERROR;
5 kx return -1;
5 kx }
5 kx (dbf->bucket_cache[index]).ca_adr = 0;
5 kx (dbf->bucket_cache[index]).ca_changed = FALSE;
5 kx (dbf->bucket_cache[index]).ca_data.hash_val = -1;
5 kx (dbf->bucket_cache[index]).ca_data.elem_loc = -1;
5 kx (dbf->bucket_cache[index]).ca_data.dptr = NULL;
5 kx }
5 kx dbf->bucket = dbf->bucket_cache[0].ca_bucket;
5 kx dbf->cache_entry = &dbf->bucket_cache[0];
5 kx }
5 kx return 0;
5 kx }