/* gdbmsync.c - Sync the disk with the in memory state. */
/* 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"
#ifdef GDBM_FAILURE_ATOMIC
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* Sometimes, to ensure durability, a new file *and* all directories
on its full path must be fsync()'d up to the root directory. */
static int
fsync_to_root (const char *f)
{
int flags = O_WRONLY;
char path[PATH_MAX], *end;
if (realpath (f, path) == NULL)
return GDBM_ERR_REALPATH;
end = path + strlen(path);
while (path < end)
{
int fd;
*end = 0;
fd = open (path, flags);
flags = O_RDONLY;
if (fd == -1)
return GDBM_FILE_OPEN_ERROR;
if (fsync (fd))
{
int ec = errno;
close (fd);
errno = ec;
return GDBM_FILE_SYNC_ERROR;
}
if (close (fd))
return GDBM_FILE_CLOSE_ERROR;
do
--end;
while (path < end && end[-1] != '/');
}
return GDBM_NO_ERROR;
}
/* Note: Valgrind complains about ioctl() call below, but it appears
that Valgrind is simply confused; it issues similar complaints
about very simple and correct uses of ioctl(FICLONE). */
int
_gdbm_snapshot (GDBM_FILE dbf)
{
int s; /* snapshot file descriptor */
if (dbf->snapfd[0] < 0)
/* crash consistency hasn't been requested on this database */
return 0;
if (!(dbf->eo == 0 || dbf->eo == 1))
{
/* Shouldn't happen, but still... */
_gdbmsync_done (dbf);
_gdbmsync_init (dbf);
GDBM_SET_ERRNO (dbf, GDBM_ERR_USAGE, TRUE);
return -1;
}
s = dbf->snapfd[dbf->eo];
dbf->eo = !dbf->eo;
/* says "DON'T recover from this snapshot, writing in progress " */
if (fchmod (s, S_IWUSR))
{
GDBM_SET_ERRNO (dbf, GDBM_ERR_FILE_MODE, FALSE);
return -1;
}
/* commit permission bits */
if (fsync (s))
{
GDBM_SET_ERRNO (dbf, GDBM_FILE_SYNC_ERROR, FALSE);
return -1;
}
/* make efficient reflink copy into snapshot file, overwrite previous
contents */
if (ioctl (s, FICLONE, dbf->desc) == -1)
{
if (errno == EINVAL || errno == ENOSYS)
{
_gdbmsync_done (dbf);
_gdbmsync_init (dbf);
}
GDBM_SET_ERRNO (dbf, GDBM_ERR_SNAPSHOT_CLONE, FALSE);
return -1;
}
/* commit snapshot data */
if (fsync (s))
{
GDBM_SET_ERRNO (dbf, GDBM_FILE_SYNC_ERROR, FALSE);
return -1;
}
/* says "DO recover from this snapshot, writing completed successfully" */
if (fchmod (s, S_IRUSR))
{
GDBM_SET_ERRNO (dbf, GDBM_ERR_FILE_MODE, FALSE);
return -1;
}
/* commit permission bits again */
if (fsync (s))
{
GDBM_SET_ERRNO (dbf, GDBM_FILE_SYNC_ERROR, FALSE);
return -1;
}
return 0;
}
/* Snapshot files even & odd must not exist already. */
int
gdbm_failure_atomic (GDBM_FILE dbf, const char *even, const char *odd)
{
int r;
/* Return immediately if the database needs recovery */
GDBM_ASSERT_CONSISTENCY (dbf, -1);
if (!even || !odd || strcmp (even, odd) == 0)
{
errno = EINVAL;
GDBM_SET_ERRNO (dbf, GDBM_ERR_USAGE, FALSE);
return -1;
}
if (dbf->snapfd[0] != -1)
{
/*
* This function has been called before for this dbf: reinitialize
* the snapshot system.
*/
_gdbmsync_done (dbf);
_gdbmsync_init (dbf);
}
dbf->snapfd[0] = open (even, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR);
if (dbf->snapfd[0] == -1)
GDBM_SET_ERRNO (dbf, GDBM_FILE_OPEN_ERROR, FALSE);
else
{
dbf->snapfd[1] = open (odd, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR);
if (dbf->snapfd[1] == -1)
GDBM_SET_ERRNO (dbf, GDBM_FILE_OPEN_ERROR, FALSE);
else if ((r = fsync_to_root (even)) != 0 ||
(r = fsync_to_root (odd)) != 0)
{
GDBM_SET_ERRNO (dbf, r, FALSE);
}
else
{
dbf->eo = 0;
if (_gdbm_snapshot (dbf) == 0)
return 0;
}
}
_gdbmsync_done (dbf);
_gdbmsync_init (dbf);
return -1;
}
static inline int
timespec_cmp (struct timespec const *a, struct timespec const *b)
{
if (a->tv_sec < b->tv_sec)
return -1;
if (a->tv_sec > b->tv_sec)
return 1;
if (a->tv_nsec < b->tv_nsec)
return -1;
if (a->tv_nsec > b->tv_nsec)
return 1;
return 0;
}
static int
stat_snapshot (const char *f, struct stat *st)
{
if (stat (f, st))
return -1;
if (!S_ISREG (st->st_mode)) /* f is not a regular file */
return -1;
if (S_IXUSR & st->st_mode) /* f is executable */
return -1;
if (S_IRUSR & st->st_mode)
{
if (S_IWUSR & st->st_mode)
return -1; /* f is both readable and writable */
}
else if (!(S_IWUSR & st->st_mode))
return -1; /* f is neither readable nor writable */
return 0;
}
static int
gdbm_numsync (const char *dbname, unsigned *numsync)
{
GDBM_FILE dbf;
int rc = -1;
dbf = gdbm_open (dbname, 0, GDBM_READER, S_IRUSR, NULL);
if (dbf)
{
if (gdbm_extra_header_load (dbf) == 0)
{
*numsync = dbf->extra_header->numsync;
rc = 0;
}
gdbm_close (dbf);
}
return rc;
}
/*
* Return:
* 0 both numsyncs equal or result undefined
* -1 a's numsync less than b's
* +1 a's numsync greater than b's
*
* Takes into account integer overflow.
*/
static int
gdbm_numsync_cmp (const char *a, const char *b)
{
int na, nb;
if (gdbm_numsync (a, &na) == 0 &&
gdbm_numsync (b, &nb) == 0)
{
if (nb != -1)
{
if (na < nb)
return -1;
else if (na > nb)
return 1;
}
}
return 0;
}
/*
* Selects among the two given snapshot files the one to be used for
* post-crash recovery and stores its value in *RET.
* Returns 0 (GDBM_SNAPSHOT_OK) on success, GDBM_SNAPSHOT_ERR on error (examine
* errno) and GDBM_SNAPSHOT_SAME in the unlikely case when two snapshots have
* the same modification time.
*/
int
gdbm_latest_snapshot (const char *even, const char *odd, const char **ret)
{
struct stat st_even, st_odd;
if (!ret || !even || !odd || strcmp (even, odd) == 0)
{
errno = EINVAL;
return GDBM_SNAPSHOT_ERR;
}
if (stat_snapshot (even, &st_even))
return GDBM_SNAPSHOT_ERR;
if (stat_snapshot (odd, &st_odd))
return GDBM_SNAPSHOT_ERR;
if (st_even.st_mode & S_IRUSR)
{
int n;
if (!(st_odd.st_mode & S_IRUSR))
{
*ret = even;
return GDBM_SNAPSHOT_OK;
}
/*
* Both readable: compare numsync value in the extended header.
* Select the snapshot with greater numsync value.
*/
n = gdbm_numsync_cmp (even, odd);
if (n == 0)
{
/*
* Check mtime.
* Select the newer snapshot, i.e. the one whose mtime
* is greater than the other's
*/
n = timespec_cmp (&st_even.st_mtim, &st_odd.st_mtim);
}
switch (n)
{
case -1:
*ret = odd;
break;
case 1:
*ret = even;
break;
case 0:
/* Shouldn't happen */
return GDBM_SNAPSHOT_SAME;
}
}
else if (st_odd.st_mode & S_IRUSR)
{
*ret = odd;
return GDBM_SNAPSHOT_OK;
}
else
{
/* neither readable: this means the crash occurred during
gdbm_failure_atomic() */
return GDBM_SNAPSHOT_BAD;
}
return GDBM_SNAPSHOT_OK;
}
#else
int
gdbm_failure_atomic (GDBM_FILE dbf, const char *even, const char *odd)
{
errno = ENOSYS;
GDBM_SET_ERRNO (dbf, GDBM_ERR_USAGE, FALSE);
return -1;
}
int
gdbm_latest_snapshot (const char *even, const char *odd, const char **ret)
{
errno = ENOSYS;
return GDBM_SNAPSHOT_ERR;
}
#endif /* GDBM_FAILURE_ATOMIC */
/* Make sure the database is all on disk. */
int
gdbm_sync (GDBM_FILE dbf)
{
/* Return immediately if the database needs recovery */
GDBM_ASSERT_CONSISTENCY (dbf, -1);
/* Initialize the gdbm_errno variable. */
gdbm_set_errno (dbf, GDBM_NO_ERROR, FALSE);
#ifdef GDBM_FAILURE_ATOMIC
if (!dbf->extra_header)
{
gdbm_extra_header_load (dbf);
}
if (dbf->extra_header)
{
dbf->extra_header->numsync++;
gdbm_extra_header_store (dbf);
}
#endif
/* Do the sync on the file. */
return gdbm_file_sync (dbf);
}