pax_global_header 0000666 0000000 0000000 00000000064 14512203222 0014503 g ustar 00root root 0000000 0000000 52 comment=385425486819502641028e01ed65727f0daf3397
dqlite-1.16.0/ 0000775 0000000 0000000 00000000000 14512203222 0013052 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/.clang-format 0000664 0000000 0000000 00000000346 14512203222 0015430 0 ustar 00root root 0000000 0000000 BasedOnStyle: Chromium
BreakBeforeBraces: Custom
BraceWrapping:
AfterFunction: true
AfterStruct: true
IndentWidth: 8
UseTab: ForContinuationAndIndentation
PointerAlignment: Right
AllowAllParametersOfDeclarationOnNextLine: false
dqlite-1.16.0/.dir-locals.el 0000664 0000000 0000000 00000000266 14512203222 0015507 0 ustar 00root root 0000000 0000000 ((nil . ((fill-column . 80)))
(c-mode . ((c-file-style . "linux-tabs-only")
(flycheck-gcc-definitions . ("_GNU_SOURCE"))
(flycheck-clang-definitions . ("_GNU_SOURCE")))))
dqlite-1.16.0/.github/ 0000775 0000000 0000000 00000000000 14512203222 0014412 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/.github/dependabot.yml 0000664 0000000 0000000 00000000542 14512203222 0017243 0 ustar 00root root 0000000 0000000 # Set update schedule for GitHub Actions
# for more info see: https://docs.github.com/en/code-security/dependabot/working-with-dependabot/keeping-your-actions-up-to-date-with-dependabot
version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/" # checks for workflow files in .github/workflows
schedule:
interval: "weekly"
dqlite-1.16.0/.github/workflows/ 0000775 0000000 0000000 00000000000 14512203222 0016447 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/.github/workflows/build-and-test.yml 0000664 0000000 0000000 00000002763 14512203222 0022016 0 ustar 00root root 0000000 0000000 name: CI Tests
on:
- push
- pull_request
jobs:
build-and-test:
strategy:
fail-fast: false
matrix:
os:
- ubuntu-20.04
- ubuntu-22.04
compiler:
- gcc
- clang
tracing:
- LIBDQLITE_TRACE=1
- NOLIBDQLITE_TRACE=1
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup dependencies
run: |
sudo apt update
sudo apt install -y lcov libsqlite3-dev liblz4-dev libuv1-dev
- name: Build raft
env:
CC: ${{ matrix.compiler }}
run: |
git clone https://github.com/canonical/raft.git --depth 1
cd raft
autoreconf -i
./configure --enable-debug --enable-sanitize
make -j4
sudo make install
sudo ldconfig
cd ..
- name: Build dqlite
env:
CC: ${{ matrix.compiler }}
run: |
autoreconf -i
./configure --enable-debug --enable-code-coverage --enable-sanitize
make CFLAGS=-O0 -j2
- name: Test
env:
CC: ${{ matrix.compiler }}
run: |
export ${{ matrix.tracing }}
make CFLAGS=-O0 -j2 check || (cat ./test-suite.log && false)
- name: Coverage
env:
CC: ${{ matrix.compiler }}
run: if [ "${CC}" = "gcc" ]; then make code-coverage-capture; fi
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
verbose: true
dqlite-1.16.0/.github/workflows/cla-check.yml 0000664 0000000 0000000 00000000271 14512203222 0021004 0 ustar 00root root 0000000 0000000 name: Canonical CLA
on:
- pull_request
jobs:
cla-check:
runs-on: ubuntu-20.04
steps:
- name: Check if CLA signed
uses: canonical/has-signed-canonical-cla@v1
dqlite-1.16.0/.github/workflows/coverity.yml 0000664 0000000 0000000 00000003016 14512203222 0021036 0 ustar 00root root 0000000 0000000 name: Coverity
on:
push:
branches:
- master
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Download Coverity Build Tool
run: |
wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=canonical/dqlite" -O cov-analysis-linux64.tar.gz
mkdir cov-analysis-linux64
tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
env:
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
- name: Install dependencies
run: |
sudo add-apt-repository -y ppa:dqlite/dev
sudo apt-get update -qq
sudo apt-get install -qq gcc libsqlite3-dev liblz4-dev libuv1-dev libraft-dev
- name: Run coverity
run: |
export PATH="$(pwd)/cov-analysis-linux64/bin:${PATH}"
# Configure
autoreconf -i
mkdir build
cd build
../configure
# Build
cov-build --dir cov-int make -j4
tar czvf dqlite.tgz cov-int
# Submit the results
curl \
--form project=canonical/dqlite \
--form token=${TOKEN} \
--form email=mathieu.bordere@canonical.com \
--form file=@dqlite.tgz \
--form version=master \
--form description="${GITHUB_SHA}" \
https://scan.coverity.com/builds?project=canonical/dqlite
env:
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
dqlite-1.16.0/.github/workflows/linting.yml 0000664 0000000 0000000 00000000526 14512203222 0020641 0 ustar 00root root 0000000 0000000 name: Linting
on:
- push
- pull_request
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: DoozyX/clang-format-lint-action@v0.16
with:
source: 'src test include'
exclude: 'test/lib/munit.*'
extensions: 'c,h'
clangFormatVersion: 14
style: file
dqlite-1.16.0/.github/workflows/packages.yml 0000664 0000000 0000000 00000003077 14512203222 0020757 0 ustar 00root root 0000000 0000000 name: Build PPA source packages
on:
- push
jobs:
build:
if: github.repository == 'canonical/dqlite'
strategy:
fail-fast: false
matrix:
target:
- focal
- jammy
- lunar
runs-on: ubuntu-20.04
environment:
name: ppa
steps:
- name: Clone the repositories
run: |
git clone https://github.com/canonical/dqlite
git clone https://github.com/canonical/dqlite-ppa -b dqlite --depth 1
- name: Setup dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -qq debhelper devscripts gnupg
- name: Setup GPG signing key
env:
PPA_SECRET_KEY: ${{ secrets.PPA_SECRET_KEY }}
run: |
echo "$PPA_SECRET_KEY" > private-key.asc
gpg --import --batch private-key.asc
- name: Delete GPG signing key file
if: always()
run: |
rm -f private-key.asc
- name: Build source package
env:
DEBFULLNAME: "Github Actions"
DEBEMAIL: "dqlitebot@lists.canonical.com"
TARGET: ${{ matrix.target }}
run: |
cp -R dqlite-ppa/debian dqlite/
cd dqlite/
VERSION="$(git describe --tags | sed -e "s/^v//" -e "s/-/+git/")"
dch --create \
--distribution ${TARGET} \
--package dqlite \
--newversion ${VERSION}~${TARGET}1 \
"Automatic build from Github"
debuild -S -sa -d -k${{ vars.PPA_PUBLIC_KEY }}
- name: Upload to Launchpad
run: |
dput -U -u ppa:dqlite/dev *.changes
dqlite-1.16.0/.gitignore 0000664 0000000 0000000 00000000406 14512203222 0015042 0 ustar 00root root 0000000 0000000 *.a
*.gcda
*.gcno
*.la
*.lo
*.log
*.o
*.so
*.trs
.deps
.dirstamp
.libs
Makefile
Makefile.in
aclocal.m4
aminclude_static.am
autom4te*.cache
confdefs.h
config.status
configure
coverage/
coverage.info
unit-test
integration-test
dqlite.pc
libtool
stamp-h*
sqlite3.c
dqlite-1.16.0/AUTHORS 0000664 0000000 0000000 00000000360 14512203222 0014121 0 ustar 00root root 0000000 0000000 Unless mentioned otherwise in a specific file's header, all code in this
project is released under the LGPL v3 license.
The list of authors and contributors can be retrieved from the git
commit history and in some cases, the file headers.
dqlite-1.16.0/Dockerfile 0000664 0000000 0000000 00000002770 14512203222 0015052 0 ustar 00root root 0000000 0000000 # FROM debian:buster-slim as dqlite-lib-builder
FROM ubuntu as dqlite-lib-builder
ARG DEBIAN_FRONTEND="noninteractive"
ENV TZ=Europe/London
ENV LD_LIBRARY_PATH=/usr/local/lib
ENV GOROOT=/usr/local/go
ENV GOPATH=/go
ENV PATH=$GOPATH/bin:$GOROOT/bin:$PATH
RUN apt-get update && apt-get install -y git build-essential dh-autoreconf pkg-config libuv1-dev libsqlite3-dev liblz4-dev tcl8.6 wget
WORKDIR /opt
RUN git clone https://github.com/canonical/raft.git && \
git clone https://github.com/canonical/go-dqlite.git && \
wget -c https://golang.org/dl/go1.15.2.linux-amd64.tar.gz -O - | tar -xzf - -C /usr/local
WORKDIR /opt/raft
RUN autoreconf -i && ./configure && make && make install
WORKDIR /opt/dqlite
COPY . .
RUN autoreconf -i && ./configure && make && make install
WORKDIR /opt/go-dqlite
RUN go get -d -v ./... && \
go install -tags libsqlite3 ./cmd/dqlite-demo && \
go install -tags libsqlite3 ./cmd/dqlite
# FROM debian:buster-slim
FROM ubuntu
ARG DEBIAN_FRONTEND="noninteractive"
ENV TZ=Europe/London
ENV LD_LIBRARY_PATH=/usr/local/lib
ENV PATH=/opt:$PATH
COPY --from=dqlite-lib-builder /go/bin /opt/
COPY --from=dqlite-lib-builder /usr/local/lib /usr/local/lib
COPY --from=dqlite-lib-builder \
/usr/lib/x86_64-linux-gnu/libuv.so \
/usr/lib/x86_64-linux-gnu/libuv.so.1\
/usr/lib/x86_64-linux-gnu/libuv.so.1.0.0\
/usr/lib/
COPY --from=dqlite-lib-builder \
/lib/x86_64-linux-gnu/libsqlite3.so \
/lib/x86_64-linux-gnu/libsqlite3.so.0 \
/usr/lib/x86_64-linux-gnu/
dqlite-1.16.0/LICENSE 0000664 0000000 0000000 00000021506 14512203222 0014063 0 ustar 00root root 0000000 0000000 All files in this repository are licensed as follows. If you contribute
to this repository, it is assumed that you license your contribution
under the same license unless you state otherwise.
All files Copyright (C) 2017-2019 Canonical Ltd. unless otherwise specified in
the file.
This software is licensed under the LGPLv3, included below.
As a special exception to the GNU Lesser General Public License version 3
("LGPL3"), the copyright holders of this Library give you permission to
convey to a third party a Combined Work that links statically or dynamically
to this Library without providing any Minimal Corresponding Source or
Minimal Application Code as set out in 4d or providing the installation
information set out in section 4e, provided that you comply with the other
provisions of LGPL3 and provided that you meet, for the Application the
terms and conditions of the license(s) which apply to the Application.
Except as stated in this special exception, the provisions of LGPL3 will
continue to comply in full to this Library. If you modify this Library, you
may apply this exception to your version of this Library, but you are not
obliged to do so. If you do not wish to do so, delete this exception
statement from your version. This exception does not (and cannot) modify any
license terms which apply to the Application, with which you must still
comply.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc.
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
dqlite-1.16.0/Makefile.am 0000664 0000000 0000000 00000007256 14512203222 0015120 0 ustar 00root root 0000000 0000000 ACLOCAL_AMFLAGS = -I m4
AM_CFLAGS += $(CODE_COVERAGE_CFLAGS)
AM_CFLAGS += $(SQLITE_CFLAGS) $(UV_CFLAGS) $(RAFT_CFLAGS) $(PTHREAD_CFLAGS)
AM_LDFLAGS = $(UV_LIBS) $(RAFT_LIBS) $(PTHREAD_LIBS)
if !BUILD_SQLITE_ENABLED
AM_LDFLAGS += $(SQLITE_LIBS)
endif
include_HEADERS = include/dqlite.h
lib_LTLIBRARIES = libdqlite.la
libdqlite_la_CFLAGS = $(AM_CFLAGS) -fvisibility=hidden
libdqlite_la_LDFLAGS = $(AM_LDFLAGS) -version-info 0:1:0
libdqlite_la_SOURCES = \
src/bind.c \
src/client/protocol.c \
src/command.c \
src/conn.c \
src/db.c \
src/dqlite.c \
src/error.c \
src/format.c \
src/fsm.c \
src/gateway.c \
src/id.c \
src/leader.c \
src/lib/addr.c \
src/lib/buffer.c \
src/lib/fs.c \
src/lib/transport.c \
src/logger.c \
src/message.c \
src/metrics.c \
src/config.c \
src/query.c \
src/registry.c \
src/request.c \
src/response.c \
src/roles.c \
src/server.c \
src/stmt.c \
src/tracing.c \
src/transport.c \
src/translate.c \
src/tuple.c \
src/vfs.c
if BUILD_SQLITE_ENABLED
libdqlite_la_SOURCES += sqlite3.c
endif
check_PROGRAMS = \
unit-test \
integration-test
TESTS = unit-test integration-test
check_LTLIBRARIES = libtest.la
libtest_la_CFLAGS = $(AM_CFLAGS) -DMUNIT_TEST_NAME_LEN=60 -Wno-unknown-warning-option -Wno-unused-result -Wno-conversion -Wno-uninitialized -Wno-maybe-uninitialized -Wno-strict-prototypes -Wno-old-style-definition
libtest_la_SOURCES = \
test/lib/endpoint.c \
test/lib/fault.c \
test/lib/fs.c \
test/lib/heap.c \
test/lib/logger.c \
test/lib/munit.c \
test/lib/raft_heap.c \
test/lib/server.c \
test/lib/sqlite.c \
test/lib/uv.c
unit_test_SOURCES = $(libdqlite_la_SOURCES)
unit_test_SOURCES += \
test/test_error.c \
test/test_integration.c \
test/unit/ext/test_uv.c \
test/unit/lib/test_addr.c \
test/unit/lib/test_buffer.c \
test/unit/lib/test_byte.c \
test/unit/lib/test_registry.c \
test/unit/lib/test_serialize.c \
test/unit/lib/test_transport.c \
test/unit/test_command.c \
test/unit/test_conn.c \
test/unit/test_gateway.c \
test/unit/test_concurrency.c \
test/unit/test_registry.c \
test/unit/test_replication.c \
test/unit/test_request.c \
test/unit/test_role_management.c \
test/unit/test_tuple.c \
test/unit/test_vfs.c \
test/unit/main.c
unit_test_CFLAGS = $(AM_CFLAGS) -Wno-unknown-warning-option -Wno-uninitialized -Wno-maybe-uninitialized -Wno-float-equal -Wno-conversion
unit_test_LDFLAGS = $(AM_LDFLAGS)
unit_test_LDADD = libtest.la
integration_test_SOURCES = \
test/integration/test_client.c \
test/integration/test_cluster.c \
test/integration/test_fsm.c \
test/integration/test_membership.c \
test/integration/test_node.c \
test/integration/test_role_management.c \
test/integration/test_server.c \
test/integration/test_vfs.c \
test/integration/main.c
integration_test_CFLAGS = $(AM_CFLAGS) -Wno-conversion
integration_test_LDFLAGS = $(AM_LDFLAGS) -no-install
integration_test_LDADD = libtest.la libdqlite.la
if DEBUG_ENABLED
AM_CFLAGS += -g
else
AM_CFLAGS += -O2
endif
if SANITIZE_ENABLED
AM_CFLAGS += -fsanitize=address
endif
if BACKTRACE_ENABLED
AM_CFLAGS += -DDQLITE_ASSERT_WITH_BACKTRACE
AM_LDFLAGS += -lbacktrace
endif
if CODE_COVERAGE_ENABLED
include $(top_srcdir)/aminclude_static.am
CODE_COVERAGE_DIRECTORY=./src
CODE_COVERAGE_OUTPUT_DIRECTORY=coverage
CODE_COVERAGE_OUTPUT_FILE=coverage.info
CODE_COVERAGE_IGNORE_PATTERN="/usr/include/*"
CODE_COVERAGE_BRANCH_COVERAGE=1
CODE_COVERAGE_LCOV_OPTIONS=$(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT) --rc lcov_excl_br_line="assert\("
clean-local: code-coverage-clean
distclean-local: code-coverage-dist-clean
endif # CODE_COVERAGE_ENABLED
pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = dqlite.pc
dqlite-1.16.0/README.md 0000664 0000000 0000000 00000007264 14512203222 0014342 0 ustar 00root root 0000000 0000000 dqlite [](https://github.com/canonical/dqlite/actions/workflows/build-and-test.yml) [](https://codecov.io/gh/canonical/dqlite)
======
[English](./README.md)|[简体中文](./README_CH.md)
[dqlite](https://dqlite.io) is a C library that implements an embeddable and replicated SQL database
engine with high availability and automatic failover.
The acronym "dqlite" stands for "distributed SQLite", meaning that dqlite extends
[SQLite](https://sqlite.org/) with a network protocol that can connect together
various instances of your application and have them act as a highly-available
cluster, with no dependency on external databases.
Design highlights
----------------
* Asynchronous single-threaded implementation using [libuv](https://libuv.org/)
as event loop.
* Custom wire protocol optimized for SQLite primitives and data types.
* Data replication based on the [Raft](https://raft.github.io/) algorithm and its
efficient [C-raft](https://github.com/canonical/raft) implementation.
License
-------
The dqlite library is released under a slightly modified version of LGPLv3, that
includes a copyright exception allowing users to statically link the library code
in their project and release the final work under their own terms. See the full
[license](https://github.com/canonical/dqlite/blob/master/LICENSE) text.
Compatibility
-------------
dqlite runs on Linux and requires a kernel with support for [native async
I/O](https://man7.org/linux/man-pages/man2/io_setup.2.html) (not to be confused
with [POSIX AIO](https://man7.org/linux/man-pages/man7/aio.7.html)), which is
used by the libuv backend of C-raft.
Try it
-------
The simplest way to see dqlite in action is to use the demo program that comes
with the Go dqlite bindings. Please see the [relevant
documentation](https://github.com/canonical/go-dqlite#demo) in that project.
Media
-----
A talk about dqlite was given at FOSDEM 2020, you can watch it
[here](https://fosdem.org/2020/schedule/event/dqlite/).
[Here](https://gcore.com/blog/comparing-litestream-rqlite-dqlite/) is a blog post from 2022 comparing dqlite with rqlite and Litestream, other replication software for SQLite.
Wire protocol
-------------
If you wish to write a client, please refer to the [wire protocol](https://dqlite.io/docs/protocol)
documentation.
Install
-------
If you are on a Debian-based system, you can get the latest development release from
dqlite's [dev PPA](https://launchpad.net/~dqlite/+archive/ubuntu/dev):
```
sudo add-apt-repository ppa:dqlite/dev
sudo apt-get update
sudo apt-get install libdqlite-dev
```
Build
-----
To build libdqlite from source you'll need:
* A reasonably recent version of [libuv](http://libuv.org/) (v1.8.0 or beyond).
* A reasonably recent version of sqlite3-dev
* A build of the [C-raft](https://github.com/canonical/raft) Raft library.
Your distribution should already provide you with a pre-built libuv shared
library and libsqlite3-dev.
For the Debian-based Linux distros you can install the build dependencies with:
```
sudo apt install autoconf libuv1-dev liblz4-dev libtool pkg-config build-essential libsqlite3-dev
```
To build the raft library:
```
git clone https://github.com/canonical/raft.git
cd raft
autoreconf -i
./configure
make
sudo make install
cd ..
```
Once all the required libraries are installed, in order to build the dqlite
shared library itself, you can run:
```
autoreconf -i
./configure
make
sudo make install
```
Usage Notes
-----------
Detailed tracing will be enabled when the environment variable `LIBDQLITE_TRACE` is set before startup.
dqlite-1.16.0/README_CH.md 0000664 0000000 0000000 00000006551 14512203222 0014712 0 ustar 00root root 0000000 0000000
# dqlite
[](https://github.com/canonical/dqlite/actions/workflows/build-and-test.yml) [](https://codecov.io/gh/canonical/dqlite)
**注意**:中文文档有可能未及时更新,请以最新的英文[readme](./README.md)为准。
[dqlite](https://dqlite.io)是一个用C语言开发的可嵌入的,支持流复制的数据库引擎,具备高可用性和自动故障转移功能。
“dqlite”是“distributed SQLite”的简写,即分布式SQLite。意味着dqlite通过网络协议扩展SQLite,将应用程序的各个实例连接在一起,让它们作为一个高可用的集群,而不依赖外部数据库。
## 设计亮点
- 使用[libuv](https://libuv.org/)实现异步单线程的事件循环机制
- 针对SQLite 原始数据类型优化的自定义网络协议
- 基于[Raft](https://raft.github.io/)算法的数据复制及其高效[C-raft](https://github.com/canonical/raft)实现
## license
dqlite库是在略微修改的 LGPLv3 版本下发布的,其中包括一个版权例外,允许用户在他们的项目中静态链接这个库的代码并按照自己的条款发布最终作品。如有需要,请查看完整[license](https://github.com/canonical/dqlite/blob/master/LICENSE)文件。
## 兼容性
dqlite 在 Linux 上运行,由于C-raft 的 libuv 后端的实现,需要一个支持 [native async
I/O](https://man7.org/linux/man-pages/man2/io_setup.2.html) 的内核(注意不要和[POSIX AIO](https://man7.org/linux/man-pages/man7/aio.7.html)混淆)。
## 尝试使用
查看和了解dqlite的最简单方式是使用绑定了Go dqlite的demo样例程序,Go dqlite的使用可以参考它的项目文档[relevant
documentation](https://github.com/canonical/go-dqlite#demo)。
## 视频
在 FOSDEM 2020 上有一个关于dqlite的演讲视频,您可以在[此处](https://fosdem.org/2020/schedule/event/dqlite/)观看。
## 网络协议
如果您想编写客户端,请参阅[网络协议](https://dqlite.io/docs/protocol)文档。
## 下载
如果您使用的是基于 Debian 的系统,您可以从 dqlite 的[dev PPA](https://launchpad.net/~dqlite/+archive/ubuntu/dev) 获得最新的开发版本:
```bash
sudo add-apt-repository ppa:dqlite/dev
sudo apt-get update
sudo apt-get install libdqlite-dev
```
## 源码构建
为了编译构建libdqlite,您需要准备:
- 较新版本的libuv(v1.18.0或之后的版本)
- 较新版本的sqlite3-dev
- 构建好的[C-raft](https://github.com/canonical/raft)库
您的linux发行版应该已经为您提供了预构建的 libuv 共享库和 libsqlite3-dev,就不需要在下载了,否则还需要下载这两个依赖。
对于基于 Debian 的 Linux 发行版,您可以使用以下命令安装构建依赖项:
```
sudo apt install autoconf libuv1-dev liblz4-dev libtool pkg-config build-essential libsqlite3-dev
```
编译raft库运行如下命令:
```bash
git clone https://github.com/canonical/raft.git
cd raft
autoreconf -i
./configure
make
sudo make install
cd ..
```
所有依赖的库都下载好后,运行如下命令手动编译dqlite库:
```bash
autoreconf -i
./configure
make
sudo make install
```
## 注意事项
当环境变量LIBRAFT_TRACE在启动时被设置,将启用详细跟踪。 dqlite-1.16.0/VERSION 0000664 0000000 0000000 00000000005 14512203222 0014115 0 ustar 00root root 0000000 0000000 0.1.0 dqlite-1.16.0/ac/ 0000775 0000000 0000000 00000000000 14512203222 0013435 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/ac/.gitignore 0000664 0000000 0000000 00000000016 14512203222 0015422 0 ustar 00root root 0000000 0000000 *
!.gitignore
dqlite-1.16.0/bt/ 0000775 0000000 0000000 00000000000 14512203222 0013457 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/bt/request 0000775 0000000 0000000 00000001423 14512203222 0015075 0 ustar 00root root 0000000 0000000 #!/bin/sh
set -o errexit
libraft_path="${LIBRAFT_SO_PATH:-/usr/local/lib/libraft.so.2}"
exec bpftrace -I resources -I include $@ - <
struct request
{
void *data;
int type;
raft_index index;
void *queue[2];
};
uprobe:$libraft_path:lifecycleRequestStart
{
\$req = (struct request *)arg1;
@start_request[\$req->data, \$req->type, \$req->index] = nsecs;
}
uprobe:$libraft_path:lifecycleRequestEnd
{
\$req = (struct request *)arg1;
\$start = @start_request[\$req->data, \$req->type, \$req->index];
\$end = nsecs;
@full[\$req->data, \$req->type, \$req->index] = (\$start, \$end);
\$elapsed_msecs = (\$end - \$start) / 1000;
@hist = lhist(\$elapsed_msecs, 100, 1000, 10);
delete(@start_request[\$req->data, \$req->type, \$req->index]);
}
EOF
dqlite-1.16.0/configure.ac 0000664 0000000 0000000 00000005451 14512203222 0015345 0 ustar 00root root 0000000 0000000 AC_PREREQ(2.60)
AC_INIT([libdqlite], [1.16.0], [https://github.com/canonical/dqlite])
AC_CONFIG_MACRO_DIR([m4])
AC_CONFIG_AUX_DIR([ac])
AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror -Wno-portability foreign])
AM_SILENT_RULES([yes])
AC_PROG_CC_STDC
AC_USE_SYSTEM_EXTENSIONS
AX_PTHREAD
LT_INIT
# TODO: eventually enable this
# AX_CHECK_COMPILE_FLAG([-Weverything], AM_CFLAGS+=" -Weverything")
# Whether to enable debugging code.
AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug[=ARG]], [enable debugging [default=no]]))
AM_CONDITIONAL(DEBUG_ENABLED, test "x$enable_debug" = "xyes")
# Whether to enable memory sanitizer.
AC_ARG_ENABLE(sanitize, AS_HELP_STRING([--enable-sanitize[=ARG]], [enable code sanitizers [default=no]]))
AM_CONDITIONAL(SANITIZE_ENABLED, test x"$enable_sanitize" = x"yes")
AM_COND_IF(SANITIZE_ENABLED,
AX_CHECK_COMPILE_FLAG([-fsanitize=address],
[true],
[AC_MSG_ERROR([address sanitizer not supported])]))
AC_ARG_ENABLE(backtrace, AS_HELP_STRING([--enable-backtrace[=ARG]], [print backtrace on assertion failure [default=no]]))
AM_CONDITIONAL(BACKTRACE_ENABLED, test "x$enable_backtrace" = "xyes")
AC_ARG_ENABLE(build-sqlite, AS_HELP_STRING([--enable-build-sqlite[=ARG]], [build libsqlite3 from sqlite3.c in the build root [default=no]]))
AM_CONDITIONAL(BUILD_SQLITE_ENABLED, test "x$enable_build_sqlite" = "xyes")
# Whether to enable code coverage.
AX_CODE_COVERAGE
# Checks for header files.
AC_CHECK_HEADERS([arpa/inet.h fcntl.h stdint.h stdlib.h string.h sys/socket.h unistd.h])
# Checks for typedefs, structures, and compiler characteristics.
AC_TYPE_SIZE_T
AC_TYPE_SSIZE_T
AC_TYPE_UINT16_T
AC_TYPE_UINT32_T
AC_TYPE_UINT8_T
# Enable large file support. This is mandatory in order to interoperate with
# libuv, which enables large file support by default, making the size of 'off_t'
# on 32-bit architecture be 8 bytes instead of the normal 4.
AC_SYS_LARGEFILE
# Checks for libraries
PKG_CHECK_MODULES(SQLITE, [sqlite3 >= 3.22.0], [], [])
PKG_CHECK_MODULES(UV, [libuv >= 1.8.0], [], [])
PKG_CHECK_MODULES(RAFT, [raft >= 0.17.1], [], [])
CC_CHECK_FLAGS_APPEND([AM_CFLAGS],[CFLAGS],[ \
-std=c11 \
-g \
--mcet \
-fcf-protection \
--param=ssp-buffer-size=4 \
-pipe \
-fno-strict-aliasing \
-fdiagnostics-color \
-fexceptions \
-fstack-clash-protection \
-fstack-protector-strong \
-fasynchronous-unwind-tables \
-fdiagnostics-show-option \
-Wall \
-Wextra \
-Wimplicit-fallthrough=5 \
-Wcast-align \
-Wstrict-prototypes \
-Wlogical-op \
-Wmissing-include-dirs \
-Wold-style-definition \
-Winit-self \
-Wfloat-equal \
-Wsuggest-attribute=noreturn \
-Wformat=2 \
-Wshadow \
-Wendif-labels \
-Wdate-time \
-Wnested-externs \
-Wconversion \
-Werror \
])
# To enable:
#
# -Wpedantic \
AC_SUBST(AM_CFLAGS)
AC_CONFIG_FILES([dqlite.pc Makefile])
AC_OUTPUT
dqlite-1.16.0/doc/ 0000775 0000000 0000000 00000000000 14512203222 0013617 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/doc/faq.md 0000664 0000000 0000000 00000000074 14512203222 0014711 0 ustar 00root root 0000000 0000000 Moved to the [website project](https://dqlite.io/docs/faq).
dqlite-1.16.0/doc/index.md 0000664 0000000 0000000 00000000070 14512203222 0015245 0 ustar 00root root 0000000 0000000 Moved to the [website project](https://dqlite.io/docs).
dqlite-1.16.0/doc/protocol.md 0000664 0000000 0000000 00000000101 14512203222 0015772 0 ustar 00root root 0000000 0000000 Moved to the [website project](https://dqlite.io/docs/protocol).
dqlite-1.16.0/dqlite.pc.in 0000664 0000000 0000000 00000000410 14512203222 0015260 0 ustar 00root root 0000000 0000000 prefix=@prefix@
exec_prefix=@exec_prefix@
libdir=@libdir@
includedir=@includedir@
Name: dqlite
Description: Distributed SQLite engine
Version: @PACKAGE_VERSION@
Libs: -L${libdir} -ldqlite
Libs.private: @SQLITE_LIBS@ @UV_LIBS@ @RAFT_LIBS@
Cflags: -I${includedir}
dqlite-1.16.0/include/ 0000775 0000000 0000000 00000000000 14512203222 0014475 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/include/dqlite.h 0000664 0000000 0000000 00000063472 14512203222 0016144 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_H
#define DQLITE_H
#include
#include
#include
#include
#ifndef DQLITE_API
#define DQLITE_API __attribute__((visibility("default")))
#endif
/**
* This "pseudo-attribute" marks declarations that are only a provisional part
* of the dqlite public API. These declarations may change or be removed
* entirely in minor or point releases of dqlite, without bumping the soversion
* of libdqlite.so. Consumers of dqlite who use these declarations are
* responsible for updating their code in response to such breaking changes.
*/
#define DQLITE_EXPERIMENTAL
/* XXX */
#define DQLITE_VISIBLE_TO_TESTS DQLITE_API
/**
* Version.
*/
#define DQLITE_VERSION_MAJOR 1
#define DQLITE_VERSION_MINOR 16
#define DQLITE_VERSION_RELEASE 0
#define DQLITE_VERSION_NUMBER \
(DQLITE_VERSION_MAJOR * 100 * 100 + DQLITE_VERSION_MINOR * 100 + \
DQLITE_VERSION_RELEASE)
DQLITE_API int dqlite_version_number(void);
/**
* Hold the value of a dqlite node ID. Guaranteed to be at least 64-bit long.
*/
typedef unsigned long long dqlite_node_id;
DQLITE_EXPERIMENTAL typedef struct dqlite_server dqlite_server;
/**
* Signature of a custom callback used to establish network connections
* to dqlite servers.
*
* @arg is a user data parameter, copied from the third argument of
* dqlite_server_set_connect_func. @addr is a (borrowed) abstract address
* string, as passed to dqlite_server_create or dqlite_server_set_auto_join. @fd
* is an address where a socket representing the connection should be stored.
* The callback should return zero if a connection was established successfully
* or nonzero if the attempt failed.
*/
DQLITE_EXPERIMENTAL typedef int (*dqlite_connect_func)(void *arg,
const char *addr,
int *fd);
/* The following dqlite_server functions return zero on success or nonzero on
* error. More specific error codes may be specified in the future. */
/**
* Start configuring a dqlite server.
*
* The server will not start running until dqlite_server_start is called. @path
* is the path to a directory where the server (and attached client) will store
* its persistent state; the directory must exist. A pointer to the new server
* object is stored in @server on success.
*
* Whether or not this function succeeds, you should call dqlite_server_destroy
* to release resources owned by the server object.
*
* No reference to @path is kept after this function returns.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_create(const char *path,
dqlite_server **server);
/**
* Set the abstract address of this server.
*
* This function must be called when the server starts for the first time, and
* is a no-op when the server is restarting. The abstract address is recorded in
* the Raft log and passed to the connect function on each server (see
* dqlite_server_set_connect_func). The server will also bind to this address to
* listen for incoming connections from clients and other servers, unless
* dqlite_server_set_bind_address is used. For the address syntax accepted by
* the default connect function (and for binding/listening), see
* dqlite_server_set_bind_address.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_set_address(
dqlite_server *server,
const char *address);
/**
* Turn on or off automatic bootstrap for this server.
*
* The bootstrap server should be the first to start up. It automatically
* becomes the leader in the first term, and is responsible for adding all other
* servers to the cluster configuration. There must be exactly one bootstrap
* server in each cluster. After the first startup, the bootstrap server is no
* longer special and this function is a no-op.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_set_auto_bootstrap(
dqlite_server *server,
bool on);
/**
* Declare the addresses of existing servers in the cluster, which should
* already be running.
*
* The server addresses declared with this function will not be used unless
* @server is starting up for the first time; after the first startup, the list
* of servers stored on disk will be used instead. (It is harmless to call this
* function unconditionally.)
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_set_auto_join(
dqlite_server *server,
const char *const *addrs,
unsigned n);
/**
* Configure @server to listen on the address @addr for incoming connections
* (from clients and other servers).
*
* If no bind address is configured with this function, the abstract address
* passed to dqlite_server_create will be used. The point of this function is to
* support decoupling the abstract address from the networking implementation
* (for example, if a proxy is going to be used).
*
* @addr must use one of the following formats:
*
* 1. ""
* 2. ":"
* 3. "@"
*
* Where is a numeric IPv4/IPv6 address, is a port number, and
* is an abstract Unix socket path. The port number defaults to 8080 if
* not specified. In the second form, if is an IPv6 address, it must be
* enclosed in square brackets "[]". In the third form, if is empty, the
* implementation will automatically select an available abstract Unix socket
* path.
*
* If an abstract Unix socket is used, the server will accept only
* connections originating from the same process.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_set_bind_address(
dqlite_server *server,
const char *addr);
/**
* Configure the function that this server will use to connect to other servers.
*
* The same function will be used by the server's attached client to establish
* connections to all servers in the cluster. @arg is a user data parameter that
* will be passed to all invocations of the connect function.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_set_connect_func(
dqlite_server *server,
dqlite_connect_func f,
void *arg);
/**
* Start running the server.
*
* Once this function returns successfully, the server will be ready to accept
* client requests using the functions below.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_start(dqlite_server *server);
/**
* Get the ID of the server.
*
* This will return 0 (an invalid ID) if the server has not been started.
*/
DQLITE_API DQLITE_EXPERIMENTAL dqlite_node_id
dqlite_server_get_id(dqlite_server *server);
/**
* Hand over the server's privileges to other servers.
*
* This is intended to be called before dqlite_server_stop. The server will try
* to surrender leadership and voting rights to other nodes in the cluster, if
* applicable. This avoids some disruptions that can result when a privileged
* server stops suddenly.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_handover(
dqlite_server *server);
/**
* Stop the server.
*
* The server will stop processing requests from client or other servers. To
* smooth over some possible disruptions to the cluster, call
* dqlite_server_handover before this function. After this function returns
* (successfully or not), you should call dqlite_server_destroy to free
* resources owned by the server.
*/
DQLITE_API DQLITE_EXPERIMENTAL int dqlite_server_stop(dqlite_server *server);
/**
* Free resources owned by the server.
*
* You should always call this function to finalize a server created with
* dqlite_server_create, whether or not that function returned successfully.
* If the server has been successfully started with dqlite_server_start,
* then you must stop it with dqlite_server_stop before calling this function.
*/
DQLITE_API DQLITE_EXPERIMENTAL void dqlite_server_destroy(
dqlite_server *server);
/**
* Error codes.
*
* These are used only with the dqlite_node family of functions.
*/
enum {
DQLITE_ERROR = 1, /* Generic error */
DQLITE_MISUSE, /* Library used incorrectly */
DQLITE_NOMEM /* A malloc() failed */
};
/**
* Dqlite node handle.
*
* Opaque handle to a single dqlite node that can serve database requests from
* connected clients and exchanges data replication messages with other dqlite
* nodes.
*/
typedef struct dqlite_node dqlite_node;
/**
* Create a new dqlite node object.
*
* The @id argument a is positive number that identifies this particular dqlite
* node in the cluster. Each dqlite node part of the same cluster must be
* created with a different ID. The very first node, used to bootstrap a new
* cluster, must have ID #1. Every time a node is started again, it must be
* passed the same ID.
* The @address argument is the network address that clients or other nodes in
* the cluster must use to connect to this dqlite node. If no custom connect
* function is going to be set using dqlite_node_set_connect_func(), then the
* format of the string must be "" or ":, where is a
* numeric IPv4/IPv6 address and is a port number. The port number
* defaults to 8080 if not specified. If a port number is specified with an
* IPv6 address, the address must be enclosed in square brackets "[]".
*
* If a custom connect function is used, then the format of the string must by
* whatever the custom connect function accepts.
*
* The @data_dir argument the file system path where the node should store its
* durable data, such as Raft log entries containing WAL frames of the SQLite
* databases being replicated.
*
* No reference to the memory pointed to by @address and @data_dir is kept by
* the dqlite library, so any memory associated with them can be released after
* the function returns.
*
* Even if an error is returned, the caller should call dqlite_node_destroy()
* on the dqlite_node* value pointed to by @n, and calling dqlite_node_errmsg()
* with that value will return a valid error string. (In some cases *n will be
* set to NULL, but dqlite_node_destroy() and dqlite_node_errmsg() will handle
* this gracefully.)
*/
DQLITE_API int dqlite_node_create(dqlite_node_id id,
const char *address,
const char *data_dir,
dqlite_node **n);
/**
* Destroy a dqlite node object.
*
* This will release all memory that was allocated by the node. If
* dqlite_node_start() was successfully invoked, then dqlite_node_stop() must be
* invoked before destroying the node.
*/
DQLITE_API void dqlite_node_destroy(dqlite_node *n);
/**
* Instruct the dqlite node to bind a network address when starting, and
* listening for incoming client connections.
*
* The given address might match the one passed to @dqlite_node_create or be a
* different one (for example if the application wants to proxy it).
*
* The format of the @address argument must be one of
*
* 1. ""
* 2. ":"
* 3. "@"
*
* Where is a numeric IPv4/IPv6 address, is a port number, and
* is an abstract Unix socket path. The port number defaults to 8080 if
* not specified. In the second form, if is an IPv6 address, it must be
* enclosed in square brackets "[]". In the third form, if is empty, the
* implementation will automatically select an available abstract Unix socket
* path, which can then be retrieved with dqlite_node_get_bind_address().
*
* If an abstract Unix socket is used the dqlite node will accept only
* connections originating from the same process.
*
* No reference to the memory pointed to by @address is kept, so any memory
* associated with them can be released after the function returns.
*
* This function must be called before calling dqlite_node_start().
*/
DQLITE_API int dqlite_node_set_bind_address(dqlite_node *n,
const char *address);
/**
* Get the network address that the dqlite node is using to accept incoming
* connections.
*/
DQLITE_API const char *dqlite_node_get_bind_address(dqlite_node *n);
/**
* Set a custom connect function.
*
* The function should block until a network connection with the dqlite node at
* the given @address is established, or an error occurs.
*
* In case of success, the file descriptor of the connected socket must be saved
* into the location pointed by the @fd argument. The socket must be either a
* TCP or a Unix socket.
*
* This function must be called before calling dqlite_node_start().
*/
DQLITE_API int dqlite_node_set_connect_func(dqlite_node *n,
int (*f)(void *arg,
const char *address,
int *fd),
void *arg);
/**
* DEPRECATED - USE `dqlite_node_set_network_latency_ms`
* Set the average one-way network latency, expressed in nanoseconds.
*
* This value is used internally by dqlite to decide how frequently the leader
* node should send heartbeats to other nodes in order to maintain its
* leadership, and how long other nodes should wait before deciding that the
* leader has died and initiate a failover.
*
* This function must be called before calling dqlite_node_start().
*/
DQLITE_API int dqlite_node_set_network_latency(dqlite_node *n,
unsigned long long nanoseconds);
/**
* Set the average one-way network latency, expressed in milliseconds.
*
* This value is used internally by dqlite to decide how frequently the leader
* node should send heartbeats to other nodes in order to maintain its
* leadership, and how long other nodes should wait before deciding that the
* leader has died and initiate a failover.
*
* This function must be called before calling dqlite_node_start().
*
* Latency should not be 0 or larger than 3600000 milliseconds.
*/
DQLITE_API int dqlite_node_set_network_latency_ms(dqlite_node *t,
unsigned milliseconds);
/**
* Set the failure domain associated with this node.
*
* This is effectively a tag applied to the node and that can be inspected later
* with the "Describe node" client request.
*/
DQLITE_API int dqlite_node_set_failure_domain(dqlite_node *n,
unsigned long long code);
/**
* Set the snapshot parameters for this node.
*
* This function determines how frequently a node will snapshot the state
* of the database and how many raft log entries will be kept around after
* a snapshot has been taken.
*
* `snapshot_threshold` : Determines the frequency of taking a snapshot, the
* lower the number, the higher the frequency.
*
* `snapshot_trailing` : Determines the amount of log entries kept around after
* taking a snapshot. Lowering this number decreases disk and memory footprint
* but increases the chance of having to send a full snapshot (instead of a
* number of log entries to a node that has fallen behind.
*
* This function must be called before calling dqlite_node_start().
*/
DQLITE_API int dqlite_node_set_snapshot_params(dqlite_node *n,
unsigned snapshot_threshold,
unsigned snapshot_trailing);
/**
* Set the block size used for performing disk IO when writing raft log segments
* to disk. @size is limited to a list of preset values.
*
* This function must be called before calling dqlite_node_start().
*/
DQLITE_API int dqlite_node_set_block_size(dqlite_node *n, size_t size);
/**
* WARNING: This is an experimental API.
*
* By default dqlite holds the SQLite database file and WAL in memory. By
* enabling disk-mode, dqlite will hold the SQLite database file on-disk while
* keeping the WAL in memory. Has to be called after `dqlite_node_create` and
* before `dqlite_node_start`.
*/
DQLITE_API int dqlite_node_enable_disk_mode(dqlite_node *n);
/**
* Set the target number of voting nodes for the cluster.
*
* If automatic role management is enabled, the cluster leader will attempt to
* promote nodes to reach the target. If automatic role management is disabled,
* this has no effect.
*
* The default target is 3 voters.
*/
DQLITE_API int dqlite_node_set_target_voters(dqlite_node *n, int voters);
/**
* Set the target number of standby nodes for the cluster.
*
* If automatic role management is enabled, the cluster leader will attempt to
* promote nodes to reach the target. If automatic role management is disabled,
* this has no effect.
*
* The default target is 0 standbys.
*/
DQLITE_API int dqlite_node_set_target_standbys(dqlite_node *n, int standbys);
/**
* Enable or disable auto-recovery for corrupted disk files.
*
* When auto-recovery is enabled, files in the data directory that are
* determined to be corrupt may be removed by dqlite at startup. This allows
* the node to start up successfully in more situations, but comes at the cost
* of possible data loss, and may mask bugs.
*
* This must be called before dqlite_node_start.
*
* Auto-recovery is enabled by default.
*/
DQLITE_API int dqlite_node_set_auto_recovery(dqlite_node *n, bool enabled);
/**
* Enable automatic role management on the server side for this node.
*
* When automatic role management is enabled, servers in a dqlite cluster will
* autonomously (without client intervention) promote and demote each other
* to maintain a specified number of voters and standbys, taking into account
* the health, failure domain, and weight of each server.
*
* By default, no automatic role management is performed.
*/
DQLITE_API int dqlite_node_enable_role_management(dqlite_node *n);
/**
* Start a dqlite node.
*
* A background thread will be spawned which will run the node's main loop. If
* this function returns successfully, the dqlite node is ready to accept new
* connections.
*/
DQLITE_API int dqlite_node_start(dqlite_node *n);
/**
* Attempt to hand over this node's privileges to other nodes in preparation
* for a graceful shutdown.
*
* Specifically, if this node is the cluster leader, this will cause another
* voting node (if one exists) to be elected leader; then, if this node is a
* voter, another non-voting node (if one exists) will be promoted to voter, and
* then this node will be demoted to spare.
*
* This function returns 0 if all privileges were handed over successfully,
* and nonzero otherwise. Callers can continue to dqlite_node_stop immediately
* after this function returns (whether or not it succeeded), or include their
* own graceful shutdown logic before dqlite_node_stop.
*/
DQLITE_API int dqlite_node_handover(dqlite_node *n);
/**
* Stop a dqlite node.
*
* The background thread running the main loop will be notified and the node
* will not accept any new client connections. Once inflight requests are
* completed, open client connections get closed and then the thread exits.
*/
DQLITE_API int dqlite_node_stop(dqlite_node *n);
struct dqlite_node_info
{
dqlite_node_id id;
const char *address;
};
typedef struct dqlite_node_info dqlite_node_info;
/* Defined to be an extensible struct, future additions to this struct should be
* 64-bits wide and 0 should not be used as a valid value. */
struct dqlite_node_info_ext
{
uint64_t size; /* The size of this struct */
uint64_t id; /* dqlite_node_id */
uint64_t address;
uint64_t dqlite_role;
};
typedef struct dqlite_node_info_ext dqlite_node_info_ext;
#define DQLITE_NODE_INFO_EXT_SZ_ORIG 32U /* (4 * 64) / 8 */
/**
* !!! Deprecated, use `dqlite_node_recover_ext` instead which also includes
* dqlite roles. !!!
*
* Force recovering a dqlite node which is part of a cluster whose majority of
* nodes have died, and therefore has become unavailable.
*
* In order for this operation to be safe you must follow these steps:
*
* 1. Make sure no dqlite node in the cluster is running.
*
* 2. Identify all dqlite nodes that have survived and that you want to be part
* of the recovered cluster.
*
* 3. Among the survived dqlite nodes, find the one with the most up-to-date
* raft term and log.
*
* 4. Invoke @dqlite_node_recover exactly one time, on the node you found in
* step 3, and pass it an array of #dqlite_node_info filled with the IDs and
* addresses of the survived nodes, including the one being recovered.
*
* 5. Copy the data directory of the node you ran @dqlite_node_recover on to all
* other non-dead nodes in the cluster, replacing their current data
* directory.
*
* 6. Restart all nodes.
*/
DQLITE_API int dqlite_node_recover(dqlite_node *n,
dqlite_node_info infos[],
int n_info);
/**
* Force recovering a dqlite node which is part of a cluster whose majority of
* nodes have died, and therefore has become unavailable.
*
* In order for this operation to be safe you must follow these steps:
*
* 1. Make sure no dqlite node in the cluster is running.
*
* 2. Identify all dqlite nodes that have survived and that you want to be part
* of the recovered cluster.
*
* 3. Among the survived dqlite nodes, find the one with the most up-to-date
* raft term and log.
*
* 4. Invoke @dqlite_node_recover_ext exactly one time, on the node you found in
* step 3, and pass it an array of #dqlite_node_info filled with the IDs,
* addresses and roles of the survived nodes, including the one being
* recovered.
*
* 5. Copy the data directory of the node you ran @dqlite_node_recover_ext on to
* all other non-dead nodes in the cluster, replacing their current data
* directory.
*
* 6. Restart all nodes.
*/
DQLITE_API int dqlite_node_recover_ext(dqlite_node *n,
dqlite_node_info_ext infos[],
int n_info);
/**
* Return a human-readable description of the last error occurred.
*/
DQLITE_API const char *dqlite_node_errmsg(dqlite_node *n);
/**
* Generate a unique ID for the given address.
*/
DQLITE_API dqlite_node_id dqlite_generate_node_id(const char *address);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Initialize the given SQLite VFS interface object with dqlite's custom
* implementation, which can be used for replication.
*/
DQLITE_API int dqlite_vfs_init(sqlite3_vfs *vfs, const char *name);
DQLITE_API int dqlite_vfs_enable_disk(sqlite3_vfs *vfs);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Release all memory used internally by a SQLite VFS object that was
* initialized using @qlite_vfs_init.
*/
DQLITE_API void dqlite_vfs_close(sqlite3_vfs *vfs);
/**
* This type is DEPRECATED and will be removed in a future major release.
*
* A single WAL frame to be replicated.
*/
struct dqlite_vfs_frame
{
unsigned long page_number; /* Database page number. */
void *data; /* Content of the database page. */
};
typedef struct dqlite_vfs_frame dqlite_vfs_frame;
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Check if the last call to sqlite3_step() has triggered a write transaction on
* the database with the given filename. In that case acquire a WAL write lock
* to prevent further write transactions, and return all new WAL frames
* generated by the transaction. These frames are meant to be replicated across
* nodes and then actually added to the WAL with dqlite_vfs_apply() once a
* quorum is reached. If a quorum is not reached within a given time, then
* dqlite_vfs_abort() can be used to abort and release the WAL write lock.
*/
DQLITE_API int dqlite_vfs_poll(sqlite3_vfs *vfs,
const char *filename,
dqlite_vfs_frame **frames,
unsigned *n);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Add to the WAL all frames that were generated by a write transaction
* triggered by sqlite3_step() and that were obtained via dqlite_vfs_poll().
*
* This interface is designed to match the typical use case of a node receiving
* the frames by sequentially reading a byte stream from a network socket and
* passing the data to this routine directly without any copy or futher
* allocation, possibly except for integer encoding/decoding.
*/
DQLITE_API int dqlite_vfs_apply(sqlite3_vfs *vfs,
const char *filename,
unsigned n,
unsigned long *page_numbers,
void *frames);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Abort a pending write transaction that was triggered by sqlite3_step() and
* whose frames were obtained via dqlite_vfs_poll().
*
* This should be called if the transaction could not be safely replicated. In
* particular it will release the write lock acquired by dqlite_vfs_poll().
*/
DQLITE_API int dqlite_vfs_abort(sqlite3_vfs *vfs, const char *filename);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Return a snapshot of the main database file and of the WAL file.
*/
DQLITE_API int dqlite_vfs_snapshot(sqlite3_vfs *vfs,
const char *filename,
void **data,
size_t *n);
/**
* This type is DEPRECATED and will be removed in a future major release.
*
* A data buffer.
*/
struct dqlite_buffer
{
void *base; /* Pointer to the buffer data. */
size_t len; /* Length of the buffer. */
};
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Return a shallow snapshot of the main database file and of the WAL file.
* Expects a bufs array of size x + 1, where x is obtained from
* `dqlite_vfs_num_pages`.
*/
DQLITE_API int dqlite_vfs_shallow_snapshot(sqlite3_vfs *vfs,
const char *filename,
struct dqlite_buffer bufs[],
unsigned n);
/**
* This function is DEPRECATED and will be removed in a future major release.
*/
DQLITE_API int dqlite_vfs_snapshot_disk(sqlite3_vfs *vfs,
const char *filename,
struct dqlite_buffer bufs[],
unsigned n);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Return the number of database pages (excluding WAL).
*/
DQLITE_API int dqlite_vfs_num_pages(sqlite3_vfs *vfs,
const char *filename,
unsigned *n);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Restore a snapshot of the main database file and of the WAL file.
*/
DQLITE_API int dqlite_vfs_restore(sqlite3_vfs *vfs,
const char *filename,
const void *data,
size_t n);
/**
* This function is DEPRECATED and will be removed in a future major release.
*
* Restore a snapshot of the main database file and of the WAL file.
*/
DQLITE_API int dqlite_vfs_restore_disk(sqlite3_vfs *vfs,
const char *filename,
const void *data,
size_t main_size,
size_t wal_size);
#endif /* DQLITE_H */
dqlite-1.16.0/m4/ 0000775 0000000 0000000 00000000000 14512203222 0013372 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/m4/.gitignore 0000664 0000000 0000000 00000000377 14512203222 0015371 0 ustar 00root root 0000000 0000000 *.m4
!attributes.m4
!ax_ac_append_to_file.m4
!ax_ac_print_to_file.m4
!ax_add_am_macro_static.m4
!ax_am_macros_static.m4
!ax_check_compile_flag.m4
!ax_check_gnu_make.m4
!ax_code_coverage.m4
!ax_compare_version.m4
!ax_file_escapes.m4
!ax_pthread.m4
!pkg.m4
dqlite-1.16.0/m4/attributes.m4 0000664 0000000 0000000 00000024021 14512203222 0016021 0 ustar 00root root 0000000 0000000 dnl Macros to check the presence of generic (non-typed) symbols.
dnl Copyright (c) 2006-2008 Diego Pettenò
dnl Copyright (c) 2006-2008 xine project
dnl Copyright (c) 2012 Lucas De Marchi
dnl
dnl This program is free software; you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation; either version 2, or (at your option)
dnl any later version.
dnl
dnl This program is distributed in the hope that it will be useful,
dnl but WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
dnl GNU General Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with this program; if not, write to the Free Software
dnl Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
dnl 02110-1301, USA.
dnl
dnl As a special exception, the copyright owners of the
dnl macro gives unlimited permission to copy, distribute and modify the
dnl configure scripts that are the output of Autoconf when processing the
dnl Macro. You need not follow the terms of the GNU General Public
dnl License when using or distributing such scripts, even though portions
dnl of the text of the Macro appear in them. The GNU General Public
dnl License (GPL) does govern all other use of the material that
dnl constitutes the Autoconf Macro.
dnl
dnl This special exception to the GPL applies to versions of the
dnl Autoconf Macro released by this project. When you make and
dnl distribute a modified version of the Autoconf Macro, you may extend
dnl this special exception to the GPL to apply to your modified version as
dnl well.
dnl Check if FLAG in ENV-VAR is supported by compiler and append it
dnl to WHERE-TO-APPEND variable. Note that we invert -Wno-* checks to
dnl -W* as gcc cannot test for negated warnings. If a C snippet is passed,
dnl use it, otherwise use a simple main() definition that just returns 0.
dnl CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG], [C-SNIPPET])
AC_DEFUN([CC_CHECK_FLAG_APPEND], [
AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2],
AS_TR_SH([cc_cv_$2_$3]),
[eval "AS_TR_SH([cc_save_$2])='${$2}'"
eval "AS_TR_SH([$2])='${cc_save_$2} -Werror `echo "$3" | sed 's/^-Wno-/-W/'`'"
AC_LINK_IFELSE([AC_LANG_SOURCE(ifelse([$4], [],
[int main(void) { return 0; } ],
[$4]))],
[eval "AS_TR_SH([cc_cv_$2_$3])='yes'"],
[eval "AS_TR_SH([cc_cv_$2_$3])='no'"])
eval "AS_TR_SH([$2])='$cc_save_$2'"])
AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes],
[eval "$1='${$1} $3'"])
])
dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2], [C-SNIPPET])
AC_DEFUN([CC_CHECK_FLAGS_APPEND], [
for flag in [$3]; do
CC_CHECK_FLAG_APPEND([$1], [$2], $flag, [$4])
done
])
dnl Check if the flag is supported by linker (cacheable)
dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND])
AC_DEFUN([CC_CHECK_LDFLAGS], [
AC_CACHE_CHECK([if $CC supports $1 flag],
AS_TR_SH([cc_cv_ldflags_$1]),
[ac_save_LDFLAGS="$LDFLAGS"
LDFLAGS="$LDFLAGS $1"
AC_LINK_IFELSE([int main() { return 1; }],
[eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"],
[eval "AS_TR_SH([cc_cv_ldflags_$1])="])
LDFLAGS="$ac_save_LDFLAGS"
])
AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes],
[$2], [$3])
])
dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for
dnl the current linker to avoid undefined references in a shared object.
AC_DEFUN([CC_NOUNDEFINED], [
dnl We check $host for which systems to enable this for.
AC_REQUIRE([AC_CANONICAL_HOST])
case $host in
dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads
dnl are requested, as different implementations are present; to avoid problems
dnl use -Wl,-z,defs only for those platform not behaving this way.
*-freebsd* | *-openbsd*) ;;
*)
dnl First of all check for the --no-undefined variant of GNU ld. This allows
dnl for a much more readable command line, so that people can understand what
dnl it does without going to look for what the heck -z defs does.
for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do
CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"])
break
done
;;
esac
AC_SUBST([LDFLAGS_NOUNDEFINED])
])
dnl Check for a -Werror flag or equivalent. -Werror is the GCC
dnl and ICC flag that tells the compiler to treat all the warnings
dnl as fatal. We usually need this option to make sure that some
dnl constructs (like attributes) are not simply ignored.
dnl
dnl Other compilers don't support -Werror per se, but they support
dnl an equivalent flag:
dnl - Sun Studio compiler supports -errwarn=%all
AC_DEFUN([CC_CHECK_WERROR], [
AC_CACHE_CHECK(
[for $CC way to treat warnings as errors],
[cc_cv_werror],
[CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror],
[CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])])
])
])
AC_DEFUN([CC_CHECK_ATTRIBUTE], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))],
AS_TR_SH([cc_cv_attribute_$1]),
[ac_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])],
[eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"],
[eval "AS_TR_SH([cc_cv_attribute_$1])='no'"])
CFLAGS="$ac_save_CFLAGS"
])
AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes],
[AC_DEFINE(
AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1,
[Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))]
)
$4],
[$5])
])
AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [
CC_CHECK_ATTRIBUTE(
[constructor],,
[void __attribute__((constructor)) ctor() { int a; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_FORMAT], [
CC_CHECK_ATTRIBUTE(
[format], [format(printf, n, n)],
[void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [
CC_CHECK_ATTRIBUTE(
[format_arg], [format_arg(printf)],
[char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [
CC_CHECK_ATTRIBUTE(
[visibility_$1], [visibility("$1")],
[void __attribute__((visibility("$1"))) $1_function() { }],
[$2], [$3])
])
AC_DEFUN([CC_ATTRIBUTE_NONNULL], [
CC_CHECK_ATTRIBUTE(
[nonnull], [nonnull()],
[void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_UNUSED], [
CC_CHECK_ATTRIBUTE(
[unused], ,
[void some_function(void *foo, __attribute__((unused)) void *bar);],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [
CC_CHECK_ATTRIBUTE(
[sentinel], ,
[void some_function(void *foo, ...) __attribute__((sentinel));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [
CC_CHECK_ATTRIBUTE(
[deprecated], ,
[void some_function(void *foo, ...) __attribute__((deprecated));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_ALIAS], [
CC_CHECK_ATTRIBUTE(
[alias], [weak, alias],
[void other_function(void *foo) { }
void some_function(void *foo) __attribute__((weak, alias("other_function")));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_MALLOC], [
CC_CHECK_ATTRIBUTE(
[malloc], ,
[void * __attribute__((malloc)) my_alloc(int n);],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_PACKED], [
CC_CHECK_ATTRIBUTE(
[packed], ,
[struct astructure { char a; int b; long c; void *d; } __attribute__((packed));],
[$1], [$2])
])
AC_DEFUN([CC_ATTRIBUTE_CONST], [
CC_CHECK_ATTRIBUTE(
[const], ,
[int __attribute__((const)) twopow(int n) { return 1 << n; } ],
[$1], [$2])
])
AC_DEFUN([CC_FLAG_VISIBILITY], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([if $CC supports -fvisibility=hidden],
[cc_cv_flag_visibility],
[cc_flag_visibility_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden],
cc_cv_flag_visibility='yes',
cc_cv_flag_visibility='no')
CFLAGS="$cc_flag_visibility_save_CFLAGS"])
AS_IF([test "x$cc_cv_flag_visibility" = "xyes"],
[AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1,
[Define this if the compiler supports the -fvisibility flag])
$1],
[$2])
])
AC_DEFUN([CC_FUNC_EXPECT], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([if compiler has __builtin_expect function],
[cc_cv_func_expect],
[ac_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
AC_COMPILE_IFELSE([AC_LANG_SOURCE(
[int some_function() {
int a = 3;
return (int)__builtin_expect(a, 3);
}])],
[cc_cv_func_expect=yes],
[cc_cv_func_expect=no])
CFLAGS="$ac_save_CFLAGS"
])
AS_IF([test "x$cc_cv_func_expect" = "xyes"],
[AC_DEFINE([SUPPORT__BUILTIN_EXPECT], 1,
[Define this if the compiler supports __builtin_expect() function])
$1],
[$2])
])
AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [
AC_REQUIRE([CC_CHECK_WERROR])
AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported],
[cc_cv_attribute_aligned],
[ac_save_CFLAGS="$CFLAGS"
CFLAGS="$CFLAGS $cc_cv_werror"
for cc_attribute_align_try in 64 32 16 8 4 2; do
AC_COMPILE_IFELSE([AC_LANG_SOURCE([
int main() {
static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0;
return c;
}])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break])
done
CFLAGS="$ac_save_CFLAGS"
])
if test "x$cc_cv_attribute_aligned" != "x"; then
AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned],
[Define the highest alignment supported])
fi
])
dqlite-1.16.0/m4/ax_ac_append_to_file.m4 0000664 0000000 0000000 00000001622 14512203222 0017740 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_ac_append_to_file.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_AC_APPEND_TO_FILE([FILE],[DATA])
#
# DESCRIPTION
#
# Appends the specified data to the specified Autoconf is run. If you want
# to append to a file when configure is run use AX_APPEND_TO_FILE instead.
#
# LICENSE
#
# Copyright (c) 2009 Allan Caffee
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 10
AC_DEFUN([AX_AC_APPEND_TO_FILE],[
AC_REQUIRE([AX_FILE_ESCAPES])
m4_esyscmd(
AX_FILE_ESCAPES
[
printf "%s" "$2" >> "$1"
])
])
dqlite-1.16.0/m4/ax_ac_print_to_file.m4 0000664 0000000 0000000 00000001611 14512203222 0017623 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_ac_print_to_file.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_AC_PRINT_TO_FILE([FILE],[DATA])
#
# DESCRIPTION
#
# Writes the specified data to the specified file when Autoconf is run. If
# you want to print to a file when configure is run use AX_PRINT_TO_FILE
# instead.
#
# LICENSE
#
# Copyright (c) 2009 Allan Caffee
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 10
AC_DEFUN([AX_AC_PRINT_TO_FILE],[
m4_esyscmd(
AC_REQUIRE([AX_FILE_ESCAPES])
[
printf "%s" "$2" > "$1"
])
])
dqlite-1.16.0/m4/ax_add_am_macro_static.m4 0000664 0000000 0000000 00000001525 14512203222 0020264 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_add_am_macro_static.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_ADD_AM_MACRO_STATIC([RULE])
#
# DESCRIPTION
#
# Adds the specified rule to $AMINCLUDE.
#
# LICENSE
#
# Copyright (c) 2009 Tom Howard
# Copyright (c) 2009 Allan Caffee
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 8
AC_DEFUN([AX_ADD_AM_MACRO_STATIC],[
AC_REQUIRE([AX_AM_MACROS_STATIC])
AX_AC_APPEND_TO_FILE(AMINCLUDE_STATIC,[$1])
])
dqlite-1.16.0/m4/ax_am_macros_static.m4 0000664 0000000 0000000 00000002125 14512203222 0017634 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_am_macros_static.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_AM_MACROS_STATIC
#
# DESCRIPTION
#
# Adds support for macros that create Automake rules. You must manually
# add the following line
#
# include $(top_srcdir)/aminclude_static.am
#
# to your Makefile.am files.
#
# LICENSE
#
# Copyright (c) 2009 Tom Howard
# Copyright (c) 2009 Allan Caffee
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 11
AC_DEFUN([AMINCLUDE_STATIC],[aminclude_static.am])
AC_DEFUN([AX_AM_MACROS_STATIC],
[
AX_AC_PRINT_TO_FILE(AMINCLUDE_STATIC,[
# ]AMINCLUDE_STATIC[ generated automatically by Autoconf
# from AX_AM_MACROS_STATIC on ]m4_esyscmd([LC_ALL=C date])[
])
])
dqlite-1.16.0/m4/ax_check_compile_flag.m4 0000664 0000000 0000000 00000004070 14512203222 0020103 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_check_compile_flag.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_CHECK_COMPILE_FLAG(FLAG, [ACTION-SUCCESS], [ACTION-FAILURE], [EXTRA-FLAGS], [INPUT])
#
# DESCRIPTION
#
# Check whether the given FLAG works with the current language's compiler
# or gives an error. (Warnings, however, are ignored)
#
# ACTION-SUCCESS/ACTION-FAILURE are shell commands to execute on
# success/failure.
#
# If EXTRA-FLAGS is defined, it is added to the current language's default
# flags (e.g. CFLAGS) when the check is done. The check is thus made with
# the flags: "CFLAGS EXTRA-FLAGS FLAG". This can for example be used to
# force the compiler to issue an error when a bad flag is given.
#
# INPUT gives an alternative input source to AC_COMPILE_IFELSE.
#
# NOTE: Implementation based on AX_CFLAGS_GCC_OPTION. Please keep this
# macro in sync with AX_CHECK_{PREPROC,LINK}_FLAG.
#
# LICENSE
#
# Copyright (c) 2008 Guido U. Draheim
# Copyright (c) 2011 Maarten Bosmans
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 6
AC_DEFUN([AX_CHECK_COMPILE_FLAG],
[AC_PREREQ(2.64)dnl for _AC_LANG_PREFIX and AS_VAR_IF
AS_VAR_PUSHDEF([CACHEVAR],[ax_cv_check_[]_AC_LANG_ABBREV[]flags_$4_$1])dnl
AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [
ax_check_save_flags=$[]_AC_LANG_PREFIX[]FLAGS
_AC_LANG_PREFIX[]FLAGS="$[]_AC_LANG_PREFIX[]FLAGS $4 $1"
AC_COMPILE_IFELSE([m4_default([$5],[AC_LANG_PROGRAM()])],
[AS_VAR_SET(CACHEVAR,[yes])],
[AS_VAR_SET(CACHEVAR,[no])])
_AC_LANG_PREFIX[]FLAGS=$ax_check_save_flags])
AS_VAR_IF(CACHEVAR,yes,
[m4_default([$2], :)],
[m4_default([$3], :)])
AS_VAR_POPDEF([CACHEVAR])dnl
])dnl AX_CHECK_COMPILE_FLAGS
dqlite-1.16.0/m4/ax_check_gnu_make.m4 0000664 0000000 0000000 00000007727 14512203222 0017264 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_check_gnu_make.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_CHECK_GNU_MAKE([run-if-true],[run-if-false])
#
# DESCRIPTION
#
# This macro searches for a GNU version of make. If a match is found:
#
# * The makefile variable `ifGNUmake' is set to the empty string, otherwise
# it is set to "#". This is useful for including a special features in a
# Makefile, which cannot be handled by other versions of make.
# * The makefile variable `ifnGNUmake' is set to #, otherwise
# it is set to the empty string. This is useful for including a special
# features in a Makefile, which can be handled
# by other versions of make or to specify else like clause.
# * The variable `_cv_gnu_make_command` is set to the command to invoke
# GNU make if it exists, the empty string otherwise.
# * The variable `ax_cv_gnu_make_command` is set to the command to invoke
# GNU make by copying `_cv_gnu_make_command`, otherwise it is unset.
# * If GNU Make is found, its version is extracted from the output of
# `make --version` as the last field of a record of space-separated
# columns and saved into the variable `ax_check_gnu_make_version`.
# * Additionally if GNU Make is found, run shell code run-if-true
# else run shell code run-if-false.
#
# Here is an example of its use:
#
# Makefile.in might contain:
#
# # A failsafe way of putting a dependency rule into a makefile
# $(DEPEND):
# $(CC) -MM $(srcdir)/*.c > $(DEPEND)
#
# @ifGNUmake@ ifeq ($(DEPEND),$(wildcard $(DEPEND)))
# @ifGNUmake@ include $(DEPEND)
# @ifGNUmake@ else
# fallback code
# @ifGNUmake@ endif
#
# Then configure.in would normally contain:
#
# AX_CHECK_GNU_MAKE()
# AC_OUTPUT(Makefile)
#
# Then perhaps to cause gnu make to override any other make, we could do
# something like this (note that GNU make always looks for GNUmakefile
# first):
#
# if ! test x$_cv_gnu_make_command = x ; then
# mv Makefile GNUmakefile
# echo .DEFAULT: > Makefile ;
# echo \ $_cv_gnu_make_command \$@ >> Makefile;
# fi
#
# Then, if any (well almost any) other make is called, and GNU make also
# exists, then the other make wraps the GNU make.
#
# LICENSE
#
# Copyright (c) 2008 John Darrington
# Copyright (c) 2015 Enrico M. Crisostomo
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 12
AC_DEFUN([AX_CHECK_GNU_MAKE],dnl
[AC_PROG_AWK
AC_CACHE_CHECK([for GNU make],[_cv_gnu_make_command],[dnl
_cv_gnu_make_command="" ;
dnl Search all the common names for GNU make
for a in "$MAKE" make gmake gnumake ; do
if test -z "$a" ; then continue ; fi ;
if "$a" --version 2> /dev/null | grep GNU 2>&1 > /dev/null ; then
_cv_gnu_make_command=$a ;
AX_CHECK_GNU_MAKE_HEADLINE=$("$a" --version 2> /dev/null | grep "GNU Make")
ax_check_gnu_make_version=$(echo ${AX_CHECK_GNU_MAKE_HEADLINE} | ${AWK} -F " " '{ print $(NF); }')
break ;
fi
done ;])
dnl If there was a GNU version, then set @ifGNUmake@ to the empty string, '#' otherwise
AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifGNUmake], ["#"])], [AS_VAR_SET([ifGNUmake], [""])])
AS_VAR_IF([_cv_gnu_make_command], [""], [AS_VAR_SET([ifnGNUmake], [""])], [AS_VAR_SET([ifnGNUmake], ["#"])])
AS_VAR_IF([_cv_gnu_make_command], [""], [AS_UNSET(ax_cv_gnu_make_command)], [AS_VAR_SET([ax_cv_gnu_make_command], [${_cv_gnu_make_command}])])
AS_VAR_IF([_cv_gnu_make_command], [""],[$2],[$1])
AC_SUBST([ifGNUmake])
AC_SUBST([ifnGNUmake])
])
dqlite-1.16.0/m4/ax_code_coverage.m4 0000664 0000000 0000000 00000027616 14512203222 0017125 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_code_coverage.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_CODE_COVERAGE()
#
# DESCRIPTION
#
# Defines CODE_COVERAGE_CPPFLAGS, CODE_COVERAGE_CFLAGS,
# CODE_COVERAGE_CXXFLAGS and CODE_COVERAGE_LIBS which should be included
# in the CPPFLAGS, CFLAGS CXXFLAGS and LIBS/LIBADD variables of every
# build target (program or library) which should be built with code
# coverage support. Also add rules using AX_ADD_AM_MACRO_STATIC; and
# $enable_code_coverage which can be used in subsequent configure output.
# CODE_COVERAGE_ENABLED is defined and substituted, and corresponds to the
# value of the --enable-code-coverage option, which defaults to being
# disabled.
#
# Test also for gcov program and create GCOV variable that could be
# substituted.
#
# Note that all optimization flags in CFLAGS must be disabled when code
# coverage is enabled.
#
# Usage example:
#
# configure.ac:
#
# AX_CODE_COVERAGE
#
# Makefile.am:
#
# include $(top_srcdir)/aminclude_static.am
#
# my_program_LIBS = ... $(CODE_COVERAGE_LIBS) ...
# my_program_CPPFLAGS = ... $(CODE_COVERAGE_CPPFLAGS) ...
# my_program_CFLAGS = ... $(CODE_COVERAGE_CFLAGS) ...
# my_program_CXXFLAGS = ... $(CODE_COVERAGE_CXXFLAGS) ...
#
# clean-local: code-coverage-clean
# distclean-local: code-coverage-dist-clean
#
# This results in a "check-code-coverage" rule being added to any
# Makefile.am which do "include $(top_srcdir)/aminclude_static.am"
# (assuming the module has been configured with --enable-code-coverage).
# Running `make check-code-coverage` in that directory will run the
# module's test suite (`make check`) and build a code coverage report
# detailing the code which was touched, then print the URI for the report.
#
# This code was derived from Makefile.decl in GLib, originally licensed
# under LGPLv2.1+.
#
# LICENSE
#
# Copyright (c) 2012, 2016 Philip Withnall
# Copyright (c) 2012 Xan Lopez
# Copyright (c) 2012 Christian Persch
# Copyright (c) 2012 Paolo Borelli
# Copyright (c) 2012 Dan Winship
# Copyright (c) 2015,2018 Bastien ROUCARIES
#
# This library is free software; you can redistribute it and/or modify it
# under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation; either version 2.1 of the License, or (at
# your option) any later version.
#
# This library 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 Lesser
# General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see .
#serial 34
m4_define(_AX_CODE_COVERAGE_RULES,[
AX_ADD_AM_MACRO_STATIC([
# Code coverage
#
# Optional:
# - CODE_COVERAGE_DIRECTORY: Top-level directory for code coverage reporting.
# Multiple directories may be specified, separated by whitespace.
# (Default: \$(top_builddir))
# - CODE_COVERAGE_OUTPUT_FILE: Filename and path for the .info file generated
# by lcov for code coverage. (Default:
# \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage.info)
# - CODE_COVERAGE_OUTPUT_DIRECTORY: Directory for generated code coverage
# reports to be created. (Default:
# \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage)
# - CODE_COVERAGE_BRANCH_COVERAGE: Set to 1 to enforce branch coverage,
# set to 0 to disable it and leave empty to stay with the default.
# (Default: empty)
# - CODE_COVERAGE_LCOV_SHOPTS_DEFAULT: Extra options shared between both lcov
# instances. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
# - CODE_COVERAGE_LCOV_SHOPTS: Extra options to shared between both lcov
# instances. (Default: $CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
# - CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH: --gcov-tool pathtogcov
# - CODE_COVERAGE_LCOV_OPTIONS_DEFAULT: Extra options to pass to the
# collecting lcov instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
# - CODE_COVERAGE_LCOV_OPTIONS: Extra options to pass to the collecting lcov
# instance. (Default: $CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
# - CODE_COVERAGE_LCOV_RMOPTS_DEFAULT: Extra options to pass to the filtering
# lcov instance. (Default: empty)
# - CODE_COVERAGE_LCOV_RMOPTS: Extra options to pass to the filtering lcov
# instance. (Default: $CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
# - CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT: Extra options to pass to the
# genhtml instance. (Default: based on $CODE_COVERAGE_BRANCH_COVERAGE)
# - CODE_COVERAGE_GENHTML_OPTIONS: Extra options to pass to the genhtml
# instance. (Default: $CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
# - CODE_COVERAGE_IGNORE_PATTERN: Extra glob pattern of files to ignore
#
# The generated report will be titled using the \$(PACKAGE_NAME) and
# \$(PACKAGE_VERSION). In order to add the current git hash to the title,
# use the git-version-gen script, available online.
# Optional variables
# run only on top dir
if CODE_COVERAGE_ENABLED
ifeq (\$(abs_builddir), \$(abs_top_builddir))
CODE_COVERAGE_DIRECTORY ?= \$(top_builddir)
CODE_COVERAGE_OUTPUT_FILE ?= \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage.info
CODE_COVERAGE_OUTPUT_DIRECTORY ?= \$(PACKAGE_NAME)-\$(PACKAGE_VERSION)-coverage
CODE_COVERAGE_BRANCH_COVERAGE ?=
CODE_COVERAGE_LCOV_SHOPTS_DEFAULT ?= \$(if \$(CODE_COVERAGE_BRANCH_COVERAGE),\
--rc lcov_branch_coverage=\$(CODE_COVERAGE_BRANCH_COVERAGE))
CODE_COVERAGE_LCOV_SHOPTS ?= \$(CODE_COVERAGE_LCOV_SHOPTS_DEFAULT)
CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH ?= --gcov-tool \"\$(GCOV)\"
CODE_COVERAGE_LCOV_OPTIONS_DEFAULT ?= \$(CODE_COVERAGE_LCOV_OPTIONS_GCOVPATH)
CODE_COVERAGE_LCOV_OPTIONS ?= \$(CODE_COVERAGE_LCOV_OPTIONS_DEFAULT)
CODE_COVERAGE_LCOV_RMOPTS_DEFAULT ?=
CODE_COVERAGE_LCOV_RMOPTS ?= \$(CODE_COVERAGE_LCOV_RMOPTS_DEFAULT)
CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT ?=\
\$(if \$(CODE_COVERAGE_BRANCH_COVERAGE),\
--rc genhtml_branch_coverage=\$(CODE_COVERAGE_BRANCH_COVERAGE))
CODE_COVERAGE_GENHTML_OPTIONS ?= \$(CODE_COVERAGE_GENHTML_OPTIONS_DEFAULT)
CODE_COVERAGE_IGNORE_PATTERN ?=
GITIGNOREFILES := \$(GITIGNOREFILES) \$(CODE_COVERAGE_OUTPUT_FILE) \$(CODE_COVERAGE_OUTPUT_DIRECTORY)
code_coverage_v_lcov_cap = \$(code_coverage_v_lcov_cap_\$(V))
code_coverage_v_lcov_cap_ = \$(code_coverage_v_lcov_cap_\$(AM_DEFAULT_VERBOSITY))
code_coverage_v_lcov_cap_0 = @echo \" LCOV --capture\" \$(CODE_COVERAGE_OUTPUT_FILE);
code_coverage_v_lcov_ign = \$(code_coverage_v_lcov_ign_\$(V))
code_coverage_v_lcov_ign_ = \$(code_coverage_v_lcov_ign_\$(AM_DEFAULT_VERBOSITY))
code_coverage_v_lcov_ign_0 = @echo \" LCOV --remove /tmp/*\" \$(CODE_COVERAGE_IGNORE_PATTERN);
code_coverage_v_genhtml = \$(code_coverage_v_genhtml_\$(V))
code_coverage_v_genhtml_ = \$(code_coverage_v_genhtml_\$(AM_DEFAULT_VERBOSITY))
code_coverage_v_genhtml_0 = @echo \" GEN \" \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\";
code_coverage_quiet = \$(code_coverage_quiet_\$(V))
code_coverage_quiet_ = \$(code_coverage_quiet_\$(AM_DEFAULT_VERBOSITY))
code_coverage_quiet_0 = --quiet
# sanitizes the test-name: replaces with underscores: dashes and dots
code_coverage_sanitize = \$(subst -,_,\$(subst .,_,\$(1)))
# Use recursive makes in order to ignore errors during check
check-code-coverage:
-\$(AM_V_at)\$(MAKE) \$(AM_MAKEFLAGS) -k check
\$(AM_V_at)\$(MAKE) \$(AM_MAKEFLAGS) code-coverage-capture
# Capture code coverage data
code-coverage-capture: code-coverage-capture-hook
\$(code_coverage_v_lcov_cap)\$(LCOV) \$(code_coverage_quiet) \$(addprefix --directory ,\$(CODE_COVERAGE_DIRECTORY)) --capture --output-file \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" --test-name \"\$(call code_coverage_sanitize,\$(PACKAGE_NAME)-\$(PACKAGE_VERSION))\" --no-checksum --compat-libtool \$(CODE_COVERAGE_LCOV_SHOPTS) \$(CODE_COVERAGE_LCOV_OPTIONS)
\$(code_coverage_v_lcov_ign)\$(LCOV) \$(code_coverage_quiet) \$(addprefix --directory ,\$(CODE_COVERAGE_DIRECTORY)) --remove \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \"/tmp/*\" \$(CODE_COVERAGE_IGNORE_PATTERN) --output-file \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \$(CODE_COVERAGE_LCOV_SHOPTS) \$(CODE_COVERAGE_LCOV_RMOPTS)
-@rm -f \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\"
\$(code_coverage_v_genhtml)LANG=C \$(GENHTML) \$(code_coverage_quiet) \$(addprefix --prefix ,\$(CODE_COVERAGE_DIRECTORY)) --output-directory \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\" --title \"\$(PACKAGE_NAME)-\$(PACKAGE_VERSION) Code Coverage\" --legend --show-details \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \$(CODE_COVERAGE_GENHTML_OPTIONS)
@echo \"file://\$(abs_builddir)/\$(CODE_COVERAGE_OUTPUT_DIRECTORY)/index.html\"
code-coverage-clean:
-\$(LCOV) --directory \$(top_builddir) -z
-rm -rf \"\$(CODE_COVERAGE_OUTPUT_FILE)\" \"\$(CODE_COVERAGE_OUTPUT_FILE).tmp\" \"\$(CODE_COVERAGE_OUTPUT_DIRECTORY)\"
-find . \\( -name \"*.gcda\" -o -name \"*.gcno\" -o -name \"*.gcov\" \\) -delete
code-coverage-dist-clean:
A][M_DISTCHECK_CONFIGURE_FLAGS := \$(A][M_DISTCHECK_CONFIGURE_FLAGS) --disable-code-coverage
else # ifneq (\$(abs_builddir), \$(abs_top_builddir))
check-code-coverage:
code-coverage-capture: code-coverage-capture-hook
code-coverage-clean:
code-coverage-dist-clean:
endif # ifeq (\$(abs_builddir), \$(abs_top_builddir))
else #! CODE_COVERAGE_ENABLED
# Use recursive makes in order to ignore errors during check
check-code-coverage:
@echo \"Need to reconfigure with --enable-code-coverage\"
# Capture code coverage data
code-coverage-capture: code-coverage-capture-hook
@echo \"Need to reconfigure with --enable-code-coverage\"
code-coverage-clean:
code-coverage-dist-clean:
endif #CODE_COVERAGE_ENABLED
# Hook rule executed before code-coverage-capture, overridable by the user
code-coverage-capture-hook:
.PHONY: check-code-coverage code-coverage-capture code-coverage-dist-clean code-coverage-clean code-coverage-capture-hook
])
])
AC_DEFUN([_AX_CODE_COVERAGE_ENABLED],[
AX_CHECK_GNU_MAKE([],[AC_MSG_ERROR([not using GNU make that is needed for coverage])])
AC_REQUIRE([AX_ADD_AM_MACRO_STATIC])
# check for gcov
AC_CHECK_TOOL([GCOV],
[$_AX_CODE_COVERAGE_GCOV_PROG_WITH],
[:])
AS_IF([test "X$GCOV" = "X:"],
[AC_MSG_ERROR([gcov is needed to do coverage])])
AC_SUBST([GCOV])
dnl Check if gcc is being used
AS_IF([ test "$GCC" = "no" ], [
AC_MSG_ERROR([not compiling with gcc, which is required for gcov code coverage])
])
AC_CHECK_PROG([LCOV], [lcov], [lcov])
AC_CHECK_PROG([GENHTML], [genhtml], [genhtml])
AS_IF([ test x"$LCOV" = x ], [
AC_MSG_ERROR([To enable code coverage reporting you must have lcov installed])
])
AS_IF([ test x"$GENHTML" = x ], [
AC_MSG_ERROR([Could not find genhtml from the lcov package])
])
dnl Build the code coverage flags
dnl Define CODE_COVERAGE_LDFLAGS for backwards compatibility
CODE_COVERAGE_CPPFLAGS="-DNDEBUG"
CODE_COVERAGE_CFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
CODE_COVERAGE_CXXFLAGS="-O0 -g -fprofile-arcs -ftest-coverage"
CODE_COVERAGE_LIBS="-lgcov"
AC_SUBST([CODE_COVERAGE_CPPFLAGS])
AC_SUBST([CODE_COVERAGE_CFLAGS])
AC_SUBST([CODE_COVERAGE_CXXFLAGS])
AC_SUBST([CODE_COVERAGE_LIBS])
])
AC_DEFUN([AX_CODE_COVERAGE],[
dnl Check for --enable-code-coverage
# allow to override gcov location
AC_ARG_WITH([gcov],
[AS_HELP_STRING([--with-gcov[=GCOV]], [use given GCOV for coverage (GCOV=gcov).])],
[_AX_CODE_COVERAGE_GCOV_PROG_WITH=$with_gcov],
[_AX_CODE_COVERAGE_GCOV_PROG_WITH=gcov])
AC_MSG_CHECKING([whether to build with code coverage support])
AC_ARG_ENABLE([code-coverage],
AS_HELP_STRING([--enable-code-coverage],
[Whether to enable code coverage support]),,
enable_code_coverage=no)
AM_CONDITIONAL([CODE_COVERAGE_ENABLED], [test "x$enable_code_coverage" = xyes])
AC_SUBST([CODE_COVERAGE_ENABLED], [$enable_code_coverage])
AC_MSG_RESULT($enable_code_coverage)
AS_IF([ test "x$enable_code_coverage" = xyes ], [
_AX_CODE_COVERAGE_ENABLED
])
_AX_CODE_COVERAGE_RULES
])
dqlite-1.16.0/m4/ax_compare_version.m4 0000664 0000000 0000000 00000014653 14512203222 0017530 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_compare_version.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE])
#
# DESCRIPTION
#
# This macro compares two version strings. Due to the various number of
# minor-version numbers that can exist, and the fact that string
# comparisons are not compatible with numeric comparisons, this is not
# necessarily trivial to do in a autoconf script. This macro makes doing
# these comparisons easy.
#
# The six basic comparisons are available, as well as checking equality
# limited to a certain number of minor-version levels.
#
# The operator OP determines what type of comparison to do, and can be one
# of:
#
# eq - equal (test A == B)
# ne - not equal (test A != B)
# le - less than or equal (test A <= B)
# ge - greater than or equal (test A >= B)
# lt - less than (test A < B)
# gt - greater than (test A > B)
#
# Additionally, the eq and ne operator can have a number after it to limit
# the test to that number of minor versions.
#
# eq0 - equal up to the length of the shorter version
# ne0 - not equal up to the length of the shorter version
# eqN - equal up to N sub-version levels
# neN - not equal up to N sub-version levels
#
# When the condition is true, shell commands ACTION-IF-TRUE are run,
# otherwise shell commands ACTION-IF-FALSE are run. The environment
# variable 'ax_compare_version' is always set to either 'true' or 'false'
# as well.
#
# Examples:
#
# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8])
# AX_COMPARE_VERSION([3.15],[lt],[3.15.8])
#
# would both be true.
#
# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8])
# AX_COMPARE_VERSION([3.15],[gt],[3.15.8])
#
# would both be false.
#
# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8])
#
# would be true because it is only comparing two minor versions.
#
# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15])
#
# would be true because it is only comparing the lesser number of minor
# versions of the two values.
#
# Note: The characters that separate the version numbers do not matter. An
# empty string is the same as version 0. OP is evaluated by autoconf, not
# configure, so must be a string, not a variable.
#
# The author would like to acknowledge Guido Draheim whose advice about
# the m4_case and m4_ifvaln functions make this macro only include the
# portions necessary to perform the specific comparison specified by the
# OP argument in the final configure script.
#
# LICENSE
#
# Copyright (c) 2008 Tim Toolan
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 13
dnl #########################################################################
AC_DEFUN([AX_COMPARE_VERSION], [
AC_REQUIRE([AC_PROG_AWK])
# Used to indicate true or false condition
ax_compare_version=false
# Convert the two version strings to be compared into a format that
# allows a simple string comparison. The end result is that a version
# string of the form 1.12.5-r617 will be converted to the form
# 0001001200050617. In other words, each number is zero padded to four
# digits, and non digits are removed.
AS_VAR_PUSHDEF([A],[ax_compare_version_A])
A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
-e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/[[^0-9]]//g'`
AS_VAR_PUSHDEF([B],[ax_compare_version_B])
B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \
-e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \
-e 's/[[^0-9]]//g'`
dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary
dnl # then the first line is used to determine if the condition is true.
dnl # The sed right after the echo is to remove any indented white space.
m4_case(m4_tolower($2),
[lt],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"`
],
[gt],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"`
],
[le],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"`
],
[ge],[
ax_compare_version=`echo "x$A
x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"`
],[
dnl Split the operator from the subversion count if present.
m4_bmatch(m4_substr($2,2),
[0],[
# A count of zero means use the length of the shorter version.
# Determine the number of characters in A and B.
ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'`
ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'`
# Set A to no more than B's length and B to no more than A's length.
A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"`
B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"`
],
[[0-9]+],[
# A count greater than zero means use only that many subversions
A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"`
],
[.+],[
AC_WARNING(
[invalid OP numeric parameter: $2])
],[])
# Pad zeros at end of numbers to make same length.
ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`"
B="$B`echo $A | sed 's/./0/g'`"
A="$ax_compare_version_tmp_A"
# Check for equality or inequality as necessary.
m4_case(m4_tolower(m4_substr($2,0,2)),
[eq],[
test "x$A" = "x$B" && ax_compare_version=true
],
[ne],[
test "x$A" != "x$B" && ax_compare_version=true
],[
AC_WARNING([invalid OP parameter: $2])
])
])
AS_VAR_POPDEF([A])dnl
AS_VAR_POPDEF([B])dnl
dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE.
if test "$ax_compare_version" = "true" ; then
m4_ifvaln([$4],[$4],[:])dnl
m4_ifvaln([$5],[else $5])dnl
fi
]) dnl AX_COMPARE_VERSION
dqlite-1.16.0/m4/ax_file_escapes.m4 0000664 0000000 0000000 00000001373 14512203222 0016752 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_file_escapes.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_FILE_ESCAPES
#
# DESCRIPTION
#
# Writes the specified data to the specified file.
#
# LICENSE
#
# Copyright (c) 2008 Tom Howard
#
# Copying and distribution of this file, with or without modification, are
# permitted in any medium without royalty provided the copyright notice
# and this notice are preserved. This file is offered as-is, without any
# warranty.
#serial 8
AC_DEFUN([AX_FILE_ESCAPES],[
AX_DOLLAR="\$"
AX_SRB="\\135"
AX_SLB="\\133"
AX_BS="\\\\"
AX_DQ="\""
])
dqlite-1.16.0/m4/ax_pthread.m4 0000664 0000000 0000000 00000052722 14512203222 0015763 0 ustar 00root root 0000000 0000000 # ===========================================================================
# https://www.gnu.org/software/autoconf-archive/ax_pthread.html
# ===========================================================================
#
# SYNOPSIS
#
# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
#
# DESCRIPTION
#
# This macro figures out how to build C programs using POSIX threads. It
# sets the PTHREAD_LIBS output variable to the threads library and linker
# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
# flags that are needed. (The user can also force certain compiler
# flags/libs to be tested by setting these environment variables.)
#
# Also sets PTHREAD_CC to any special C compiler that is needed for
# multi-threaded programs (defaults to the value of CC otherwise). (This
# is necessary on AIX to use the special cc_r compiler alias.)
#
# NOTE: You are assumed to not only compile your program with these flags,
# but also to link with them as well. For example, you might link with
# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
#
# If you are only building threaded programs, you may wish to use these
# variables in your default LIBS, CFLAGS, and CC:
#
# LIBS="$PTHREAD_LIBS $LIBS"
# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
# CC="$PTHREAD_CC"
#
# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to
# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
#
# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
# PTHREAD_CFLAGS.
#
# ACTION-IF-FOUND is a list of shell commands to run if a threads library
# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
# is not found. If ACTION-IF-FOUND is not specified, the default action
# will define HAVE_PTHREAD.
#
# Please let the authors know if this macro fails on any platform, or if
# you have any other suggestions or comments. This macro was based on work
# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
# Alejandro Forero Cuervo to the autoconf macro repository. We are also
# grateful for the helpful feedback of numerous users.
#
# Updated for Autoconf 2.68 by Daniel Richard G.
#
# LICENSE
#
# Copyright (c) 2008 Steven G. Johnson
# Copyright (c) 2011 Daniel Richard G.
# Copyright (c) 2019 Marc Stevens
#
# This program 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 of the License, or (at your
# option) any later version.
#
# This program 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 this program. If not, see .
#
# As a special exception, the respective Autoconf Macro's copyright owner
# gives unlimited permission to copy, distribute and modify the configure
# scripts that are the output of Autoconf when processing the Macro. You
# need not follow the terms of the GNU General Public License when using
# or distributing such scripts, even though portions of the text of the
# Macro appear in them. The GNU General Public License (GPL) does govern
# all other use of the material that constitutes the Autoconf Macro.
#
# This special exception to the GPL applies to versions of the Autoconf
# Macro released by the Autoconf Archive. When you make and distribute a
# modified version of the Autoconf Macro, you may extend this special
# exception to the GPL to apply to your modified version as well.
#serial 27
AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
AC_DEFUN([AX_PTHREAD], [
AC_REQUIRE([AC_CANONICAL_HOST])
AC_REQUIRE([AC_PROG_CC])
AC_REQUIRE([AC_PROG_SED])
AC_LANG_PUSH([C])
ax_pthread_ok=no
# We used to check for pthread.h first, but this fails if pthread.h
# requires special compiler flags (e.g. on Tru64 or Sequent).
# It gets checked for in the link test anyway.
# First of all, check if the user has set any of the PTHREAD_LIBS,
# etcetera environment variables, and if threads linking works using
# them:
if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
ax_pthread_save_CC="$CC"
ax_pthread_save_CFLAGS="$CFLAGS"
ax_pthread_save_LIBS="$LIBS"
AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"])
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
LIBS="$PTHREAD_LIBS $LIBS"
AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS])
AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes])
AC_MSG_RESULT([$ax_pthread_ok])
if test "x$ax_pthread_ok" = "xno"; then
PTHREAD_LIBS=""
PTHREAD_CFLAGS=""
fi
CC="$ax_pthread_save_CC"
CFLAGS="$ax_pthread_save_CFLAGS"
LIBS="$ax_pthread_save_LIBS"
fi
# We must check for the threads library under a number of different
# names; the ordering is very important because some systems
# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
# libraries is broken (non-POSIX).
# Create a list of thread flags to try. Items with a "," contain both
# C compiler flags (before ",") and linker flags (after ","). Other items
# starting with a "-" are C compiler flags, and remaining items are
# library names, except for "none" which indicates that we try without
# any flags at all, and "pthread-config" which is a program returning
# the flags for the Pth emulation library.
ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
# The ordering *is* (sometimes) important. Some notes on the
# individual items follow:
# pthreads: AIX (must check this before -lpthread)
# none: in case threads are in libc; should be tried before -Kthread and
# other compiler flags to prevent continual compiler warnings
# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64
# (Note: HP C rejects this with "bad form for `-t' option")
# -pthreads: Solaris/gcc (Note: HP C also rejects)
# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
# doesn't hurt to check since this sometimes defines pthreads and
# -D_REENTRANT too), HP C (must be checked before -lpthread, which
# is present but should not be used directly; and before -mthreads,
# because the compiler interprets this as "-mt" + "-hreads")
# -mthreads: Mingw32/gcc, Lynx/gcc
# pthread: Linux, etcetera
# --thread-safe: KAI C++
# pthread-config: use pthread-config program (for GNU Pth library)
case $host_os in
freebsd*)
# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
ax_pthread_flags="-kthread lthread $ax_pthread_flags"
;;
hpux*)
# From the cc(1) man page: "[-mt] Sets various -D flags to enable
# multi-threading and also sets -lpthread."
ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags"
;;
openedition*)
# IBM z/OS requires a feature-test macro to be defined in order to
# enable POSIX threads at all, so give the user a hint if this is
# not set. (We don't define these ourselves, as they can affect
# other portions of the system API in unpredictable ways.)
AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING],
[
# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS)
AX_PTHREAD_ZOS_MISSING
# endif
],
[AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])])
;;
solaris*)
# On Solaris (at least, for some versions), libc contains stubbed
# (non-functional) versions of the pthreads routines, so link-based
# tests will erroneously succeed. (N.B.: The stubs are missing
# pthread_cleanup_push, or rather a function called by this macro,
# so we could check for that, but who knows whether they'll stub
# that too in a future libc.) So we'll check first for the
# standard Solaris way of linking pthreads (-mt -lpthread).
ax_pthread_flags="-mt,-lpthread pthread $ax_pthread_flags"
;;
esac
# Are we compiling with Clang?
AC_CACHE_CHECK([whether $CC is Clang],
[ax_cv_PTHREAD_CLANG],
[ax_cv_PTHREAD_CLANG=no
# Note that Autoconf sets GCC=yes for Clang as well as GCC
if test "x$GCC" = "xyes"; then
AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG],
[/* Note: Clang 2.7 lacks __clang_[a-z]+__ */
# if defined(__clang__) && defined(__llvm__)
AX_PTHREAD_CC_IS_CLANG
# endif
],
[ax_cv_PTHREAD_CLANG=yes])
fi
])
ax_pthread_clang="$ax_cv_PTHREAD_CLANG"
# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC)
# Note that for GCC and Clang -pthread generally implies -lpthread,
# except when -nostdlib is passed.
# This is problematic using libtool to build C++ shared libraries with pthread:
# [1] https://gcc.gnu.org/bugzilla/show_bug.cgi?id=25460
# [2] https://bugzilla.redhat.com/show_bug.cgi?id=661333
# [3] https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=468555
# To solve this, first try -pthread together with -lpthread for GCC
AS_IF([test "x$GCC" = "xyes"],
[ax_pthread_flags="-pthread,-lpthread -pthread -pthreads $ax_pthread_flags"])
# Clang takes -pthread (never supported any other flag), but we'll try with -lpthread first
AS_IF([test "x$ax_pthread_clang" = "xyes"],
[ax_pthread_flags="-pthread,-lpthread -pthread"])
# The presence of a feature test macro requesting re-entrant function
# definitions is, on some systems, a strong hint that pthreads support is
# correctly enabled
case $host_os in
darwin* | hpux* | linux* | osf* | solaris*)
ax_pthread_check_macro="_REENTRANT"
;;
aix*)
ax_pthread_check_macro="_THREAD_SAFE"
;;
*)
ax_pthread_check_macro="--"
;;
esac
AS_IF([test "x$ax_pthread_check_macro" = "x--"],
[ax_pthread_check_cond=0],
[ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
if test "x$ax_pthread_ok" = "xno"; then
for ax_pthread_try_flag in $ax_pthread_flags; do
case $ax_pthread_try_flag in
none)
AC_MSG_CHECKING([whether pthreads work without any flags])
;;
*,*)
PTHREAD_CFLAGS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\1/"`
PTHREAD_LIBS=`echo $ax_pthread_try_flag | sed "s/^\(.*\),\(.*\)$/\2/"`
AC_MSG_CHECKING([whether pthreads work with "$PTHREAD_CFLAGS" and "$PTHREAD_LIBS"])
;;
-*)
AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag])
PTHREAD_CFLAGS="$ax_pthread_try_flag"
;;
pthread-config)
AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
AS_IF([test "x$ax_pthread_config" = "xno"], [continue])
PTHREAD_CFLAGS="`pthread-config --cflags`"
PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
;;
*)
AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag])
PTHREAD_LIBS="-l$ax_pthread_try_flag"
;;
esac
ax_pthread_save_CFLAGS="$CFLAGS"
ax_pthread_save_LIBS="$LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
LIBS="$PTHREAD_LIBS $LIBS"
# Check for various functions. We must include pthread.h,
# since some functions may be macros. (On the Sequent, we
# need a special flag -Kthread to make this header compile.)
# We check for pthread_join because it is in -lpthread on IRIX
# while pthread_create is in libc. We check for pthread_attr_init
# due to DEC craziness with -lpthreads. We check for
# pthread_cleanup_push because it is one of the few pthread
# functions on Solaris that doesn't have a non-functional libc stub.
# We try pthread_create on general principles.
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include
# if $ax_pthread_check_cond
# error "$ax_pthread_check_macro must be defined"
# endif
static void *some_global = NULL;
static void routine(void *a)
{
/* To avoid any unused-parameter or
unused-but-set-parameter warning. */
some_global = a;
}
static void *start_routine(void *a) { return a; }],
[pthread_t th; pthread_attr_t attr;
pthread_create(&th, 0, start_routine, 0);
pthread_join(th, 0);
pthread_attr_init(&attr);
pthread_cleanup_push(routine, 0);
pthread_cleanup_pop(0) /* ; */])],
[ax_pthread_ok=yes],
[])
CFLAGS="$ax_pthread_save_CFLAGS"
LIBS="$ax_pthread_save_LIBS"
AC_MSG_RESULT([$ax_pthread_ok])
AS_IF([test "x$ax_pthread_ok" = "xyes"], [break])
PTHREAD_LIBS=""
PTHREAD_CFLAGS=""
done
fi
# Clang needs special handling, because older versions handle the -pthread
# option in a rather... idiosyncratic way
if test "x$ax_pthread_clang" = "xyes"; then
# Clang takes -pthread; it has never supported any other flag
# (Note 1: This will need to be revisited if a system that Clang
# supports has POSIX threads in a separate library. This tends not
# to be the way of modern systems, but it's conceivable.)
# (Note 2: On some systems, notably Darwin, -pthread is not needed
# to get POSIX threads support; the API is always present and
# active. We could reasonably leave PTHREAD_CFLAGS empty. But
# -pthread does define _REENTRANT, and while the Darwin headers
# ignore this macro, third-party headers might not.)
# However, older versions of Clang make a point of warning the user
# that, in an invocation where only linking and no compilation is
# taking place, the -pthread option has no effect ("argument unused
# during compilation"). They expect -pthread to be passed in only
# when source code is being compiled.
#
# Problem is, this is at odds with the way Automake and most other
# C build frameworks function, which is that the same flags used in
# compilation (CFLAGS) are also used in linking. Many systems
# supported by AX_PTHREAD require exactly this for POSIX threads
# support, and in fact it is often not straightforward to specify a
# flag that is used only in the compilation phase and not in
# linking. Such a scenario is extremely rare in practice.
#
# Even though use of the -pthread flag in linking would only print
# a warning, this can be a nuisance for well-run software projects
# that build with -Werror. So if the active version of Clang has
# this misfeature, we search for an option to squash it.
AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread],
[ax_cv_PTHREAD_CLANG_NO_WARN_FLAG],
[ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown
# Create an alternate version of $ac_link that compiles and
# links in two steps (.c -> .o, .o -> exe) instead of one
# (.c -> exe), because the warning occurs only in the second
# step
ax_pthread_save_ac_link="$ac_link"
ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g'
ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"`
ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)"
ax_pthread_save_CFLAGS="$CFLAGS"
for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do
AS_IF([test "x$ax_pthread_try" = "xunknown"], [break])
CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS"
ac_link="$ax_pthread_save_ac_link"
AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
[ac_link="$ax_pthread_2step_ac_link"
AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
[break])
])
done
ac_link="$ax_pthread_save_ac_link"
CFLAGS="$ax_pthread_save_CFLAGS"
AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no])
ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try"
])
case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in
no | unknown) ;;
*) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;;
esac
fi # $ax_pthread_clang = yes
# Various other checks:
if test "x$ax_pthread_ok" = "xyes"; then
ax_pthread_save_CFLAGS="$CFLAGS"
ax_pthread_save_LIBS="$LIBS"
CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
LIBS="$PTHREAD_LIBS $LIBS"
# Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
AC_CACHE_CHECK([for joinable pthread attribute],
[ax_cv_PTHREAD_JOINABLE_ATTR],
[ax_cv_PTHREAD_JOINABLE_ATTR=unknown
for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
AC_LINK_IFELSE([AC_LANG_PROGRAM([#include ],
[int attr = $ax_pthread_attr; return attr /* ; */])],
[ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break],
[])
done
])
AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \
test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \
test "x$ax_pthread_joinable_attr_defined" != "xyes"],
[AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE],
[$ax_cv_PTHREAD_JOINABLE_ATTR],
[Define to necessary symbol if this constant
uses a non-standard name on your system.])
ax_pthread_joinable_attr_defined=yes
])
AC_CACHE_CHECK([whether more special flags are required for pthreads],
[ax_cv_PTHREAD_SPECIAL_FLAGS],
[ax_cv_PTHREAD_SPECIAL_FLAGS=no
case $host_os in
solaris*)
ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS"
;;
esac
])
AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \
test "x$ax_pthread_special_flags_added" != "xyes"],
[PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS"
ax_pthread_special_flags_added=yes])
AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
[ax_cv_PTHREAD_PRIO_INHERIT],
[AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]],
[[int i = PTHREAD_PRIO_INHERIT;
return i;]])],
[ax_cv_PTHREAD_PRIO_INHERIT=yes],
[ax_cv_PTHREAD_PRIO_INHERIT=no])
])
AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \
test "x$ax_pthread_prio_inherit_defined" != "xyes"],
[AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])
ax_pthread_prio_inherit_defined=yes
])
CFLAGS="$ax_pthread_save_CFLAGS"
LIBS="$ax_pthread_save_LIBS"
# More AIX lossage: compile with *_r variant
if test "x$GCC" != "xyes"; then
case $host_os in
aix*)
AS_CASE(["x/$CC"],
[x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
[#handle absolute path differently from PATH based program lookup
AS_CASE(["x$CC"],
[x/*],
[AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
[AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
;;
esac
fi
fi
test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
AC_SUBST([PTHREAD_LIBS])
AC_SUBST([PTHREAD_CFLAGS])
AC_SUBST([PTHREAD_CC])
# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
if test "x$ax_pthread_ok" = "xyes"; then
ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
:
else
ax_pthread_ok=no
$2
fi
AC_LANG_POP
])dnl AX_PTHREAD
dqlite-1.16.0/m4/pkg.m4 0000664 0000000 0000000 00000024011 14512203222 0014413 0 ustar 00root root 0000000 0000000 dnl pkg.m4 - Macros to locate and utilise pkg-config. -*- Autoconf -*-
dnl serial 11 (pkg-config-0.29.1)
dnl
dnl Copyright © 2004 Scott James Remnant .
dnl Copyright © 2012-2015 Dan Nicholson
dnl
dnl This program is free software; you can redistribute it and/or modify
dnl it under the terms of the GNU General Public License as published by
dnl the Free Software Foundation; either version 2 of the License, or
dnl (at your option) any later version.
dnl
dnl This program is distributed in the hope that it will be useful, but
dnl WITHOUT ANY WARRANTY; without even the implied warranty of
dnl MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
dnl General Public License for more details.
dnl
dnl You should have received a copy of the GNU General Public License
dnl along with this program; if not, write to the Free Software
dnl Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
dnl 02111-1307, USA.
dnl
dnl As a special exception to the GNU General Public License, if you
dnl distribute this file as part of a program that contains a
dnl configuration script generated by Autoconf, you may include it under
dnl the same distribution terms that you use for the rest of that
dnl program.
dnl PKG_PREREQ(MIN-VERSION)
dnl -----------------------
dnl Since: 0.29
dnl
dnl Verify that the version of the pkg-config macros are at least
dnl MIN-VERSION. Unlike PKG_PROG_PKG_CONFIG, which checks the user's
dnl installed version of pkg-config, this checks the developer's version
dnl of pkg.m4 when generating configure.
dnl
dnl To ensure that this macro is defined, also add:
dnl m4_ifndef([PKG_PREREQ],
dnl [m4_fatal([must install pkg-config 0.29 or later before running autoconf/autogen])])
dnl
dnl See the "Since" comment for each macro you use to see what version
dnl of the macros you require.
m4_defun([PKG_PREREQ],
[m4_define([PKG_MACROS_VERSION], [0.29.1])
m4_if(m4_version_compare(PKG_MACROS_VERSION, [$1]), -1,
[m4_fatal([pkg.m4 version $1 or higher is required but ]PKG_MACROS_VERSION[ found])])
])dnl PKG_PREREQ
dnl PKG_PROG_PKG_CONFIG([MIN-VERSION])
dnl ----------------------------------
dnl Since: 0.16
dnl
dnl Search for the pkg-config tool and set the PKG_CONFIG variable to
dnl first found in the path. Checks that the version of pkg-config found
dnl is at least MIN-VERSION. If MIN-VERSION is not specified, 0.9.0 is
dnl used since that's the first version where most current features of
dnl pkg-config existed.
AC_DEFUN([PKG_PROG_PKG_CONFIG],
[m4_pattern_forbid([^_?PKG_[A-Z_]+$])
m4_pattern_allow([^PKG_CONFIG(_(PATH|LIBDIR|SYSROOT_DIR|ALLOW_SYSTEM_(CFLAGS|LIBS)))?$])
m4_pattern_allow([^PKG_CONFIG_(DISABLE_UNINSTALLED|TOP_BUILD_DIR|DEBUG_SPEW)$])
AC_ARG_VAR([PKG_CONFIG], [path to pkg-config utility])
AC_ARG_VAR([PKG_CONFIG_PATH], [directories to add to pkg-config's search path])
AC_ARG_VAR([PKG_CONFIG_LIBDIR], [path overriding pkg-config's built-in search path])
if test "x$ac_cv_env_PKG_CONFIG_set" != "xset"; then
AC_PATH_TOOL([PKG_CONFIG], [pkg-config])
fi
if test -n "$PKG_CONFIG"; then
_pkg_min_version=m4_default([$1], [0.9.0])
AC_MSG_CHECKING([pkg-config is at least version $_pkg_min_version])
if $PKG_CONFIG --atleast-pkgconfig-version $_pkg_min_version; then
AC_MSG_RESULT([yes])
else
AC_MSG_RESULT([no])
PKG_CONFIG=""
fi
fi[]dnl
])dnl PKG_PROG_PKG_CONFIG
dnl PKG_CHECK_EXISTS(MODULES, [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
dnl -------------------------------------------------------------------
dnl Since: 0.18
dnl
dnl Check to see whether a particular set of modules exists. Similar to
dnl PKG_CHECK_MODULES(), but does not set variables or print errors.
dnl
dnl Please remember that m4 expands AC_REQUIRE([PKG_PROG_PKG_CONFIG])
dnl only at the first occurence in configure.ac, so if the first place
dnl it's called might be skipped (such as if it is within an "if", you
dnl have to call PKG_CHECK_EXISTS manually
AC_DEFUN([PKG_CHECK_EXISTS],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
if test -n "$PKG_CONFIG" && \
AC_RUN_LOG([$PKG_CONFIG --exists --print-errors "$1"]); then
m4_default([$2], [:])
m4_ifvaln([$3], [else
$3])dnl
fi])
dnl _PKG_CONFIG([VARIABLE], [COMMAND], [MODULES])
dnl ---------------------------------------------
dnl Internal wrapper calling pkg-config via PKG_CONFIG and setting
dnl pkg_failed based on the result.
m4_define([_PKG_CONFIG],
[if test -n "$$1"; then
pkg_cv_[]$1="$$1"
elif test -n "$PKG_CONFIG"; then
PKG_CHECK_EXISTS([$3],
[pkg_cv_[]$1=`$PKG_CONFIG --[]$2 "$3" 2>/dev/null`
test "x$?" != "x0" && pkg_failed=yes ],
[pkg_failed=yes])
else
pkg_failed=untried
fi[]dnl
])dnl _PKG_CONFIG
dnl _PKG_SHORT_ERRORS_SUPPORTED
dnl ---------------------------
dnl Internal check to see if pkg-config supports short errors.
AC_DEFUN([_PKG_SHORT_ERRORS_SUPPORTED],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])
if $PKG_CONFIG --atleast-pkgconfig-version 0.20; then
_pkg_short_errors_supported=yes
else
_pkg_short_errors_supported=no
fi[]dnl
])dnl _PKG_SHORT_ERRORS_SUPPORTED
dnl PKG_CHECK_MODULES(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
dnl [ACTION-IF-NOT-FOUND])
dnl --------------------------------------------------------------
dnl Since: 0.4.0
dnl
dnl Note that if there is a possibility the first call to
dnl PKG_CHECK_MODULES might not happen, you should be sure to include an
dnl explicit call to PKG_PROG_PKG_CONFIG in your configure.ac
AC_DEFUN([PKG_CHECK_MODULES],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
AC_ARG_VAR([$1][_CFLAGS], [C compiler flags for $1, overriding pkg-config])dnl
AC_ARG_VAR([$1][_LIBS], [linker flags for $1, overriding pkg-config])dnl
pkg_failed=no
AC_MSG_CHECKING([for $1])
_PKG_CONFIG([$1][_CFLAGS], [cflags], [$2])
_PKG_CONFIG([$1][_LIBS], [libs], [$2])
m4_define([_PKG_TEXT], [Alternatively, you may set the environment variables $1[]_CFLAGS
and $1[]_LIBS to avoid the need to call pkg-config.
See the pkg-config man page for more details.])
if test $pkg_failed = yes; then
AC_MSG_RESULT([no])
_PKG_SHORT_ERRORS_SUPPORTED
if test $_pkg_short_errors_supported = yes; then
$1[]_PKG_ERRORS=`$PKG_CONFIG --short-errors --print-errors --cflags --libs "$2" 2>&1`
else
$1[]_PKG_ERRORS=`$PKG_CONFIG --print-errors --cflags --libs "$2" 2>&1`
fi
# Put the nasty error message in config.log where it belongs
echo "$$1[]_PKG_ERRORS" >&AS_MESSAGE_LOG_FD
m4_default([$4], [AC_MSG_ERROR(
[Package requirements ($2) were not met:
$$1_PKG_ERRORS
Consider adjusting the PKG_CONFIG_PATH environment variable if you
installed software in a non-standard prefix.
_PKG_TEXT])[]dnl
])
elif test $pkg_failed = untried; then
AC_MSG_RESULT([no])
m4_default([$4], [AC_MSG_FAILURE(
[The pkg-config script could not be found or is too old. Make sure it
is in your PATH or set the PKG_CONFIG environment variable to the full
path to pkg-config.
_PKG_TEXT
To get pkg-config, see .])[]dnl
])
else
$1[]_CFLAGS=$pkg_cv_[]$1[]_CFLAGS
$1[]_LIBS=$pkg_cv_[]$1[]_LIBS
AC_MSG_RESULT([yes])
$3
fi[]dnl
])dnl PKG_CHECK_MODULES
dnl PKG_CHECK_MODULES_STATIC(VARIABLE-PREFIX, MODULES, [ACTION-IF-FOUND],
dnl [ACTION-IF-NOT-FOUND])
dnl ---------------------------------------------------------------------
dnl Since: 0.29
dnl
dnl Checks for existence of MODULES and gathers its build flags with
dnl static libraries enabled. Sets VARIABLE-PREFIX_CFLAGS from --cflags
dnl and VARIABLE-PREFIX_LIBS from --libs.
dnl
dnl Note that if there is a possibility the first call to
dnl PKG_CHECK_MODULES_STATIC might not happen, you should be sure to
dnl include an explicit call to PKG_PROG_PKG_CONFIG in your
dnl configure.ac.
AC_DEFUN([PKG_CHECK_MODULES_STATIC],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
_save_PKG_CONFIG=$PKG_CONFIG
PKG_CONFIG="$PKG_CONFIG --static"
PKG_CHECK_MODULES($@)
PKG_CONFIG=$_save_PKG_CONFIG[]dnl
])dnl PKG_CHECK_MODULES_STATIC
dnl PKG_INSTALLDIR([DIRECTORY])
dnl -------------------------
dnl Since: 0.27
dnl
dnl Substitutes the variable pkgconfigdir as the location where a module
dnl should install pkg-config .pc files. By default the directory is
dnl $libdir/pkgconfig, but the default can be changed by passing
dnl DIRECTORY. The user can override through the --with-pkgconfigdir
dnl parameter.
AC_DEFUN([PKG_INSTALLDIR],
[m4_pushdef([pkg_default], [m4_default([$1], ['${libdir}/pkgconfig'])])
m4_pushdef([pkg_description],
[pkg-config installation directory @<:@]pkg_default[@:>@])
AC_ARG_WITH([pkgconfigdir],
[AS_HELP_STRING([--with-pkgconfigdir], pkg_description)],,
[with_pkgconfigdir=]pkg_default)
AC_SUBST([pkgconfigdir], [$with_pkgconfigdir])
m4_popdef([pkg_default])
m4_popdef([pkg_description])
])dnl PKG_INSTALLDIR
dnl PKG_NOARCH_INSTALLDIR([DIRECTORY])
dnl --------------------------------
dnl Since: 0.27
dnl
dnl Substitutes the variable noarch_pkgconfigdir as the location where a
dnl module should install arch-independent pkg-config .pc files. By
dnl default the directory is $datadir/pkgconfig, but the default can be
dnl changed by passing DIRECTORY. The user can override through the
dnl --with-noarch-pkgconfigdir parameter.
AC_DEFUN([PKG_NOARCH_INSTALLDIR],
[m4_pushdef([pkg_default], [m4_default([$1], ['${datadir}/pkgconfig'])])
m4_pushdef([pkg_description],
[pkg-config arch-independent installation directory @<:@]pkg_default[@:>@])
AC_ARG_WITH([noarch-pkgconfigdir],
[AS_HELP_STRING([--with-noarch-pkgconfigdir], pkg_description)],,
[with_noarch_pkgconfigdir=]pkg_default)
AC_SUBST([noarch_pkgconfigdir], [$with_noarch_pkgconfigdir])
m4_popdef([pkg_default])
m4_popdef([pkg_description])
])dnl PKG_NOARCH_INSTALLDIR
dnl PKG_CHECK_VAR(VARIABLE, MODULE, CONFIG-VARIABLE,
dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])
dnl -------------------------------------------
dnl Since: 0.28
dnl
dnl Retrieves the value of the pkg-config variable for the given module.
AC_DEFUN([PKG_CHECK_VAR],
[AC_REQUIRE([PKG_PROG_PKG_CONFIG])dnl
AC_ARG_VAR([$1], [value of $3 for $2, overriding pkg-config])dnl
_PKG_CONFIG([$1], [variable="][$3]["], [$2])
AS_VAR_COPY([$1], [pkg_cv_][$1])
AS_VAR_IF([$1], [""], [$5], [$4])dnl
])dnl PKG_CHECK_VAR
dqlite-1.16.0/resources/ 0000775 0000000 0000000 00000000000 14512203222 0015064 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/resources/stdbool.h 0000664 0000000 0000000 00000002046 14512203222 0016705 0 ustar 00root root 0000000 0000000 /*===---- stdbool.h - Standard header for booleans -------------------------===
*
* Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
* See https://llvm.org/LICENSE.txt for license information.
* SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
*
*===-----------------------------------------------------------------------===
*/
#ifndef __STDBOOL_H
#define __STDBOOL_H
#define __bool_true_false_are_defined 1
#if defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L
/* FIXME: We should be issuing a deprecation warning here, but cannot yet due
* to system headers which include this header file unconditionally.
*/
#elif !defined(__cplusplus)
#define bool _Bool
#define true 1
#define false 0
#elif defined(__GNUC__) && !defined(__STRICT_ANSI__)
/* Define _Bool as a GNU extension. */
#define _Bool bool
#if defined(__cplusplus) && __cplusplus < 201103L
/* For C++98, define bool, false, true as a GNU extension. */
#define bool bool
#define false false
#define true true
#endif
#endif
#endif /* __STDBOOL_H */
dqlite-1.16.0/src/ 0000775 0000000 0000000 00000000000 14512203222 0013641 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/src/bind.c 0000664 0000000 0000000 00000004052 14512203222 0014722 0 ustar 00root root 0000000 0000000 #include "bind.h"
#include "tuple.h"
/* Bind a single parameter. */
static int bind_one(sqlite3_stmt *stmt, int n, struct value *value)
{
int rc;
/* TODO: the binding calls below currently use SQLITE_TRANSIENT when
* passing pointers to data (for TEXT or BLOB datatypes). This way
* SQLite makes its private copy of the data before the bind call
* returns, and we can reuse the message body buffer. The overhead of
* the copy is typically low, but if it becomes a concern, this could be
* optimized to make no copy and instead prevent the message body from
* being reused. */
switch (value->type) {
case SQLITE_INTEGER:
rc = sqlite3_bind_int64(stmt, n, value->integer);
break;
case SQLITE_FLOAT:
rc = sqlite3_bind_double(stmt, n, value->float_);
break;
case SQLITE_BLOB:
rc = sqlite3_bind_blob(stmt, n, value->blob.base,
(int)value->blob.len,
SQLITE_TRANSIENT);
break;
case SQLITE_NULL:
rc = sqlite3_bind_null(stmt, n);
break;
case SQLITE_TEXT:
rc = sqlite3_bind_text(stmt, n, value->text, -1,
SQLITE_TRANSIENT);
break;
case DQLITE_ISO8601:
rc = sqlite3_bind_text(stmt, n, value->text, -1,
SQLITE_TRANSIENT);
break;
case DQLITE_BOOLEAN:
rc = sqlite3_bind_int64(stmt, n,
value->boolean == 0 ? 0 : 1);
break;
default:
rc = DQLITE_PROTO;
break;
}
return rc;
}
int bind__params(sqlite3_stmt *stmt, struct cursor *cursor, int format)
{
struct tuple_decoder decoder;
unsigned long i;
int rc;
assert(format == TUPLE__PARAMS || format == TUPLE__PARAMS32);
sqlite3_reset(stmt);
/* If the payload has been fully consumed, it means there are no
* parameters to bind. */
if (cursor->cap == 0) {
return 0;
}
rc = tuple_decoder__init(&decoder, 0, format, cursor);
if (rc != 0) {
return rc;
}
for (i = 0; i < tuple_decoder__n(&decoder); i++) {
struct value value;
rc = tuple_decoder__next(&decoder, &value);
if (rc != 0) {
return rc;
}
rc = bind_one(stmt, (int)(i + 1), &value);
if (rc != 0) {
return rc;
}
}
return 0;
}
dqlite-1.16.0/src/bind.h 0000664 0000000 0000000 00000000531 14512203222 0014725 0 ustar 00root root 0000000 0000000 /**
* Bind statement parameters decoding them from a client request payload.
*/
#ifndef BIND_H_
#define BIND_H_
#include
#include "lib/serialize.h"
/**
* Bind the parameters of the given statement by decoding the given payload.
*/
int bind__params(sqlite3_stmt *stmt, struct cursor *cursor, int format);
#endif /* BIND_H_*/
dqlite-1.16.0/src/client/ 0000775 0000000 0000000 00000000000 14512203222 0015117 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/src/client/protocol.c 0000664 0000000 0000000 00000063246 14512203222 0017137 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include
#include "../lib/assert.h"
#include "../message.h"
#include "../protocol.h"
#include "../request.h"
#include "../response.h"
#include "../tracing.h"
#include "../tuple.h"
#include "protocol.h"
static void oom(void)
{
abort();
}
void *mallocChecked(size_t n)
{
void *p = malloc(n);
if (p == NULL) {
oom();
}
return p;
}
void *callocChecked(size_t count, size_t n)
{
void *p = calloc(count, n);
if (p == NULL) {
oom();
}
return p;
}
char *strdupChecked(const char *s)
{
char *p = strdup(s);
if (p == NULL) {
oom();
}
return p;
}
char *strndupChecked(const char *s, size_t n)
{
char *p = strndup(s, n);
if (p == NULL) {
oom();
}
return p;
}
/* Convert a value that potentially borrows data from the client_proto read
* buffer into one that owns its data. The owned data must be free with
* freeOwnedValue. */
static void makeValueOwned(struct value *val)
{
char *p;
switch (val->type) {
case SQLITE_TEXT:
val->text = strdupChecked(val->text);
break;
case DQLITE_ISO8601:
val->iso8601 = strdupChecked(val->iso8601);
break;
case SQLITE_BLOB:
p = mallocChecked(val->blob.len);
memcpy(p, val->blob.base, val->blob.len);
val->blob.base = p;
break;
default:;
}
}
/* Free the owned data of a value, which must have had makeValueOwned called
* on it previously. This takes its argument by value because it does *not*
* free the memory that stores the `struct value` itself, only the pointers
* held by `struct value`. */
static void freeOwnedValue(struct value val)
{
switch (val.type) {
case SQLITE_TEXT:
free((char *)val.text);
break;
case DQLITE_ISO8601:
free((char *)val.iso8601);
break;
case SQLITE_BLOB:
free(val.blob.base);
break;
default:;
}
}
static int peekUint64(struct cursor cursor, uint64_t *val)
{
if (cursor.cap < 8) {
return DQLITE_CLIENT_PROTO_ERROR;
}
*val = ByteFlipLe64(*(uint64_t *)cursor.p);
return 0;
}
/* Read data from fd into buf until one of the following occurs:
*
* - The full count n of bytes is read.
* - A read returns 0 (EOF).
* - The context's deadline is reached.
* - An error occurs.
*
* On error, -1 is returned. Otherwise the return value is the count
* of bytes read. This may be less than n if either EOF happened or
* the deadline kicked in. */
static ssize_t doRead(int fd,
void *buf,
size_t buf_len,
struct client_context *context)
{
ssize_t total;
struct pollfd pfd;
struct timespec now;
long millis;
ssize_t n;
int rv;
pfd.fd = fd;
pfd.events = POLLIN;
pfd.revents = 0;
total = 0;
while ((size_t)total < buf_len) {
rv = clock_gettime(CLOCK_REALTIME, &now);
assert(rv == 0);
if (context != NULL) {
millis =
(context->deadline.tv_sec - now.tv_sec) * 1000 +
(context->deadline.tv_nsec - now.tv_nsec) / 1000000;
if (millis < 0) {
/* poll(2) will block indefinitely if the
* timeout argument is negative, and we don't
* want that here. Signal a timeout. */
break;
}
} else {
/* The caller has explicitly asked us to block
* indefinitely. */
millis = -1;
}
rv = poll(&pfd, 1, (millis > INT_MAX) ? INT_MAX : (int)millis);
if (rv < 0) {
if (errno == EINTR) {
continue;
} else {
return -1;
}
} else if (rv == 0) {
/* Timeout */
break;
}
assert(rv == 1);
if (pfd.revents != POLLIN) {
/* If some other bits are set in the out parameter, an
* error occurred. */
return -1;
}
n = read(fd, (char *)buf + (size_t)total,
buf_len - (size_t)total);
if (n < 0) {
if (errno == EINTR) {
continue;
} else {
return -1;
}
} else if (n == 0) {
/* EOF */
break;
}
total += n;
}
return total;
}
/* Write data into fd from buf until one of the following occurs:
*
* - The full count n of bytes is written.
* - A write returns 0 (EOF).
* - The context's deadline is reached.
* - An error occurs.
*
* On error, -1 is returned. Otherwise the return value is the count
* of bytes written. This may be less than n if either EOF happened or
* the deadline kicked in. */
static ssize_t doWrite(int fd,
void *buf,
size_t buf_len,
struct client_context *context)
{
ssize_t total;
struct pollfd pfd;
struct timespec now;
long millis;
ssize_t n;
int rv;
pfd.fd = fd;
pfd.events = POLLOUT;
pfd.revents = 0;
total = 0;
while ((size_t)total < buf_len) {
rv = clock_gettime(CLOCK_REALTIME, &now);
assert(rv == 0);
if (context != NULL) {
millis =
(context->deadline.tv_sec - now.tv_sec) * 1000 +
(context->deadline.tv_nsec - now.tv_nsec) / 1000000;
if (millis < 0) {
/* poll(2) will block indefinitely if the
* timeout argument is negative, and we don't
* want that here. Signal a timeout. */
break;
}
} else {
/* The caller has explicitly asked us to block
* indefinitely. */
millis = -1;
}
rv = poll(&pfd, 1, (millis > INT_MAX) ? INT_MAX : (int)millis);
if (rv < 0) {
if (errno == EINTR) {
continue;
} else {
return -1;
}
} else if (rv == 0) {
/* Timeout */
break;
}
assert(rv == 1);
if (pfd.revents != POLLOUT) {
/* If some other bits are set in the out parameter, an
* error occurred. */
return -1;
}
n = write(fd, (char *)buf + (size_t)total,
buf_len - (size_t)total);
if (n < 0) {
if (errno == EINTR) {
continue;
} else {
return -1;
}
} else if (n == 0) {
/* EOF */
break;
}
total += n;
}
return total;
}
static int handleFailure(struct client_proto *c)
{
struct response_failure failure;
struct cursor cursor;
int rv;
cursor.p = buffer__cursor(&c->read, 0);
cursor.cap = buffer__offset(&c->read);
rv = response_failure__decode(&cursor, &failure);
if (rv != 0) {
tracef("decode as failure failed rv:%d", rv);
return DQLITE_CLIENT_PROTO_ERROR;
}
c->errcode = failure.code;
if (c->errmsg != NULL) {
free(c->errmsg);
}
c->errmsg = strdupChecked(failure.message);
return DQLITE_CLIENT_PROTO_RECEIVED_FAILURE;
}
void clientContextMillis(struct client_context *context, long millis)
{
int rv;
rv = clock_gettime(CLOCK_REALTIME, &context->deadline);
assert(rv == 0);
context->deadline.tv_nsec += millis * 1000000;
while (context->deadline.tv_nsec >= 1000000000) {
context->deadline.tv_nsec -= 1000000000;
context->deadline.tv_sec += 1;
}
}
/* TODO accept a context here? */
int clientOpen(struct client_proto *c, const char *addr, uint64_t server_id)
{
int rv;
rv = c->connect(c->connect_arg, addr, &c->fd);
if (rv != 0) {
c->fd = -1;
return DQLITE_CLIENT_PROTO_ERROR;
}
c->server_id = server_id;
rv = buffer__init(&c->read);
if (rv != 0) {
oom();
}
rv = buffer__init(&c->write);
if (rv != 0) {
oom();
}
c->errcode = 0;
c->errmsg = NULL;
return 0;
}
void clientClose(struct client_proto *c)
{
tracef("client close");
if (c->fd == -1) {
return;
}
close(c->fd);
c->fd = -1;
buffer__close(&c->write);
buffer__close(&c->read);
free(c->db_name);
c->db_name = NULL;
free(c->errmsg);
c->errmsg = NULL;
c->server_id = 0;
}
int clientSendHandshake(struct client_proto *c, struct client_context *context)
{
uint64_t protocol;
ssize_t rv;
tracef("client send handshake");
protocol = ByteFlipLe64(DQLITE_PROTOCOL_VERSION);
rv = doWrite(c->fd, &protocol, sizeof protocol, context);
if (rv < 0) {
tracef("client send handshake failed %zd", rv);
return DQLITE_CLIENT_PROTO_ERROR;
} else if ((size_t)rv < sizeof protocol) {
return DQLITE_CLIENT_PROTO_SHORT;
}
return 0;
}
static int writeMessage(struct client_proto *c,
uint8_t type,
uint8_t schema,
struct client_context *context)
{
struct message message = {0};
size_t n;
size_t words;
void *cursor;
ssize_t rv;
n = buffer__offset(&c->write);
words = (n - message__sizeof(&message)) / 8;
message.words = (uint32_t)words;
message.type = type;
message.schema = schema;
cursor = buffer__cursor(&c->write, 0);
message__encode(&message, &cursor);
rv = doWrite(c->fd, buffer__cursor(&c->write, 0), n, context);
if (rv < 0) {
tracef("request write failed rv:%zd", rv);
return DQLITE_CLIENT_PROTO_ERROR;
} else if ((size_t)rv < n) {
return DQLITE_CLIENT_PROTO_SHORT;
}
return 0;
}
#define BUFFER_REQUEST(LOWER, UPPER) \
{ \
struct message _message = {0}; \
size_t _n1; \
size_t _n2; \
void *_cursor; \
_n1 = message__sizeof(&_message); \
_n2 = request_##LOWER##__sizeof(&request); \
buffer__reset(&c->write); \
_cursor = buffer__advance(&c->write, _n1 + _n2); \
if (_cursor == NULL) { \
oom(); \
} \
assert(_n2 % 8 == 0); \
message__encode(&_message, &_cursor); \
request_##LOWER##__encode(&request, &_cursor); \
}
/* Write out a request. */
#define REQUEST(LOWER, UPPER, SCHEMA) \
{ \
int _rv; \
BUFFER_REQUEST(LOWER, UPPER); \
_rv = \
writeMessage(c, DQLITE_REQUEST_##UPPER, SCHEMA, context); \
if (_rv != 0) { \
return _rv; \
} \
}
static int readMessage(struct client_proto *c,
uint8_t *type,
struct client_context *context)
{
struct message message = {0};
struct cursor cursor;
void *p;
size_t n;
ssize_t rv;
buffer__reset(&c->read);
n = message__sizeof(&message);
p = buffer__advance(&c->read, n);
if (p == NULL) {
oom();
}
rv = doRead(c->fd, p, n, context);
if (rv < 0) {
return DQLITE_CLIENT_PROTO_ERROR;
} else if (rv < (ssize_t)n) {
return DQLITE_CLIENT_PROTO_SHORT;
}
cursor.p = p;
cursor.cap = n;
rv = message__decode(&cursor, &message);
if (rv != 0) {
tracef("message decode failed rv:%zd", rv);
return DQLITE_CLIENT_PROTO_ERROR;
}
buffer__reset(&c->read);
n = message.words * 8;
p = buffer__advance(&c->read, n);
if (p == NULL) {
oom();
}
rv = doRead(c->fd, p, n, context);
if (rv < 0) {
return DQLITE_ERROR;
} else if (rv < (ssize_t)n) {
return DQLITE_CLIENT_PROTO_SHORT;
}
*type = message.type;
return 0;
}
/* Read and decode a response. */
#define RESPONSE(LOWER, UPPER) \
{ \
uint8_t _type; \
int _rv; \
_rv = readMessage(c, &_type, context); \
if (_rv != 0) { \
return _rv; \
} \
if (_type == DQLITE_RESPONSE_FAILURE && \
_type != DQLITE_RESPONSE_##UPPER) { \
_rv = handleFailure(c); \
return _rv; \
} else if (_type != DQLITE_RESPONSE_##UPPER) { \
return DQLITE_CLIENT_PROTO_ERROR; \
} \
cursor.p = buffer__cursor(&c->read, 0); \
cursor.cap = buffer__offset(&c->read); \
_rv = response_##LOWER##__decode(&cursor, &response); \
if (_rv != 0) { \
return DQLITE_CLIENT_PROTO_ERROR; \
} \
}
int clientSendLeader(struct client_proto *c, struct client_context *context)
{
tracef("client send leader");
struct request_leader request = {0};
REQUEST(leader, LEADER, 0);
return 0;
}
int clientSendClient(struct client_proto *c,
uint64_t id,
struct client_context *context)
{
tracef("client send client");
struct request_client request;
request.id = id;
REQUEST(client, CLIENT, 0);
return 0;
}
int clientSendOpen(struct client_proto *c,
const char *name,
struct client_context *context)
{
tracef("client send open name %s", name);
struct request_open request;
c->db_name = strdupChecked(name);
request.filename = name;
request.flags = 0; /* unused */
request.vfs = "test"; /* unused */
REQUEST(open, OPEN, 0);
return 0;
}
int clientRecvDb(struct client_proto *c, struct client_context *context)
{
tracef("client recvdb");
struct cursor cursor;
struct response_db response;
RESPONSE(db, DB);
c->db_id = response.id;
c->db_is_init = true;
return 0;
}
int clientSendPrepare(struct client_proto *c,
const char *sql,
struct client_context *context)
{
tracef("client send prepare");
struct request_prepare request;
request.db_id = c->db_id;
request.sql = sql;
REQUEST(prepare, PREPARE, DQLITE_PREPARE_STMT_SCHEMA_V1);
return 0;
}
int clientRecvStmt(struct client_proto *c,
uint32_t *stmt_id,
uint64_t *n_params,
uint64_t *offset,
struct client_context *context)
{
struct cursor cursor;
struct response_stmt_with_offset response;
RESPONSE(stmt_with_offset, STMT_WITH_OFFSET);
if (stmt_id != NULL) {
*stmt_id = response.id;
}
if (n_params != NULL) {
*n_params = response.params;
}
if (offset != NULL) {
*offset = response.offset;
}
return 0;
}
static int bufferParams(struct client_proto *c,
struct value *params,
unsigned n_params)
{
struct tuple_encoder tup;
size_t i;
int rv;
if (n_params == 0) {
return 0;
}
rv = tuple_encoder__init(&tup, n_params, TUPLE__PARAMS32, &c->write);
if (rv != 0) {
return DQLITE_CLIENT_PROTO_ERROR;
}
for (i = 0; i < n_params; ++i) {
rv = tuple_encoder__next(&tup, ¶ms[i]);
if (rv != 0) {
return DQLITE_CLIENT_PROTO_ERROR;
}
}
return 0;
}
int clientSendExec(struct client_proto *c,
uint32_t stmt_id,
struct value *params,
unsigned n_params,
struct client_context *context)
{
tracef("client send exec id %" PRIu32, stmt_id);
struct request_exec request;
int rv;
request.db_id = c->db_id;
request.stmt_id = stmt_id;
BUFFER_REQUEST(exec, EXEC);
rv = bufferParams(c, params, n_params);
if (rv != 0) {
return rv;
}
rv = writeMessage(c, DQLITE_REQUEST_EXEC, 1, context);
return rv;
}
int clientSendExecSQL(struct client_proto *c,
const char *sql,
struct value *params,
unsigned n_params,
struct client_context *context)
{
tracef("client send exec sql");
struct request_exec_sql request;
int rv;
request.db_id = c->db_id;
request.sql = sql;
BUFFER_REQUEST(exec_sql, EXEC_SQL);
rv = bufferParams(c, params, n_params);
if (rv != 0) {
return rv;
}
rv = writeMessage(c, DQLITE_REQUEST_EXEC_SQL, 1, context);
return rv;
}
int clientRecvResult(struct client_proto *c,
uint64_t *last_insert_id,
uint64_t *rows_affected,
struct client_context *context)
{
struct cursor cursor;
struct response_result response;
RESPONSE(result, RESULT);
if (last_insert_id != NULL) {
*last_insert_id = response.last_insert_id;
}
if (rows_affected != NULL) {
*rows_affected = response.rows_affected;
}
return 0;
}
int clientSendQuery(struct client_proto *c,
uint32_t stmt_id,
struct value *params,
unsigned n_params,
struct client_context *context)
{
tracef("client send query stmt_id %" PRIu32, stmt_id);
struct request_query request;
int rv;
request.db_id = c->db_id;
request.stmt_id = stmt_id;
BUFFER_REQUEST(query, QUERY);
rv = bufferParams(c, params, n_params);
if (rv != 0) {
return rv;
}
rv = writeMessage(c, DQLITE_REQUEST_QUERY, 1, context);
return rv;
}
int clientSendQuerySQL(struct client_proto *c,
const char *sql,
struct value *params,
unsigned n_params,
struct client_context *context)
{
tracef("client send query sql sql %s", sql);
struct request_query_sql request;
int rv;
request.db_id = c->db_id;
request.sql = sql;
BUFFER_REQUEST(query_sql, QUERY_SQL);
rv = bufferParams(c, params, n_params);
if (rv != 0) {
return rv;
}
rv = writeMessage(c, DQLITE_REQUEST_QUERY_SQL, 1, context);
return rv;
}
int clientRecvRows(struct client_proto *c,
struct rows *rows,
bool *done,
struct client_context *context)
{
tracef("client recv rows");
struct cursor cursor;
uint8_t type;
uint64_t column_count;
unsigned i;
unsigned j;
const char *raw;
struct row *row;
struct row *last;
uint64_t eof;
struct tuple_decoder tup;
int rv;
rv = readMessage(c, &type, context);
if (rv != 0) {
return rv;
}
if (type == DQLITE_RESPONSE_FAILURE) {
rv = handleFailure(c);
return rv;
} else if (type != DQLITE_RESPONSE_ROWS) {
return DQLITE_CLIENT_PROTO_ERROR;
}
cursor.p = buffer__cursor(&c->read, 0);
cursor.cap = buffer__offset(&c->read);
rv = uint64__decode(&cursor, &column_count);
if (rv != 0) {
return DQLITE_CLIENT_PROTO_ERROR;
}
rows->column_count = (unsigned)column_count;
assert((uint64_t)rows->column_count == column_count);
rows->column_names =
callocChecked(rows->column_count, sizeof *rows->column_names);
for (i = 0; i < rows->column_count; ++i) {
rv = text__decode(&cursor, &raw);
if (rv != 0) {
rv = DQLITE_CLIENT_PROTO_ERROR;
goto err_after_alloc_column_names;
}
rows->column_names[i] = strdupChecked(raw);
}
rows->next = NULL;
last = NULL;
while (1) {
rv = peekUint64(cursor, &eof);
if (rv != 0) {
goto err_after_alloc_column_names;
}
if (eof == DQLITE_RESPONSE_ROWS_DONE ||
eof == DQLITE_RESPONSE_ROWS_PART) {
break;
}
row = mallocChecked(sizeof *row);
row->values =
callocChecked(rows->column_count, sizeof *row->values);
row->next = NULL;
/* Make sure that `goto err_after_alloc_row_values` will do the
* right thing even before we enter the for loop. */
i = 0;
rv = tuple_decoder__init(&tup, rows->column_count, TUPLE__ROW,
&cursor);
if (rv != 0) {
rv = DQLITE_CLIENT_PROTO_ERROR;
goto err_after_alloc_row_values;
}
for (; i < rows->column_count; ++i) {
rv = tuple_decoder__next(&tup, &row->values[i]);
if (rv != 0) {
rv = DQLITE_CLIENT_PROTO_ERROR;
goto err_after_alloc_row_values;
}
makeValueOwned(&row->values[i]);
}
if (last == NULL) {
rows->next = row;
} else {
last->next = row;
}
last = row;
}
assert(eof == DQLITE_RESPONSE_ROWS_DONE ||
eof == DQLITE_RESPONSE_ROWS_PART);
if (done != NULL) {
*done = eof == DQLITE_RESPONSE_ROWS_DONE;
}
return 0;
err_after_alloc_row_values:
for (j = 0; j < i; ++j) {
freeOwnedValue(row->values[j]);
}
free(row->values);
free(row);
err_after_alloc_column_names:
clientCloseRows(rows);
return rv;
}
void clientCloseRows(struct rows *rows)
{
uint64_t i;
struct row *row = rows->next;
struct row *next;
/* Note that we take care to still do the right thing if this was
* called before clientRecvRows completed. */
for (row = rows->next; row != NULL; row = next) {
next = row->next;
row->next = NULL;
for (i = 0; i < rows->column_count; ++i) {
freeOwnedValue(row->values[i]);
}
free(row->values);
row->values = NULL;
free(row);
}
rows->next = NULL;
if (rows->column_names != NULL) {
for (i = 0; i < rows->column_count; ++i) {
free(rows->column_names[i]);
rows->column_names[i] = NULL;
}
}
free(rows->column_names);
}
int clientSendInterrupt(struct client_proto *c, struct client_context *context)
{
tracef("client send interrupt");
struct request_interrupt request;
request.db_id = c->db_id;
REQUEST(interrupt, INTERRUPT, 0);
return 0;
}
int clientSendFinalize(struct client_proto *c,
uint32_t stmt_id,
struct client_context *context)
{
tracef("client send finalize %u", stmt_id);
struct request_finalize request;
request.db_id = c->db_id;
request.stmt_id = stmt_id;
REQUEST(finalize, FINALIZE, 0);
return 0;
}
int clientSendAdd(struct client_proto *c,
uint64_t id,
const char *address,
struct client_context *context)
{
tracef("client send add id %" PRIu64 " address %s", id, address);
struct request_add request;
request.id = id;
request.address = address;
REQUEST(add, ADD, 0);
return 0;
}
int clientSendAssign(struct client_proto *c,
uint64_t id,
int role,
struct client_context *context)
{
tracef("client send assign id %" PRIu64 " role %d", id, role);
assert(role == DQLITE_VOTER || role == DQLITE_STANDBY ||
role == DQLITE_SPARE);
struct request_assign request;
request.id = id;
request.role = (uint64_t)role;
REQUEST(assign, ASSIGN, 0);
return 0;
}
int clientSendRemove(struct client_proto *c,
uint64_t id,
struct client_context *context)
{
tracef("client send remove id %" PRIu64, id);
struct request_remove request;
request.id = id;
REQUEST(remove, REMOVE, 0);
return 0;
}
int clientSendDump(struct client_proto *c, struct client_context *context)
{
tracef("client send dump");
struct request_dump request;
assert(c->db_is_init);
assert(c->db_name != NULL);
request.filename = c->db_name;
REQUEST(dump, DUMP, 0);
return 0;
}
int clientSendCluster(struct client_proto *c, struct client_context *context)
{
tracef("client send cluster");
struct request_cluster request;
request.format = DQLITE_REQUEST_CLUSTER_FORMAT_V1;
REQUEST(cluster, CLUSTER, 0);
return 0;
}
int clientSendTransfer(struct client_proto *c,
uint64_t id,
struct client_context *context)
{
tracef("client send transfer id %" PRIu64, id);
struct request_transfer request;
request.id = id;
REQUEST(transfer, TRANSFER, 0);
return 0;
}
int clientSendDescribe(struct client_proto *c, struct client_context *context)
{
tracef("client send describe");
struct request_describe request;
request.format = DQLITE_REQUEST_DESCRIBE_FORMAT_V0;
REQUEST(describe, DESCRIBE, 0);
return 0;
}
int clientSendWeight(struct client_proto *c,
uint64_t weight,
struct client_context *context)
{
tracef("client send weight %" PRIu64, weight);
struct request_weight request;
request.weight = weight;
REQUEST(weight, WEIGHT, 0);
return 0;
}
int clientRecvServer(struct client_proto *c,
uint64_t *id,
char **address,
struct client_context *context)
{
tracef("client recv server");
struct cursor cursor;
struct response_server response;
*id = 0;
*address = NULL;
RESPONSE(server, SERVER);
*address = strdupChecked(response.address);
*id = response.id;
return 0;
}
int clientRecvWelcome(struct client_proto *c, struct client_context *context)
{
tracef("client recv welcome");
struct cursor cursor;
struct response_welcome response;
RESPONSE(welcome, WELCOME);
return 0;
}
int clientRecvEmpty(struct client_proto *c, struct client_context *context)
{
tracef("client recv empty");
struct cursor cursor;
struct response_empty response;
RESPONSE(empty, EMPTY);
return 0;
}
int clientRecvFailure(struct client_proto *c,
uint64_t *code,
char **msg,
struct client_context *context)
{
tracef("client recv failure");
struct cursor cursor;
struct response_failure response;
RESPONSE(failure, FAILURE);
*code = response.code;
*msg = strdupChecked(response.message);
return 0;
}
int clientRecvServers(struct client_proto *c,
struct client_node_info **servers,
uint64_t *n_servers,
struct client_context *context)
{
tracef("client recv servers");
struct cursor cursor;
size_t n;
uint64_t i = 0;
uint64_t j;
uint64_t raw_role;
const char *raw_addr;
struct response_servers response;
int rv;
*servers = NULL;
*n_servers = 0;
RESPONSE(servers, SERVERS);
n = (size_t)response.n;
assert((uint64_t)n == response.n);
struct client_node_info *srvs = callocChecked(n, sizeof *srvs);
for (; i < response.n; ++i) {
rv = uint64__decode(&cursor, &srvs[i].id);
if (rv != 0) {
goto err_after_alloc_srvs;
}
rv = text__decode(&cursor, &raw_addr);
if (rv != 0) {
goto err_after_alloc_srvs;
}
srvs[i].addr = strdupChecked(raw_addr);
rv = uint64__decode(&cursor, &raw_role);
if (rv != 0) {
free(srvs[i].addr);
goto err_after_alloc_srvs;
}
srvs[i].role = (int)raw_role;
}
*n_servers = n;
*servers = srvs;
return 0;
err_after_alloc_srvs:
for (j = 0; j < i; ++j) {
free(srvs[i].addr);
}
free(srvs);
return rv;
}
int clientRecvFiles(struct client_proto *c,
struct client_file **files,
size_t *n_files,
struct client_context *context)
{
tracef("client recv files");
struct cursor cursor;
struct response_files response;
struct client_file *fs;
size_t n;
size_t z;
size_t i = 0;
size_t j;
const char *raw_name;
int rv;
*files = NULL;
*n_files = 0;
RESPONSE(files, FILES);
n = (size_t)response.n;
assert((uint64_t)n == response.n);
fs = callocChecked(n, sizeof *fs);
for (; i < response.n; ++i) {
rv = text__decode(&cursor, &raw_name);
if (rv != 0) {
goto err_after_alloc_fs;
}
fs[i].name = strdupChecked(raw_name);
rv = uint64__decode(&cursor, &fs[i].size);
if (rv != 0) {
free(fs[i].name);
goto err_after_alloc_fs;
}
if (cursor.cap != fs[i].size) {
free(fs[i].name);
rv = DQLITE_PARSE;
goto err_after_alloc_fs;
}
z = (size_t)fs[i].size;
assert((uint64_t)z == fs[i].size);
fs[i].blob = mallocChecked(z);
memcpy(fs[i].blob, cursor.p, z);
}
*files = fs;
*n_files = n;
return 0;
err_after_alloc_fs:
for (j = 0; j < i; ++j) {
free(fs[i].name);
free(fs[i].blob);
}
free(fs);
return rv;
}
int clientRecvMetadata(struct client_proto *c,
uint64_t *failure_domain,
uint64_t *weight,
struct client_context *context)
{
tracef("client recv metadata");
struct cursor cursor;
struct response_metadata response;
RESPONSE(metadata, METADATA);
*failure_domain = response.failure_domain;
*weight = response.weight;
return 0;
}
dqlite-1.16.0/src/client/protocol.h 0000664 0000000 0000000 00000023363 14512203222 0017140 0 ustar 00root root 0000000 0000000 /* Core dqlite client logic for encoding requests and decoding responses. */
#ifndef DQLITE_CLIENT_PROTOCOL_H_
#define DQLITE_CLIENT_PROTOCOL_H_
#include "../../include/dqlite.h"
#include "../lib/buffer.h"
#include "../tuple.h"
/* All functions declared in this header file return 0 for success or one
* of the follow error codes on failure. */
enum {
/* We received a FAILURE response when we expected another response.
*
* The data carried by the FAILURE response can be retrieved from the
* errcode and errmsg fields of struct client_proto.
*
* It's safe to continue using the client_proto object after receiving
* this error code. */
DQLITE_CLIENT_PROTO_RECEIVED_FAILURE = 1,
/* We timed out while reading from or writing to our fd, or a read/write
* returned EOF before the expected number of bytes were read/written.
*
* It is not generally safe to continue using the client_proto object
* after receiving this error code. */
DQLITE_CLIENT_PROTO_SHORT,
/* Another kind of error occurred, like a syscall failure.
*
* It is not generally safe to continue using the client_proto object
* after receiving this error code. */
DQLITE_CLIENT_PROTO_ERROR
};
struct client_proto
{
/* TODO find a better approach to initializing these fields? */
int (*connect)(void *, const char *, int *);
void *connect_arg;
int fd; /* Connected socket */
uint32_t db_id; /* Database ID provided by the server */
char *db_name; /* Database filename (owned) */
bool db_is_init; /* Whether the database ID has been initialized */
uint64_t server_id;
struct buffer read; /* Read buffer */
struct buffer write; /* Write buffer */
uint64_t errcode; /* Last error code returned by the server (owned) */
char *errmsg; /* Last error string returned by the server */
};
/* All of the Send and Recv functions take an `struct client_context *context`
* argument, which controls timeouts for read and write operations (and possibly
* other knobs in the future).
*
* Passing NULL for the context argument is permitted and disables all timeouts.
*/
struct client_context
{
/* An absolute CLOCK_REALTIME timestamp that limits how long will be
* spent trying to complete the requested send or receive operation.
* Whenever we are about to make a blocking syscall (read or write), we
* first poll(2) using a timeout computed based on how much time remains
* before the deadline. If the poll times out, we return early instead
* of completing the operation. */
struct timespec deadline;
};
/* TODO Consider using a dynamic array instead of a linked list here? */
struct row
{
struct value *values;
struct row *next;
};
struct rows
{
unsigned column_count;
char **column_names;
struct row *next;
};
struct client_node_info
{
uint64_t id;
char *addr;
int role;
};
struct client_file
{
char *name;
uint64_t size;
void *blob;
};
/* Checked allocation functions that abort the process on allocation failure. */
void *mallocChecked(size_t n);
void *callocChecked(size_t nmemb, size_t size);
char *strdupChecked(const char *s);
char *strndupCheck(const char *s, size_t n);
/* Initialize a context whose deadline will fall after the given duration
* in milliseconds. */
DQLITE_VISIBLE_TO_TESTS void clientContextMillis(struct client_context *context,
long millis);
/* Initialize a new client. */
DQLITE_VISIBLE_TO_TESTS int clientOpen(struct client_proto *c,
const char *addr,
uint64_t server_id);
/* Release all memory used by the client, and close the client socket. */
DQLITE_VISIBLE_TO_TESTS void clientClose(struct client_proto *c);
/* Initialize the connection by writing the protocol version. This must be
* called before using any other API. */
DQLITE_VISIBLE_TO_TESTS int clientSendHandshake(struct client_proto *c,
struct client_context *context);
/* Send a request to get the current leader. */
DQLITE_VISIBLE_TO_TESTS int clientSendLeader(struct client_proto *c,
struct client_context *context);
/* Send a request identifying this client to the attached server. */
DQLITE_VISIBLE_TO_TESTS int clientSendClient(struct client_proto *c,
uint64_t id,
struct client_context *context);
/* Send a request to open a database */
DQLITE_VISIBLE_TO_TESTS int clientSendOpen(struct client_proto *c,
const char *name,
struct client_context *context);
/* Receive the response to an open request. */
DQLITE_VISIBLE_TO_TESTS int clientRecvDb(struct client_proto *c,
struct client_context *context);
/* Send a request to prepare a statement. */
DQLITE_VISIBLE_TO_TESTS int clientSendPrepare(struct client_proto *c,
const char *sql,
struct client_context *context);
/* Receive the response to a prepare request. */
DQLITE_VISIBLE_TO_TESTS int clientRecvStmt(struct client_proto *c,
uint32_t *stmt_id,
uint64_t *n_params,
uint64_t *offset,
struct client_context *context);
/* Send a request to execute a statement. */
DQLITE_VISIBLE_TO_TESTS int clientSendExec(struct client_proto *c,
uint32_t stmt_id,
struct value *params,
unsigned n_params,
struct client_context *context);
/* Send a request to execute a non-prepared statement. */
DQLITE_VISIBLE_TO_TESTS int clientSendExecSQL(struct client_proto *c,
const char *sql,
struct value *params,
unsigned n_params,
struct client_context *context);
/* Receive the response to an exec request. */
DQLITE_VISIBLE_TO_TESTS int clientRecvResult(struct client_proto *c,
uint64_t *last_insert_id,
uint64_t *rows_affected,
struct client_context *context);
/* Send a request to perform a query. */
DQLITE_VISIBLE_TO_TESTS int clientSendQuery(struct client_proto *c,
uint32_t stmt_id,
struct value *params,
unsigned n_params,
struct client_context *context);
/* Send a request to perform a non-prepared query. */
DQLITE_VISIBLE_TO_TESTS int clientSendQuerySQL(struct client_proto *c,
const char *sql,
struct value *params,
unsigned n_params,
struct client_context *context);
/* Receive the response of a query request. */
DQLITE_VISIBLE_TO_TESTS int clientRecvRows(struct client_proto *c,
struct rows *rows,
bool *done,
struct client_context *context);
/* Release all memory used in the given rows object. */
DQLITE_VISIBLE_TO_TESTS void clientCloseRows(struct rows *rows);
/* Send a request to interrupt a server that's sending rows. */
DQLITE_VISIBLE_TO_TESTS int clientSendInterrupt(struct client_proto *c,
struct client_context *context);
/* Send a request to finalize a prepared statement. */
DQLITE_VISIBLE_TO_TESTS int clientSendFinalize(struct client_proto *c,
uint32_t stmt_id,
struct client_context *context);
/* Send a request to add a dqlite node. */
DQLITE_VISIBLE_TO_TESTS int clientSendAdd(struct client_proto *c,
uint64_t id,
const char *address,
struct client_context *context);
/* Send a request to assign a role to a node. */
DQLITE_VISIBLE_TO_TESTS int clientSendAssign(struct client_proto *c,
uint64_t id,
int role,
struct client_context *context);
/* Send a request to remove a server from the cluster. */
DQLITE_VISIBLE_TO_TESTS int clientSendRemove(struct client_proto *c,
uint64_t id,
struct client_context *context);
/* Send a request to dump the contents of the attached database. */
DQLITE_VISIBLE_TO_TESTS int clientSendDump(struct client_proto *c,
struct client_context *context);
/* Send a request to list the nodes of the cluster with their addresses and
* roles. */
DQLITE_VISIBLE_TO_TESTS int clientSendCluster(struct client_proto *c,
struct client_context *context);
/* Send a request to transfer leadership to node with id `id`. */
DQLITE_VISIBLE_TO_TESTS int clientSendTransfer(struct client_proto *c,
uint64_t id,
struct client_context *context);
/* Send a request to retrieve metadata about the attached server. */
DQLITE_VISIBLE_TO_TESTS int clientSendDescribe(struct client_proto *c,
struct client_context *context);
/* Send a request to set the weight metadata for the attached server. */
DQLITE_VISIBLE_TO_TESTS int clientSendWeight(struct client_proto *c,
uint64_t weight,
struct client_context *context);
/* Receive a response with the ID and address of a single node. */
DQLITE_VISIBLE_TO_TESTS int clientRecvServer(struct client_proto *c,
uint64_t *id,
char **address,
struct client_context *context);
/* Receive a "welcome" handshake response. */
DQLITE_VISIBLE_TO_TESTS int clientRecvWelcome(struct client_proto *c,
struct client_context *context);
/* Receive an empty response. */
DQLITE_VISIBLE_TO_TESTS int clientRecvEmpty(struct client_proto *c,
struct client_context *context);
/* Receive a failure response. */
DQLITE_VISIBLE_TO_TESTS int clientRecvFailure(struct client_proto *c,
uint64_t *code,
char **msg,
struct client_context *context);
/* Receive a list of nodes in the cluster. */
DQLITE_VISIBLE_TO_TESTS int clientRecvServers(struct client_proto *c,
struct client_node_info **servers,
uint64_t *n_servers,
struct client_context *context);
/* Receive a list of files that make up a database. */
DQLITE_VISIBLE_TO_TESTS int clientRecvFiles(struct client_proto *c,
struct client_file **files,
size_t *n_files,
struct client_context *context);
/* Receive metadata for a single server. */
DQLITE_VISIBLE_TO_TESTS int clientRecvMetadata(struct client_proto *c,
uint64_t *failure_domain,
uint64_t *weight,
struct client_context *context);
#endif /* DQLITE_CLIENT_PROTOCOL_H_ */
dqlite-1.16.0/src/command.c 0000664 0000000 0000000 00000010300 14512203222 0015415 0 ustar 00root root 0000000 0000000 #include
#include "../include/dqlite.h"
#include "lib/serialize.h"
#include "command.h"
#include "protocol.h"
#define FORMAT 1 /* Format version */
#define HEADER(X, ...) \
X(uint8, format, ##__VA_ARGS__) \
X(uint8, type, ##__VA_ARGS__) \
X(uint8, _unused1, ##__VA_ARGS__) \
X(uint8, _unused2, ##__VA_ARGS__) \
X(uint32, _unused3, ##__VA_ARGS__)
SERIALIZE__DEFINE(header, HEADER);
SERIALIZE__IMPLEMENT(header, HEADER);
static size_t frames__sizeof(const frames_t *frames)
{
size_t s = uint32__sizeof(&frames->n_pages) +
uint16__sizeof(&frames->page_size) +
uint16__sizeof(&frames->__unused__) +
sizeof(uint64_t) * frames->n_pages + /* Page numbers */
frames->page_size * frames->n_pages; /* Page data */
return s;
}
static void frames__encode(const frames_t *frames, void **cursor)
{
const dqlite_vfs_frame *list;
unsigned i;
uint32__encode(&frames->n_pages, cursor);
uint16__encode(&frames->page_size, cursor);
uint16__encode(&frames->__unused__, cursor);
list = frames->data;
for (i = 0; i < frames->n_pages; i++) {
uint64_t pgno = list[i].page_number;
uint64__encode(&pgno, cursor);
}
for (i = 0; i < frames->n_pages; i++) {
memcpy(*cursor, list[i].data, frames->page_size);
*cursor += frames->page_size;
}
}
static int frames__decode(struct cursor *cursor, frames_t *frames)
{
int rc;
rc = uint32__decode(cursor, &frames->n_pages);
if (rc != 0) {
return rc;
}
rc = uint16__decode(cursor, &frames->page_size);
if (rc != 0) {
return rc;
}
rc = uint16__decode(cursor, &frames->__unused__);
if (rc != 0) {
return rc;
}
frames->data = cursor->p;
return 0;
}
#define COMMAND__IMPLEMENT(LOWER, UPPER, _) \
SERIALIZE__IMPLEMENT(command_##LOWER, COMMAND__##UPPER);
COMMAND__TYPES(COMMAND__IMPLEMENT, );
#define ENCODE(LOWER, UPPER, _) \
case COMMAND_##UPPER: \
h.type = COMMAND_##UPPER; \
buf->len = header__sizeof(&h); \
buf->len += command_##LOWER##__sizeof(command); \
buf->base = raft_malloc(buf->len); \
if (buf->base == NULL) { \
return DQLITE_NOMEM; \
} \
cursor = buf->base; \
header__encode(&h, &cursor); \
command_##LOWER##__encode(command, &cursor); \
break;
int command__encode(int type, const void *command, struct raft_buffer *buf)
{
struct header h = {0};
void *cursor;
int rc = 0;
h.format = FORMAT;
switch (type) {
COMMAND__TYPES(ENCODE, )
};
return rc;
}
#define DECODE(LOWER, UPPER, _) \
case COMMAND_##UPPER: \
*command = raft_malloc(sizeof(struct command_##LOWER)); \
if (*command == NULL) { \
return DQLITE_NOMEM; \
} \
rc = command_##LOWER##__decode(&cursor, *command); \
break;
int command__decode(const struct raft_buffer *buf, int *type, void **command)
{
struct header h;
struct cursor cursor;
int rc;
cursor.p = buf->base;
cursor.cap = buf->len;
rc = header__decode(&cursor, &h);
if (rc != 0) {
return rc;
}
if (h.format != FORMAT) {
return DQLITE_PROTO;
}
switch (h.type) {
COMMAND__TYPES(DECODE, )
default:
rc = DQLITE_PROTO;
break;
};
if (rc != 0) {
return rc;
}
*type = h.type;
return 0;
}
int command_frames__page_numbers(const struct command_frames *c,
unsigned long *page_numbers[])
{
unsigned i;
struct cursor cursor;
cursor.p = c->frames.data;
cursor.cap = sizeof(uint64_t) * c->frames.n_pages;
*page_numbers =
sqlite3_malloc64(sizeof **page_numbers * c->frames.n_pages);
if (*page_numbers == NULL) {
return DQLITE_NOMEM;
}
for (i = 0; i < c->frames.n_pages; i++) {
uint64_t pgno;
int r = uint64__decode(&cursor, &pgno);
if (r != 0) {
return r;
}
(*page_numbers)[i] = (unsigned long)pgno;
}
return 0;
}
void command_frames__pages(const struct command_frames *c, void **pages)
{
*pages =
(void *)(c->frames.data + (sizeof(uint64_t) * c->frames.n_pages));
}
dqlite-1.16.0/src/command.h 0000664 0000000 0000000 00000004300 14512203222 0015425 0 ustar 00root root 0000000 0000000 /**
* Encode and decode dqlite Raft FSM commands.
*/
#ifndef COMMAND_H_
#define COMMAND_H_
#include
#include "../include/dqlite.h"
#include "lib/serialize.h"
/* Command type codes */
enum { COMMAND_OPEN = 1, COMMAND_FRAMES, COMMAND_UNDO, COMMAND_CHECKPOINT };
/* Hold information about an array of WAL frames. */
struct frames
{
uint32_t n_pages;
uint16_t page_size;
uint16_t __unused__;
/* TODO: because the sqlite3 replication APIs are asymmetrics, the
* format differs between encode and decode. When encoding data is
* expected to be a sqlite3_wal_replication_frame* array, and when
* decoding it will be a pointer to raw memory which can be further
* decoded with the command_frames__page_numbers() and
* command_frames__pages() helpers. */
const void *data;
};
typedef struct frames frames_t;
/* Serialization definitions for a raft FSM command. */
#define COMMAND__DEFINE(LOWER, UPPER, _) \
SERIALIZE__DEFINE_STRUCT(command_##LOWER, COMMAND__##UPPER);
#define COMMAND__OPEN(X, ...) X(text, filename, ##__VA_ARGS__)
#define COMMAND__FRAMES(X, ...) \
X(text, filename, ##__VA_ARGS__) \
X(uint64, tx_id, ##__VA_ARGS__) \
X(uint32, truncate, ##__VA_ARGS__) \
X(uint8, is_commit, ##__VA_ARGS__) \
X(uint8, __unused1__, ##__VA_ARGS__) \
X(uint16, __unused2__, ##__VA_ARGS__) \
X(frames, frames, ##__VA_ARGS__)
#define COMMAND__UNDO(X, ...) X(uint64, tx_id, ##__VA_ARGS__)
#define COMMAND__CHECKPOINT(X, ...) X(text, filename, ##__VA_ARGS__)
#define COMMAND__TYPES(X, ...) \
X(open, OPEN, __VA_ARGS__) \
X(frames, FRAMES, __VA_ARGS__) \
X(undo, UNDO, __VA_ARGS__) \
X(checkpoint, CHECKPOINT, __VA_ARGS__)
COMMAND__TYPES(COMMAND__DEFINE);
DQLITE_VISIBLE_TO_TESTS int command__encode(int type,
const void *command,
struct raft_buffer *buf);
DQLITE_VISIBLE_TO_TESTS int command__decode(const struct raft_buffer *buf,
int *type,
void **command);
DQLITE_VISIBLE_TO_TESTS int command_frames__page_numbers(
const struct command_frames *c,
unsigned long *page_numbers[]);
DQLITE_VISIBLE_TO_TESTS void command_frames__pages(
const struct command_frames *c,
void **pages);
#endif /* COMMAND_H_*/
dqlite-1.16.0/src/config.c 0000664 0000000 0000000 00000003002 14512203222 0015245 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include "../include/dqlite.h"
#include "./lib/assert.h"
#include "config.h"
#include "logger.h"
/* Default heartbeat timeout in milliseconds.
*
* Clients will be disconnected if the server does not receive a heartbeat
* message within this time. */
#define DEFAULT_HEARTBEAT_TIMEOUT 15000
/* Default database page size in bytes. */
#define DEFAULT_PAGE_SIZE 4096
/* Number of outstanding WAL frames after which a checkpoint is triggered as
* soon as possible. */
#define DEFAULT_CHECKPOINT_THRESHOLD 1000
/* For generating unique replication/VFS registration names.
*
* TODO: make this thread safe. */
static unsigned serial = 1;
int config__init(struct config *c,
dqlite_node_id id,
const char *address,
const char *dir)
{
int rv;
c->id = id;
c->address = sqlite3_malloc((int)strlen(address) + 1);
if (c->address == NULL) {
return DQLITE_NOMEM;
}
strcpy(c->address, address);
c->heartbeat_timeout = DEFAULT_HEARTBEAT_TIMEOUT;
c->page_size = DEFAULT_PAGE_SIZE;
c->checkpoint_threshold = DEFAULT_CHECKPOINT_THRESHOLD;
rv = snprintf(c->name, sizeof c->name, "dqlite-%u", serial);
assert(rv < (int)(sizeof c->name));
c->logger.data = NULL;
c->logger.emit = loggerDefaultEmit;
c->failure_domain = 0;
c->weight = 0;
strncpy(c->dir, dir, sizeof(c->dir) - 1);
c->dir[sizeof(c->dir) - 1] = '\0';
c->disk = false;
c->voters = 3;
c->standbys = 0;
serial++;
return 0;
}
void config__close(struct config *c)
{
sqlite3_free(c->address);
}
dqlite-1.16.0/src/config.h 0000664 0000000 0000000 00000002446 14512203222 0015265 0 ustar 00root root 0000000 0000000 #ifndef CONFIG_H_
#define CONFIG_H_
#include "logger.h"
/**
* Value object holding dqlite configuration.
*/
struct config
{
dqlite_node_id id; /* Unique instance ID */
char *address; /* Instance address */
unsigned heartbeat_timeout; /* In milliseconds */
unsigned page_size; /* Database page size */
unsigned checkpoint_threshold; /* In outstanding WAL frames */
struct logger logger; /* Custom logger */
char name[256]; /* VFS/replication registriatio name */
unsigned long long failure_domain; /* User-provided failure domain */
unsigned long long int weight; /* User-provided node weight */
char dir[1024]; /* Data dir for on-disk database */
bool disk; /* Disk-mode or not */
int voters; /* Target number of voters */
int standbys; /* Target number of standbys */
};
/**
* Initialize the config object with required values and set the rest to sane
* defaults. A copy will be made of the given @address.
*/
int config__init(struct config *c,
dqlite_node_id id,
const char *address,
const char *dir);
/**
* Release any memory held by the config object.
*/
void config__close(struct config *c);
#endif /* DQLITE_OPTIONS_H */
dqlite-1.16.0/src/conn.c 0000664 0000000 0000000 00000017016 14512203222 0014747 0 ustar 00root root 0000000 0000000 #include "conn.h"
#include "message.h"
#include "protocol.h"
#include "request.h"
#include "tracing.h"
#include "transport.h"
/* Initialize the given buffer for reading, ensure it has the given size. */
static int init_read(struct conn *c, uv_buf_t *buf, size_t size)
{
buffer__reset(&c->read);
buf->base = buffer__advance(&c->read, size);
if (buf->base == NULL) {
return DQLITE_NOMEM;
}
buf->len = size;
return 0;
}
static int read_message(struct conn *c);
static void conn_write_cb(struct transport *transport, int status)
{
struct conn *c = transport->data;
bool finished;
int rv;
if (status != 0) {
tracef("write cb status %d", status);
goto abort;
}
buffer__reset(&c->write);
buffer__advance(&c->write, message__sizeof(&c->response)); /* Header */
rv = gateway__resume(&c->gateway, &finished);
if (rv != 0) {
goto abort;
}
if (!finished) {
return;
}
/* Start reading the next request */
rv = read_message(c);
if (rv != 0) {
goto abort;
}
return;
abort:
conn__stop(c);
}
static void gateway_handle_cb(struct handle *req,
int status,
uint8_t type,
uint8_t schema)
{
struct conn *c = req->data;
size_t n;
void *cursor;
uv_buf_t buf;
int rv;
assert(schema <= req->schema);
/* Ignore results firing after we started closing. TODO: instead, we
* should make gateway__close() asynchronous. */
if (c->closed) {
tracef("gateway handle cb closed");
return;
}
if (status != 0) {
tracef("gateway handle cb status %d", status);
goto abort;
}
n = buffer__offset(&c->write) - message__sizeof(&c->response);
assert(n % 8 == 0);
c->response.type = type;
c->response.words = (uint32_t)(n / 8);
c->response.schema = schema;
c->response.extra = 0;
cursor = buffer__cursor(&c->write, 0);
message__encode(&c->response, &cursor);
buf.base = buffer__cursor(&c->write, 0);
buf.len = buffer__offset(&c->write);
rv = transport__write(&c->transport, &buf, conn_write_cb);
if (rv != 0) {
tracef("transport write failed %d", rv);
goto abort;
}
return;
abort:
conn__stop(c);
}
static void closeCb(struct transport *transport)
{
struct conn *c = transport->data;
buffer__close(&c->write);
buffer__close(&c->read);
if (c->close_cb != NULL) {
c->close_cb(c);
}
}
static void raft_connect(struct conn *c)
{
struct cursor *cursor = &c->handle.cursor;
struct request_connect request;
int rv;
tracef("raft_connect");
rv = request_connect__decode(cursor, &request);
if (rv != 0) {
tracef("request connect decode failed %d", rv);
conn__stop(c);
return;
}
raftProxyAccept(c->uv_transport, request.id, request.address,
c->transport.stream);
/* Close the connection without actually closing the transport, since
* the stream will be used by raft */
c->closed = true;
closeCb(&c->transport);
}
static void read_request_cb(struct transport *transport, int status)
{
struct conn *c = transport->data;
struct cursor *cursor = &c->handle.cursor;
int rv;
if (status != 0) {
tracef("read error %d", status);
// errorf(c->logger, "read error");
conn__stop(c);
return;
}
cursor->p = buffer__cursor(&c->read, 0);
cursor->cap = buffer__offset(&c->read);
buffer__reset(&c->write);
buffer__advance(&c->write, message__sizeof(&c->response)); /* Header */
switch (c->request.type) {
case DQLITE_REQUEST_CONNECT:
raft_connect(c);
return;
}
rv = gateway__handle(&c->gateway, &c->handle, c->request.type,
c->request.schema, &c->write, gateway_handle_cb);
if (rv != 0) {
tracef("read gateway handle error %d", rv);
conn__stop(c);
}
}
/* Start reading the body of the next request */
static int read_request(struct conn *c)
{
uv_buf_t buf;
int rv;
if (UINT64_C(8) * (uint64_t)c->request.words > (uint64_t)UINT32_MAX) {
return DQLITE_ERROR;
}
rv = init_read(c, &buf, c->request.words * 8);
if (rv != 0) {
tracef("init read failed %d", rv);
return rv;
}
if (c->request.words == 0) {
return 0;
}
rv = transport__read(&c->transport, &buf, read_request_cb);
if (rv != 0) {
tracef("transport read failed %d", rv);
return rv;
}
return 0;
}
static void read_message_cb(struct transport *transport, int status)
{
struct conn *c = transport->data;
struct cursor cursor;
int rv;
if (status != 0) {
// errorf(c->logger, "read error");
tracef("read error %d", status);
conn__stop(c);
return;
}
cursor.p = buffer__cursor(&c->read, 0);
cursor.cap = buffer__offset(&c->read);
rv = message__decode(&cursor, &c->request);
assert(rv == 0); /* Can't fail, we know we have enough bytes */
rv = read_request(c);
if (rv != 0) {
tracef("read request error %d", rv);
conn__stop(c);
return;
}
}
/* Start reading metadata about the next message */
static int read_message(struct conn *c)
{
uv_buf_t buf;
int rv;
rv = init_read(c, &buf, message__sizeof(&c->request));
if (rv != 0) {
tracef("init read failed %d", rv);
return rv;
}
rv = transport__read(&c->transport, &buf, read_message_cb);
if (rv != 0) {
tracef("transport read failed %d", rv);
return rv;
}
return 0;
}
static void read_protocol_cb(struct transport *transport, int status)
{
struct conn *c = transport->data;
struct cursor cursor;
int rv;
if (status != 0) {
// errorf(c->logger, "read error");
tracef("read error %d", status);
goto abort;
}
cursor.p = buffer__cursor(&c->read, 0);
cursor.cap = buffer__offset(&c->read);
rv = uint64__decode(&cursor, &c->protocol);
assert(rv == 0); /* Can't fail, we know we have enough bytes */
if (c->protocol != DQLITE_PROTOCOL_VERSION &&
c->protocol != DQLITE_PROTOCOL_VERSION_LEGACY) {
/* errorf(c->logger, "unknown protocol version: %lx", */
/* c->protocol); */
/* TODO: instead of closing the connection we should return
* error messages */
tracef("unknown protocol version %" PRIu64, c->protocol);
goto abort;
}
c->gateway.protocol = c->protocol;
rv = read_message(c);
if (rv != 0) {
goto abort;
}
return;
abort:
conn__stop(c);
}
/* Start reading the protocol format version */
static int read_protocol(struct conn *c)
{
uv_buf_t buf;
int rv;
rv = init_read(c, &buf, sizeof c->protocol);
if (rv != 0) {
tracef("init read failed %d", rv);
return rv;
}
rv = transport__read(&c->transport, &buf, read_protocol_cb);
if (rv != 0) {
tracef("transport read failed %d", rv);
return rv;
}
return 0;
}
int conn__start(struct conn *c,
struct config *config,
struct uv_loop_s *loop,
struct registry *registry,
struct raft *raft,
struct uv_stream_s *stream,
struct raft_uv_transport *uv_transport,
struct id_state seed,
conn_close_cb close_cb)
{
int rv;
(void)loop;
tracef("conn start");
rv = transport__init(&c->transport, stream);
if (rv != 0) {
tracef("conn start - transport init failed %d", rv);
goto err;
}
c->config = config;
c->transport.data = c;
c->uv_transport = uv_transport;
c->close_cb = close_cb;
gateway__init(&c->gateway, config, registry, raft, seed);
rv = buffer__init(&c->read);
if (rv != 0) {
goto err_after_transport_init;
}
rv = buffer__init(&c->write);
if (rv != 0) {
goto err_after_read_buffer_init;
}
c->handle.data = c;
c->closed = false;
/* First, we expect the client to send us the protocol version. */
rv = read_protocol(c);
if (rv != 0) {
goto err_after_write_buffer_init;
}
return 0;
err_after_write_buffer_init:
buffer__close(&c->write);
err_after_read_buffer_init:
buffer__close(&c->read);
err_after_transport_init:
transport__close(&c->transport, NULL);
err:
return rv;
}
void conn__stop(struct conn *c)
{
tracef("conn stop");
if (c->closed) {
return;
}
c->closed = true;
gateway__close(&c->gateway);
transport__close(&c->transport, closeCb);
}
dqlite-1.16.0/src/conn.h 0000664 0000000 0000000 00000003222 14512203222 0014746 0 ustar 00root root 0000000 0000000 /**
* Handle a single client connection.
*/
#ifndef DQLITE_CONN_H_
#define DQLITE_CONN_H_
#include
#include "lib/buffer.h"
#include "lib/queue.h"
#include "lib/transport.h"
#include "gateway.h"
#include "id.h"
#include "message.h"
/**
* Callbacks.
*/
struct conn;
typedef void (*conn_close_cb)(struct conn *c);
struct conn
{
struct config *config;
struct raft_uv_transport *uv_transport; /* Raft transport */
conn_close_cb close_cb; /* Close callback */
struct transport transport; /* Async network read/write */
struct gateway gateway; /* Request handler */
struct buffer read; /* Read buffer */
struct buffer write; /* Write buffer */
uint64_t protocol; /* Protocol format version */
struct message request; /* Request message meta data */
struct message response; /* Response message meta data */
struct handle handle;
bool closed;
queue queue;
};
/**
* Initialize and start a connection.
*
* If no error is returned, the connection should be considered started. Any
* error occurring after this point will trigger the @close_cb callback.
*/
int conn__start(struct conn *c,
struct config *config,
struct uv_loop_s *loop,
struct registry *registry,
struct raft *raft,
struct uv_stream_s *stream,
struct raft_uv_transport *uv_transport,
struct id_state seed,
conn_close_cb close_cb);
/**
* Force closing the connection. The close callback will be invoked when it's
* safe to release the memory of the connection object.
*/
void conn__stop(struct conn *c);
#endif /* DQLITE_CONN_H_ */
dqlite-1.16.0/src/db.c 0000664 0000000 0000000 00000006725 14512203222 0014404 0 ustar 00root root 0000000 0000000 #include
#include "../include/dqlite.h"
#include "./lib/assert.h"
#include "db.h"
#include "tracing.h"
/* Limit taken from sqlite unix vfs. */
#define MAX_PATHNAME 512
/* Open a SQLite connection and set it to follower mode. */
static int open_follower_conn(const char *filename,
const char *vfs,
unsigned page_size,
sqlite3 **conn);
int db__init(struct db *db, struct config *config, const char *filename)
{
tracef("db init %s", filename);
int rv;
db->config = config;
db->filename = sqlite3_malloc((int)(strlen(filename) + 1));
if (db->filename == NULL) {
rv = DQLITE_NOMEM;
goto err;
}
strcpy(db->filename, filename);
db->path = sqlite3_malloc(MAX_PATHNAME + 1);
if (db->path == NULL) {
rv = DQLITE_NOMEM;
goto err_after_filename_alloc;
}
if (db->config->disk) {
rv = snprintf(db->path, MAX_PATHNAME + 1, "%s/%s",
db->config->dir, db->filename);
} else {
rv = snprintf(db->path, MAX_PATHNAME + 1, "%s", db->filename);
}
if (rv < 0 || rv >= MAX_PATHNAME + 1) {
goto err_after_path_alloc;
}
db->follower = NULL;
db->tx_id = 0;
db->read_lock = 0;
QUEUE__INIT(&db->leaders);
return 0;
err_after_path_alloc:
sqlite3_free(db->path);
err_after_filename_alloc:
sqlite3_free(db->filename);
err:
return rv;
}
void db__close(struct db *db)
{
assert(QUEUE__IS_EMPTY(&db->leaders));
if (db->follower != NULL) {
int rc;
rc = sqlite3_close(db->follower);
assert(rc == SQLITE_OK);
}
sqlite3_free(db->path);
sqlite3_free(db->filename);
}
int db__open_follower(struct db *db)
{
int rc;
assert(db->follower == NULL);
rc = open_follower_conn(db->path, db->config->name,
db->config->page_size, &db->follower);
return rc;
}
static int open_follower_conn(const char *filename,
const char *vfs,
unsigned page_size,
sqlite3 **conn)
{
char pragma[255];
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
char *msg = NULL;
int rc;
tracef("open follower conn: %s page_size:%u", filename, page_size);
rc = sqlite3_open_v2(filename, conn, flags, vfs);
if (rc != SQLITE_OK) {
tracef("open_v2 failed %d", rc);
goto err;
}
/* Enable extended result codes */
rc = sqlite3_extended_result_codes(*conn, 1);
if (rc != SQLITE_OK) {
goto err;
}
/* The vfs, db, gateway, and leader code currently assumes that
* each connection will operate on only one DB file/WAL file
* pair. Make sure that the client can't use ATTACH DATABASE to
* break this assumption. We apply the same limit in openConnection
* in leader.c.
*
* Note, 0 instead of 1 -- apparently the "initial database" is not
* counted when evaluating this limit. */
sqlite3_limit(*conn, SQLITE_LIMIT_ATTACHED, 0);
/* Set the page size. */
sprintf(pragma, "PRAGMA page_size=%d", page_size);
rc = sqlite3_exec(*conn, pragma, NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("page_size=%d failed", page_size);
goto err;
}
/* Disable syncs. */
rc = sqlite3_exec(*conn, "PRAGMA synchronous=OFF", NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("synchronous=OFF failed");
goto err;
}
/* Set WAL journaling. */
rc = sqlite3_exec(*conn, "PRAGMA journal_mode=WAL", NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("journal_mode=WAL failed");
goto err;
}
rc =
sqlite3_db_config(*conn, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, NULL);
if (rc != SQLITE_OK) {
goto err;
}
return 0;
err:
if (*conn != NULL) {
sqlite3_close(*conn);
*conn = NULL;
}
if (msg != NULL) {
sqlite3_free(msg);
}
return rc;
}
dqlite-1.16.0/src/db.h 0000664 0000000 0000000 00000002045 14512203222 0014400 0 ustar 00root root 0000000 0000000 /**
* State of a single database.
*/
#ifndef DB_H_
#define DB_H_
#include "lib/queue.h"
#include "config.h"
struct db
{
struct config *config; /* Dqlite configuration */
char *filename; /* Database filename */
char *path; /* Used for on-disk db */
sqlite3 *follower; /* Follower connection */
queue leaders; /* Open leader connections */
unsigned tx_id; /* Current ongoing transaction ID, if any */
queue queue; /* Prev/next database, used by the registry */
int read_lock; /* Lock used by snapshots & checkpoints */
};
/**
* Initialize a database object.
*
* The given @filename will be copied.
* Return 0 on success.
*/
int db__init(struct db *db, struct config *config, const char *filename);
/**
* Release all memory associated with a database object.
*
* If the follower connection was opened, it will be closed.
*/
void db__close(struct db *db);
/**
* Open the follower connection associated with this database.
*/
int db__open_follower(struct db *db);
#endif /* DB_H_*/
dqlite-1.16.0/src/dqlite.c 0000664 0000000 0000000 00000003705 14512203222 0015274 0 ustar 00root root 0000000 0000000 #include "../include/dqlite.h"
#include "vfs.h"
int dqlite_version_number(void)
{
return DQLITE_VERSION_NUMBER;
}
int dqlite_vfs_init(sqlite3_vfs *vfs, const char *name)
{
return VfsInit(vfs, name);
}
int dqlite_vfs_enable_disk(sqlite3_vfs *vfs)
{
return VfsEnableDisk(vfs);
}
void dqlite_vfs_close(sqlite3_vfs *vfs)
{
VfsClose(vfs);
}
int dqlite_vfs_poll(sqlite3_vfs *vfs,
const char *filename,
dqlite_vfs_frame **frames,
unsigned *n)
{
return VfsPoll(vfs, filename, frames, n);
}
int dqlite_vfs_apply(sqlite3_vfs *vfs,
const char *filename,
unsigned n,
unsigned long *page_numbers,
void *frames)
{
return VfsApply(vfs, filename, n, page_numbers, frames);
}
int dqlite_vfs_abort(sqlite3_vfs *vfs, const char *filename)
{
return VfsAbort(vfs, filename);
}
int dqlite_vfs_snapshot(sqlite3_vfs *vfs,
const char *filename,
void **data,
size_t *n)
{
return VfsSnapshot(vfs, filename, data, n);
}
int dqlite_vfs_snapshot_disk(sqlite3_vfs *vfs,
const char *filename,
struct dqlite_buffer bufs[],
unsigned n)
{
int rv;
if (n != 2) {
return -1;
}
rv = VfsDiskSnapshotDb(vfs, filename, &bufs[0]);
if (rv != 0) {
return rv;
}
rv = VfsDiskSnapshotWal(vfs, filename, &bufs[1]);
return rv;
}
int dqlite_vfs_num_pages(sqlite3_vfs *vfs, const char *filename, unsigned *n)
{
return VfsDatabaseNumPages(vfs, filename, n);
}
int dqlite_vfs_shallow_snapshot(sqlite3_vfs *vfs,
const char *filename,
struct dqlite_buffer bufs[],
unsigned n)
{
return VfsShallowSnapshot(vfs, filename, bufs, n);
}
int dqlite_vfs_restore(sqlite3_vfs *vfs,
const char *filename,
const void *data,
size_t n)
{
return VfsRestore(vfs, filename, data, n);
}
int dqlite_vfs_restore_disk(sqlite3_vfs *vfs,
const char *filename,
const void *data,
size_t main_size,
size_t wal_size)
{
return VfsDiskRestore(vfs, filename, data, main_size, wal_size);
}
dqlite-1.16.0/src/error.c 0000664 0000000 0000000 00000006570 14512203222 0015146 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include
#include "../include/dqlite.h"
#include "./lib/assert.h"
#include "error.h"
/* Fallback message returned when failing to allocate the error message
* itself. */
static char *dqlite__error_oom_msg =
"error message unavailable (out of memory)";
void dqlite__error_init(dqlite__error *e)
{
*e = NULL;
}
void dqlite__error_close(dqlite__error *e)
{
if (*e != NULL && *e != dqlite__error_oom_msg) {
sqlite3_free(*e);
}
}
/* Set an error message by rendering the given format against the given
* parameters.
*
* Any previously set error message will be cleared. */
static void dqlite__error_vprintf(dqlite__error *e,
const char *fmt,
va_list args)
{
assert(fmt != NULL);
/* If a previous error was set (other than the hard-coded OOM fallback
* fallback), let's free it. */
if (*e != NULL && *e != dqlite__error_oom_msg) {
sqlite3_free(*e);
}
/* Render the message. In case of error we fallback to the hard-coded
* OOM fallback message. */
*e = sqlite3_vmprintf(fmt, args);
if (*e == NULL) {
*e = dqlite__error_oom_msg;
}
}
void dqlite__error_printf(dqlite__error *e, const char *fmt, ...)
{
va_list args;
va_start(args, fmt);
dqlite__error_vprintf(e, fmt, args);
va_end(args);
}
static void dqlite__error_vwrapf(dqlite__error *e,
const char *cause,
const char *fmt,
va_list args)
{
dqlite__error tmp;
char *msg;
/* First, print the format and arguments, using a temporary error. */
dqlite__error_init(&tmp);
dqlite__error_vprintf(&tmp, fmt, args);
if (cause == NULL) {
/* Special case the cause error being empty. */
dqlite__error_printf(e, "%s: (null)", tmp);
} else if (cause == *e) {
/* When the error is wrapping itself, we need to make a copy */
dqlite__error_copy(e, &msg);
dqlite__error_printf(e, "%s: %s", tmp, msg);
sqlite3_free(msg);
} else {
dqlite__error_printf(e, "%s: %s", tmp, cause);
}
dqlite__error_close(&tmp);
}
void dqlite__error_wrapf(dqlite__error *e,
const dqlite__error *cause,
const char *fmt,
...)
{
va_list args;
va_start(args, fmt);
dqlite__error_vwrapf(e, (const char *)(*cause), fmt, args);
va_end(args);
}
void dqlite__error_oom(dqlite__error *e, const char *msg, ...)
{
va_list args;
va_start(args, msg);
dqlite__error_vwrapf(e, "out of memory", msg, args);
va_end(args);
}
void dqlite__error_sys(dqlite__error *e, const char *msg)
{
dqlite__error_printf(e, "%s: %s", msg, strerror(errno));
}
void dqlite__error_uv(dqlite__error *e, int err, const char *msg)
{
dqlite__error_printf(e, "%s: %s (%s)", msg, uv_strerror(err),
uv_err_name(err));
}
int dqlite__error_copy(dqlite__error *e, char **msg)
{
char *copy;
size_t len;
assert(e != NULL);
assert(msg != NULL);
/* Trying to copy an empty error message is an error. */
if (*e == NULL) {
*msg = NULL;
return DQLITE_ERROR;
}
len = strlen(*e) + 1;
copy = sqlite3_malloc((int)(len * sizeof *copy));
if (copy == NULL) {
*msg = NULL;
return DQLITE_NOMEM;
}
memcpy(copy, *e, len);
*msg = copy;
return 0;
}
int dqlite__error_is_null(dqlite__error *e)
{
return *e == NULL;
}
int dqlite__error_is_disconnect(dqlite__error *e)
{
if (*e == NULL)
return 0;
if (strstr(*e, uv_err_name(UV_EOF)) != NULL)
return 1;
if (strstr(*e, uv_err_name(UV_ECONNRESET)) != NULL)
return 1;
return 0;
}
dqlite-1.16.0/src/error.h 0000664 0000000 0000000 00000002446 14512203222 0015151 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_ERROR_H
#define DQLITE_ERROR_H
#include
#include
/* A message describing the last error occurred on an object */
typedef char *dqlite__error;
/* Initialize the error with an empty message */
void dqlite__error_init(dqlite__error *e);
/* Release the memory of the error message, if any is set */
void dqlite__error_close(dqlite__error *e);
/* Set the error message */
void dqlite__error_printf(dqlite__error *e, const char *fmt, ...);
/* Wrap an error with an additional message */
void dqlite__error_wrapf(dqlite__error *e,
const dqlite__error *cause,
const char *fmt,
...);
/* Out of memory error */
void dqlite__error_oom(dqlite__error *e, const char *msg, ...);
/* Wrap a system error */
void dqlite__error_sys(dqlite__error *e, const char *msg);
/* Wrap an error from libuv */
void dqlite__error_uv(dqlite__error *e, int err, const char *msg);
/* Copy the underlying error message.
*
* Client code is responsible of invoking sqlite3_free to deallocate the
* returned string.
*/
int dqlite__error_copy(dqlite__error *e, char **msg);
/* Whether the error is not set */
int dqlite__error_is_null(dqlite__error *e);
/* Whether the error is due to client disconnection */
int dqlite__error_is_disconnect(dqlite__error *e);
#endif /* DQLITE_ERROR_H */
dqlite-1.16.0/src/format.c 0000664 0000000 0000000 00000006753 14512203222 0015310 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include "./lib/assert.h"
#include "format.h"
/* tinycc doesn't have this builtin, nor the warning that it's meant to silence.
*/
#ifdef __TINYC__
#define __builtin_assume_aligned(x, y) x
#endif
/* WAL magic value. Either this value, or the same value with the least
* significant bit also set (FORMAT__WAL_MAGIC | 0x00000001) is stored in 32-bit
* big-endian format in the first 4 bytes of a WAL file.
*
* If the LSB is set, then the checksums for each frame within the WAL file are
* calculated by treating all data as an array of 32-bit big-endian
* words. Otherwise, they are calculated by interpreting all data as 32-bit
* little-endian words. */
#define FORMAT__WAL_MAGIC 0x377f0682
#define FORMAT__WAL_MAX_VERSION 3007000
static void formatGet32(const uint8_t buf[4], uint32_t *v)
{
*v = 0;
*v += (uint32_t)(buf[0] << 24);
*v += (uint32_t)(buf[1] << 16);
*v += (uint32_t)(buf[2] << 8);
*v += (uint32_t)(buf[3]);
}
/* Encode a 32-bit number to big endian format */
static void formatPut32(uint32_t v, uint8_t *buf)
{
buf[0] = (uint8_t)(v >> 24);
buf[1] = (uint8_t)(v >> 16);
buf[2] = (uint8_t)(v >> 8);
buf[3] = (uint8_t)v;
}
/*
* Generate or extend an 8 byte checksum based on the data in array data[] and
* the initial values of in[0] and in[1] (or initial values of 0 and 0 if
* in==NULL).
*
* The checksum is written back into out[] before returning.
*
* n must be a positive multiple of 8. */
static void formatWalChecksumBytes(
bool native, /* True for native byte-order, false for non-native */
uint8_t *data, /* Content to be checksummed */
unsigned n, /* Bytes of content in a[]. Must be a multiple of 8. */
const uint32_t in[2], /* Initial checksum value input */
uint32_t out[2] /* OUT: Final checksum value output */
)
{
uint32_t s1, s2;
/* `data` is an alias for the `hdr` member of a `struct vfsWal`. `hdr`
* is the first member of this struct. Because `struct vfsWal` contains
* pointer members, the struct itself will have the alignment of the
* pointer members. As `hdr` is the first member, it will have this
* alignment too. Therefore it is safe to assume pointer alignment (and
* silence the compiler). more info ->
* http://www.catb.org/esr/structure-packing/ */
uint32_t *cur =
(uint32_t *)__builtin_assume_aligned(data, sizeof(void *));
uint32_t *end =
(uint32_t *)__builtin_assume_aligned(&data[n], sizeof(void *));
if (in) {
s1 = in[0];
s2 = in[1];
} else {
s1 = s2 = 0;
}
assert(n >= 8);
assert((n & 0x00000007) == 0);
assert(n <= 65536);
if (native) {
do {
s1 += *cur++ + s2;
s2 += *cur++ + s1;
} while (cur < end);
} else {
do {
uint32_t d;
formatPut32(cur[0], (uint8_t *)&d);
s1 += d + s2;
formatPut32(cur[1], (uint8_t *)&d);
s2 += d + s1;
cur += 2;
} while (cur < end);
}
out[0] = s1;
out[1] = s2;
}
void formatWalRestartHeader(uint8_t *header)
{
uint32_t checksum[2] = {0, 0};
uint32_t checkpoint;
uint32_t salt1;
/* Increase the checkpoint sequence. */
formatGet32(&header[12], &checkpoint);
checkpoint++;
formatPut32(checkpoint, &header[12]);
/* Increase salt1. */
formatGet32(&header[16], &salt1);
salt1++;
formatPut32(salt1, &header[16]);
/* Generate a random salt2. */
sqlite3_randomness(4, &header[20]);
/* Update the checksum. */
formatWalChecksumBytes(true, header, 24, checksum, checksum);
formatPut32(checksum[0], header + 24);
formatPut32(checksum[1], header + 28);
}
dqlite-1.16.0/src/format.h 0000664 0000000 0000000 00000002436 14512203222 0015307 0 ustar 00root root 0000000 0000000 /* Utilities around SQLite file formats.
*
* See https://sqlite.org/fileformat.html. */
#ifndef FORMAT_H_
#define FORMAT_H_
#include
#include
/* Minumum and maximum page size. */
#define FORMAT__PAGE_SIZE_MIN 512
#define FORMAT__PAGE_SIZE_MAX 65536
/* Database header size. */
#define FORMAT__DB_HDR_SIZE 100
/* Write ahead log header size. */
#define FORMAT__WAL_HDR_SIZE 32
/* Write ahead log frame header size. */
#define FORMAT__WAL_FRAME_HDR_SIZE 24
/* Number of reader marks in the wal index header. */
#define FORMAT__WAL_NREADER 5
/* Given the page size, calculate the size of a full WAL frame (frame header
* plus page data). */
#define formatWalCalcFrameSize(PAGE_SIZE) \
(FORMAT__WAL_FRAME_HDR_SIZE + PAGE_SIZE)
/* Given the page size and the WAL file size, calculate the number of frames it
* has. */
#define formatWalCalcFramesNumber(PAGE_SIZE, SIZE) \
((SIZE - FORMAT__WAL_HDR_SIZE) / formatWalCalcFrameSize(PAGE_SIZE))
/* Given the page size, calculate the WAL page number of the frame starting at
* the given offset. */
#define formatWalCalcFrameIndex(PAGE_SIZE, OFFSET) \
(formatWalCalcFramesNumber(PAGE_SIZE, OFFSET) + 1)
/* Restart the header of a WAL file after a checkpoint. */
void formatWalRestartHeader(uint8_t *header);
#endif /* FORMAT_H */
dqlite-1.16.0/src/fsm.c 0000664 0000000 0000000 00000063700 14512203222 0014600 0 ustar 00root root 0000000 0000000 #include
#include "lib/assert.h"
#include "lib/serialize.h"
#include "command.h"
#include "fsm.h"
#include "tracing.h"
#include "vfs.h"
#include
struct fsm
{
struct logger *logger;
struct registry *registry;
struct
{
unsigned n_pages;
unsigned long *page_numbers;
uint8_t *pages;
} pending; /* For upgrades from V1 */
};
static int apply_open(struct fsm *f, const struct command_open *c)
{
tracef("fsm apply open");
(void)f;
(void)c;
return 0;
}
static int add_pending_pages(struct fsm *f,
unsigned long *page_numbers,
uint8_t *pages,
unsigned n_pages,
unsigned page_size)
{
unsigned n = f->pending.n_pages + n_pages;
unsigned i;
f->pending.page_numbers = sqlite3_realloc64(
f->pending.page_numbers, n * sizeof *f->pending.page_numbers);
if (f->pending.page_numbers == NULL) {
return DQLITE_NOMEM;
}
f->pending.pages = sqlite3_realloc64(f->pending.pages, n * page_size);
if (f->pending.pages == NULL) {
return DQLITE_NOMEM;
}
for (i = 0; i < n_pages; i++) {
unsigned j = f->pending.n_pages + i;
f->pending.page_numbers[j] = page_numbers[i];
memcpy(f->pending.pages + j * page_size,
(uint8_t *)pages + i * page_size, page_size);
}
f->pending.n_pages = n;
return 0;
}
static int databaseReadLock(struct db *db)
{
if (!db->read_lock) {
db->read_lock = 1;
return 0;
} else {
return -1;
}
}
static int databaseReadUnlock(struct db *db)
{
if (db->read_lock) {
db->read_lock = 0;
return 0;
} else {
return -1;
}
}
static void maybeCheckpoint(struct db *db)
{
tracef("maybe checkpoint");
struct sqlite3_file *main_f;
struct sqlite3_file *wal;
volatile void *region;
sqlite3_int64 size;
unsigned page_size;
unsigned pages;
int wal_size;
int ckpt;
int i;
int rv;
/* Don't run when a snapshot is busy. Running a checkpoint while a
* snapshot is busy will result in illegal memory accesses by the
* routines that try to access database page pointers contained in the
* snapshot. */
rv = databaseReadLock(db);
if (rv != 0) {
tracef("busy snapshot %d", rv);
return;
}
assert(db->follower == NULL);
rv = db__open_follower(db);
if (rv != 0) {
tracef("open follower failed %d", rv);
goto err_after_db_lock;
}
page_size = db->config->page_size;
/* Get the database wal file associated with this connection */
rv = sqlite3_file_control(db->follower, "main",
SQLITE_FCNTL_JOURNAL_POINTER, &wal);
assert(rv == SQLITE_OK); /* Should never fail */
rv = wal->pMethods->xFileSize(wal, &size);
assert(rv == SQLITE_OK); /* Should never fail */
/* Calculate the number of frames. */
pages = (unsigned)((size - 32) / (24 + page_size));
/* Check if the size of the WAL is beyond the threshold. */
if (pages < db->config->checkpoint_threshold) {
tracef("wal size (%u) < threshold (%u)", pages,
db->config->checkpoint_threshold);
goto err_after_db_open;
}
/* Get the database file associated with this db->follower connection */
rv = sqlite3_file_control(db->follower, "main",
SQLITE_FCNTL_FILE_POINTER, &main_f);
assert(rv == SQLITE_OK); /* Should never fail */
/* Get the first SHM region, which contains the WAL header. */
rv = main_f->pMethods->xShmMap(main_f, 0, 0, 0, ®ion);
assert(rv == SQLITE_OK); /* Should never fail */
rv = main_f->pMethods->xShmUnmap(main_f, 0);
assert(rv == SQLITE_OK); /* Should never fail */
/* Try to acquire all locks. */
for (i = 0; i < SQLITE_SHM_NLOCK; i++) {
int flags = SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE;
rv = main_f->pMethods->xShmLock(main_f, i, 1, flags);
if (rv == SQLITE_BUSY) {
tracef("busy reader or writer - retry next time");
goto err_after_db_open;
}
/* Not locked. Let's release the lock we just
* acquired. */
flags = SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE;
main_f->pMethods->xShmLock(main_f, i, 1, flags);
}
rv = sqlite3_wal_checkpoint_v2(
db->follower, "main", SQLITE_CHECKPOINT_TRUNCATE, &wal_size, &ckpt);
/* TODO assert(rv == 0) here? Which failure modes do we expect? */
if (rv != 0) {
tracef("sqlite3_wal_checkpoint_v2 failed %d", rv);
goto err_after_db_open;
}
tracef("sqlite3_wal_checkpoint_v2 success");
/* Since no reader transaction is in progress, we must be able to
* checkpoint the entire WAL */
assert(wal_size == 0);
assert(ckpt == 0);
err_after_db_open:
sqlite3_close(db->follower);
db->follower = NULL;
err_after_db_lock:
rv = databaseReadUnlock(db);
assert(rv == 0);
}
static int apply_frames(struct fsm *f, const struct command_frames *c)
{
tracef("fsm apply frames");
struct db *db;
sqlite3_vfs *vfs;
unsigned long *page_numbers = NULL;
void *pages;
int exists;
int rv;
rv = registry__db_get(f->registry, c->filename, &db);
if (rv != 0) {
tracef("db get failed %d", rv);
return rv;
}
vfs = sqlite3_vfs_find(db->config->name);
/* Check if the database file exists, and create it by opening a
* connection if it doesn't. */
rv = vfs->xAccess(vfs, db->path, 0, &exists);
assert(rv == 0);
if (!exists) {
rv = db__open_follower(db);
if (rv != 0) {
tracef("open follower failed %d", rv);
return rv;
}
sqlite3_close(db->follower);
db->follower = NULL;
}
rv = command_frames__page_numbers(c, &page_numbers);
if (rv != 0) {
if (page_numbers != NULL) {
sqlite3_free(page_numbers);
}
tracef("page numbers failed %d", rv);
return rv;
}
command_frames__pages(c, &pages);
/* If the commit marker is set, we apply the changes directly to the
* VFS. Otherwise, if the commit marker is not set, this must be an
* upgrade from V1, we accumulate uncommitted frames in memory until the
* final commit or a rollback. */
if (c->is_commit) {
if (f->pending.n_pages > 0) {
rv = add_pending_pages(f, page_numbers, pages,
c->frames.n_pages,
db->config->page_size);
if (rv != 0) {
tracef("malloc");
sqlite3_free(page_numbers);
return DQLITE_NOMEM;
}
rv =
VfsApply(vfs, db->path, f->pending.n_pages,
f->pending.page_numbers, f->pending.pages);
if (rv != 0) {
tracef("VfsApply failed %d", rv);
sqlite3_free(page_numbers);
return rv;
}
sqlite3_free(f->pending.page_numbers);
sqlite3_free(f->pending.pages);
f->pending.n_pages = 0;
f->pending.page_numbers = NULL;
f->pending.pages = NULL;
} else {
rv = VfsApply(vfs, db->path, c->frames.n_pages,
page_numbers, pages);
if (rv != 0) {
tracef("VfsApply failed %d", rv);
sqlite3_free(page_numbers);
return rv;
}
}
} else {
rv =
add_pending_pages(f, page_numbers, pages, c->frames.n_pages,
db->config->page_size);
if (rv != 0) {
tracef("add pending pages failed %d", rv);
sqlite3_free(page_numbers);
return DQLITE_NOMEM;
}
}
sqlite3_free(page_numbers);
maybeCheckpoint(db);
return 0;
}
static int apply_undo(struct fsm *f, const struct command_undo *c)
{
tracef("apply undo %" PRIu64, c->tx_id);
(void)c;
if (f->pending.n_pages == 0) {
return 0;
}
sqlite3_free(f->pending.page_numbers);
sqlite3_free(f->pending.pages);
f->pending.n_pages = 0;
f->pending.page_numbers = NULL;
f->pending.pages = NULL;
return 0;
}
/* Checkpoints used to be coordinated cluster-wide, these days a node
* checkpoints independently in `apply_frames`, the checkpoint command becomes a
* no-op for modern nodes. */
static int apply_checkpoint(struct fsm *f, const struct command_checkpoint *c)
{
(void)f;
(void)c;
tracef("apply no-op checkpoint");
return 0;
}
static int fsm__apply(struct raft_fsm *fsm,
const struct raft_buffer *buf,
void **result)
{
tracef("fsm apply");
struct fsm *f = fsm->data;
int type;
void *command;
int rc;
rc = command__decode(buf, &type, &command);
if (rc != 0) {
tracef("fsm: decode command: %d", rc);
goto err;
}
switch (type) {
case COMMAND_OPEN:
rc = apply_open(f, command);
break;
case COMMAND_FRAMES:
rc = apply_frames(f, command);
break;
case COMMAND_UNDO:
rc = apply_undo(f, command);
break;
case COMMAND_CHECKPOINT:
rc = apply_checkpoint(f, command);
break;
default:
rc = RAFT_MALFORMED;
break;
}
raft_free(command);
err:
*result = NULL;
return rc;
}
#define SNAPSHOT_FORMAT 1
#define SNAPSHOT_HEADER(X, ...) \
X(uint64, format, ##__VA_ARGS__) \
X(uint64, n, ##__VA_ARGS__)
SERIALIZE__DEFINE(snapshotHeader, SNAPSHOT_HEADER);
SERIALIZE__IMPLEMENT(snapshotHeader, SNAPSHOT_HEADER);
#define SNAPSHOT_DATABASE(X, ...) \
X(text, filename, ##__VA_ARGS__) \
X(uint64, main_size, ##__VA_ARGS__) \
X(uint64, wal_size, ##__VA_ARGS__)
SERIALIZE__DEFINE(snapshotDatabase, SNAPSHOT_DATABASE);
SERIALIZE__IMPLEMENT(snapshotDatabase, SNAPSHOT_DATABASE);
/* Encode the global snapshot header. */
static int encodeSnapshotHeader(unsigned n, struct raft_buffer *buf)
{
struct snapshotHeader header;
void *cursor;
header.format = SNAPSHOT_FORMAT;
header.n = n;
buf->len = snapshotHeader__sizeof(&header);
buf->base = sqlite3_malloc64(buf->len);
if (buf->base == NULL) {
return RAFT_NOMEM;
}
cursor = buf->base;
snapshotHeader__encode(&header, &cursor);
return 0;
}
/* Encode the given database. */
static int encodeDatabase(struct db *db,
struct raft_buffer r_bufs[],
uint32_t n)
{
struct snapshotDatabase header;
sqlite3_vfs *vfs;
uint32_t database_size = 0;
uint8_t *page;
void *cursor;
struct dqlite_buffer *bufs = (struct dqlite_buffer *)r_bufs;
int rv;
header.filename = db->filename;
vfs = sqlite3_vfs_find(db->config->name);
rv = VfsShallowSnapshot(vfs, db->filename, &bufs[1], n - 1);
if (rv != 0) {
goto err;
}
/* Extract the database size from the first page. */
page = bufs[1].base;
database_size += (uint32_t)(page[28] << 24);
database_size += (uint32_t)(page[29] << 16);
database_size += (uint32_t)(page[30] << 8);
database_size += (uint32_t)(page[31]);
header.main_size =
(uint64_t)database_size * (uint64_t)db->config->page_size;
header.wal_size = bufs[n - 1].len;
/* Database header. */
bufs[0].len = snapshotDatabase__sizeof(&header);
bufs[0].base = sqlite3_malloc64(bufs[0].len);
if (bufs[0].base == NULL) {
rv = RAFT_NOMEM;
goto err_after_snapshot;
}
cursor = bufs[0].base;
snapshotDatabase__encode(&header, &cursor);
return 0;
err_after_snapshot:
/* Free the wal buffer */
sqlite3_free(bufs[n - 1].base);
err:
assert(rv != 0);
return rv;
}
/* Decode the database contained in a snapshot. */
static int decodeDatabase(struct fsm *f, struct cursor *cursor)
{
struct snapshotDatabase header;
struct db *db;
sqlite3_vfs *vfs;
size_t n;
int exists;
int rv;
rv = snapshotDatabase__decode(cursor, &header);
if (rv != 0) {
return rv;
}
rv = registry__db_get(f->registry, header.filename, &db);
if (rv != 0) {
return rv;
}
vfs = sqlite3_vfs_find(db->config->name);
/* Check if the database file exists, and create it by opening a
* connection if it doesn't. */
rv = vfs->xAccess(vfs, header.filename, 0, &exists);
assert(rv == 0);
if (!exists) {
rv = db__open_follower(db);
if (rv != 0) {
return rv;
}
sqlite3_close(db->follower);
db->follower = NULL;
}
tracef("main_size:%" PRIu64 " wal_size:%" PRIu64, header.main_size,
header.wal_size);
if (header.main_size + header.wal_size > SIZE_MAX) {
tracef("main_size + wal_size would overflow max DB size");
return -1;
}
/* Due to the check above, this cast is safe. */
n = (size_t)(header.main_size + header.wal_size);
rv = VfsRestore(vfs, db->filename, cursor->p, n);
if (rv != 0) {
return rv;
}
cursor->p += n;
return 0;
}
static unsigned dbNumPages(struct db *db)
{
sqlite3_vfs *vfs;
int rv;
uint32_t n;
vfs = sqlite3_vfs_find(db->config->name);
rv = VfsDatabaseNumPages(vfs, db->filename, &n);
assert(rv == 0);
return n;
}
/* Determine the total number of raft buffers needed for a snapshot */
static unsigned snapshotNumBufs(struct fsm *f)
{
struct db *db;
queue *head;
unsigned n = 1; /* snapshot header */
QUEUE__FOREACH(head, &f->registry->dbs)
{
n += 2; /* database header & wal */
db = QUEUE__DATA(head, struct db, queue);
n += dbNumPages(db); /* 1 buffer per page (zero copy) */
}
return n;
}
/* An example array of snapshot buffers looks like this:
*
* bufs: SH DH1 P1 P2 P3 WAL1 DH2 P1 P2 WAL2
* index: 0 1 2 3 4 5 6 7 8 9
*
* SH: Snapshot Header
* DHx: Database Header
* Px: Database Page (not to be freed)
* WALx: a WAL
* */
static void freeSnapshotBufs(struct fsm *f,
struct raft_buffer bufs[],
unsigned n_bufs)
{
queue *head;
struct db *db;
unsigned i;
if (bufs == NULL || n_bufs == 0) {
return;
}
/* Free snapshot header */
sqlite3_free(bufs[0].base);
i = 1;
/* Free all database headers & WAL buffers */
QUEUE__FOREACH(head, &f->registry->dbs)
{
if (i == n_bufs) {
break;
}
db = QUEUE__DATA(head, struct db, queue);
/* i is the index of the database header */
sqlite3_free(bufs[i].base);
/* i is now the index of the next database header (if any) */
i += 1 /* db header */ + dbNumPages(db) + 1 /* WAL */;
/* free WAL buffer */
sqlite3_free(bufs[i - 1].base);
}
}
static int fsm__snapshot(struct raft_fsm *fsm,
struct raft_buffer *bufs[],
unsigned *n_bufs)
{
struct fsm *f = fsm->data;
queue *head;
struct db *db;
unsigned n_db = 0;
unsigned i;
int rv;
/* First count how many databases we have and check that no transaction
* nor checkpoint nor other snapshot is in progress. */
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
if (db->tx_id != 0 || db->read_lock) {
return RAFT_BUSY;
}
n_db++;
}
/* Lock all databases, preventing the checkpoint from running */
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
rv = databaseReadLock(db);
assert(rv == 0);
}
*n_bufs = snapshotNumBufs(f);
*bufs = sqlite3_malloc64(*n_bufs * sizeof **bufs);
if (*bufs == NULL) {
rv = RAFT_NOMEM;
goto err;
}
rv = encodeSnapshotHeader(n_db, &(*bufs)[0]);
if (rv != 0) {
goto err_after_bufs_alloc;
}
/* Encode individual databases. */
i = 1;
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
/* database_header + num_pages + wal */
unsigned n = 1 + dbNumPages(db) + 1;
rv = encodeDatabase(db, &(*bufs)[i], n);
if (rv != 0) {
goto err_after_encode_header;
}
i += n;
}
assert(i == *n_bufs);
return 0;
err_after_encode_header:
freeSnapshotBufs(f, *bufs, i);
err_after_bufs_alloc:
sqlite3_free(*bufs);
err:
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
databaseReadUnlock(db);
}
assert(rv != 0);
return rv;
}
static int fsm__snapshot_finalize(struct raft_fsm *fsm,
struct raft_buffer *bufs[],
unsigned *n_bufs)
{
struct fsm *f = fsm->data;
queue *head;
struct db *db;
unsigned n_db;
struct snapshotHeader header;
int rv;
if (bufs == NULL) {
return 0;
}
/* Decode the header to determine the number of databases. */
struct cursor cursor = {(*bufs)[0].base, (*bufs)[0].len};
rv = snapshotHeader__decode(&cursor, &header);
if (rv != 0) {
tracef("decode failed %d", rv);
return -1;
}
if (header.format != SNAPSHOT_FORMAT) {
tracef("bad format");
return -1;
}
/* Free allocated buffers */
freeSnapshotBufs(f, *bufs, *n_bufs);
sqlite3_free(*bufs);
*bufs = NULL;
*n_bufs = 0;
/* Unlock all databases that were locked for the snapshot, this is safe
* because DB's are only ever added at the back of the queue. */
n_db = 0;
QUEUE__FOREACH(head, &f->registry->dbs)
{
if (n_db == header.n) {
break;
}
db = QUEUE__DATA(head, struct db, queue);
rv = databaseReadUnlock(db);
assert(rv == 0);
n_db++;
}
return 0;
}
static int fsm__restore(struct raft_fsm *fsm, struct raft_buffer *buf)
{
tracef("fsm restore");
struct fsm *f = fsm->data;
struct cursor cursor = {buf->base, buf->len};
struct snapshotHeader header;
unsigned i;
int rv;
rv = snapshotHeader__decode(&cursor, &header);
if (rv != 0) {
tracef("decode failed %d", rv);
return rv;
}
if (header.format != SNAPSHOT_FORMAT) {
tracef("bad format");
return RAFT_MALFORMED;
}
for (i = 0; i < header.n; i++) {
rv = decodeDatabase(f, &cursor);
if (rv != 0) {
tracef("decode failed");
return rv;
}
}
/* Don't use sqlite3_free as this buffer is allocated by raft. */
raft_free(buf->base);
return 0;
}
int fsm__init(struct raft_fsm *fsm,
struct config *config,
struct registry *registry)
{
tracef("fsm init");
struct fsm *f = raft_malloc(sizeof *f);
if (f == NULL) {
return DQLITE_NOMEM;
}
f->logger = &config->logger;
f->registry = registry;
f->pending.n_pages = 0;
f->pending.page_numbers = NULL;
f->pending.pages = NULL;
fsm->version = 2;
fsm->data = f;
fsm->apply = fsm__apply;
fsm->snapshot = fsm__snapshot;
fsm->snapshot_finalize = fsm__snapshot_finalize;
fsm->restore = fsm__restore;
return 0;
}
void fsm__close(struct raft_fsm *fsm)
{
tracef("fsm close");
struct fsm *f = fsm->data;
raft_free(f);
}
/******************************************************************************
Disk-based FSM
*****************************************************************************/
/* The synchronous part of the database encoding */
static int encodeDiskDatabaseSync(struct db *db, struct raft_buffer *r_buf)
{
sqlite3_vfs *vfs;
struct dqlite_buffer *buf = (struct dqlite_buffer *)r_buf;
int rv;
vfs = sqlite3_vfs_find(db->config->name);
rv = VfsDiskSnapshotWal(vfs, db->path, buf);
if (rv != 0) {
goto err;
}
return 0;
err:
assert(rv != 0);
return rv;
}
/* The asynchronous part of the database encoding */
static int encodeDiskDatabaseAsync(struct db *db,
struct raft_buffer r_bufs[],
uint32_t n)
{
struct snapshotDatabase header;
sqlite3_vfs *vfs;
void *cursor;
struct dqlite_buffer *bufs = (struct dqlite_buffer *)r_bufs;
int rv;
assert(n == 3);
vfs = sqlite3_vfs_find(db->config->name);
rv = VfsDiskSnapshotDb(vfs, db->path, &bufs[1]);
if (rv != 0) {
goto err;
}
/* Database header. */
header.filename = db->filename;
header.main_size = bufs[1].len;
header.wal_size = bufs[2].len;
bufs[0].len = snapshotDatabase__sizeof(&header);
bufs[0].base = sqlite3_malloc64(bufs[0].len);
if (bufs[0].base == NULL) {
rv = RAFT_NOMEM;
goto err;
}
cursor = bufs[0].base;
snapshotDatabase__encode(&header, &cursor);
return 0;
/* Cleanup is performed by call to snapshot_finalize */
err:
assert(rv != 0);
return rv;
}
/* Determine the total number of raft buffers needed
* for a snapshot in disk-mode */
static unsigned snapshotNumBufsDisk(struct fsm *f)
{
queue *head;
unsigned n = 1; /* snapshot header */
QUEUE__FOREACH(head, &f->registry->dbs)
{
n += 3; /* database header, database file and wal */
}
return n;
}
/* An example array of snapshot buffers looks like this:
*
* bufs: SH DH1 DBMMAP1 WAL1 DH2 DMMAP2 WAL2
* index: 0 1 2 3 4 5 6
*
* SH: Snapshot Header
* DHx: Database Header
* DBMMAP: Pointer to mmap'ed database file
* WALx: a WAL
* */
static void freeSnapshotBufsDisk(struct fsm *f,
struct raft_buffer bufs[],
unsigned n_bufs)
{
queue *head;
unsigned i;
if (bufs == NULL || n_bufs == 0) {
return;
}
/* Free snapshot header */
sqlite3_free(bufs[0].base);
i = 1;
/* Free all database headers & WAL buffers. Unmap the DB file. */
QUEUE__FOREACH(head, &f->registry->dbs)
{
if (i == n_bufs) {
break;
}
/* i is the index of the database header */
sqlite3_free(bufs[i].base);
if (bufs[i + 1].base != NULL) {
munmap(bufs[i + 1].base, bufs[i + 1].len);
}
sqlite3_free(bufs[i + 2].base);
/* i is now the index of the next database header (if any) */
i += 3;
}
}
static int fsm__snapshot_disk(struct raft_fsm *fsm,
struct raft_buffer *bufs[],
unsigned *n_bufs)
{
struct fsm *f = fsm->data;
queue *head;
struct db *db = NULL;
unsigned n_db = 0;
unsigned i;
int rv;
/* First count how many databases we have and check that no transaction
* nor checkpoint nor other snapshot is in progress. */
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
if (db->tx_id != 0 || db->read_lock) {
return RAFT_BUSY;
}
n_db++;
}
/* Lock all databases, preventing the checkpoint from running. This
* ensures the database is not written while it is mmap'ed and copied by
* raft. */
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
rv = databaseReadLock(db);
assert(rv == 0);
}
*n_bufs = snapshotNumBufsDisk(f);
*bufs = sqlite3_malloc64(*n_bufs * sizeof **bufs);
if (*bufs == NULL) {
rv = RAFT_NOMEM;
goto err;
}
/* zero-init buffers, helps with cleanup */
for (unsigned j = 0; j < *n_bufs; j++) {
(*bufs)[j].base = NULL;
(*bufs)[j].len = 0;
}
rv = encodeSnapshotHeader(n_db, &(*bufs)[0]);
if (rv != 0) {
goto err_after_bufs_alloc;
}
/* Copy WAL of all databases. */
i = 1;
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
/* database_header + db + WAL */
unsigned n = 3;
/* pass pointer to buffer that will contain WAL. */
rv = encodeDiskDatabaseSync(db, &(*bufs)[i + n - 1]);
if (rv != 0) {
goto err_after_encode_sync;
}
i += n;
}
assert(i == *n_bufs);
return 0;
err_after_encode_sync:
freeSnapshotBufsDisk(f, *bufs, i);
err_after_bufs_alloc:
sqlite3_free(*bufs);
err:
QUEUE__FOREACH(head, &f->registry->dbs)
{
db = QUEUE__DATA(head, struct db, queue);
databaseReadUnlock(db);
}
assert(rv != 0);
return rv;
}
static int fsm__snapshot_async_disk(struct raft_fsm *fsm,
struct raft_buffer *bufs[],
unsigned *n_bufs)
{
struct fsm *f = fsm->data;
queue *head;
struct snapshotHeader header;
struct db *db = NULL;
unsigned i;
int rv;
/* Decode the header to determine the number of databases. */
struct cursor cursor = {(*bufs)[0].base, (*bufs)[0].len};
rv = snapshotHeader__decode(&cursor, &header);
if (rv != 0) {
tracef("decode failed %d", rv);
return -1;
}
if (header.format != SNAPSHOT_FORMAT) {
tracef("bad format");
return -1;
}
/* Encode individual databases. */
i = 1;
QUEUE__FOREACH(head, &f->registry->dbs)
{
if (i == *n_bufs) {
/* In case a db was added in meanwhile */
break;
}
db = QUEUE__DATA(head, struct db, queue);
/* database_header + database file + wal */
unsigned n = 3;
rv = encodeDiskDatabaseAsync(db, &(*bufs)[i], n);
if (rv != 0) {
goto err;
}
i += n;
}
return 0;
err:
assert(rv != 0);
return rv;
}
static int fsm__snapshot_finalize_disk(struct raft_fsm *fsm,
struct raft_buffer *bufs[],
unsigned *n_bufs)
{
struct fsm *f = fsm->data;
queue *head;
struct db *db;
unsigned n_db;
struct snapshotHeader header;
int rv;
if (bufs == NULL) {
return 0;
}
/* Decode the header to determine the number of databases. */
struct cursor cursor = {(*bufs)[0].base, (*bufs)[0].len};
rv = snapshotHeader__decode(&cursor, &header);
if (rv != 0) {
tracef("decode failed %d", rv);
return -1;
}
if (header.format != SNAPSHOT_FORMAT) {
tracef("bad format");
return -1;
}
/* Free allocated buffers */
freeSnapshotBufsDisk(f, *bufs, *n_bufs);
sqlite3_free(*bufs);
*bufs = NULL;
*n_bufs = 0;
/* Unlock all databases that were locked for the snapshot, this is safe
* because DB's are only ever added at the back of the queue. */
n_db = 0;
QUEUE__FOREACH(head, &f->registry->dbs)
{
if (n_db == header.n) {
break;
}
db = QUEUE__DATA(head, struct db, queue);
databaseReadUnlock(db);
n_db++;
}
return 0;
}
/* Decode the disk database contained in a snapshot. */
static int decodeDiskDatabase(struct fsm *f, struct cursor *cursor)
{
struct snapshotDatabase header;
struct db *db;
sqlite3_vfs *vfs;
int exists;
int rv;
rv = snapshotDatabase__decode(cursor, &header);
if (rv != 0) {
return rv;
}
rv = registry__db_get(f->registry, header.filename, &db);
if (rv != 0) {
return rv;
}
vfs = sqlite3_vfs_find(db->config->name);
/* Check if the database file exists, and create it by opening a
* connection if it doesn't. */
rv = vfs->xAccess(vfs, db->path, 0, &exists);
assert(rv == 0);
if (!exists) {
rv = db__open_follower(db);
if (rv != 0) {
return rv;
}
sqlite3_close(db->follower);
db->follower = NULL;
}
/* The last check can overflow, but we would already be lost anyway, as
* the raft snapshot restore API only supplies one buffer and the data
* has to fit in size_t bytes anyway. */
if (header.main_size > SIZE_MAX || header.wal_size > SIZE_MAX ||
header.main_size + header.wal_size > SIZE_MAX) {
tracef("main_size:%" PRIu64 "B wal_size:%" PRIu64
"B would overflow max DB size (%zuB)",
header.main_size, header.wal_size, SIZE_MAX);
return -1;
}
/* Due to the check above, these casts are safe. */
rv = VfsDiskRestore(vfs, db->path, cursor->p, (size_t)header.main_size,
(size_t)header.wal_size);
if (rv != 0) {
tracef("VfsDiskRestore %d", rv);
return rv;
}
cursor->p += header.main_size + header.wal_size;
return 0;
}
static int fsm__restore_disk(struct raft_fsm *fsm, struct raft_buffer *buf)
{
tracef("fsm restore disk");
struct fsm *f = fsm->data;
struct cursor cursor = {buf->base, buf->len};
struct snapshotHeader header;
unsigned i;
int rv;
rv = snapshotHeader__decode(&cursor, &header);
if (rv != 0) {
tracef("decode failed %d", rv);
return rv;
}
if (header.format != SNAPSHOT_FORMAT) {
tracef("bad format");
return RAFT_MALFORMED;
}
for (i = 0; i < header.n; i++) {
rv = decodeDiskDatabase(f, &cursor);
if (rv != 0) {
tracef("decode failed");
return rv;
}
}
/* Don't use sqlite3_free as this buffer is allocated by raft. */
raft_free(buf->base);
return 0;
}
int fsm__init_disk(struct raft_fsm *fsm,
struct config *config,
struct registry *registry)
{
tracef("fsm init");
struct fsm *f = raft_malloc(sizeof *f);
if (f == NULL) {
return DQLITE_NOMEM;
}
f->logger = &config->logger;
f->registry = registry;
f->pending.n_pages = 0;
f->pending.page_numbers = NULL;
f->pending.pages = NULL;
fsm->version = 3;
fsm->data = f;
fsm->apply = fsm__apply;
fsm->snapshot = fsm__snapshot_disk;
fsm->snapshot_async = fsm__snapshot_async_disk;
fsm->snapshot_finalize = fsm__snapshot_finalize_disk;
fsm->restore = fsm__restore_disk;
return 0;
}
dqlite-1.16.0/src/fsm.h 0000664 0000000 0000000 00000001201 14512203222 0014571 0 ustar 00root root 0000000 0000000 /**
* Dqlite Raft FSM
*/
#ifndef DQLITE_FSM_H_
#define DQLITE_FSM_H_
#include
#include "config.h"
#include "registry.h"
/**
* Initialize the given SQLite replication interface with dqlite's raft based
* implementation.
*/
int fsm__init(struct raft_fsm *fsm,
struct config *config,
struct registry *registry);
/**
* Initialize the given SQLite replication interface with dqlite's on-disk
* raft based implementation.
*/
int fsm__init_disk(struct raft_fsm *fsm,
struct config *config,
struct registry *registry);
void fsm__close(struct raft_fsm *fsm);
#endif /* DQLITE_REPLICATION_METHODS_H_ */
dqlite-1.16.0/src/gateway.c 0000664 0000000 0000000 00000111274 14512203222 0015454 0 ustar 00root root 0000000 0000000 #include "gateway.h"
#include "bind.h"
#include "id.h"
#include "protocol.h"
#include "query.h"
#include "request.h"
#include "response.h"
#include "tracing.h"
#include "translate.h"
#include "tuple.h"
#include "vfs.h"
void gateway__init(struct gateway *g,
struct config *config,
struct registry *registry,
struct raft *raft,
struct id_state seed)
{
tracef("gateway init");
g->config = config;
g->registry = registry;
g->raft = raft;
g->leader = NULL;
g->req = NULL;
g->exec.data = g;
stmt__registry_init(&g->stmts);
g->barrier.data = g;
g->barrier.cb = NULL;
g->barrier.leader = NULL;
g->protocol = DQLITE_PROTOCOL_VERSION;
g->client_id = 0;
g->random_state = seed;
}
void gateway__leader_close(struct gateway *g, int reason)
{
if (g == NULL || g->leader == NULL) {
tracef("gateway:%p or gateway->leader are NULL", g);
return;
}
if (g->req != NULL) {
if (g->leader->inflight != NULL) {
tracef("finish inflight apply request");
struct raft_apply *req = &g->leader->inflight->req;
req->cb(req, reason, NULL);
assert(g->req == NULL);
} else if (g->barrier.cb != NULL) {
tracef("finish inflight barrier");
/* This is not a typo, g->barrier.req.cb is a wrapper
* around g->barrier.cb and will set g->barrier.cb to
* NULL when called. */
struct raft_barrier *b = &g->barrier.req;
b->cb(b, reason);
assert(g->barrier.cb == NULL);
} else if (g->leader->exec != NULL &&
g->leader->exec->barrier.cb != NULL) {
tracef("finish inflight exec barrier");
struct raft_barrier *b = &g->leader->exec->barrier.req;
b->cb(b, reason);
assert(g->leader->exec == NULL);
} else if (g->req->type == DQLITE_REQUEST_QUERY_SQL) {
/* Finalize the statement that was in the process of
* yielding rows. We only need to handle QUERY_SQL
* because for QUERY and EXEC the statement is finalized
* by the call to stmt__registry_close, below (and for
* EXEC_SQL the lifetimes of the statements are managed
* by leader__exec and the associated callback).
*
* It's okay if g->req->stmt is NULL since
* sqlite3_finalize(NULL) is documented to be a no-op.
*/
sqlite3_finalize(g->req->stmt);
g->req = NULL;
}
}
stmt__registry_close(&g->stmts);
leader__close(g->leader);
sqlite3_free(g->leader);
g->leader = NULL;
}
void gateway__close(struct gateway *g)
{
tracef("gateway close");
if (g->leader == NULL) {
stmt__registry_close(&g->stmts);
return;
}
gateway__leader_close(g, RAFT_SHUTDOWN);
}
/* Declare a request struct and a response struct of the appropriate types and
* decode the request. This is used in the common case where only one schema
* version is extant. */
#define START_V0(REQ, RES, ...) \
struct request_##REQ request = {0}; \
struct response_##RES response = {0}; \
{ \
int rv_; \
if (req->schema != 0) { \
tracef("bad schema version %d", req->schema); \
failure(req, DQLITE_PARSE, \
"unrecognized schema version"); \
return 0; \
} \
rv_ = request_##REQ##__decode(cursor, &request); \
if (rv_ != 0) { \
return rv_; \
} \
}
#define CHECK_LEADER(REQ) \
if (raft_state(g->raft) != RAFT_LEADER) { \
failure(REQ, SQLITE_IOERR_NOT_LEADER, "not leader"); \
return 0; \
}
#define SUCCESS(LOWER, UPPER, RESP, SCHEMA) \
{ \
size_t _n = response_##LOWER##__sizeof(&RESP); \
void *_cursor; \
assert(_n % 8 == 0); \
_cursor = buffer__advance(req->buffer, _n); \
/* Since responses are small and the buffer it's at least 4096 \
* bytes, this can't fail. */ \
assert(_cursor != NULL); \
response_##LOWER##__encode(&RESP, &_cursor); \
req->cb(req, 0, DQLITE_RESPONSE_##UPPER, SCHEMA); \
}
/* Encode the given success response and invoke the request callback,
* using schema version 0. */
#define SUCCESS_V0(LOWER, UPPER) SUCCESS(LOWER, UPPER, response, 0)
/* Lookup the database with the given ID.
*
* TODO: support more than one database per connection? */
#define LOOKUP_DB(ID) \
if (ID != 0 || g->leader == NULL) { \
failure(req, SQLITE_NOTFOUND, "no database opened"); \
return 0; \
}
/* Lookup the statement with the given ID. */
#define LOOKUP_STMT(ID) \
stmt = stmt__registry_get(&g->stmts, ID); \
if (stmt == NULL) { \
failure(req, SQLITE_NOTFOUND, \
"no statement with the given id"); \
return 0; \
}
#define FAIL_IF_CHECKPOINTING \
{ \
struct sqlite3_file *_file; \
int _rv; \
_rv = sqlite3_file_control(g->leader->conn, "main", \
SQLITE_FCNTL_FILE_POINTER, &_file); \
assert(_rv == SQLITE_OK); /* Should never fail */ \
\
_rv = _file->pMethods->xShmLock( \
_file, 1 /* checkpoint lock */, 1, \
SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE); \
if (_rv != 0) { \
assert(_rv == SQLITE_BUSY); \
failure(req, SQLITE_BUSY, "checkpoint in progress"); \
return 0; \
} \
_file->pMethods->xShmLock( \
_file, 1 /* checkpoint lock */, 1, \
SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE); \
}
/* Encode fa failure response and invoke the request callback */
static void failure(struct handle *req, int code, const char *message)
{
struct response_failure failure;
size_t n;
void *cursor;
failure.code = (uint64_t)code;
failure.message = message;
n = response_failure__sizeof(&failure);
assert(n % 8 == 0);
cursor = buffer__advance(req->buffer, n);
/* The buffer has at least 4096 bytes, and error messages are shorter
* than that. So this can't fail. */
assert(cursor != NULL);
response_failure__encode(&failure, &cursor);
req->cb(req, 0, DQLITE_RESPONSE_FAILURE, 0);
}
static void emptyRows(struct handle *req)
{
void *cursor = buffer__advance(req->buffer, 8 + 8);
uint64_t val;
assert(cursor != NULL);
val = 0;
uint64__encode(&val, &cursor);
val = DQLITE_RESPONSE_ROWS_DONE;
uint64__encode(&val, &cursor);
req->cb(req, 0, DQLITE_RESPONSE_ROWS, 0);
}
static int handle_leader_legacy(struct gateway *g, struct handle *req)
{
tracef("handle leader legacy");
struct cursor *cursor = &req->cursor;
START_V0(leader, server_legacy);
raft_id id;
raft_leader(g->raft, &id, &response.address);
if (response.address == NULL) {
response.address = "";
}
SUCCESS_V0(server_legacy, SERVER_LEGACY);
return 0;
}
static int handle_leader(struct gateway *g, struct handle *req)
{
tracef("handle leader");
struct cursor *cursor = &req->cursor;
raft_id id = 0;
const char *address = NULL;
unsigned i;
if (g->protocol == DQLITE_PROTOCOL_VERSION_LEGACY) {
return handle_leader_legacy(g, req);
}
START_V0(leader, server);
/* Only voters might now who the leader is. */
for (i = 0; i < g->raft->configuration.n; i++) {
struct raft_server *server = &g->raft->configuration.servers[i];
if (server->id == g->raft->id && server->role == RAFT_VOTER) {
tracef("handle leader - dispatch to %llu", server->id);
raft_leader(g->raft, &id, &address);
break;
}
}
response.id = id;
response.address = address;
if (response.address == NULL) {
response.address = "";
}
SUCCESS_V0(server, SERVER);
return 0;
}
static int handle_client(struct gateway *g, struct handle *req)
{
tracef("handle client");
struct cursor *cursor = &req->cursor;
START_V0(client, welcome);
g->client_id = request.id;
response.heartbeat_timeout = g->config->heartbeat_timeout;
SUCCESS_V0(welcome, WELCOME);
return 0;
}
static int handle_open(struct gateway *g, struct handle *req)
{
tracef("handle open");
struct cursor *cursor = &req->cursor;
struct db *db;
int rc;
START_V0(open, db);
if (g->leader != NULL) {
tracef("already open");
failure(req, SQLITE_BUSY,
"a database for this connection is already open");
return 0;
}
rc = registry__db_get(g->registry, request.filename, &db);
if (rc != 0) {
tracef("registry db get failed %d", rc);
return rc;
}
g->leader = sqlite3_malloc(sizeof *g->leader);
if (g->leader == NULL) {
tracef("malloc failed");
return DQLITE_NOMEM;
}
rc = leader__init(g->leader, db, g->raft);
if (rc != 0) {
tracef("leader init failed %d", rc);
sqlite3_free(g->leader);
g->leader = NULL;
return rc;
}
response.id = 0;
SUCCESS_V0(db, DB);
return 0;
}
static void prepareBarrierCb(struct barrier *barrier, int status)
{
tracef("prepare barrier cb status:%d", status);
struct gateway *g = barrier->data;
struct handle *req = g->req;
struct response_stmt response_v0 = {0};
struct response_stmt_with_offset response_v1 = {0};
const char *sql = req->sql;
struct stmt *stmt;
const char *tail;
sqlite3_stmt *tail_stmt;
int rc;
assert(req != NULL);
stmt = stmt__registry_get(&g->stmts, req->stmt_id);
assert(stmt != NULL);
g->req = NULL;
if (status != 0) {
stmt__registry_del(&g->stmts, stmt);
failure(req, status, "barrier error");
return;
}
rc = sqlite3_prepare_v2(g->leader->conn, sql, -1, &stmt->stmt, &tail);
if (rc != SQLITE_OK) {
failure(req, rc, sqlite3_errmsg(g->leader->conn));
stmt__registry_del(&g->stmts, stmt);
return;
}
if (stmt->stmt == NULL) {
tracef("prepare barrier cb empty statement");
stmt__registry_del(&g->stmts, stmt);
/* FIXME Should we use a code other than 0 here? */
failure(req, 0, "empty statement");
return;
}
if (req->schema == DQLITE_PREPARE_STMT_SCHEMA_V0) {
rc = sqlite3_prepare_v2(g->leader->conn, tail, -1, &tail_stmt,
NULL);
if (rc != 0 || tail_stmt != NULL) {
stmt__registry_del(&g->stmts, stmt);
sqlite3_finalize(tail_stmt);
failure(req, SQLITE_ERROR, "nonempty statement tail");
return;
}
}
switch (req->schema) {
case DQLITE_PREPARE_STMT_SCHEMA_V0:
response_v0.db_id = (uint32_t)req->db_id;
response_v0.id = (uint32_t)stmt->id;
response_v0.params =
(uint64_t)sqlite3_bind_parameter_count(stmt->stmt);
SUCCESS(stmt, STMT, response_v0,
DQLITE_PREPARE_STMT_SCHEMA_V0);
break;
case DQLITE_PREPARE_STMT_SCHEMA_V1:
response_v1.db_id = (uint32_t)req->db_id;
response_v1.id = (uint32_t)stmt->id;
response_v1.params =
(uint64_t)sqlite3_bind_parameter_count(stmt->stmt);
response_v1.offset = (uint64_t)(tail - sql);
SUCCESS(stmt_with_offset, STMT_WITH_OFFSET, response_v1,
DQLITE_PREPARE_STMT_SCHEMA_V1);
break;
default:
assert(0);
}
}
static int handle_prepare(struct gateway *g, struct handle *req)
{
tracef("handle prepare");
struct cursor *cursor = &req->cursor;
struct stmt *stmt;
struct request_prepare request = {0};
int rc;
if (req->schema != DQLITE_PREPARE_STMT_SCHEMA_V0 &&
req->schema != DQLITE_PREPARE_STMT_SCHEMA_V1) {
failure(req, SQLITE_ERROR, "unrecognized schema version");
return 0;
}
rc = request_prepare__decode(cursor, &request);
if (rc != 0) {
return rc;
}
CHECK_LEADER(req);
LOOKUP_DB(request.db_id);
rc = stmt__registry_add(&g->stmts, &stmt);
if (rc != 0) {
tracef("handle prepare registry add failed %d", rc);
return rc;
}
assert(stmt != NULL);
/* This cast is safe as long as the TODO in LOOKUP_DB is not
* implemented. */
req->db_id = (size_t)request.db_id;
req->stmt_id = stmt->id;
req->sql = request.sql;
g->req = req;
rc = leader__barrier(g->leader, &g->barrier, prepareBarrierCb);
if (rc != 0) {
tracef("handle prepare barrier failed %d", rc);
stmt__registry_del(&g->stmts, stmt);
g->req = NULL;
return rc;
}
return 0;
}
/* Fill a result response with the last inserted ID and number of rows
* affected. */
static void fill_result(struct gateway *g, struct response_result *response)
{
assert(g->leader != NULL);
response->last_insert_id =
(uint64_t)sqlite3_last_insert_rowid(g->leader->conn);
/* FIXME eventually we should consider using sqlite3_changes64 here */
response->rows_affected = (uint64_t)sqlite3_changes(g->leader->conn);
}
static const char *error_message(sqlite3 *db, int rc)
{
switch (rc) {
case SQLITE_IOERR_LEADERSHIP_LOST:
return "disk I/O error";
case SQLITE_IOERR_WRITE:
return "disk I/O error";
case SQLITE_ABORT:
return "abort";
case SQLITE_ROW:
return "rows yielded when none expected for EXEC "
"request";
}
return sqlite3_errmsg(db);
}
static void leader_exec_cb(struct exec *exec, int status)
{
struct gateway *g = exec->data;
struct handle *req = g->req;
struct stmt *stmt = stmt__registry_get(&g->stmts, req->stmt_id);
assert(stmt != NULL);
struct response_result response;
g->req = NULL;
if (status == SQLITE_DONE) {
fill_result(g, &response);
SUCCESS_V0(result, RESULT);
} else {
assert(g->leader != NULL);
failure(req, status, error_message(g->leader->conn, status));
sqlite3_reset(stmt->stmt);
}
}
static int handle_exec(struct gateway *g, struct handle *req)
{
tracef("handle exec schema:%" PRIu8, req->schema);
struct cursor *cursor = &req->cursor;
struct stmt *stmt;
struct request_exec request = {0};
int tuple_format;
uint64_t req_id;
int rv;
switch (req->schema) {
case DQLITE_REQUEST_PARAMS_SCHEMA_V0:
tuple_format = TUPLE__PARAMS;
break;
case DQLITE_REQUEST_PARAMS_SCHEMA_V1:
tuple_format = TUPLE__PARAMS32;
break;
default:
tracef("bad schema version %d", req->schema);
failure(req, DQLITE_PARSE,
"unrecognized schema version");
return 0;
}
/* The v0 and v1 schemas only differ in the layout of the tuple,
* so we can use the same decode function for both. */
rv = request_exec__decode(cursor, &request);
if (rv != 0) {
return rv;
}
CHECK_LEADER(req);
LOOKUP_DB(request.db_id);
LOOKUP_STMT(request.stmt_id);
FAIL_IF_CHECKPOINTING;
rv = bind__params(stmt->stmt, cursor, tuple_format);
if (rv != 0) {
tracef("handle exec bind failed %d", rv);
failure(req, rv, "bind parameters");
return 0;
}
req->stmt_id = stmt->id;
g->req = req;
req_id = idNext(&g->random_state);
rv = leader__exec(g->leader, &g->exec, stmt->stmt, req_id,
leader_exec_cb);
if (rv != 0) {
tracef("handle exec leader exec failed %d", rv);
g->req = NULL;
return rv;
}
return 0;
}
/* Step through the given statement and populate the response buffer of the
* given request with a single batch of rows.
*
* A single batch of rows is typically about the size of a memory page. */
static void query_batch(struct gateway *g)
{
struct handle *req = g->req;
assert(req != NULL);
g->req = NULL;
sqlite3_stmt *stmt = req->stmt;
assert(stmt != NULL);
struct response_rows response;
int rc;
rc = query__batch(stmt, req->buffer);
if (rc != SQLITE_ROW && rc != SQLITE_DONE) {
assert(g->leader != NULL);
failure(req, rc, sqlite3_errmsg(g->leader->conn));
sqlite3_reset(stmt);
goto done;
}
if (rc == SQLITE_ROW) {
response.eof = DQLITE_RESPONSE_ROWS_PART;
g->req = req;
SUCCESS_V0(rows, ROWS);
return;
} else {
response.eof = DQLITE_RESPONSE_ROWS_DONE;
SUCCESS_V0(rows, ROWS);
}
done:
if (req->type == DQLITE_REQUEST_QUERY_SQL) {
sqlite3_finalize(stmt);
}
}
static void query_barrier_cb(struct barrier *barrier, int status)
{
tracef("query barrier cb status:%d", status);
struct gateway *g = barrier->data;
struct handle *req = g->req;
assert(req != NULL);
g->req = NULL;
struct stmt *stmt = stmt__registry_get(&g->stmts, req->stmt_id);
assert(stmt != NULL);
if (status != 0) {
failure(req, status, "barrier error");
return;
}
req->stmt = stmt->stmt;
g->req = req;
query_batch(g);
}
static void leaderModifyingQueryCb(struct exec *exec, int status)
{
struct gateway *g = exec->data;
struct handle *req = g->req;
assert(req != NULL);
g->req = NULL;
struct stmt *stmt = stmt__registry_get(&g->stmts, req->stmt_id);
assert(stmt != NULL);
if (status == SQLITE_DONE) {
emptyRows(req);
} else {
assert(g->leader != NULL);
failure(req, status, error_message(g->leader->conn, status));
sqlite3_reset(stmt->stmt);
}
}
static int handle_query(struct gateway *g, struct handle *req)
{
tracef("handle query schema:%" PRIu8, req->schema);
struct cursor *cursor = &req->cursor;
struct stmt *stmt;
struct request_query request = {0};
int tuple_format;
bool is_readonly;
uint64_t req_id;
int rv;
switch (req->schema) {
case DQLITE_REQUEST_PARAMS_SCHEMA_V0:
tuple_format = TUPLE__PARAMS;
break;
case DQLITE_REQUEST_PARAMS_SCHEMA_V1:
tuple_format = TUPLE__PARAMS32;
break;
default:
tracef("bad schema version %d", req->schema);
failure(req, DQLITE_PARSE,
"unrecognized schema version");
return 0;
}
/* The only difference in layout between the v0 and v1 requests is in
* the tuple, which isn't parsed until bind__params later on. */
rv = request_query__decode(cursor, &request);
if (rv != 0) {
return rv;
}
CHECK_LEADER(req);
LOOKUP_DB(request.db_id);
LOOKUP_STMT(request.stmt_id);
FAIL_IF_CHECKPOINTING;
rv = bind__params(stmt->stmt, cursor, tuple_format);
if (rv != 0) {
tracef("handle query bind failed %d", rv);
failure(req, rv, "bind parameters");
return 0;
}
req->stmt_id = stmt->id;
g->req = req;
is_readonly = (bool)sqlite3_stmt_readonly(stmt->stmt);
if (is_readonly) {
rv = leader__barrier(g->leader, &g->barrier, query_barrier_cb);
} else {
req_id = idNext(&g->random_state);
rv = leader__exec(g->leader, &g->exec, stmt->stmt, req_id,
leaderModifyingQueryCb);
}
if (rv != 0) {
g->req = NULL;
return rv;
}
return 0;
}
static int handle_finalize(struct gateway *g, struct handle *req)
{
tracef("handle finalize");
struct cursor *cursor = &req->cursor;
struct stmt *stmt;
int rv;
START_V0(finalize, empty);
LOOKUP_DB(request.db_id);
LOOKUP_STMT(request.stmt_id);
rv = stmt__registry_del(&g->stmts, stmt);
if (rv != 0) {
tracef("handle finalize registry del failed %d", rv);
failure(req, rv, "finalize statement");
return 0;
}
SUCCESS_V0(empty, EMPTY);
return 0;
}
static void handle_exec_sql_next(struct gateway *g,
struct handle *req,
bool done);
static void handle_exec_sql_cb(struct exec *exec, int status)
{
tracef("handle exec sql cb status %d", status);
struct gateway *g = exec->data;
struct handle *req = g->req;
req->exec_count += 1;
sqlite3_finalize(exec->stmt);
if (status == SQLITE_DONE) {
handle_exec_sql_next(g, req, true);
} else {
assert(g->leader != NULL);
failure(req, status, error_message(g->leader->conn, status));
g->req = NULL;
}
}
static void handle_exec_sql_next(struct gateway *g,
struct handle *req,
bool done)
{
tracef("handle exec sql next");
struct cursor *cursor = &req->cursor;
struct response_result response = {0};
sqlite3_stmt *stmt = NULL;
const char *tail;
int tuple_format;
uint64_t req_id;
int rv;
if (req->sql == NULL || strcmp(req->sql, "") == 0) {
goto success;
}
/* stmt will be set to NULL by sqlite when an error occurs. */
assert(g->leader != NULL);
rv = sqlite3_prepare_v2(g->leader->conn, req->sql, -1, &stmt, &tail);
if (rv != SQLITE_OK) {
tracef("exec sql prepare failed %d", rv);
failure(req, rv, sqlite3_errmsg(g->leader->conn));
goto done;
}
if (stmt == NULL) {
goto success;
}
if (!done) {
switch (req->schema) {
case DQLITE_REQUEST_PARAMS_SCHEMA_V0:
tuple_format = TUPLE__PARAMS;
break;
case DQLITE_REQUEST_PARAMS_SCHEMA_V1:
tuple_format = TUPLE__PARAMS32;
break;
default:
/* Should have been caught by handle_exec_sql */
assert(0);
}
rv = bind__params(stmt, cursor, tuple_format);
if (rv != SQLITE_OK) {
failure(req, rv, "bind parameters");
goto done_after_prepare;
}
}
req->sql = tail;
g->req = req;
req_id = idNext(&g->random_state);
/* At this point, leader__exec takes ownership of stmt */
rv =
leader__exec(g->leader, &g->exec, stmt, req_id, handle_exec_sql_cb);
if (rv != SQLITE_OK) {
failure(req, rv, sqlite3_errmsg(g->leader->conn));
goto done_after_prepare;
}
return;
success:
tracef("handle exec sql next success");
if (req->exec_count > 0) {
fill_result(g, &response);
}
SUCCESS_V0(result, RESULT);
done_after_prepare:
sqlite3_finalize(stmt);
done:
g->req = NULL;
}
static void execSqlBarrierCb(struct barrier *barrier, int status)
{
tracef("exec sql barrier cb status:%d", status);
struct gateway *g = barrier->data;
struct handle *req = g->req;
assert(req != NULL);
g->req = NULL;
if (status != 0) {
failure(req, status, "barrier error");
return;
}
handle_exec_sql_next(g, req, false);
}
static int handle_exec_sql(struct gateway *g, struct handle *req)
{
tracef("handle exec sql schema:%" PRIu8, req->schema);
struct cursor *cursor = &req->cursor;
struct request_exec_sql request = {0};
int rc;
/* Fail early if the schema version isn't recognized, even though we
* won't use it until later. */
if (req->schema != 0 && req->schema != 1) {
tracef("bad schema version %d", req->schema);
failure(req, DQLITE_PARSE, "unrecognized schema version");
return 0;
}
/* The only difference in layout between the v0 and v1 requests is in
* the tuple, which isn't parsed until bind__params later on. */
rc = request_exec_sql__decode(cursor, &request);
if (rc != 0) {
return rc;
}
CHECK_LEADER(req);
LOOKUP_DB(request.db_id);
FAIL_IF_CHECKPOINTING;
req->sql = request.sql;
req->exec_count = 0;
g->req = req;
rc = leader__barrier(g->leader, &g->barrier, execSqlBarrierCb);
if (rc != 0) {
tracef("handle exec sql barrier failed %d", rc);
g->req = NULL;
return rc;
}
return 0;
}
static void leaderModifyingQuerySqlCb(struct exec *exec, int status)
{
struct gateway *g = exec->data;
struct handle *req = g->req;
assert(req != NULL);
g->req = NULL;
sqlite3_stmt *stmt = exec->stmt;
assert(stmt != NULL);
sqlite3_finalize(stmt);
if (status == SQLITE_DONE) {
emptyRows(req);
} else {
assert(g->leader != NULL);
failure(req, status, error_message(g->leader->conn, status));
}
}
static void querySqlBarrierCb(struct barrier *barrier, int status)
{
tracef("query sql barrier cb status:%d", status);
struct gateway *g = barrier->data;
struct handle *req = g->req;
assert(req != NULL);
g->req = NULL;
struct cursor *cursor = &req->cursor;
const char *sql = req->sql;
sqlite3_stmt *stmt;
const char *tail;
sqlite3_stmt *tail_stmt;
int tuple_format;
bool is_readonly;
uint64_t req_id;
int rv;
if (status != 0) {
failure(req, status, "barrier error");
return;
}
rv = sqlite3_prepare_v2(g->leader->conn, sql, -1, &stmt, &tail);
if (rv != SQLITE_OK) {
tracef("handle query sql prepare failed %d", rv);
failure(req, rv, sqlite3_errmsg(g->leader->conn));
return;
}
if (stmt == NULL) {
tracef("handle query sql empty statement");
failure(req, rv, "empty statement");
return;
}
rv = sqlite3_prepare_v2(g->leader->conn, tail, -1, &tail_stmt, NULL);
if (rv != 0 || tail_stmt != NULL) {
sqlite3_finalize(stmt);
sqlite3_finalize(tail_stmt);
failure(req, SQLITE_ERROR, "nonempty statement tail");
return;
}
switch (req->schema) {
case DQLITE_REQUEST_PARAMS_SCHEMA_V0:
tuple_format = TUPLE__PARAMS;
break;
case DQLITE_REQUEST_PARAMS_SCHEMA_V1:
tuple_format = TUPLE__PARAMS32;
break;
default:
/* Should have been caught by handle_query_sql */
assert(0);
}
rv = bind__params(stmt, cursor, tuple_format);
if (rv != 0) {
tracef("handle query sql bind failed %d", rv);
sqlite3_finalize(stmt);
failure(req, rv, "bind parameters");
return;
}
req->stmt = stmt;
g->req = req;
is_readonly = (bool)sqlite3_stmt_readonly(stmt);
if (is_readonly) {
query_batch(g);
} else {
req_id = idNext(&g->random_state);
rv = leader__exec(g->leader, &g->exec, stmt, req_id,
leaderModifyingQuerySqlCb);
if (rv != 0) {
sqlite3_finalize(stmt);
g->req = NULL;
failure(req, rv, "leader exec");
}
}
}
static int handle_query_sql(struct gateway *g, struct handle *req)
{
tracef("handle query sql schema:%" PRIu8, req->schema);
struct cursor *cursor = &req->cursor;
struct request_query_sql request = {0};
int rv;
/* Fail early if the schema version isn't recognized. */
if (req->schema != 0 && req->schema != 1) {
tracef("bad schema version %d", req->schema);
failure(req, DQLITE_PARSE, "unrecognized schema version");
return 0;
}
/* Schema version only affect the tuple format, which is parsed later */
rv = request_query_sql__decode(cursor, &request);
if (rv != 0) {
return rv;
}
CHECK_LEADER(req);
LOOKUP_DB(request.db_id);
FAIL_IF_CHECKPOINTING;
req->sql = request.sql;
g->req = req;
rv = leader__barrier(g->leader, &g->barrier, querySqlBarrierCb);
if (rv != 0) {
tracef("handle query sql barrier failed %d", rv);
g->req = NULL;
return rv;
}
return 0;
}
/*
* An interrupt can only be handled when a query is already yielding rows.
*/
static int handle_interrupt(struct gateway *g, struct handle *req)
{
tracef("handle interrupt");
g->req = NULL;
struct cursor *cursor = &req->cursor;
START_V0(interrupt, empty);
sqlite3_finalize(req->stmt);
req->stmt = NULL;
SUCCESS_V0(empty, EMPTY);
return 0;
}
struct change
{
struct gateway *gateway;
struct raft_change req;
};
static void raftChangeCb(struct raft_change *change, int status)
{
tracef("raft change cb id:%" PRIu64 " status:%d",
idExtract(change->req_id), status);
struct change *r = change->data;
struct gateway *g = r->gateway;
struct handle *req = g->req;
struct response_empty response = {0};
g->req = NULL;
sqlite3_free(r);
if (status != 0) {
failure(req, translateRaftErrCode(status),
raft_strerror(status));
} else {
SUCCESS_V0(empty, EMPTY);
}
}
static int handle_add(struct gateway *g, struct handle *req)
{
tracef("handle add");
struct cursor *cursor = &req->cursor;
struct change *r;
uint64_t req_id;
int rv;
START_V0(add, empty);
(void)response;
CHECK_LEADER(req);
r = sqlite3_malloc(sizeof *r);
if (r == NULL) {
return DQLITE_NOMEM;
}
r->gateway = g;
r->req.data = r;
req_id = idNext(&g->random_state);
idSet(r->req.req_id, req_id);
g->req = req;
rv = raft_add(g->raft, &r->req, request.id, request.address,
raftChangeCb);
if (rv != 0) {
tracef("raft add failed %d", rv);
g->req = NULL;
sqlite3_free(r);
failure(req, translateRaftErrCode(rv), raft_strerror(rv));
return 0;
}
return 0;
}
static int handle_promote_or_assign(struct gateway *g, struct handle *req)
{
tracef("handle assign");
struct cursor *cursor = &req->cursor;
struct change *r;
uint64_t role = DQLITE_VOTER;
uint64_t req_id;
int rv;
START_V0(promote_or_assign, empty);
(void)response;
CHECK_LEADER(req);
/* Detect if this is an assign role request, instead of the former
* promote request. */
if (cursor->cap > 0) {
rv = uint64__decode(cursor, &role);
if (rv != 0) {
tracef("handle assign promote rv %d", rv);
return rv;
}
}
r = sqlite3_malloc(sizeof *r);
if (r == NULL) {
tracef("malloc failed");
return DQLITE_NOMEM;
}
r->gateway = g;
r->req.data = r;
req_id = idNext(&g->random_state);
idSet(r->req.req_id, req_id);
g->req = req;
rv = raft_assign(g->raft, &r->req, request.id,
translateDqliteRole((int)role), raftChangeCb);
if (rv != 0) {
tracef("raft_assign failed %d", rv);
g->req = NULL;
sqlite3_free(r);
failure(req, translateRaftErrCode(rv), raft_strerror(rv));
return 0;
}
return 0;
}
static int handle_remove(struct gateway *g, struct handle *req)
{
tracef("handle remove");
struct cursor *cursor = &req->cursor;
struct change *r;
uint64_t req_id;
int rv;
START_V0(remove, empty);
(void)response;
CHECK_LEADER(req);
r = sqlite3_malloc(sizeof *r);
if (r == NULL) {
tracef("malloc failed");
return DQLITE_NOMEM;
}
r->gateway = g;
r->req.data = r;
req_id = idNext(&g->random_state);
idSet(r->req.req_id, req_id);
g->req = req;
rv = raft_remove(g->raft, &r->req, request.id, raftChangeCb);
if (rv != 0) {
tracef("raft_remote failed %d", rv);
g->req = NULL;
sqlite3_free(r);
failure(req, translateRaftErrCode(rv), raft_strerror(rv));
return 0;
}
return 0;
}
static int dumpFile(const char *filename,
uint8_t *data,
size_t n,
struct buffer *buffer)
{
void *cur;
uint64_t len = n;
cur = buffer__advance(buffer, text__sizeof(&filename));
if (cur == NULL) {
goto oom;
}
text__encode(&filename, &cur);
cur = buffer__advance(buffer, uint64__sizeof(&len));
if (cur == NULL) {
goto oom;
}
uint64__encode(&len, &cur);
if (n == 0) {
return 0;
}
assert(n % 8 == 0);
assert(data != NULL);
cur = buffer__advance(buffer, n);
if (cur == NULL) {
goto oom;
}
memcpy(cur, data, n);
return 0;
oom:
return DQLITE_NOMEM;
}
static int handle_dump(struct gateway *g, struct handle *req)
{
tracef("handle dump");
struct cursor *cursor = &req->cursor;
bool err = true;
sqlite3_vfs *vfs;
void *cur;
char filename[1024] = {0};
void *data;
size_t n;
uint8_t *page;
uint32_t database_size = 0;
uint8_t *database;
uint8_t *wal;
size_t n_database;
size_t n_wal;
int rv;
START_V0(dump, files);
response.n = 2;
cur = buffer__advance(req->buffer, response_files__sizeof(&response));
assert(cur != NULL);
response_files__encode(&response, &cur);
vfs = sqlite3_vfs_find(g->config->name);
rv = VfsSnapshot(vfs, request.filename, &data, &n);
if (rv != 0) {
tracef("dump failed");
failure(req, rv, "failed to dump database");
return 0;
}
if (data != NULL) {
/* Extract the database size from the first page. */
page = data;
database_size += (uint32_t)(page[28] << 24);
database_size += (uint32_t)(page[29] << 16);
database_size += (uint32_t)(page[30] << 8);
database_size += (uint32_t)(page[31]);
n_database = database_size * g->config->page_size;
n_wal = n - n_database;
database = data;
wal = database + n_database;
} else {
assert(n == 0);
n_database = 0;
n_wal = 0;
database = NULL;
wal = NULL;
}
rv = dumpFile(request.filename, database, n_database, req->buffer);
if (rv != 0) {
tracef("dump failed");
failure(req, rv, "failed to dump database");
goto out_free_data;
}
/* filename is zero inited and initially we allow only writing 1024 - 4
* - 1 bytes to it, so after strncpy filename will be zero-terminated
* and will not have overflowed. strcat adds the 4 byte suffix and
* also zero terminates the resulting string. */
const char *wal_suffix = "-wal";
strncpy(filename, request.filename,
sizeof(filename) - strlen(wal_suffix) - 1);
strcat(filename, wal_suffix);
rv = dumpFile(filename, wal, n_wal, req->buffer);
if (rv != 0) {
tracef("wal dump failed");
failure(req, rv, "failed to dump wal file");
goto out_free_data;
}
err = false;
out_free_data:
if (data != NULL) {
raft_free(data);
}
if (!err) {
req->cb(req, 0, DQLITE_RESPONSE_FILES, 0);
}
return 0;
}
static int encodeServer(struct gateway *g,
unsigned i,
struct buffer *buffer,
int format)
{
void *cur;
uint64_t id;
uint64_t role;
text_t address;
assert(format == DQLITE_REQUEST_CLUSTER_FORMAT_V0 ||
format == DQLITE_REQUEST_CLUSTER_FORMAT_V1);
id = g->raft->configuration.servers[i].id;
address = g->raft->configuration.servers[i].address;
role =
(uint64_t)translateRaftRole(g->raft->configuration.servers[i].role);
cur = buffer__advance(buffer, uint64__sizeof(&id));
if (cur == NULL) {
return DQLITE_NOMEM;
}
uint64__encode(&id, &cur);
cur = buffer__advance(buffer, text__sizeof(&address));
if (cur == NULL) {
return DQLITE_NOMEM;
}
text__encode(&address, &cur);
if (format == DQLITE_REQUEST_CLUSTER_FORMAT_V0) {
return 0;
}
cur = buffer__advance(buffer, uint64__sizeof(&role));
if (cur == NULL) {
return DQLITE_NOMEM;
}
uint64__encode(&role, &cur);
return 0;
}
static int handle_cluster(struct gateway *g, struct handle *req)
{
tracef("handle cluster");
struct cursor *cursor = &req->cursor;
unsigned i;
void *cur;
int rv;
START_V0(cluster, servers);
if (request.format != DQLITE_REQUEST_CLUSTER_FORMAT_V0 &&
request.format != DQLITE_REQUEST_CLUSTER_FORMAT_V1) {
tracef("bad cluster format");
failure(req, DQLITE_PARSE, "unrecognized cluster format");
return 0;
}
response.n = g->raft->configuration.n;
cur = buffer__advance(req->buffer, response_servers__sizeof(&response));
assert(cur != NULL);
response_servers__encode(&response, &cur);
for (i = 0; i < response.n; i++) {
rv = encodeServer(g, i, req->buffer, (int)request.format);
if (rv != 0) {
tracef("encode failed");
failure(req, rv, "failed to encode server");
return 0;
}
}
req->cb(req, 0, DQLITE_RESPONSE_SERVERS, 0);
return 0;
}
void raftTransferCb(struct raft_transfer *r)
{
struct gateway *g = r->data;
struct handle *req = g->req;
struct response_empty response = {0};
g->req = NULL;
sqlite3_free(r);
if (g->raft->state == RAFT_LEADER) {
tracef("transfer failed");
failure(req, DQLITE_ERROR, "leadership transfer failed");
} else {
SUCCESS_V0(empty, EMPTY);
}
}
static int handle_transfer(struct gateway *g, struct handle *req)
{
tracef("handle transfer");
struct cursor *cursor = &req->cursor;
struct raft_transfer *r;
int rv;
START_V0(transfer, empty);
(void)response;
CHECK_LEADER(req);
r = sqlite3_malloc(sizeof *r);
if (r == NULL) {
tracef("malloc failed");
return DQLITE_NOMEM;
}
r->data = g;
g->req = req;
rv = raft_transfer(g->raft, r, request.id, raftTransferCb);
if (rv != 0) {
tracef("raft_transfer failed %d", rv);
g->req = NULL;
sqlite3_free(r);
failure(req, translateRaftErrCode(rv), raft_strerror(rv));
return 0;
}
return 0;
}
static int handle_describe(struct gateway *g, struct handle *req)
{
tracef("handle describe");
struct cursor *cursor = &req->cursor;
START_V0(describe, metadata);
if (request.format != DQLITE_REQUEST_DESCRIBE_FORMAT_V0) {
tracef("bad format");
failure(req, SQLITE_PROTOCOL, "bad format version");
}
response.failure_domain = g->config->failure_domain;
response.weight = g->config->weight;
SUCCESS_V0(metadata, METADATA);
return 0;
}
static int handle_weight(struct gateway *g, struct handle *req)
{
tracef("handle weight");
struct cursor *cursor = &req->cursor;
START_V0(weight, empty);
g->config->weight = request.weight;
SUCCESS_V0(empty, EMPTY);
return 0;
}
int gateway__handle(struct gateway *g,
struct handle *req,
int type,
int schema,
struct buffer *buffer,
handle_cb cb)
{
tracef("gateway handle");
int rc = 0;
sqlite3_stmt *stmt = NULL; // used for DQLITE_REQUEST_INTERRUPT
if (g->req == NULL) {
goto handle;
}
/* Request in progress. TODO The current implementation doesn't allow
* reading a new request while a query is yielding rows, in that case
* gateway__resume in write_cb will indicate it has not finished
* returning results and a new request (in this case, the interrupt)
* will not be read. */
if (g->req->type == DQLITE_REQUEST_QUERY &&
type == DQLITE_REQUEST_INTERRUPT) {
goto handle;
}
if (g->req->type == DQLITE_REQUEST_QUERY_SQL &&
type == DQLITE_REQUEST_INTERRUPT) {
stmt = g->req->stmt;
goto handle;
}
/* Receiving a request when one is ongoing on the same connection
* is a hard error. The connection will be stopped due to the non-0
* return code in case asserts are off. */
assert(false);
return SQLITE_BUSY;
handle:
req->type = type;
req->schema = schema;
req->cb = cb;
req->buffer = buffer;
req->db_id = 0;
req->stmt_id = 0;
req->sql = NULL;
req->stmt = stmt;
req->exec_count = 0;
switch (type) {
#define DISPATCH(LOWER, UPPER, _) \
case DQLITE_REQUEST_##UPPER: \
rc = handle_##LOWER(g, req); \
break;
REQUEST__TYPES(DISPATCH);
default:
tracef("unrecognized request type %d", type);
failure(req, DQLITE_PARSE, "unrecognized request type");
rc = 0;
}
return rc;
}
int gateway__resume(struct gateway *g, bool *finished)
{
if (g->req == NULL || (g->req->type != DQLITE_REQUEST_QUERY &&
g->req->type != DQLITE_REQUEST_QUERY_SQL)) {
tracef("gateway resume - finished");
*finished = true;
return 0;
}
tracef("gateway resume - not finished");
*finished = false;
query_batch(g);
return 0;
}
dqlite-1.16.0/src/gateway.h 0000664 0000000 0000000 00000010123 14512203222 0015450 0 ustar 00root root 0000000 0000000 /**
* Core dqlite server engine, calling out SQLite for serving client requests.
*/
#ifndef DQLITE_GATEWAY_H_
#define DQLITE_GATEWAY_H_
#include
#include "../include/dqlite.h"
#include "lib/buffer.h"
#include "lib/serialize.h"
#include "config.h"
#include "id.h"
#include "leader.h"
#include "registry.h"
#include "stmt.h"
struct handle;
/**
* Handle requests from a single connected client and forward them to
* SQLite.
*/
struct gateway
{
struct config *config; /* Configuration */
struct registry *registry; /* Register of existing databases */
struct raft *raft; /* Raft instance */
struct leader *leader; /* Leader connection to the database */
struct handle *req; /* Asynchronous request being handled */
struct exec exec; /* Low-level exec async request */
struct stmt__registry stmts; /* Registry of prepared statements */
struct barrier barrier; /* Barrier for query requests */
uint64_t protocol; /* Protocol format version */
uint64_t client_id;
struct id_state random_state; /* For generating IDs */
};
void gateway__init(struct gateway *g,
struct config *config,
struct registry *registry,
struct raft *raft,
struct id_state seed);
void gateway__close(struct gateway *g);
/**
* Closes the leader connection to the database, reason should contain a raft
* error code.
*/
void gateway__leader_close(struct gateway *g, int reason);
/**
* Asynchronous request to handle a client command.
*
* We also use the handle as a place to save request-scoped data that we need
* to access from a callback.
*/
typedef void (*handle_cb)(struct handle *req,
int status,
uint8_t type,
uint8_t schema);
struct handle
{
/* User data. */
void *data;
/* Type code for this request. */
int type;
/* Schema version for this request. */
int schema;
/* Buffer where the response to this request will be written. */
struct buffer *buffer;
/* Cursor for reading the request. */
struct cursor cursor;
/* Database ID parsed from this request.
*
* This is used by handle_prepare. */
size_t db_id;
/* ID of the statement associated with this request.
*
* This is used by handle_prepare. */
size_t stmt_id;
/* SQL string associated with this request.
*
* This is used by handle_prepare, handle_query_sql, and handle_exec_sql
* to save the provided SQL string across calls to leader__barrier and
* leader__exec, since there's no prepared statement that can be saved
* instead. In the case of handle_exec_sql, after preparing each
* statement we update this field to point to the "tail" that has not
* been prepared yet. */
const char *sql;
/* Prepared statement that will be queried to process this request.
*
* This is used by handle_query and handle_query_sql. */
sqlite3_stmt *stmt;
/* Number of times a statement parsed from this request has been
* executed.
*
* This is used by handle_exec_sql, which parses zero or more statements
* from the provided SQL string and executes them successively. Only if
* at least one statement was executed should we fill the RESULT
* response using sqlite3_last_insert_rowid and sqlite3_changes. */
unsigned exec_count;
/* Callback that will be invoked at the end of request processing to
* write the response. */
handle_cb cb;
};
/**
* Start handling a new client request.
*
* At most one request can be outstanding at any given time. This function will
* return an error if user code calls it and there's already a request in
* progress.
*
* The @type parameter holds the request type code (e.g. #REQUEST_LEADER), and
* the @buffer parameter is a buffer for writing the response.
*/
int gateway__handle(struct gateway *g,
struct handle *req,
int type,
int schema,
struct buffer *buffer,
handle_cb cb);
/**
* Resume execution of a query that was yielding a lot of rows and has been
* interrupted in order to start sending a first batch of rows. The response
* write buffer associated with the request must have been reset.
*/
int gateway__resume(struct gateway *g, bool *finished);
#endif /* DQLITE_GATEWAY_H_ */
dqlite-1.16.0/src/id.c 0000664 0000000 0000000 00000002742 14512203222 0014406 0 ustar 00root root 0000000 0000000 #include "id.h"
#include
/* The PRNG used for generating request IDs is xoshiro256**, developed by
* David Blackman and Sebastiano Vigna and released into the public domain.
* See . */
static uint64_t rotl(uint64_t x, int k)
{
return (x << k) | (x >> (64 - k));
}
uint64_t idNext(struct id_state *state)
{
uint64_t result = rotl(state->data[1] * 5, 7) * 9;
uint64_t t = state->data[1] << 17;
state->data[2] ^= state->data[0];
state->data[3] ^= state->data[1];
state->data[1] ^= state->data[2];
state->data[0] ^= state->data[3];
state->data[2] ^= t;
state->data[3] = rotl(state->data[3], 45);
return result;
}
void idJump(struct id_state *state)
{
static const uint64_t JUMP[] = {0x180ec6d33cfd0aba, 0xd5a61266f0c9392c,
0xa9582618e03fc9aa, 0x39abdc4529b1661c};
uint64_t s0 = 0;
uint64_t s1 = 0;
uint64_t s2 = 0;
uint64_t s3 = 0;
for (size_t i = 0; i < sizeof(JUMP) / sizeof(*JUMP); i++) {
for (size_t b = 0; b < 64; b++) {
if (JUMP[i] & UINT64_C(1) << b) {
s0 ^= state->data[0];
s1 ^= state->data[1];
s2 ^= state->data[2];
s3 ^= state->data[3];
}
idNext(state);
}
}
state->data[0] = s0;
state->data[1] = s1;
state->data[2] = s2;
state->data[3] = s3;
}
uint64_t idExtract(const uint8_t buf[16])
{
uint64_t id;
memcpy(&id, buf, sizeof(id));
return id;
}
void idSet(uint8_t buf[16], uint64_t id)
{
memset(buf, 0, 16);
memcpy(buf, &id, sizeof(id));
buf[15] = (uint8_t)-1;
}
dqlite-1.16.0/src/id.h 0000664 0000000 0000000 00000002015 14512203222 0014404 0 ustar 00root root 0000000 0000000 /**
* Generate, set, and extract dqlite-generated request IDs.
*
* A fresh ID is generated for each config or exec client request that
* arrives at a gateway. These IDs are passed down into raft via the
* req_id field of RAFT__REQUEST, and are suitable for diagnostic use
* only.
*/
#ifndef DQLITE_ID_H_
#define DQLITE_ID_H_
#include
/**
* State used to generate a request ID.
*/
struct id_state
{
uint64_t data[4];
};
/**
* Generate a request ID, mutating the input state in the process.
*/
uint64_t idNext(struct id_state *state);
/**
* Cause the given state to yield a different sequence of IDs.
*
* This is used to ensure that the sequences of IDs generated for
* distinct clients are (in practice) disjoint.
*/
void idJump(struct id_state *state);
/**
* Read a request ID from the req_id field of RAFT__REQUEST.
*/
uint64_t idExtract(const uint8_t buf[16]);
/**
* Write a request ID to the req_id field of RAFT__REQUEST.
*/
void idSet(uint8_t buf[16], uint64_t id);
#endif /* DQLITE_ID_H_ */
dqlite-1.16.0/src/leader.c 0000664 0000000 0000000 00000026127 14512203222 0015251 0 ustar 00root root 0000000 0000000 #include
#include "../include/dqlite.h"
#include "./lib/assert.h"
#include "command.h"
#include "id.h"
#include "leader.h"
#include "tracing.h"
#include "vfs.h"
/* Called when a leader exec request terminates and the associated callback can
* be invoked. */
static void leaderExecDone(struct exec *req)
{
tracef("leader exec done id:%" PRIu64, req->id);
req->leader->exec = NULL;
if (req->cb != NULL) {
req->cb(req, req->status);
}
}
/* Open a SQLite connection and set it to leader replication mode. */
static int openConnection(const char *filename,
const char *vfs,
unsigned page_size,
sqlite3 **conn)
{
tracef("open connection filename %s", filename);
char pragma[255];
int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE;
char *msg = NULL;
int rc;
rc = sqlite3_open_v2(filename, conn, flags, vfs);
if (rc != SQLITE_OK) {
tracef("open failed %d", rc);
goto err;
}
/* Enable extended result codes */
rc = sqlite3_extended_result_codes(*conn, 1);
if (rc != SQLITE_OK) {
tracef("extended codes failed %d", rc);
goto err;
}
/* The vfs, db, gateway, and leader code currently assumes that
* each connection will operate on only one DB file/WAL file
* pair. Make sure that the client can't use ATTACH DATABASE to
* break this assumption. We apply the same limit in open_follower_conn
* in db.c.
*
* Note, 0 instead of 1 -- apparently the "initial database" is not
* counted when evaluating this limit. */
sqlite3_limit(*conn, SQLITE_LIMIT_ATTACHED, 0);
/* Set the page size. */
sprintf(pragma, "PRAGMA page_size=%d", page_size);
rc = sqlite3_exec(*conn, pragma, NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("page size set failed %d page size %u", rc, page_size);
goto err;
}
/* Disable syncs. */
rc = sqlite3_exec(*conn, "PRAGMA synchronous=OFF", NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("sync off failed %d", rc);
goto err;
}
/* Set WAL journaling. */
rc = sqlite3_exec(*conn, "PRAGMA journal_mode=WAL", NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("wal on failed %d", rc);
goto err;
}
rc = sqlite3_exec(*conn, "PRAGMA wal_autocheckpoint=0", NULL, NULL,
&msg);
if (rc != SQLITE_OK) {
tracef("wal autocheckpoint off failed %d", rc);
goto err;
}
rc =
sqlite3_db_config(*conn, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, NULL);
if (rc != SQLITE_OK) {
tracef("db config failed %d", rc);
goto err;
}
/* TODO: make setting foreign keys optional. */
rc = sqlite3_exec(*conn, "PRAGMA foreign_keys=1", NULL, NULL, &msg);
if (rc != SQLITE_OK) {
tracef("enable foreign keys failed %d", rc);
goto err;
}
return 0;
err:
if (*conn != NULL) {
sqlite3_close(*conn);
*conn = NULL;
}
if (msg != NULL) {
sqlite3_free(msg);
}
return rc;
}
/* Whether we need to submit a barrier request because there is no transaction
* in progress in the underlying database and the FSM is behind the last log
* index. */
static bool needsBarrier(struct leader *l)
{
return l->db->tx_id == 0 &&
raft_last_applied(l->raft) < raft_last_index(l->raft);
}
int leader__init(struct leader *l, struct db *db, struct raft *raft)
{
tracef("leader init");
int rc;
l->db = db;
l->raft = raft;
rc = openConnection(db->path, db->config->name, db->config->page_size,
&l->conn);
if (rc != 0) {
tracef("open failed %d", rc);
return rc;
}
l->exec = NULL;
l->inflight = NULL;
QUEUE__PUSH(&db->leaders, &l->queue);
return 0;
}
void leader__close(struct leader *l)
{
tracef("leader close");
int rc;
/* TODO: there shouldn't be any ongoing exec request. */
if (l->exec != NULL) {
assert(l->inflight == NULL);
l->exec->status = SQLITE_ERROR;
leaderExecDone(l->exec);
}
rc = sqlite3_close(l->conn);
assert(rc == 0);
QUEUE__REMOVE(&l->queue);
}
/* A checkpoint command that fails to commit is not a huge issue.
* The WAL will not be checkpointed this time around on these nodes,
* a new checkpoint command will be issued once the WAL on the leader reaches
* threshold size again. It's improbable that the WAL in this way could grow
* without bound, it would mean that apply frames commands commit without
* issues, while the checkpoint command would somehow always fail to commit. */
static void leaderCheckpointApplyCb(struct raft_apply *req,
int status,
void *result)
{
(void)result;
raft_free(req);
if (status != 0) {
tracef("checkpoint apply failed %d", status);
}
}
/* Attempt to perform a checkpoint on nodes running a version of dqlite that
* doens't perform autonomous checkpoints. For recent nodes, the checkpoint
* command will just be a no-op.
* This function will run after the WAL might have been checkpointed during a
* call to `apply_frames`.
* */
static void leaderMaybeCheckpointLegacy(struct leader *l)
{
tracef("leader maybe checkpoint legacy");
struct sqlite3_file *wal;
struct raft_buffer buf;
struct command_checkpoint command;
sqlite3_int64 size;
int rv;
/* Get the database file associated with this connection */
rv = sqlite3_file_control(l->conn, "main", SQLITE_FCNTL_JOURNAL_POINTER,
&wal);
assert(rv == SQLITE_OK); /* Should never fail */
rv = wal->pMethods->xFileSize(wal, &size);
assert(rv == SQLITE_OK); /* Should never fail */
/* size of the WAL will be 0 if it has just been checkpointed on this
* leader as a result of running apply_frames. */
if (size != 0) {
return;
}
tracef("issue checkpoint command");
/* Attempt to perfom a checkpoint across nodes that don't perform
* autonomous snapshots. */
command.filename = l->db->filename;
rv = command__encode(COMMAND_CHECKPOINT, &command, &buf);
if (rv != 0) {
tracef("encode failed %d", rv);
return;
}
struct raft_apply *apply = raft_malloc(sizeof(*apply));
if (apply == NULL) {
tracef("raft_malloc - no mem");
goto err_after_buf_alloc;
}
rv = raft_apply(l->raft, apply, &buf, 1, leaderCheckpointApplyCb);
if (rv != 0) {
tracef("raft_apply failed %d", rv);
raft_free(apply);
goto err_after_buf_alloc;
}
return;
err_after_buf_alloc:
raft_free(buf.base);
}
static void leaderApplyFramesCb(struct raft_apply *req,
int status,
void *result)
{
tracef("apply frames cb id:%" PRIu64, idExtract(req->req_id));
struct apply *apply = req->data;
struct leader *l = apply->leader;
if (l == NULL) {
raft_free(apply);
return;
}
(void)result;
if (status != 0) {
tracef("apply frames cb failed status %d", status);
sqlite3_vfs *vfs = sqlite3_vfs_find(l->db->config->name);
switch (status) {
case RAFT_LEADERSHIPLOST:
l->exec->status = SQLITE_IOERR_LEADERSHIP_LOST;
break;
case RAFT_NOSPACE:
l->exec->status = SQLITE_IOERR_WRITE;
break;
case RAFT_SHUTDOWN:
/* If we got here it means we have manually
* fired the apply callback from
* gateway__close(). In this case we don't
* free() the apply object, since it will be
* freed when the callback is fired again by
* raft.
*
* TODO: we should instead make gatewa__close()
* itself asynchronous. */
apply->leader = NULL;
l->exec->status = SQLITE_ABORT;
goto finish;
break;
default:
l->exec->status = SQLITE_IOERR;
break;
}
VfsAbort(vfs, l->db->path);
}
raft_free(apply);
if (status == 0) {
leaderMaybeCheckpointLegacy(l);
}
finish:
l->inflight = NULL;
l->db->tx_id = 0;
leaderExecDone(l->exec);
}
static int leaderApplyFrames(struct exec *req,
dqlite_vfs_frame *frames,
unsigned n)
{
tracef("leader apply frames id:%" PRIu64, req->id);
struct leader *l = req->leader;
struct db *db = l->db;
struct command_frames c;
struct raft_buffer buf;
struct apply *apply;
int rv;
c.filename = db->filename;
c.tx_id = 0;
c.truncate = 0;
c.is_commit = 1;
c.frames.n_pages = (uint32_t)n;
c.frames.page_size = (uint16_t)db->config->page_size;
c.frames.data = frames;
apply = raft_malloc(sizeof *req);
if (apply == NULL) {
tracef("malloc");
rv = DQLITE_NOMEM;
goto err;
}
rv = command__encode(COMMAND_FRAMES, &c, &buf);
if (rv != 0) {
tracef("encode %d", rv);
goto err_after_apply_alloc;
}
apply->leader = req->leader;
apply->req.data = apply;
apply->type = COMMAND_FRAMES;
idSet(apply->req.req_id, req->id);
rv = raft_apply(l->raft, &apply->req, &buf, 1, leaderApplyFramesCb);
if (rv != 0) {
tracef("raft apply failed %d", rv);
goto err_after_command_encode;
}
db->tx_id = 1;
l->inflight = apply;
return 0;
err_after_command_encode:
raft_free(buf.base);
err_after_apply_alloc:
raft_free(apply);
err:
assert(rv != 0);
return rv;
}
static void leaderExecV2(struct exec *req)
{
tracef("leader exec v2 id:%" PRIu64, req->id);
struct leader *l = req->leader;
struct db *db = l->db;
sqlite3_vfs *vfs = sqlite3_vfs_find(db->config->name);
dqlite_vfs_frame *frames;
uint64_t size;
unsigned n;
unsigned i;
int rv;
req->status = sqlite3_step(req->stmt);
rv = VfsPoll(vfs, db->path, &frames, &n);
if (rv != 0 || n == 0) {
tracef("vfs poll");
goto finish;
}
/* Check if the new frames would create an overfull database */
size = VfsDatabaseSize(vfs, db->path, n, db->config->page_size);
if (size > VfsDatabaseSizeLimit(vfs)) {
rv = SQLITE_FULL;
goto abort;
}
rv = leaderApplyFrames(req, frames, n);
if (rv != 0) {
goto abort;
}
for (i = 0; i < n; i++) {
sqlite3_free(frames[i].data);
}
sqlite3_free(frames);
return;
abort:
for (i = 0; i < n; i++) {
sqlite3_free(frames[i].data);
}
sqlite3_free(frames);
VfsAbort(vfs, l->db->path);
finish:
if (rv != 0) {
tracef("exec v2 failed %d", rv);
l->exec->status = rv;
}
leaderExecDone(l->exec);
}
static void execBarrierCb(struct barrier *barrier, int status)
{
tracef("exec barrier cb status %d", status);
struct exec *req = barrier->data;
struct leader *l = req->leader;
if (status != 0) {
l->exec->status = status;
leaderExecDone(l->exec);
return;
}
leaderExecV2(req);
}
int leader__exec(struct leader *l,
struct exec *req,
sqlite3_stmt *stmt,
uint64_t id,
exec_cb cb)
{
tracef("leader exec id:%" PRIu64, id);
int rv;
if (l->exec != NULL) {
tracef("busy");
return SQLITE_BUSY;
}
l->exec = req;
req->leader = l;
req->stmt = stmt;
req->id = id;
req->cb = cb;
req->barrier.data = req;
req->barrier.cb = NULL;
rv = leader__barrier(l, &req->barrier, execBarrierCb);
if (rv != 0) {
l->exec = NULL;
return rv;
}
return 0;
}
static void raftBarrierCb(struct raft_barrier *req, int status)
{
tracef("raft barrier cb status %d", status);
struct barrier *barrier = req->data;
int rv = 0;
if (status != 0) {
if (status == RAFT_LEADERSHIPLOST) {
rv = SQLITE_IOERR_LEADERSHIP_LOST;
} else {
rv = SQLITE_ERROR;
}
}
barrier_cb cb = barrier->cb;
if (cb == NULL) {
tracef("barrier cb already fired");
return;
}
barrier->cb = NULL;
cb(barrier, rv);
}
int leader__barrier(struct leader *l, struct barrier *barrier, barrier_cb cb)
{
tracef("leader barrier");
int rv;
if (!needsBarrier(l)) {
tracef("not needed");
cb(barrier, 0);
return 0;
}
barrier->cb = cb;
barrier->leader = l;
barrier->req.data = barrier;
rv = raft_barrier(l->raft, &barrier->req, raftBarrierCb);
if (rv != 0) {
tracef("raft barrier failed %d", rv);
barrier->req.data = NULL;
barrier->leader = NULL;
barrier->cb = NULL;
return rv;
}
return 0;
}
dqlite-1.16.0/src/leader.h 0000664 0000000 0000000 00000006307 14512203222 0015254 0 ustar 00root root 0000000 0000000 /**
* Track the state of leader connection and execute statements asynchronously.
*/
#ifndef LEADER_H_
#define LEADER_H_
#include
#include
#include
#include "./lib/queue.h"
#include "db.h"
#define SQLITE_IOERR_NOT_LEADER (SQLITE_IOERR | (40 << 8))
#define SQLITE_IOERR_LEADERSHIP_LOST (SQLITE_IOERR | (41 << 8))
struct exec;
struct barrier;
struct leader;
typedef void (*exec_cb)(struct exec *req, int status);
typedef void (*barrier_cb)(struct barrier *req, int status);
/* Wrapper around raft_apply, saving context information. */
struct apply
{
struct raft_apply req; /* Raft apply request */
int status; /* Raft apply result */
struct leader *leader; /* Leader connection that triggered the hook */
int type; /* Command type */
union { /* Command-specific data */
struct
{
bool is_commit;
} frames;
};
};
struct leader
{
struct db *db; /* Database the connection. */
sqlite3 *conn; /* Underlying SQLite connection. */
struct raft *raft; /* Raft instance. */
struct exec *exec; /* Exec request in progress, if any. */
queue queue; /* Prev/next leader, used by struct db. */
struct apply *inflight; /* TODO: make leader__close async */
};
struct barrier
{
void *data;
struct leader *leader;
struct raft_barrier req;
barrier_cb cb;
};
/**
* Asynchronous request to execute a statement.
*/
struct exec
{
void *data;
struct leader *leader;
struct barrier barrier;
sqlite3_stmt *stmt;
uint64_t id;
int status;
queue queue;
exec_cb cb;
};
/**
* Initialize a new leader connection.
*
* This function will start the leader loop coroutine and pause it immediately,
* transfering control back to main coroutine and then opening a new leader
* connection against the given database.
*/
int leader__init(struct leader *l, struct db *db, struct raft *raft);
void leader__close(struct leader *l);
/**
* Submit a request to step a SQLite statement.
*
* The request will be dispatched to the leader loop coroutine, which will be
* resumed and will invoke sqlite_step(). If the statement triggers the
* replication hooks and one or more new Raft log entries need to be appended,
* then the loop coroutine will be paused and control will be transferred back
* to the main coroutine. In this state the leader loop coroutine call stack
* will be "blocked" on the xFrames() replication hook call triggered by the top
* sqlite_step() call. The leader loop coroutine will be resumed once the Raft
* append request completes (either successfully or not) and at that point the
* stack will rewind back to the @sqlite_step() call, returning to the leader
* loop which will then have completed the request and transfer control back to
* the main coroutine, pausing until the next request.
*/
int leader__exec(struct leader *l,
struct exec *req,
sqlite3_stmt *stmt,
uint64_t id,
exec_cb cb);
/**
* Submit a raft barrier request if there is no transaction in progress in the
* underlying database and the FSM is behind the last log index.
*
* Otherwise, just invoke the given @cb immediately.
*/
int leader__barrier(struct leader *l, struct barrier *barrier, barrier_cb cb);
#endif /* LEADER_H_*/
dqlite-1.16.0/src/lib/ 0000775 0000000 0000000 00000000000 14512203222 0014407 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/src/lib/addr.c 0000664 0000000 0000000 00000005133 14512203222 0015467 0 ustar 00root root 0000000 0000000 #include "addr.h"
#include
#include
#include
#include
#include
#include
#include "../../include/dqlite.h"
int AddrParse(const char *input,
struct sockaddr *addr,
socklen_t *addr_len,
const char *service,
int flags)
{
int rv;
char *node = NULL;
size_t input_len = strlen(input);
char c = input[0];
struct sockaddr_un *addr_un;
const char *name, *addr_start, *close_bracket, *colon;
size_t name_len;
struct addrinfo hints, *res;
if (c == '@') {
/* Unix domain address.
* FIXME the use of the "abstract namespace" here is
* Linux-specific */
if (!(flags & DQLITE_ADDR_PARSE_UNIX)) {
return DQLITE_MISUSE;
}
addr_un = (struct sockaddr_un *)addr;
if (*addr_len < sizeof(*addr_un)) {
return DQLITE_ERROR;
}
name = input + 1;
name_len = input_len - 1;
if (name_len == 0) {
/* Autogenerated abstract socket name */
addr_un->sun_family = AF_UNIX;
*addr_len = sizeof(addr_un->sun_family);
return 0;
}
/* Leading null byte, no trailing null byte */
if (name_len + 1 > sizeof(addr_un->sun_path)) {
return DQLITE_ERROR;
}
memset(addr_un->sun_path, 0, sizeof(addr_un->sun_path));
memcpy(addr_un->sun_path + 1, name, name_len);
addr_un->sun_family = AF_UNIX;
*addr_len = (socklen_t)offsetof(struct sockaddr_un, sun_path) +
(socklen_t)name_len + 1;
return 0;
} else if (c == '[') {
/* IPv6 address with port */
addr_start = input + 1;
close_bracket = memchr(input, ']', input_len);
if (!close_bracket) {
return DQLITE_ERROR;
}
colon = close_bracket + 1;
if (*colon != ':') {
return DQLITE_ERROR;
}
service = colon + 1;
node =
strndup(addr_start, (size_t)(close_bracket - addr_start));
} else if (memchr(input, '.', input_len)) {
/* IPv4 address */
colon = memchr(input, ':', input_len);
if (colon) {
service = colon + 1;
node = strndup(input, (size_t)(colon - input));
} else {
node = strdup(input);
}
} else {
/* IPv6 address without port */
node = strdup(input);
}
if (!node) {
return DQLITE_NOMEM;
}
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV;
rv = getaddrinfo(node, service, &hints, &res);
if (rv != 0) {
rv = DQLITE_ERROR;
goto err_after_strdup;
}
if (res->ai_addrlen > *addr_len) {
rv = DQLITE_ERROR;
goto err_after_getaddrinfo;
}
memcpy(addr, res->ai_addr, res->ai_addrlen);
*addr_len = res->ai_addrlen;
err_after_getaddrinfo:
freeaddrinfo(res);
err_after_strdup:
free(node);
return rv;
}
dqlite-1.16.0/src/lib/addr.h 0000664 0000000 0000000 00000001700 14512203222 0015470 0 ustar 00root root 0000000 0000000 #ifndef ADDR_H_
#define ADDR_H_
#include
enum {
/* Parse Unix socket addresses in @ notation */
DQLITE_ADDR_PARSE_UNIX = 1 << 0
};
/** Parse a socket address from the string @input.
*
* On success, the resulting address is placed in @addr, and its size is placed
* in @addr_len. If @addr is not large enough (based on the initial value of
* @addr_len) to hold the result, DQLITE_ERROR is returned.
*
* @service should be a string representing a port number, e.g. "8080".
*
* @flags customizes the behavior of the function. Currently the only flag is
* DQLITE_ADDR_PARSE_UNIX: when this is ORed in @flags, AddrParse will also
* parse Unix socket addresses in the form `@NAME`, where NAME may be empty.
* This creates a socket address in the (Linux-specific) "abstract namespace".
*/
int AddrParse(const char *input,
struct sockaddr *addr,
socklen_t *addr_len,
const char *service,
int flags);
#endif
dqlite-1.16.0/src/lib/assert.h 0000664 0000000 0000000 00000001715 14512203222 0016065 0 ustar 00root root 0000000 0000000 /**
* Define the assert() macro, either as the standard one or the test one.
*/
#ifndef LIB_ASSERT_H_
#define LIB_ASSERT_H_
#if defined(DQLITE_TEST)
#include "../../test/lib/munit.h"
#define assert(expr) munit_assert(expr)
#elif defined(DQLITE_ASSERT_WITH_BACKTRACE)
#include /* for __assert_fail */
#include
#include
#undef assert
#define assert(x) \
do { \
struct backtrace_state *state_; \
if (!(x)) { \
state_ = backtrace_create_state(NULL, 0, NULL, NULL); \
backtrace_print(state_, 0, stderr); \
__assert_fail(#x, __FILE__, __LINE__, __func__); \
} \
} while (0)
#else
#include
#endif
#endif /* LIB_ASSERT_H_ */
dqlite-1.16.0/src/lib/buffer.c 0000664 0000000 0000000 00000002631 14512203222 0016026 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include "buffer.h"
#include "../../include/dqlite.h"
/* How large is the buffer currently */
#define SIZE(B) (B->n_pages * B->page_size)
/* How many remaining bytes the buffer currently */
#define CAP(B) (SIZE(B) - B->offset)
int buffer__init(struct buffer *b)
{
b->page_size = (unsigned)sysconf(_SC_PAGESIZE);
b->n_pages = 1;
b->data = malloc(SIZE(b));
if (b->data == NULL) {
return DQLITE_NOMEM;
}
b->offset = 0;
return 0;
}
void buffer__close(struct buffer *b)
{
free(b->data);
}
/* Ensure that the buffer has at least @size spare bytes */
static inline bool ensure(struct buffer *b, size_t size)
{
void *data;
uint32_t n_pages = b->n_pages;
/* Double the buffer until we have enough capacity */
while (size > CAP(b)) {
b->n_pages *= 2;
}
/* CAP(b) was insufficient */
if (b->n_pages > n_pages) {
data = realloc(b->data, SIZE(b));
if (data == NULL) {
b->n_pages = n_pages;
return false;
}
b->data = data;
}
return true;
}
void *buffer__advance(struct buffer *b, size_t size)
{
void *cursor;
if (!ensure(b, size)) {
return NULL;
}
cursor = buffer__cursor(b, b->offset);
b->offset += size;
return cursor;
}
size_t buffer__offset(struct buffer *b)
{
return b->offset;
}
void *buffer__cursor(struct buffer *b, size_t offset)
{
return b->data + offset;
}
void buffer__reset(struct buffer *b)
{
b->offset = 0;
}
dqlite-1.16.0/src/lib/buffer.h 0000664 0000000 0000000 00000003023 14512203222 0016027 0 ustar 00root root 0000000 0000000 /**
* A dynamic buffer which can grow as needed when writing to it.
*
* The buffer size is always a multiple of the OS virtual memory page size, so
* resizing the buffer *should* not incur in memory being copied.
*
* See https://stackoverflow.com/questions/16765389
*
* TODO: consider using mremap.
*/
#ifndef LIB_BUFFER_H_
#define LIB_BUFFER_H_
#include
#include "../../include/dqlite.h"
struct buffer
{
void *data; /* Allocated buffer */
unsigned page_size; /* Size of an OS page */
unsigned n_pages; /* Number of pages allocated */
size_t offset; /* Next byte to write in the buffer */
};
/**
* Initialize the buffer. It will initially have 1 memory page.
*/
DQLITE_VISIBLE_TO_TESTS int buffer__init(struct buffer *b);
/**
* Release the memory of the buffer.
*/
DQLITE_VISIBLE_TO_TESTS void buffer__close(struct buffer *b);
/**
* Return a write cursor pointing to the next byte to write, ensuring that the
* buffer has at least @size spare bytes.
*
* Return #NULL in case of out-of-memory errors.
*/
DQLITE_VISIBLE_TO_TESTS void *buffer__advance(struct buffer *b, size_t size);
/**
* Return the offset of next byte to write.
*/
DQLITE_VISIBLE_TO_TESTS size_t buffer__offset(struct buffer *b);
/**
* Return a write cursor pointing to the @offset'th byte of the buffer.
*/
DQLITE_VISIBLE_TO_TESTS void *buffer__cursor(struct buffer *b, size_t offset);
/**
* Reset the write offset of the buffer.
*/
DQLITE_VISIBLE_TO_TESTS void buffer__reset(struct buffer *b);
#endif /* LIB_BUFFER_H_ */
dqlite-1.16.0/src/lib/byte.h 0000664 0000000 0000000 00000005527 14512203222 0015534 0 ustar 00root root 0000000 0000000 #ifndef LIB_BYTE_H_
#define LIB_BYTE_H_
#include
#include
#include
#if defined(__cplusplus)
#define DQLITE_INLINE inline
#else
#define DQLITE_INLINE static inline
#endif
#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__)
#define DQLITE_LITTLE_ENDIAN
#elif defined(__BYTE_ORDER__) && (__BYTE_ORDER__ == __ORDER_BIG_ENDIAN__)
#define DQLITE_BIG_ENDIAN
#endif
#if defined(__GNUC__) && __GNUC__ >= 4 && __GNUC_MINOR__ >= 8
#define DQLITE_HAVE_BSWAP
#endif
/* Flip a 16-bit number to little-endian byte order */
DQLITE_INLINE uint16_t ByteFlipLe16(uint16_t v)
{
#if defined(DQLITE_LITTLE_ENDIAN)
return v;
#elif defined(DQLITE_BIG_ENDIAN) && defined(DQLITE_HAVE_BSWAP)
return __builtin_bswap16(v);
#else
union {
uint16_t u;
uint8_t v[2];
} s;
s.v[0] = (uint8_t)v;
s.v[1] = (uint8_t)(v >> 8);
return s.u;
#endif
}
/* Flip a 32-bit number to little-endian byte order */
DQLITE_INLINE uint32_t ByteFlipLe32(uint32_t v)
{
#if defined(DQLITE_LITTLE_ENDIAN)
return v;
#elif defined(DQLITE_BIG_ENDIAN) && defined(DQLITE_HAVE_BSWAP)
return __builtin_bswap32(v);
#else
union {
uint32_t u;
uint8_t v[4];
} s;
s.v[0] = (uint8_t)v;
s.v[1] = (uint8_t)(v >> 8);
s.v[2] = (uint8_t)(v >> 16);
s.v[3] = (uint8_t)(v >> 24);
return s.u;
#endif
}
/* Flip a 64-bit number to little-endian byte order */
DQLITE_INLINE uint64_t ByteFlipLe64(uint64_t v)
{
#if defined(DQLITE_LITTLE_ENDIAN)
return v;
#elif defined(DQLITE_BIG_ENDIAN) && defined(DQLITE_HAVE_BSWAP)
return __builtin_bswap64(v);
#else
union {
uint64_t u;
uint8_t v[8];
} s;
s.v[0] = (uint8_t)v;
s.v[1] = (uint8_t)(v >> 8);
s.v[2] = (uint8_t)(v >> 16);
s.v[3] = (uint8_t)(v >> 24);
s.v[4] = (uint8_t)(v >> 32);
s.v[5] = (uint8_t)(v >> 40);
s.v[6] = (uint8_t)(v >> 48);
s.v[7] = (uint8_t)(v >> 56);
return s.u;
#endif
}
/* -Wconversion before GCC 10 is overly sensitive. */
#if defined(__GNUC__) && __GNUC__ < 10
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wconversion"
#endif
DQLITE_INLINE uint16_t ByteGetBe16(const uint8_t *buf)
{
uint16_t x = buf[0];
uint16_t y = buf[1];
x <<= 8;
return x | y;
}
DQLITE_INLINE uint32_t ByteGetBe32(const uint8_t *buf)
{
uint32_t w = buf[0];
uint32_t x = buf[1];
uint32_t y = buf[2];
uint32_t z = buf[3];
w <<= 24;
x <<= 16;
y <<= 8;
return w | x | y | z;
}
DQLITE_INLINE void BytePutBe32(uint32_t v, uint8_t *buf)
{
buf[0] = (uint8_t)(v >> 24);
buf[1] = (uint8_t)(v >> 16);
buf[2] = (uint8_t)(v >> 8);
buf[3] = (uint8_t)v;
}
/**
* Add padding to size if it's not a multiple of 8. E.g. if 11 is passed, 16 is
* returned.
*/
DQLITE_INLINE size_t BytePad64(size_t size)
{
size_t rest = size % sizeof(uint64_t);
if (rest != 0) {
size += sizeof(uint64_t) - rest;
}
return size;
}
#if defined(__GNUC__) && __GNUC__ < 10
#pragma GCC diagnostic pop
#endif
#endif /* LIB_BYTE_H_ */
dqlite-1.16.0/src/lib/fs.c 0000664 0000000 0000000 00000001545 14512203222 0015170 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include "../tracing.h"
#include "fs.h"
int FsEnsureDir(const char *path)
{
int rv;
struct stat st = {0};
rv = stat(path, &st);
if (rv == 0) {
if (!S_ISDIR(st.st_mode)) {
tracef("%s is not a directory", path);
return -1;
}
}
/* Directory does not exist */
if (rv == -1) {
return mkdir(path, 0755);
}
return 0;
}
static int fsRemoveDirFilesNftwFn(const char *path,
const struct stat *sb,
int type,
struct FTW *ftwb)
{
int rv;
(void)sb;
(void)type;
(void)ftwb;
rv = 0;
/* Don't remove directory */
if (S_ISREG(sb->st_mode)) {
rv = remove(path);
}
return rv;
}
int FsRemoveDirFiles(const char *path)
{
int rv;
rv = nftw(path, fsRemoveDirFilesNftwFn, 10,
FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
return rv;
}
dqlite-1.16.0/src/lib/fs.h 0000664 0000000 0000000 00000000374 14512203222 0015174 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_LIB_FS_H
#define DQLITE_LIB_FS_H
/* Create a directory if it does not already exist. */
int FsEnsureDir(const char *path);
/* Removes all files from a directory. */
int FsRemoveDirFiles(const char *path);
#endif /* DQLITE_LIB_FS_H */
dqlite-1.16.0/src/lib/queue.h 0000664 0000000 0000000 00000003417 14512203222 0015711 0 ustar 00root root 0000000 0000000 #ifndef LIB_QUEUE_H_
#define LIB_QUEUE_H_
#include
typedef void *queue[2];
/* Private macros. */
#define QUEUE__NEXT(q) (*(queue **)&((*(q))[0]))
#define QUEUE__PREV(q) (*(queue **)&((*(q))[1]))
#define QUEUE__PREV_NEXT(q) (QUEUE__NEXT(QUEUE__PREV(q)))
#define QUEUE__NEXT_PREV(q) (QUEUE__PREV(QUEUE__NEXT(q)))
/**
* Initialize an empty queue.
*/
#define QUEUE__INIT(q) \
{ \
QUEUE__NEXT(q) = (q); \
QUEUE__PREV(q) = (q); \
}
/**
* Return true if the queue has no element.
*/
#define QUEUE__IS_EMPTY(q) ((const queue *)(q) == (const queue *)QUEUE__NEXT(q))
/**
* Insert an element at the back of a queue.
*/
#define QUEUE__PUSH(q, e) \
{ \
QUEUE__NEXT(e) = (q); \
QUEUE__PREV(e) = QUEUE__PREV(q); \
QUEUE__PREV_NEXT(e) = (e); \
QUEUE__PREV(q) = (e); \
}
/**
* Remove the given element from the queue. Any element can be removed at any
* time.
*/
#define QUEUE__REMOVE(e) \
{ \
QUEUE__PREV_NEXT(e) = QUEUE__NEXT(e); \
QUEUE__NEXT_PREV(e) = QUEUE__PREV(e); \
}
/**
* Return the element at the front of the queue.
*/
#define QUEUE__HEAD(q) (QUEUE__NEXT(q))
/**
* Return the element at the back of the queue.
*/
#define QUEUE__TAIL(q) (QUEUE__PREV(q))
/**
* Iternate over the element of a queue.
*
* Mutating the queue while iterating results in undefined behavior.
*/
#define QUEUE__FOREACH(q, e) \
for ((q) = QUEUE__NEXT(e); (q) != (e); (q) = QUEUE__NEXT(q))
/**
* Return the structure holding the given element.
*/
#define QUEUE__DATA(e, type, field) \
((type *)(uintptr_t)((char *)(e)-offsetof(type, field)))
#endif /* LIB_QUEUE_H_*/
dqlite-1.16.0/src/lib/registry.h 0000664 0000000 0000000 00000034244 14512203222 0016437 0 ustar 00root root 0000000 0000000 #ifndef LIB_REGISTRY_H_
#define LIB_REGISTRY_H_
#include
#include
#include
#include
#include "../../include/dqlite.h"
#include "assert.h"
#define DQLITE_NOTFOUND 1002
/**
* Define a type-safe registry able to allocate and lookup items of a given
* type.
*
* The item TYPE is required to implement three methods: TYPE##_init,
* TYPE##_close and TYPE##_hash.
*/
#define REGISTRY(NAME, TYPE) \
\
struct NAME \
{ \
struct TYPE **buf; /* Array of registry item slots */ \
size_t len; /* Index of the highest used slot */ \
size_t cap; /* Total number of slots */ \
}; \
\
/* Initialize the registry. */ \
void NAME##_init(struct NAME *r); \
\
/* Close the registry. */ \
void NAME##_close(struct NAME *r); \
\
/* Add an item to the registry. \
* \
* Return a pointer to a newly allocated an initialized item. \
* The "id" field of the item will be set to a unique value \
* identifying the item in the registry. */ \
int NAME##_add(struct NAME *r, struct TYPE **item); \
\
/* Given its ID, retrieve an item previously added to the \
* registry. */ \
struct TYPE *NAME##_get(struct NAME *r, size_t id); \
\
/* Get the index of the first item matching the given hash key. Return \
* 0 on success and DQLITE_NOTFOUND otherwise. */ \
int NAME##_idx(struct NAME *r, const char *key, size_t *i); \
\
/* Delete a previously added item. */ \
int NAME##_del(struct NAME *r, struct TYPE *item)
/**
* Define the methods of a registry
*/
#define REGISTRY_METHODS(NAME, TYPE) \
void NAME##_init(struct NAME *r) \
{ \
assert(r != NULL); \
\
r->buf = NULL; \
r->len = 0; \
r->cap = 0; \
} \
\
void NAME##_close(struct NAME *r) \
{ \
size_t i; \
struct TYPE *item; \
\
assert(r != NULL); \
\
/* Loop through all items currently in the registry, \
* and close them. */ \
for (i = 0; i < r->len; i++) { \
item = *(r->buf + i); \
/* Some slots may have been deleted, so we need \
* to check if the slot is actually used. */ \
if (item != NULL) { \
TYPE##_close(item); \
sqlite3_free(item); \
} \
} \
\
r->len = 0; \
r->cap = 0; \
if (r->buf != NULL) { \
sqlite3_free(r->buf); \
r->buf = NULL; \
} \
} \
\
int NAME##_add(struct NAME *r, struct TYPE **item) \
{ \
struct TYPE **buf; \
size_t cap; \
size_t i; \
\
assert(r != NULL); \
assert(item != NULL); \
\
/* Check if there is an unllocated slot. */ \
for (i = 0; i < r->len; i++) { \
if (*(r->buf + i) == NULL) { \
goto ok_slot; \
} \
} \
\
/* There are no unallocated slots. */ \
assert(i == r->len); \
\
/* If we are full, then double the capacity. */ \
if (r->len + 1 > r->cap) { \
cap = (r->cap == 0) ? 1 : r->cap * 2; \
buf = sqlite3_realloc(r->buf, \
(int)(cap * sizeof(*r->buf))); \
if (buf == NULL) { \
return DQLITE_NOMEM; \
} \
r->buf = buf; \
r->cap = cap; \
} \
r->len++; \
\
ok_slot: \
assert(i < r->len); \
\
/* Allocate and initialize the new item */ \
*item = sqlite3_malloc(sizeof **item); \
if (*item == NULL) \
return DQLITE_NOMEM; \
\
(*item)->id = i; \
\
TYPE##_init(*item); \
\
/* Save the item in its registry slot */ \
*(r->buf + i) = *item; \
\
return 0; \
} \
\
struct TYPE *NAME##_get(struct NAME *r, size_t id) \
{ \
struct TYPE *item; \
size_t i = id; \
\
assert(r != NULL); \
\
if (i >= r->len) { \
return NULL; \
} \
\
item = *(r->buf + i); \
\
assert(item->id == id); \
\
return item; \
} \
\
int NAME##_idx(struct NAME *r, const char *key, size_t *i) \
{ \
struct TYPE *item; \
\
assert(r != NULL); \
assert(key != NULL); \
assert(i != NULL); \
\
for (*i = 0; *i < r->len; (*i)++) { \
const char *hash; \
\
item = *(r->buf + *i); \
\
if (item == NULL) { \
continue; \
} \
\
hash = TYPE##_hash(item); \
\
if (hash != NULL && strcmp(hash, key) == 0) { \
return 0; \
} \
} \
\
return DQLITE_NOTFOUND; \
} \
\
int NAME##_del(struct NAME *r, struct TYPE *item) \
{ \
struct TYPE **buf; \
size_t cap; \
size_t i = item->id; \
\
assert(r != NULL); \
\
if (i >= r->len) { \
return DQLITE_NOTFOUND; \
} \
\
/* Check that the item address actually matches the one \
* we have in the registry */ \
if (*(r->buf + i) != item) { \
return DQLITE_NOTFOUND; \
} \
\
TYPE##_close(item); \
sqlite3_free(item); \
\
*(r->buf + i) = NULL; \
\
/* If this was the last item in the registry buffer, \
* decrease the length. */ \
if (i == r->len - 1) { \
r->len--; \
} \
\
/* If the new length is less than half of the capacity, \
* try to shrink the registry. */ \
if (r->len < (r->cap / 2)) { \
cap = r->cap / 2; \
buf = sqlite3_realloc(r->buf, \
(int)(cap * sizeof *r->buf)); \
if (buf != NULL) { \
r->buf = buf; \
r->cap = cap; \
} \
} \
\
return 0; \
}
#endif /* LIB_REGISTRY_H_ */
dqlite-1.16.0/src/lib/serialize.h 0000664 0000000 0000000 00000017552 14512203222 0016561 0 ustar 00root root 0000000 0000000 #ifndef LIB_SERIALIZE_H_
#define LIB_SERIALIZE_H_
#include
#include
#include
#include "../../include/dqlite.h"
#include "assert.h"
#include "byte.h"
#define DQLITE_PARSE 1005
/**
* The size in bytes of a single serialized word.
*/
#define SERIALIZE__WORD_SIZE 8
/* We rely on the size of double to be 64 bit, since that's what is sent over
* the wire.
*
* See https://stackoverflow.com/questions/752309/ensuring-c-doubles-are-64-bits
*/
#ifndef __STDC_IEC_559__
#if __SIZEOF_DOUBLE__ != 8
#error "Requires IEEE 754 floating point!"
#endif
#endif
#ifdef static_assert
static_assert(sizeof(double) == sizeof(uint64_t),
"Size of 'double' is not 64 bits");
#endif
/**
* Basic type aliases to used by macro-based processing.
*/
typedef const char *text_t;
typedef double float_t;
typedef uv_buf_t blob_t;
/**
* Cursor to progressively read a buffer.
*/
struct cursor
{
const void *p; /* Next byte to read */
size_t cap; /* Number of bytes left in the buffer */
};
/**
* Define a serializable struct.
*
* NAME: Name of the structure which will be defined.
* FIELDS: List of X-based macros defining the fields in the schema, in the form
* of X(KIND, NAME, ##__VA_ARGS__). E.g. X(uint64, id, ##__VA_ARGS__).
*
* A new struct called NAME will be defined, along with sizeof, encode and
* decode functions.
*/
#define SERIALIZE__DEFINE(NAME, FIELDS) \
SERIALIZE__DEFINE_STRUCT(NAME, FIELDS); \
SERIALIZE__DEFINE_METHODS(NAME, FIELDS)
#define SERIALIZE__DEFINE_STRUCT(NAME, FIELDS) \
struct NAME \
{ \
FIELDS(SERIALIZE__DEFINE_FIELD) \
}
#define SERIALIZE__DEFINE_METHODS(NAME, FIELDS) \
size_t NAME##__sizeof(const struct NAME *p); \
void NAME##__encode(const struct NAME *p, void **cursor); \
int NAME##__decode(struct cursor *cursor, struct NAME *p)
/* Define a single field in serializable struct.
*
* KIND: Type code (e.g. uint64, text, etc).
* MEMBER: Field name. */
#define SERIALIZE__DEFINE_FIELD(KIND, MEMBER) KIND##_t MEMBER;
/**
* Implement the sizeof, encode and decode function of a serializable struct.
*/
#define SERIALIZE__IMPLEMENT(NAME, FIELDS) \
size_t NAME##__sizeof(const struct NAME *p) \
{ \
size_t size = 0; \
FIELDS(SERIALIZE__SIZEOF_FIELD, p); \
return size; \
} \
void NAME##__encode(const struct NAME *p, void **cursor) \
{ \
FIELDS(SERIALIZE__ENCODE_FIELD, p, cursor); \
} \
int NAME##__decode(struct cursor *cursor, struct NAME *p) \
{ \
int rc; \
FIELDS(SERIALIZE__DECODE_FIELD, p, cursor); \
return 0; \
}
#define SERIALIZE__SIZEOF_FIELD(KIND, MEMBER, P) \
size += KIND##__sizeof(&((P)->MEMBER));
#define SERIALIZE__ENCODE_FIELD(KIND, MEMBER, P, CURSOR) \
KIND##__encode(&((P)->MEMBER), CURSOR);
#define SERIALIZE__DECODE_FIELD(KIND, MEMBER, P, CURSOR) \
rc = KIND##__decode(CURSOR, &((P)->MEMBER)); \
if (rc != 0) { \
return rc; \
}
DQLITE_INLINE size_t uint8__sizeof(const uint8_t *value)
{
(void)value;
return sizeof(uint8_t);
}
DQLITE_INLINE size_t uint16__sizeof(const uint16_t *value)
{
(void)value;
return sizeof(uint16_t);
}
DQLITE_INLINE size_t uint32__sizeof(const uint32_t *value)
{
(void)value;
return sizeof(uint32_t);
}
DQLITE_INLINE size_t uint64__sizeof(const uint64_t *value)
{
(void)value;
return sizeof(uint64_t);
}
DQLITE_INLINE size_t int64__sizeof(const int64_t *value)
{
(void)value;
return sizeof(int64_t);
}
DQLITE_INLINE size_t float__sizeof(const float_t *value)
{
(void)value;
return sizeof(double);
}
DQLITE_INLINE size_t text__sizeof(const text_t *value)
{
return BytePad64(strlen(*value) + 1);
}
DQLITE_INLINE size_t blob__sizeof(const blob_t *value)
{
/* length + data */
return sizeof(uint64_t) + BytePad64(value->len);
}
DQLITE_INLINE void uint8__encode(const uint8_t *value, void **cursor)
{
*(uint8_t *)(*cursor) = *value;
*cursor += sizeof(uint8_t);
}
DQLITE_INLINE void uint16__encode(const uint16_t *value, void **cursor)
{
*(uint16_t *)(*cursor) = ByteFlipLe16(*value);
*cursor += sizeof(uint16_t);
}
DQLITE_INLINE void uint32__encode(const uint32_t *value, void **cursor)
{
*(uint32_t *)(*cursor) = ByteFlipLe32(*value);
*cursor += sizeof(uint32_t);
}
DQLITE_INLINE void uint64__encode(const uint64_t *value, void **cursor)
{
*(uint64_t *)(*cursor) = ByteFlipLe64(*value);
*cursor += sizeof(uint64_t);
}
DQLITE_INLINE void int64__encode(const int64_t *value, void **cursor)
{
*(int64_t *)(*cursor) = (int64_t)ByteFlipLe64((uint64_t)*value);
*cursor += sizeof(int64_t);
}
DQLITE_INLINE void float__encode(const float_t *value, void **cursor)
{
*(uint64_t *)(*cursor) = ByteFlipLe64(*(uint64_t *)value);
*cursor += sizeof(uint64_t);
}
DQLITE_INLINE void text__encode(const text_t *value, void **cursor)
{
size_t len = BytePad64(strlen(*value) + 1);
memset(*cursor, 0, len);
strcpy(*cursor, *value);
*cursor += len;
}
DQLITE_INLINE void blob__encode(const blob_t *value, void **cursor)
{
size_t len = BytePad64(value->len);
uint64_t value_len = value->len;
uint64__encode(&value_len, cursor);
memcpy(*cursor, value->base, value->len);
*cursor += len;
}
DQLITE_INLINE int uint8__decode(struct cursor *cursor, uint8_t *value)
{
size_t n = sizeof(uint8_t);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
*value = *(uint8_t *)cursor->p;
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int uint16__decode(struct cursor *cursor, uint16_t *value)
{
size_t n = sizeof(uint16_t);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
*value = ByteFlipLe16(*(uint16_t *)cursor->p);
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int uint32__decode(struct cursor *cursor, uint32_t *value)
{
size_t n = sizeof(uint32_t);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
*value = ByteFlipLe32(*(uint32_t *)cursor->p);
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int uint64__decode(struct cursor *cursor, uint64_t *value)
{
size_t n = sizeof(uint64_t);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
*value = ByteFlipLe64(*(uint64_t *)cursor->p);
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int int64__decode(struct cursor *cursor, int64_t *value)
{
size_t n = sizeof(int64_t);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
*value = (int64_t)ByteFlipLe64((uint64_t) * (int64_t *)cursor->p);
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int float__decode(struct cursor *cursor, float_t *value)
{
size_t n = sizeof(double);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
*(uint64_t *)value = ByteFlipLe64(*(uint64_t *)cursor->p);
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int text__decode(struct cursor *cursor, text_t *value)
{
/* Find the terminating null byte of the next string, if any. */
size_t len = strnlen(cursor->p, cursor->cap);
size_t n;
if (len == cursor->cap) {
return DQLITE_PARSE;
}
*value = cursor->p;
n = BytePad64(strlen(*value) + 1);
cursor->p += n;
cursor->cap -= n;
return 0;
}
DQLITE_INLINE int blob__decode(struct cursor *cursor, blob_t *value)
{
uint64_t len;
size_t n;
int rv;
rv = uint64__decode(cursor, &len);
if (rv != 0) {
return rv;
}
n = BytePad64((size_t)len);
if (n > cursor->cap) {
return DQLITE_PARSE;
}
value->base = (char *)cursor->p;
value->len = (size_t)len;
cursor->p += n;
cursor->cap -= n;
return 0;
}
#endif /* LIB_SERIALIZE_H_ */
dqlite-1.16.0/src/lib/transport.c 0000664 0000000 0000000 00000007004 14512203222 0016610 0 ustar 00root root 0000000 0000000 #include
#include "../../include/dqlite.h"
#include "assert.h"
#include "transport.h"
/* Called to allocate a buffer for the next stream read. */
static void alloc_cb(uv_handle_t *stream, size_t suggested_size, uv_buf_t *buf)
{
struct transport *t;
(void)suggested_size;
t = stream->data;
assert(t->read.base != NULL);
assert(t->read.len > 0);
*buf = t->read;
}
/* Invoke the read callback. */
static void read_done(struct transport *t, ssize_t status)
{
transport_read_cb cb;
int rv;
rv = uv_read_stop(t->stream);
assert(rv == 0);
cb = t->read_cb;
assert(cb != NULL);
t->read_cb = NULL;
t->read.base = NULL;
t->read.len = 0;
cb(t, (int)status);
}
static void read_cb(uv_stream_t *stream, ssize_t nread, const uv_buf_t *buf)
{
struct transport *t;
(void)buf;
t = stream->data;
if (nread > 0) {
size_t n = (size_t)nread;
/* We shouldn't have read more data than the pending amount. */
assert(n <= t->read.len);
/* Advance the read window */
t->read.base += n;
t->read.len -= n;
/* If there's more data to read in order to fill the current
* read buffer, just return, we'll be invoked again. */
if (t->read.len > 0) {
return;
}
/* Read completed, invoke the callback. */
read_done(t, 0);
return;
}
assert(nread <= 0);
if (nread == 0) {
/* Empty read */
return;
}
assert(nread < 0);
/* Failure. */
read_done(t, nread);
}
int transport__stream(struct uv_loop_s *loop,
int fd,
struct uv_stream_s **stream)
{
struct uv_pipe_s *pipe;
struct uv_tcp_s *tcp;
int rv;
switch (uv_guess_handle(fd)) {
case UV_TCP:
tcp = raft_malloc(sizeof *tcp);
if (tcp == NULL) {
return DQLITE_NOMEM;
}
rv = uv_tcp_init(loop, tcp);
assert(rv == 0);
rv = uv_tcp_open(tcp, fd);
if (rv != 0) {
raft_free(tcp);
return TRANSPORT__BADSOCKET;
}
*stream = (struct uv_stream_s *)tcp;
break;
case UV_NAMED_PIPE:
pipe = raft_malloc(sizeof *pipe);
if (pipe == NULL) {
return DQLITE_NOMEM;
}
rv = uv_pipe_init(loop, pipe, 0);
assert(rv == 0);
rv = uv_pipe_open(pipe, fd);
if (rv != 0) {
raft_free(pipe);
return TRANSPORT__BADSOCKET;
}
*stream = (struct uv_stream_s *)pipe;
break;
default:
return TRANSPORT__BADSOCKET;
};
return 0;
}
int transport__init(struct transport *t, struct uv_stream_s *stream)
{
t->stream = stream;
t->stream->data = t;
t->read.base = NULL;
t->read.len = 0;
t->write.data = t;
t->read_cb = NULL;
t->write_cb = NULL;
t->close_cb = NULL;
return 0;
}
static void close_cb(uv_handle_t *handle)
{
struct transport *t = handle->data;
raft_free(t->stream);
if (t->close_cb != NULL) {
t->close_cb(t);
}
}
void transport__close(struct transport *t, transport_close_cb cb)
{
assert(t->close_cb == NULL);
t->close_cb = cb;
uv_close((uv_handle_t *)t->stream, close_cb);
}
int transport__read(struct transport *t, uv_buf_t *buf, transport_read_cb cb)
{
int rv;
assert(t->read.base == NULL);
assert(t->read.len == 0);
t->read = *buf;
t->read_cb = cb;
rv = uv_read_start(t->stream, alloc_cb, read_cb);
if (rv != 0) {
return DQLITE_ERROR;
}
return 0;
}
static void write_cb(uv_write_t *req, int status)
{
struct transport *t = req->data;
transport_write_cb cb = t->write_cb;
assert(cb != NULL);
t->write_cb = NULL;
cb(t, status);
}
int transport__write(struct transport *t, uv_buf_t *buf, transport_write_cb cb)
{
int rv;
assert(t->write_cb == NULL);
t->write_cb = cb;
rv = uv_write(&t->write, t->stream, buf, 1, write_cb);
if (rv != 0) {
return rv;
}
return 0;
}
dqlite-1.16.0/src/lib/transport.h 0000664 0000000 0000000 00000003223 14512203222 0016614 0 ustar 00root root 0000000 0000000 /**
* Asynchronously read and write buffer from and to the network.
*/
#ifndef LIB_TRANSPORT_H_
#define LIB_TRANSPORT_H_
#include
#define TRANSPORT__BADSOCKET 1000
/**
* Callbacks.
*/
struct transport;
typedef void (*transport_read_cb)(struct transport *t, int status);
typedef void (*transport_write_cb)(struct transport *t, int status);
typedef void (*transport_close_cb)(struct transport *t);
/**
* Light wrapper around a libuv stream handle, providing a more convenient way
* to read a certain amount of bytes.
*/
struct transport
{
void *data; /* User defined */
struct uv_stream_s *stream; /* Data stream */
uv_buf_t read; /* Read buffer */
uv_write_t write; /* Write request */
transport_read_cb read_cb; /* Read callback */
transport_write_cb write_cb; /* Write callback */
transport_close_cb close_cb; /* Close callback */
};
/**
* Initialize a transport of the appropriate type (TCP or PIPE) attached to the
* given file descriptor.
*/
int transport__init(struct transport *t, struct uv_stream_s *stream);
/**
* Start closing by the transport.
*/
void transport__close(struct transport *t, transport_close_cb cb);
/**
* Read from the transport file descriptor until the given buffer is full.
*/
int transport__read(struct transport *t, uv_buf_t *buf, transport_read_cb cb);
/**
* Write the given buffer to the transport.
*/
int transport__write(struct transport *t, uv_buf_t *buf, transport_write_cb cb);
/* Create an UV stream object from the given fd. */
int transport__stream(struct uv_loop_s *loop,
int fd,
struct uv_stream_s **stream);
#endif /* LIB_TRANSPORT_H_ */
dqlite-1.16.0/src/logger.c 0000664 0000000 0000000 00000001621 14512203222 0015264 0 ustar 00root root 0000000 0000000 #include
#include
#include "logger.h"
#define EMIT_BUF_LEN 1024
void loggerDefaultEmit(void *data, int level, const char *fmt, va_list args)
{
char buf[EMIT_BUF_LEN];
char *cursor = buf;
size_t n;
(void)data;
/* First, render the logging level. */
switch (level) {
case DQLITE_DEBUG:
sprintf(cursor, "[DEBUG]: ");
break;
case DQLITE_INFO:
sprintf(cursor, "[INFO ]: ");
break;
case DQLITE_WARN:
sprintf(cursor, "[WARN ]: ");
break;
case DQLITE_LOG_ERROR:
sprintf(cursor, "[ERROR]: ");
break;
default:
sprintf(cursor, "[ ]: ");
break;
};
cursor = buf + strlen(buf);
/* Then render the message, possibly truncating it. */
n = EMIT_BUF_LEN - strlen(buf) - 1;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
vsnprintf(cursor, n, fmt, args);
#pragma GCC diagnostic pop
fprintf(stderr, "%s\n", buf);
}
dqlite-1.16.0/src/logger.h 0000664 0000000 0000000 00000001517 14512203222 0015275 0 ustar 00root root 0000000 0000000 #ifndef LOGGER_H_
#define LOGGER_H_
#include
#include "../include/dqlite.h"
/* Log levels */
enum { DQLITE_DEBUG = 0, DQLITE_INFO, DQLITE_WARN, DQLITE_LOG_ERROR };
/* Function to emit log messages. */
typedef void (*dqlite_emit)(void *data,
int level,
const char *fmt,
va_list args);
struct logger
{
void *data;
dqlite_emit emit;
};
/* Default implementation of dqlite_emit, using stderr. */
void loggerDefaultEmit(void *data, int level, const char *fmt, va_list args);
/* Emit a log message with a certain level. */
/* #define debugf(L, FORMAT, ...) \ */
/* logger__emit(L, DQLITE_DEBUG, FORMAT, ##__VA_ARGS__) */
#define debugf(C, FORMAT, ...) \
C->gateway.raft->io->emit(C->gateway.raft->io, RAFT_DEBUG, FORMAT, \
##__VA_ARGS__)
#endif /* LOGGER_H_ */
dqlite-1.16.0/src/message.c 0000664 0000000 0000000 00000000076 14512203222 0015434 0 ustar 00root root 0000000 0000000 #include "message.h"
SERIALIZE__IMPLEMENT(message, MESSAGE);
dqlite-1.16.0/src/message.h 0000664 0000000 0000000 00000000566 14512203222 0015445 0 ustar 00root root 0000000 0000000 #ifndef MESSAGE_H_
#define MESSAGE_H_
#include "lib/serialize.h"
/**
* Metadata about an incoming or outgoing RPC message.
*/
#define MESSAGE(X, ...) \
X(uint32, words, ##__VA_ARGS__) \
X(uint8, type, ##__VA_ARGS__) \
X(uint8, schema, ##__VA_ARGS__) \
X(uint16, extra, ##__VA_ARGS__)
SERIALIZE__DEFINE(message, MESSAGE);
#endif /* MESSAGE_H_x */
dqlite-1.16.0/src/metrics.c 0000664 0000000 0000000 00000000270 14512203222 0015452 0 ustar 00root root 0000000 0000000 #include
#include "./lib/assert.h"
#include "metrics.h"
void dqlite__metrics_init(struct dqlite__metrics *m)
{
assert(m != NULL);
m->requests = 0;
m->duration = 0;
}
dqlite-1.16.0/src/metrics.h 0000664 0000000 0000000 00000001003 14512203222 0015452 0 ustar 00root root 0000000 0000000 /******************************************************************************
*
* Collect various performance metrics.
*
*****************************************************************************/
#ifndef DQLITE_METRICS_H
#define DQLITE_METRICS_H
#include
struct dqlite__metrics
{
uint64_t requests; /* Total number of requests served. */
uint64_t duration; /* Total time spent to server requests. */
};
void dqlite__metrics_init(struct dqlite__metrics *m);
#endif /* DQLITE_METRICS_H */
dqlite-1.16.0/src/protocol.h 0000664 0000000 0000000 00000005035 14512203222 0015656 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_PROTOCOL_H_
#define DQLITE_PROTOCOL_H_
/* Special datatypes */
#define DQLITE_UNIXTIME 9
#define DQLITE_ISO8601 10
#define DQLITE_BOOLEAN 11
#define DQLITE_PROTO 1001 /* Protocol error */
/* Role codes */
enum { DQLITE_VOTER, DQLITE_STANDBY, DQLITE_SPARE };
/* Current protocol version */
#define DQLITE_PROTOCOL_VERSION 1
/* Legacly pre-1.0 version. */
#define DQLITE_PROTOCOL_VERSION_LEGACY 0x86104dd760433fe5
/* Special value indicating that a batch of rows is over, but there are more. */
#define DQLITE_RESPONSE_ROWS_PART 0xeeeeeeeeeeeeeeee
/* Special value indicating that the result set is complete. */
#define DQLITE_RESPONSE_ROWS_DONE 0xffffffffffffffff
/* Request types */
enum {
DQLITE_REQUEST_LEADER,
DQLITE_REQUEST_CLIENT,
DQLITE_REQUEST_HEARTBEAT,
DQLITE_REQUEST_OPEN,
DQLITE_REQUEST_PREPARE,
DQLITE_REQUEST_EXEC,
DQLITE_REQUEST_QUERY,
DQLITE_REQUEST_FINALIZE,
DQLITE_REQUEST_EXEC_SQL,
DQLITE_REQUEST_QUERY_SQL,
DQLITE_REQUEST_INTERRUPT,
DQLITE_REQUEST_CONNECT,
DQLITE_REQUEST_ADD,
/* The PROMOTE and ASSIGN requests share a type tag. We expose it under
* two names here to facilitate the macro shenanigans in request.h. */
DQLITE_REQUEST_PROMOTE_OR_ASSIGN,
DQLITE_REQUEST_ASSIGN = DQLITE_REQUEST_PROMOTE_OR_ASSIGN,
DQLITE_REQUEST_REMOVE,
DQLITE_REQUEST_DUMP,
DQLITE_REQUEST_CLUSTER,
DQLITE_REQUEST_TRANSFER,
DQLITE_REQUEST_DESCRIBE,
DQLITE_REQUEST_WEIGHT
};
#define DQLITE_REQUEST_CLUSTER_FORMAT_V0 0 /* ID and address */
#define DQLITE_REQUEST_CLUSTER_FORMAT_V1 1 /* ID, address and role */
#define DQLITE_REQUEST_DESCRIBE_FORMAT_V0 0 /* Failure domain and weight */
/* These apply to REQUEST_EXEC, REQUEST_EXEC_SQL, REQUEST_QUERY, and
* REQUEST_QUERY_SQL. */
#define DQLITE_REQUEST_PARAMS_SCHEMA_V0 0 /* One-byte params count */
#define DQLITE_REQUEST_PARAMS_SCHEMA_V1 1 /* Four-byte params count */
/* These apply to REQUEST_PREPARE and RESPONSE_STMT. */
/* At most one statement in request, no tail offset in response */
#define DQLITE_PREPARE_STMT_SCHEMA_V0 0
/* Any number of statements in request, tail offset in response */
#define DQLITE_PREPARE_STMT_SCHEMA_V1 1
/* Response types */
enum {
DQLITE_RESPONSE_FAILURE,
DQLITE_RESPONSE_SERVER,
DQLITE_RESPONSE_SERVER_LEGACY = DQLITE_RESPONSE_SERVER,
DQLITE_RESPONSE_WELCOME,
DQLITE_RESPONSE_SERVERS,
DQLITE_RESPONSE_DB,
DQLITE_RESPONSE_STMT,
DQLITE_RESPONSE_STMT_WITH_OFFSET = DQLITE_RESPONSE_STMT,
DQLITE_RESPONSE_RESULT,
DQLITE_RESPONSE_ROWS,
DQLITE_RESPONSE_EMPTY,
DQLITE_RESPONSE_FILES,
DQLITE_RESPONSE_METADATA
};
#endif /* DQLITE_PROTOCOL_H_ */
dqlite-1.16.0/src/query.c 0000664 0000000 0000000 00000006454 14512203222 0015163 0 ustar 00root root 0000000 0000000 #include "query.h"
#include "tuple.h"
/* Return the type code of the i'th column value.
*
* TODO: find a better way to handle time types. */
static int value_type(sqlite3_stmt *stmt, int i)
{
int type = sqlite3_column_type(stmt, i);
const char *column_type_name = sqlite3_column_decltype(stmt, i);
if (column_type_name != NULL) {
if ((strcasecmp(column_type_name, "DATETIME") == 0) ||
(strcasecmp(column_type_name, "DATE") == 0) ||
(strcasecmp(column_type_name, "TIMESTAMP") == 0)) {
if (type == SQLITE_INTEGER) {
type = DQLITE_UNIXTIME;
} else {
assert(type == SQLITE_TEXT ||
type == SQLITE_NULL);
type = DQLITE_ISO8601;
}
} else if (strcasecmp(column_type_name, "BOOLEAN") == 0) {
assert(type == SQLITE_INTEGER || type == SQLITE_NULL);
type = DQLITE_BOOLEAN;
}
}
assert(type < 16);
return type;
}
/* Append a single row to the message. */
static int encode_row(sqlite3_stmt *stmt, struct buffer *buffer, int n)
{
struct tuple_encoder encoder;
int rc;
int i;
rc = tuple_encoder__init(&encoder, (unsigned)n, TUPLE__ROW, buffer);
if (rc != 0) {
return SQLITE_ERROR;
}
/* Encode the row values */
for (i = 0; i < n; i++) {
/* Figure the type */
struct value value;
value.type = value_type(stmt, i);
switch (value.type) {
case SQLITE_INTEGER:
value.integer = sqlite3_column_int64(stmt, i);
break;
case SQLITE_FLOAT:
value.float_ = sqlite3_column_double(stmt, i);
break;
case SQLITE_BLOB:
value.blob.base =
(char *)sqlite3_column_blob(stmt, i);
value.blob.len =
(size_t)sqlite3_column_bytes(stmt, i);
break;
case SQLITE_NULL:
/* TODO: allow null to be encoded with 0 bytes
*/
value.null = 0;
break;
case SQLITE_TEXT:
value.text =
(text_t)sqlite3_column_text(stmt, i);
break;
case DQLITE_UNIXTIME:
value.integer = sqlite3_column_int64(stmt, i);
break;
case DQLITE_ISO8601:
value.text =
(text_t)sqlite3_column_text(stmt, i);
if (value.text == NULL) {
value.text = "";
}
break;
case DQLITE_BOOLEAN:
value.integer = sqlite3_column_int64(stmt, i);
break;
default:
return SQLITE_ERROR;
}
rc = tuple_encoder__next(&encoder, &value);
if (rc != 0) {
return rc;
}
}
return SQLITE_OK;
}
int query__batch(sqlite3_stmt *stmt, struct buffer *buffer)
{
int n; /* Column count */
int i;
uint64_t n64;
void *cursor;
int rc;
n = sqlite3_column_count(stmt);
if (n <= 0) {
return SQLITE_ERROR;
}
n64 = (uint64_t)n;
/* Insert the column count */
cursor = buffer__advance(buffer, sizeof(uint64_t));
assert(cursor != NULL);
uint64__encode(&n64, &cursor);
/* Insert the column names */
for (i = 0; i < n; i++) {
const char *name = sqlite3_column_name(stmt, i);
cursor = buffer__advance(buffer, text__sizeof(&name));
if (cursor == NULL) {
return SQLITE_NOMEM;
}
text__encode(&name, &cursor);
}
/* Insert the rows. */
do {
if (buffer__offset(buffer) >= buffer->page_size) {
/* If we are already filled a memory page, let's break
* for now, we'll send more rows in a separate
* response. */
rc = SQLITE_ROW;
break;
}
rc = sqlite3_step(stmt);
if (rc != SQLITE_ROW) {
break;
}
rc = encode_row(stmt, buffer, n);
if (rc != SQLITE_OK) {
break;
}
} while (1);
return rc;
}
dqlite-1.16.0/src/query.h 0000664 0000000 0000000 00000000705 14512203222 0015161 0 ustar 00root root 0000000 0000000 /**
* Step through a query progressively encoding a the row tuples.
*/
#ifndef QUERY_H_
#define QUERY_H_
#include
#include "lib/buffer.h"
#include "lib/serialize.h"
/**
* Step through the given query statement progressively encoding the yielded row
* tuples, either until #SQLITE_DONE is returned or a full page of the given
* buffer is filled.
*/
int query__batch(sqlite3_stmt *stmt, struct buffer *buffer);
#endif /* QUERY_H_*/
dqlite-1.16.0/src/registry.c 0000664 0000000 0000000 00000001556 14512203222 0015664 0 ustar 00root root 0000000 0000000 #include
#include "../include/dqlite.h"
#include "lib/assert.h"
#include "registry.h"
void registry__init(struct registry *r, struct config *config)
{
r->config = config;
QUEUE__INIT(&r->dbs);
}
void registry__close(struct registry *r)
{
while (!QUEUE__IS_EMPTY(&r->dbs)) {
struct db *db;
queue *head;
head = QUEUE__HEAD(&r->dbs);
QUEUE__REMOVE(head);
db = QUEUE__DATA(head, struct db, queue);
db__close(db);
sqlite3_free(db);
}
}
int registry__db_get(struct registry *r, const char *filename, struct db **db)
{
queue *head;
QUEUE__FOREACH(head, &r->dbs)
{
*db = QUEUE__DATA(head, struct db, queue);
if (strcmp((*db)->filename, filename) == 0) {
return 0;
}
}
*db = sqlite3_malloc(sizeof **db);
if (*db == NULL) {
return DQLITE_NOMEM;
}
db__init(*db, r->config, filename);
QUEUE__PUSH(&r->dbs, &(*db)->queue);
return 0;
}
dqlite-1.16.0/src/registry.h 0000664 0000000 0000000 00000000740 14512203222 0015663 0 ustar 00root root 0000000 0000000 #ifndef REGISTRY_H_
#define REGISTRY_H_
#include
#include
#include "lib/queue.h"
#include "db.h"
struct registry
{
struct config *config;
queue dbs;
};
void registry__init(struct registry *r, struct config *config);
void registry__close(struct registry *r);
/**
* Get the db with the given filename. If no one is registered, create one.
*/
int registry__db_get(struct registry *r, const char *filename, struct db **db);
#endif /* REGISTRY_H_*/
dqlite-1.16.0/src/request.c 0000664 0000000 0000000 00000000423 14512203222 0015474 0 ustar 00root root 0000000 0000000 #include "request.h"
#define REQUEST__IMPLEMENT(LOWER, UPPER, _) \
SERIALIZE__IMPLEMENT(request_##LOWER, REQUEST_##UPPER);
REQUEST__TYPES(REQUEST__IMPLEMENT, );
SERIALIZE__IMPLEMENT(request_connect, REQUEST_CONNECT);
SERIALIZE__IMPLEMENT(request_assign, REQUEST_ASSIGN);
dqlite-1.16.0/src/request.h 0000664 0000000 0000000 00000007116 14512203222 0015507 0 ustar 00root root 0000000 0000000 #ifndef REQUEST_H_
#define REQUEST_H_
#include "lib/serialize.h"
/**
* Request types.
*/
#define REQUEST_LEADER(X, ...) X(uint64, __unused__, ##__VA_ARGS__)
#define REQUEST_CLIENT(X, ...) X(uint64, id, ##__VA_ARGS__)
#define REQUEST_OPEN(X, ...) \
X(text, filename, ##__VA_ARGS__) \
X(uint64, flags, ##__VA_ARGS__) \
X(text, vfs, ##__VA_ARGS__)
#define REQUEST_PREPARE(X, ...) \
X(uint64, db_id, ##__VA_ARGS__) \
X(text, sql, ##__VA_ARGS__)
#define REQUEST_EXEC(X, ...) \
X(uint32, db_id, ##__VA_ARGS__) \
X(uint32, stmt_id, ##__VA_ARGS__)
#define REQUEST_QUERY(X, ...) \
X(uint32, db_id, ##__VA_ARGS__) \
X(uint32, stmt_id, ##__VA_ARGS__)
#define REQUEST_FINALIZE(X, ...) \
X(uint32, db_id, ##__VA_ARGS__) \
X(uint32, stmt_id, ##__VA_ARGS__)
#define REQUEST_EXEC_SQL(X, ...) \
X(uint64, db_id, ##__VA_ARGS__) \
X(text, sql, ##__VA_ARGS__)
#define REQUEST_QUERY_SQL(X, ...) \
X(uint64, db_id, ##__VA_ARGS__) \
X(text, sql, ##__VA_ARGS__)
#define REQUEST_INTERRUPT(X, ...) X(uint64, db_id, ##__VA_ARGS__)
#define REQUEST_ADD(X, ...) \
X(uint64, id, ##__VA_ARGS__) \
X(text, address, ##__VA_ARGS__)
#define REQUEST_PROMOTE_OR_ASSIGN(X, ...) X(uint64, id, ##__VA_ARGS__)
#define REQUEST_REMOVE(X, ...) X(uint64, id, ##__VA_ARGS__)
#define REQUEST_DUMP(X, ...) X(text, filename, ##__VA_ARGS__)
#define REQUEST_CLUSTER(X, ...) X(uint64, format, ##__VA_ARGS__)
#define REQUEST_TRANSFER(X, ...) X(uint64, id, ##__VA_ARGS__)
#define REQUEST_DESCRIBE(X, ...) X(uint64, format, ##__VA_ARGS__)
#define REQUEST_WEIGHT(X, ...) X(uint64, weight, ##__VA_ARGS__)
#define REQUEST__DEFINE(LOWER, UPPER, _) \
SERIALIZE__DEFINE(request_##LOWER, REQUEST_##UPPER);
#define REQUEST__TYPES(X, ...) \
X(leader, LEADER, __VA_ARGS__) \
X(client, CLIENT, __VA_ARGS__) \
X(open, OPEN, __VA_ARGS__) \
X(prepare, PREPARE, __VA_ARGS__) \
X(exec, EXEC, __VA_ARGS__) \
X(query, QUERY, __VA_ARGS__) \
X(finalize, FINALIZE, __VA_ARGS__) \
X(exec_sql, EXEC_SQL, __VA_ARGS__) \
X(query_sql, QUERY_SQL, __VA_ARGS__) \
X(interrupt, INTERRUPT, __VA_ARGS__) \
X(add, ADD, __VA_ARGS__) \
X(promote_or_assign, PROMOTE_OR_ASSIGN, __VA_ARGS__) \
X(remove, REMOVE, __VA_ARGS__) \
X(dump, DUMP, __VA_ARGS__) \
X(cluster, CLUSTER, __VA_ARGS__) \
X(transfer, TRANSFER, __VA_ARGS__) \
X(describe, DESCRIBE, __VA_ARGS__) \
X(weight, WEIGHT, __VA_ARGS__)
REQUEST__TYPES(REQUEST__DEFINE);
#define REQUEST_CONNECT(X, ...) \
X(uint64, id, ##__VA_ARGS__) \
X(text, address, ##__VA_ARGS__)
SERIALIZE__DEFINE(request_connect, REQUEST_CONNECT);
/* Definition of the ASSIGN request that's used only for serialization.
*
* The one-field PROMOTE request and the two-field ASSIGN request have the
* same type tag, so we can't dispatch deserialization based on that field.
* Instead, we deserialize as the least-common-denominator PROMOTE_OR_ASSIGN
* and then manually read the second field if appropriate.
*
* But when serializing, we can just decide to send an ASSIGN request, so
* we provide the message definition here for that purpose. */
#define REQUEST_ASSIGN(X, ...) \
X(uint64, id, ##__VA_ARGS__) \
X(uint64, role, ##__VA_ARGS__)
SERIALIZE__DEFINE(request_assign, REQUEST_ASSIGN);
#endif /* REQUEST_H_ */
dqlite-1.16.0/src/response.c 0000664 0000000 0000000 00000000252 14512203222 0015642 0 ustar 00root root 0000000 0000000 #include "response.h"
#define RESPONSE__IMPLEMENT(LOWER, UPPER, _) \
SERIALIZE__IMPLEMENT(response_##LOWER, RESPONSE_##UPPER);
RESPONSE__TYPES(RESPONSE__IMPLEMENT, );
dqlite-1.16.0/src/response.h 0000664 0000000 0000000 00000004420 14512203222 0015650 0 ustar 00root root 0000000 0000000 #ifndef RESPONSE_H_
#define RESPONSE_H_
#include "lib/serialize.h"
/**
* Response types.
*/
#define RESPONSE_SERVER(X, ...) \
X(uint64, id, ##__VA_ARGS__) \
X(text, address, ##__VA_ARGS__)
#define RESPONSE_SERVER_LEGACY(X, ...) X(text, address, ##__VA_ARGS__)
#define RESPONSE_WELCOME(X, ...) X(uint64, heartbeat_timeout, ##__VA_ARGS__)
#define RESPONSE_FAILURE(X, ...) \
X(uint64, code, ##__VA_ARGS__) \
X(text, message, ##__VA_ARGS__)
#define RESPONSE_DB(X, ...) \
X(uint32, id, ##__VA_ARGS__) \
X(uint32, __pad__, ##__VA_ARGS__)
#define RESPONSE_STMT(X, ...) \
X(uint32, db_id, ##__VA_ARGS__) \
X(uint32, id, ##__VA_ARGS__) \
X(uint64, params, ##__VA_ARGS__)
#define RESPONSE_STMT_WITH_OFFSET(X, ...) \
X(uint32, db_id, ##__VA_ARGS__) \
X(uint32, id, ##__VA_ARGS__) \
X(uint64, params, ##__VA_ARGS__) \
X(uint64, offset, ##__VA_ARGS__)
#define RESPONSE_RESULT(X, ...) \
X(uint64, last_insert_id, ##__VA_ARGS__) \
X(uint64, rows_affected, ##__VA_ARGS__)
#define RESPONSE_ROWS(X, ...) X(uint64, eof, ##__VA_ARGS__)
#define RESPONSE_EMPTY(X, ...) X(uint64, __unused__, ##__VA_ARGS__)
#define RESPONSE_FILES(X, ...) X(uint64, n, ##__VA_ARGS__)
#define RESPONSE_SERVERS(X, ...) X(uint64, n, ##__VA_ARGS__)
#define RESPONSE_METADATA(X, ...) \
X(uint64, failure_domain, ##__VA_ARGS__) \
X(uint64, weight, ##__VA_ARGS__)
#define RESPONSE__DEFINE(LOWER, UPPER, _) \
SERIALIZE__DEFINE(response_##LOWER, RESPONSE_##UPPER);
#define RESPONSE__TYPES(X, ...) \
X(server, SERVER, __VA_ARGS__) \
X(server_legacy, SERVER_LEGACY, __VA_ARGS__) \
X(welcome, WELCOME, __VA_ARGS__) \
X(failure, FAILURE, __VA_ARGS__) \
X(db, DB, __VA_ARGS__) \
X(stmt, STMT, __VA_ARGS__) \
X(stmt_with_offset, STMT_WITH_OFFSET, __VA_ARGS__) \
X(result, RESULT, __VA_ARGS__) \
X(rows, ROWS, __VA_ARGS__) \
X(empty, EMPTY, __VA_ARGS__) \
X(files, FILES, __VA_ARGS__) \
X(servers, SERVERS, __VA_ARGS__) \
X(metadata, METADATA, __VA_ARGS__)
RESPONSE__TYPES(RESPONSE__DEFINE);
#endif /* RESPONSE_H_ */
dqlite-1.16.0/src/roles.c 0000664 0000000 0000000 00000046231 14512203222 0015137 0 ustar 00root root 0000000 0000000 #include
#include
#include "client/protocol.h"
#include "lib/queue.h"
#include "roles.h"
#include "server.h"
#include "translate.h"
/* Overview
* --------
*
* This file implements automatic role management for dqlite servers. When
* automatic role management is enabled, servers in a dqlite cluster will
* autonomously (without client intervention) promote and demote each other
* to maintain a specified number of voters and standbys, taking into account
* the health, failure domain, and weight of each server.
*
* We implement two ingredients of role management: adjustments and handovers.
* Adjustment runs on the cluster leader every tick (the frequency is defined
* in server.c). The first step is to "poll" every server in the cluster to find
* out whether it's online, and if so, its failure domain and weight. It demotes
* to spare any servers that appear to have gone offline, then, if the numbers
* of (online) voters and standbys don't match the target values, chooses
* servers that should be promoted or demoted. The preference ordering for
* promotion is based on the failure domains and weights previously gathered,
* and is defined in compareNodesForPromotion, below.
*
* The actual roles changes are computed in a batch each time adjustment
* occurs, and are stored in a queue. Individual "change records" are taken
* off this queue and applied asynchronously. Since we only have a blocking
* client implementation available, the exchanges of requests and responses
* that implements polling a single server happens on the libuv blocking
* thread pool (see pollClusterAfterWorkCb). We don't start a new round of
* adjustment if a "tick" occurs while the queue of changes from the last
* round is still nonempty.
*
* A handover is triggered when we call dqlite_node_handover on a node that's
* the current cluster leader, or is a voter. Before shutting down for real,
* the node in question tries to cause another node to become leader (using
* raft_transfer), if applicable, and then promotes another node to voter
* (if possible) before demoting itself. This is intended to smooth over
* availability problems that can result if a privileged node (leader or
* non-leader voter) crashes out of the cluster unceremoniously. The handover
* task also needs to poll the cluster to figure out which nodes are good
* candidates for promotion to voter.
*
* Unresolved
* ----------
*
* - Should the failure-domains accounting for standbys use information about
* voters' failure domains? Vice versa?
* - Should we try multiple candidates when doing an adjustment, if the
* preferred candidate can't be promoted?
* - Should we retry when some step in the handover process fails? How, and
* how many times?
* - Should we have dedicated code somewhere to (possibly) promote newly-
* joined nodes? go-dqlite does this, but I'm not convinced it's important,
* or that it should run on the server if we do decide we want it.
*/
/* XXX */
#define NUM_TRACKED_DOMAINS 5
struct change_record
{
raft_id id;
int role; /* dqlite role codes */
queue queue;
};
struct counted_failure_domain
{
unsigned long long domain;
int count;
};
struct compare_data
{
unsigned n;
struct counted_failure_domain domains[NUM_TRACKED_DOMAINS];
};
struct polling
{
void (*cb)(struct polling *);
struct dqlite_node *node;
struct all_node_info *cluster;
unsigned *count;
unsigned n_cluster;
unsigned i;
};
struct handover_voter_data
{
struct dqlite_node *node;
dqlite_node_id target_id;
char *leader_addr;
dqlite_node_id leader_id;
};
static int domainCount(uint64_t needle, const struct compare_data *data)
{
unsigned i;
for (i = 0; i < data->n; i += 1) {
if (data->domains[i].domain == needle) {
return data->domains[i].count;
}
}
return 0;
}
static void addDomain(uint64_t domain, struct compare_data *data)
{
unsigned i;
for (i = 0; i < data->n; i += 1) {
if (data->domains[i].domain == domain) {
data->domains[i].count += 1;
return;
}
}
if (i < NUM_TRACKED_DOMAINS) {
data->domains[i].domain = domain;
data->domains[i].count = 1;
data->n += 1;
}
}
static void removeDomain(uint64_t domain, struct compare_data *data)
{
unsigned i;
for (i = 0; i < data->n; i += 1) {
if (data->domains[i].domain == domain) {
if (data->domains[i].count > 0) {
data->domains[i].count -= 1;
}
return;
}
}
}
static int compareNodesForPromotion(const void *l, const void *r, void *p)
{
struct compare_data *data = p;
const struct all_node_info *left = l;
const struct all_node_info *right = r;
int result;
/* Nodes whose failure domains appear fewer times are preferred. */
result = domainCount(left->failure_domain, data) -
domainCount(right->failure_domain, data);
if (result != 0) {
return result;
}
/* Nodes with lower weights are preferred. */
result = (int)(left->weight - right->weight);
if (result != 0) {
return result;
}
/* We prefer to promote a standby rather than a spare. If
* left->role > right->role, then right is more "senior" than left,
* so we want right to come first, so return 1.*/
return (left->role > right->role) - (left->role < right->role);
}
static int compareNodesForDemotion(const void *l, const void *r, void *p)
{
/* XXX */
return -compareNodesForPromotion(l, r, p);
}
static void changeCb(struct raft_change *change, int status);
/* Take one role change record off the queue and apply it. */
static void startChange(struct dqlite_node *d)
{
queue *head;
struct change_record *rec;
struct raft_change *change;
uint64_t id;
int role;
int rv;
if (QUEUE__IS_EMPTY(&d->roles_changes)) {
return;
}
head = QUEUE__HEAD(&d->roles_changes);
QUEUE__REMOVE(head);
rec = QUEUE__DATA(head, struct change_record, queue);
id = rec->id;
role = rec->role;
raft_free(rec);
change = raft_malloc(sizeof *change);
if (change == NULL) {
return;
}
change->data = d;
/* TODO request ID */
rv = raft_assign(&d->raft, change, id, translateDqliteRole(role),
changeCb);
if (rv != 0) {
/* TODO */
raft_free(change);
}
}
/* When a role change has completed, start the next one. */
static void changeCb(struct raft_change *change, int status)
{
struct dqlite_node *d = change->data;
raft_free(change);
if (status != 0) {
/* TODO */
}
startChange(d);
}
static void queueChange(uint64_t id, int role, void *arg)
{
struct dqlite_node *d = arg;
queue *head;
struct change_record *rec;
/* If we already queued a role change for this node, just update
* that record instead of queueing a new one. */
QUEUE__FOREACH(head, &d->roles_changes)
{
rec = QUEUE__DATA(head, struct change_record, queue);
if (rec->id == id) {
rec->role = role;
return;
}
}
rec = raft_malloc(sizeof *rec);
if (rec == NULL) {
return;
}
rec->id = id;
rec->role = role;
QUEUE__PUSH(&d->roles_changes, &rec->queue);
}
void RolesComputeChanges(int voters,
int standbys,
struct all_node_info *cluster,
unsigned n_cluster,
dqlite_node_id my_id,
void (*cb)(uint64_t, int, void *),
void *arg)
{
int voter_count = 0;
int standby_count = 0;
struct compare_data voter_compare = {0};
struct compare_data standby_compare = {0};
unsigned i;
/* Count (online) voters and standbys in the cluster, and demote any
* offline nodes to spare. */
for (i = 0; i < n_cluster; i += 1) {
if (!cluster[i].online && cluster[i].role != DQLITE_SPARE) {
cb(cluster[i].id, DQLITE_SPARE, arg);
cluster[i].role = DQLITE_SPARE;
} else if (cluster[i].online &&
cluster[i].role == DQLITE_VOTER) {
voter_count += 1;
addDomain(cluster[i].failure_domain, &voter_compare);
} else if (cluster[i].online &&
cluster[i].role == DQLITE_STANDBY) {
standby_count += 1;
addDomain(cluster[i].failure_domain, &standby_compare);
}
}
/* If we don't have enough voters, promote some standbys and spares. */
if (voter_count < voters) {
qsort_r(cluster, n_cluster, sizeof *cluster,
compareNodesForPromotion, &voter_compare);
}
for (i = 0; i < n_cluster && voter_count < voters; i += 1) {
if (!cluster[i].online || cluster[i].role == DQLITE_VOTER) {
continue;
}
cb(cluster[i].id, DQLITE_VOTER, arg);
if (cluster[i].role == DQLITE_STANDBY) {
standby_count -= 1;
removeDomain(cluster[i].failure_domain,
&standby_compare);
}
cluster[i].role = DQLITE_VOTER;
voter_count += 1;
addDomain(cluster[i].failure_domain, &voter_compare);
}
/* If we have too many voters, demote some of them. We always demote
* to spare in this step -- if it turns out that it would be better
* for some of these nodes to end up as standbys, that change will
* be picked up in the next step, and the two role changes will be
* consolidated by queueChangeCb. */
if (voter_count > voters) {
qsort_r(cluster, n_cluster, sizeof *cluster,
compareNodesForDemotion, &voter_compare);
}
for (i = 0; i < n_cluster && voter_count > voters; i += 1) {
if (cluster[i].role != DQLITE_VOTER || cluster[i].id == my_id) {
continue;
}
cb(cluster[i].id, DQLITE_SPARE, arg);
cluster[i].role = DQLITE_SPARE;
voter_count -= 1;
removeDomain(cluster[i].failure_domain, &voter_compare);
}
/* If we don't have enough standbys, promote some spares. */
if (standby_count < standbys) {
qsort_r(cluster, n_cluster, sizeof *cluster,
compareNodesForPromotion, &standby_compare);
}
for (i = 0; i < n_cluster && standby_count < standbys; i += 1) {
if (!cluster[i].online || cluster[i].role != DQLITE_SPARE) {
continue;
}
cb(cluster[i].id, DQLITE_STANDBY, arg);
cluster[i].role = DQLITE_STANDBY;
standby_count += 1;
addDomain(cluster[i].failure_domain, &standby_compare);
}
/* If we have too many standbys, demote some of them. */
if (standby_count > standbys) {
qsort_r(cluster, n_cluster, sizeof *cluster,
compareNodesForDemotion, &standby_compare);
}
for (i = 0; i < n_cluster && standby_count > standbys; i += 1) {
if (cluster[i].role != DQLITE_STANDBY) {
continue;
}
cb(cluster[i].id, DQLITE_SPARE, arg);
cluster[i].role = DQLITE_SPARE;
standby_count -= 1;
removeDomain(cluster[i].failure_domain, &standby_compare);
}
}
/* Process information about the state of the cluster and queue up any
* necessary role adjustments. This runs on the main thread. */
static void adjustClusterCb(struct polling *polling)
{
struct dqlite_node *d;
if (polling == NULL) {
return;
}
d = polling->node;
RolesComputeChanges(d->config.voters, d->config.standbys,
polling->cluster, polling->n_cluster, d->config.id,
queueChange, d);
/* Start pulling role changes off the queue. */
startChange(d);
}
/* Runs on the blocking thread pool to retrieve information about a single
* server for use in roles adjustment. */
static void pollClusterWorkCb(uv_work_t *work)
{
struct polling *polling = work->data;
struct dqlite_node *d = polling->node;
struct client_proto proto = {0};
struct client_context context;
int rv;
proto.connect = d->connect_func;
proto.connect_arg = d->connect_func_arg;
rv = clientOpen(&proto, polling->cluster[polling->i].address,
polling->cluster[polling->i].id);
if (rv != 0) {
return;
}
clientContextMillis(&context, 5000);
rv = clientSendHandshake(&proto, &context);
if (rv != 0) {
goto close;
}
rv = clientSendDescribe(&proto, &context);
rv = clientRecvMetadata(&proto,
&polling->cluster[polling->i].failure_domain,
&polling->cluster[polling->i].weight, &context);
if (rv != 0) {
goto close;
}
polling->cluster[polling->i].online = true;
close:
clientClose(&proto);
}
/* Runs on the main thread after polling each server for roles adjustment. */
static void pollClusterAfterWorkCb(uv_work_t *work, int status)
{
struct polling *polling = work->data;
uv_work_t *work_objs;
struct polling *polling_objs;
unsigned i;
/* The only path to status != 0 involves calling uv_cancel on this task,
* which we don't do. */
assert(status == 0);
*polling->count += 1;
/* If all nodes have been polled, invoke the callback. */
if (*polling->count == polling->n_cluster) {
polling->cb(polling);
/* Free the shared data, now that all tasks have finished. */
raft_free(polling->count);
for (i = 0; i < polling->n_cluster; i += 1) {
raft_free(polling->cluster[i].address);
}
raft_free(polling->cluster);
work_objs = work - polling->i;
raft_free(work_objs);
polling_objs = polling - polling->i;
raft_free(polling_objs);
}
}
/* Poll every node in the cluster to learn whether it's online, and if so, its
* weight and failure domain. */
static void pollCluster(struct dqlite_node *d, void (*cb)(struct polling *))
{
struct all_node_info *cluster;
const struct raft_server *server;
struct polling *polling_objs;
struct polling *polling;
struct uv_work_s *work_objs;
struct uv_work_s *work;
unsigned *count;
unsigned n;
unsigned i;
unsigned j;
unsigned ii;
int rv;
n = d->raft.configuration.n;
cluster = raft_calloc(n, sizeof *cluster);
if (cluster == NULL) {
goto err;
}
count = raft_malloc(sizeof *count);
if (count == NULL) {
goto err_after_alloc_cluster;
}
*count = 0;
for (i = 0; i < n; i += 1) {
server = &d->raft.configuration.servers[i];
cluster[i].id = server->id;
cluster[i].address = raft_malloc(strlen(server->address) + 1);
if (cluster[i].address == NULL) {
goto err_after_alloc_addrs;
}
memcpy(cluster[i].address, server->address,
strlen(server->address) + 1);
cluster[i].role = translateRaftRole(server->role);
}
polling_objs = raft_calloc(n, sizeof *polling_objs);
if (polling_objs == NULL) {
goto err_after_alloc_addrs;
}
work_objs = raft_calloc(n, sizeof *work_objs);
if (work_objs == NULL) {
goto err_after_alloc_polling;
}
for (j = 0; j < n; j += 1) {
polling = &polling_objs[j];
polling->cb = cb;
polling->node = d;
polling->cluster = cluster;
polling->n_cluster = n;
polling->count = count;
polling->i = j;
work = &work_objs[j];
work->data = polling;
rv = uv_queue_work(&d->loop, work, pollClusterWorkCb,
pollClusterAfterWorkCb);
/* uv_queue_work can't fail unless a NULL callback is passed. */
assert(rv == 0);
}
return;
err_after_alloc_polling:
raft_free(polling_objs);
err_after_alloc_addrs:
for (ii = 0; ii < i; ii += 1) {
raft_free(cluster[ii].address);
}
raft_free(count);
err_after_alloc_cluster:
raft_free(cluster);
err:
cb(NULL);
}
/* Runs on the thread pool to open a connection to the leader, promote another
* node to voter, and demote the calling node to spare. */
static void handoverVoterWorkCb(uv_work_t *work)
{
struct handover_voter_data *data = work->data;
struct client_proto proto = {0};
struct client_context context;
int rv;
proto.connect = data->node->connect_func;
proto.connect_arg = data->node->connect_func_arg;
rv = clientOpen(&proto, data->leader_addr, data->leader_id);
if (rv != 0) {
return;
}
clientContextMillis(&context, 5000);
rv = clientSendHandshake(&proto, &context);
if (rv != 0) {
goto close;
}
rv = clientSendAssign(&proto, data->target_id, DQLITE_VOTER, &context);
if (rv != 0) {
goto close;
}
rv = clientRecvEmpty(&proto, &context);
if (rv != 0) {
goto close;
}
rv = clientSendAssign(&proto, data->node->config.id, DQLITE_SPARE,
&context);
if (rv != 0) {
goto close;
}
rv = clientRecvEmpty(&proto, &context);
close:
clientClose(&proto);
}
static void handoverVoterAfterWorkCb(uv_work_t *work, int status)
{
struct handover_voter_data *data = work->data;
struct dqlite_node *node = data->node;
int handover_status = 0;
void (*cb)(struct dqlite_node *, int);
if (status != 0) {
handover_status = DQLITE_ERROR;
}
raft_free(data->leader_addr);
raft_free(data);
raft_free(work);
cb = node->handover_done_cb;
cb(node, handover_status);
node->handover_done_cb = NULL;
}
/* Having gathered information about the cluster, pick a non-voter node
* to promote in our place. */
static void handoverVoterCb(struct polling *polling)
{
struct dqlite_node *node;
raft_id leader_id;
const char *borrowed_addr;
char *leader_addr;
struct compare_data voter_compare = {0};
unsigned i;
struct all_node_info *cluster;
unsigned n_cluster;
dqlite_node_id target_id;
struct handover_voter_data *data;
uv_work_t *work;
void (*cb)(struct dqlite_node *, int);
int rv;
if (polling == NULL) {
return;
}
node = polling->node;
cluster = polling->cluster;
n_cluster = polling->n_cluster;
cb = node->handover_done_cb;
raft_leader(&node->raft, &leader_id, &borrowed_addr);
if (leader_id == node->raft.id || leader_id == 0) {
goto finish;
}
leader_addr = raft_malloc(strlen(borrowed_addr) + 1);
if (leader_addr == NULL) {
goto finish;
}
memcpy(leader_addr, borrowed_addr, strlen(borrowed_addr) + 1);
/* Select a non-voter to transfer to -- the logic is similar to
* adjustClusterCb. */
for (i = 0; i < n_cluster; i += 1) {
if (cluster[i].online && cluster[i].role == DQLITE_VOTER &&
cluster[i].id != node->raft.id) {
addDomain(cluster[i].failure_domain, &voter_compare);
}
}
qsort_r(cluster, n_cluster, sizeof *cluster, compareNodesForPromotion,
&voter_compare);
target_id = 0;
for (i = 0; i < n_cluster; i += 1) {
if (cluster[i].online && cluster[i].role != DQLITE_VOTER &&
cluster[i].id != node->raft.id) {
target_id = cluster[i].id;
break;
}
}
/* If no transfer candidates found, give up. */
if (target_id == 0) {
goto err_after_alloc_leader_addr;
}
/* Submit the handover work. */
data = raft_malloc(sizeof *data);
if (data == NULL) {
goto err_after_alloc_leader_addr;
}
data->node = node;
data->target_id = target_id;
data->leader_addr = leader_addr;
data->leader_id = leader_id;
work = raft_malloc(sizeof *work);
if (work == NULL) {
goto err_after_alloc_data;
}
work->data = data;
rv = uv_queue_work(&node->loop, work, handoverVoterWorkCb,
handoverVoterAfterWorkCb);
if (rv != 0) {
goto err_after_alloc_work;
}
return;
err_after_alloc_work:
raft_free(work);
err_after_alloc_data:
raft_free(data);
err_after_alloc_leader_addr:
raft_free(leader_addr);
finish:
node->handover_done_cb = NULL;
cb(node, DQLITE_ERROR);
}
static void handoverTransferCb(struct raft_transfer *req)
{
struct dqlite_node *d = req->data;
raft_free(req);
pollCluster(d, handoverVoterCb);
}
void RolesAdjust(struct dqlite_node *d)
{
/* Only the leader can assign roles. */
if (raft_state(&d->raft) != RAFT_LEADER) {
return;
}
/* If a series of role adjustments is already in progress, don't kick
* off another one. */
if (!QUEUE__IS_EMPTY(&d->roles_changes)) {
return;
}
assert(d->running);
pollCluster(d, adjustClusterCb);
}
void RolesHandover(struct dqlite_node *d, void (*cb)(struct dqlite_node *, int))
{
struct raft_transfer *req;
int rv;
req = raft_malloc(sizeof *req);
if (req == NULL) {
goto err;
}
d->handover_done_cb = cb;
req->data = d;
/* We try the leadership transfer unconditionally -- Raft will tell us
* if we're not the leader. */
rv = raft_transfer(&d->raft, req, 0, handoverTransferCb);
if (rv == RAFT_NOTLEADER) {
raft_free(req);
pollCluster(d, handoverVoterCb);
return;
} else if (rv != 0) {
raft_free(req);
goto err;
}
return;
err:
d->handover_done_cb = NULL;
cb(d, DQLITE_ERROR);
}
void RolesCancelPendingChanges(struct dqlite_node *d)
{
queue *head;
struct change_record *rec;
while (!QUEUE__IS_EMPTY(&d->roles_changes)) {
head = QUEUE__HEAD(&d->roles_changes);
rec = QUEUE__DATA(head, struct change_record, queue);
QUEUE__REMOVE(head);
raft_free(rec);
}
}
dqlite-1.16.0/src/roles.h 0000664 0000000 0000000 00000003421 14512203222 0015136 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_ROLE_MANAGEMENT_H
#define DQLITE_ROLE_MANAGEMENT_H
#include "server.h"
struct all_node_info
{
uint64_t id;
char *address;
int role;
bool online;
uint64_t failure_domain;
uint64_t weight;
};
/* Determine what roles changes should be made to the cluster, without
* side-effects. The given callback will be invoked for each computed change,
* with first argument the node whose role should be adjusted, second argument
* the node's new role, and third argument taken from the last argument of this
* function.
*
* The memory pointed to by @cluster is "borrowed" and not freed by this
* function, but it may be modified as part of this function's bookkeeping. */
void RolesComputeChanges(int voters,
int standbys,
struct all_node_info *cluster,
unsigned n_cluster,
dqlite_node_id my_id,
void (*cb)(uint64_t, int, void *),
void *arg);
/* If necessary, try to assign new roles to nodes in the cluster to achieve
* the configured number of voters and standbys. Polling the cluster and
* assigning roles happens asynchronously. This can safely be called on any
* server, but does nothing if called on a server that is not the leader. */
void RolesAdjust(struct dqlite_node *d);
/* Begin a graceful shutdown of this node. Leadership and the voter role will
* be transferred to other nodes if necessary, and then the callback will be
* invoked on the loop thread. The callback's second argument will be 0 if the
* handover succeeded and nonzero otherwise. */
void RolesHandover(struct dqlite_node *d,
void (*cb)(struct dqlite_node *, int));
/* Drain the queue of changes computed by RoleManagementAdjust. This should be
* done when the node is shutting down, to avoid a memory leak. */
void RolesCancelPendingChanges(struct dqlite_node *d);
#endif
dqlite-1.16.0/src/server.c 0000664 0000000 0000000 00000116054 14512203222 0015322 0 ustar 00root root 0000000 0000000 #include "server.h"
#include
#include
#include
#include
#include "../include/dqlite.h"
#include "client/protocol.h"
#include "conn.h"
#include "fsm.h"
#include "id.h"
#include "lib/addr.h"
#include "lib/assert.h"
#include "lib/fs.h"
#include "logger.h"
#include "protocol.h"
#include "roles.h"
#include "tracing.h"
#include "translate.h"
#include "transport.h"
#include "utils.h"
#include "vfs.h"
/* Special ID for the bootstrap node. Equals to raft_digest("1", 0). */
#define BOOTSTRAP_ID 0x2dc171858c3155be
#define DATABASE_DIR_FMT "%s/database"
#define NODE_STORE_INFO_FORMAT_V1 "v1"
int dqlite__init(struct dqlite_node *d,
dqlite_node_id id,
const char *address,
const char *dir)
{
int rv;
char db_dir_path[1024];
int urandom;
ssize_t count;
d->initialized = false;
memset(d->errmsg, 0, sizeof d->errmsg);
rv = snprintf(db_dir_path, sizeof db_dir_path, DATABASE_DIR_FMT, dir);
if (rv == -1 || rv >= (int)(sizeof db_dir_path)) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"failed to init: snprintf(rv:%d)", rv);
goto err;
}
rv = config__init(&d->config, id, address, db_dir_path);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"config__init(rv:%d)", rv);
goto err;
}
rv = VfsInit(&d->vfs, d->config.name);
sqlite3_vfs_register(&d->vfs, 0);
if (rv != 0) {
goto err_after_config_init;
}
registry__init(&d->registry, &d->config);
rv = uv_loop_init(&d->loop);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"uv_loop_init(): %s", uv_strerror(rv));
rv = DQLITE_ERROR;
goto err_after_vfs_init;
}
rv = raftProxyInit(&d->raft_transport, &d->loop);
if (rv != 0) {
goto err_after_loop_init;
}
rv = raft_uv_init(&d->raft_io, &d->loop, dir, &d->raft_transport);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"raft_uv_init(): %s", d->raft_io.errmsg);
rv = DQLITE_ERROR;
goto err_after_raft_transport_init;
}
rv = fsm__init(&d->raft_fsm, &d->config, &d->registry);
if (rv != 0) {
goto err_after_raft_io_init;
}
/* TODO: properly handle closing the dqlite server without running it */
rv = raft_init(&d->raft, &d->raft_io, &d->raft_fsm, d->config.id,
d->config.address);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE, "raft_init(): %s",
raft_errmsg(&d->raft));
return DQLITE_ERROR;
}
/* TODO: expose these values through some API */
raft_set_election_timeout(&d->raft, 3000);
raft_set_heartbeat_timeout(&d->raft, 500);
raft_set_snapshot_threshold(&d->raft, 1024);
raft_set_snapshot_trailing(&d->raft, 8192);
raft_set_pre_vote(&d->raft, true);
raft_set_max_catch_up_rounds(&d->raft, 100);
raft_set_max_catch_up_round_duration(&d->raft, 50 * 1000); /* 50 secs */
rv = sem_init(&d->ready, 0, 0);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE, "sem_init(): %s",
strerror(errno));
rv = DQLITE_ERROR;
goto err_after_raft_fsm_init;
}
rv = sem_init(&d->stopped, 0, 0);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE, "sem_init(): %s",
strerror(errno));
rv = DQLITE_ERROR;
goto err_after_ready_init;
}
rv = sem_init(&d->handover_done, 0, 0);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE, "sem_init(): %s",
strerror(errno));
rv = DQLITE_ERROR;
goto err_after_stopped_init;
}
QUEUE__INIT(&d->queue);
QUEUE__INIT(&d->conns);
QUEUE__INIT(&d->roles_changes);
d->raft_state = RAFT_UNAVAILABLE;
d->running = false;
d->listener = NULL;
d->bind_address = NULL;
d->role_management = false;
d->connect_func = transportDefaultConnect;
d->connect_func_arg = NULL;
urandom = open("/dev/urandom", O_RDONLY);
assert(urandom != -1);
count = read(urandom, d->random_state.data, sizeof(uint64_t[4]));
(void)count;
close(urandom);
d->initialized = true;
return 0;
err_after_stopped_init:
sem_destroy(&d->stopped);
err_after_ready_init:
sem_destroy(&d->ready);
err_after_raft_fsm_init:
fsm__close(&d->raft_fsm);
err_after_raft_io_init:
raft_uv_close(&d->raft_io);
err_after_raft_transport_init:
raftProxyClose(&d->raft_transport);
err_after_loop_init:
uv_loop_close(&d->loop);
err_after_vfs_init:
VfsClose(&d->vfs);
err_after_config_init:
config__close(&d->config);
err:
return rv;
}
void dqlite__close(struct dqlite_node *d)
{
int rv;
if (!d->initialized) {
return;
}
raft_free(d->listener);
rv = sem_destroy(&d->stopped);
assert(rv == 0); /* Fails only if sem object is not valid */
rv = sem_destroy(&d->ready);
assert(rv == 0); /* Fails only if sem object is not valid */
rv = sem_destroy(&d->handover_done);
assert(rv == 0);
fsm__close(&d->raft_fsm);
// TODO assert rv of uv_loop_close after fixing cleanup logic related to
// the TODO above referencing the cleanup logic without running the
// node. See https://github.com/canonical/dqlite/issues/504.
uv_loop_close(&d->loop);
raftProxyClose(&d->raft_transport);
registry__close(&d->registry);
sqlite3_vfs_unregister(&d->vfs);
VfsClose(&d->vfs);
config__close(&d->config);
if (d->bind_address != NULL) {
sqlite3_free(d->bind_address);
}
}
int dqlite_node_create(dqlite_node_id id,
const char *address,
const char *data_dir,
dqlite_node **t)
{
*t = sqlite3_malloc(sizeof **t);
if (*t == NULL) {
return DQLITE_NOMEM;
}
return dqlite__init(*t, id, address, data_dir);
}
int dqlite_node_set_bind_address(dqlite_node *t, const char *address)
{
/* sockaddr_un is large enough for our purposes */
struct sockaddr_un addr_un;
struct sockaddr *addr = (struct sockaddr *)&addr_un;
socklen_t addr_len = sizeof(addr_un);
sa_family_t domain;
size_t path_len;
int fd;
int rv;
if (t->running) {
return DQLITE_MISUSE;
}
rv =
AddrParse(address, addr, &addr_len, "8080", DQLITE_ADDR_PARSE_UNIX);
if (rv != 0) {
return rv;
}
domain = addr->sa_family;
fd = socket(domain, SOCK_STREAM, 0);
if (fd == -1) {
return DQLITE_ERROR;
}
rv = fcntl(fd, FD_CLOEXEC);
if (rv != 0) {
close(fd);
return DQLITE_ERROR;
}
if (domain == AF_INET || domain == AF_INET6) {
int reuse = 1;
rv = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
(const char *)&reuse, sizeof(reuse));
if (rv != 0) {
close(fd);
return DQLITE_ERROR;
}
}
rv = bind(fd, addr, addr_len);
if (rv != 0) {
close(fd);
return DQLITE_ERROR;
}
rv = transport__stream(&t->loop, fd, &t->listener);
if (rv != 0) {
close(fd);
return DQLITE_ERROR;
}
if (domain == AF_INET || domain == AF_INET6) {
int sz = ((int)strlen(address)) + 1; /* Room for '\0' */
t->bind_address = sqlite3_malloc(sz);
if (t->bind_address == NULL) {
close(fd);
return DQLITE_NOMEM;
}
strcpy(t->bind_address, address);
} else {
path_len = sizeof addr_un.sun_path;
t->bind_address = sqlite3_malloc((int)path_len);
if (t->bind_address == NULL) {
close(fd);
return DQLITE_NOMEM;
}
memset(t->bind_address, 0, path_len);
rv = uv_pipe_getsockname((struct uv_pipe_s *)t->listener,
t->bind_address, &path_len);
if (rv != 0) {
close(fd);
sqlite3_free(t->bind_address);
t->bind_address = NULL;
return DQLITE_ERROR;
}
t->bind_address[0] = '@';
}
return 0;
}
const char *dqlite_node_get_bind_address(dqlite_node *t)
{
return t->bind_address;
}
int dqlite_node_set_connect_func(dqlite_node *t,
int (*f)(void *arg,
const char *address,
int *fd),
void *arg)
{
if (t->running) {
return DQLITE_MISUSE;
}
raftProxySetConnectFunc(&t->raft_transport, f, arg);
/* Also save this info for use in automatic role management. */
t->connect_func = f;
t->connect_func_arg = arg;
return 0;
}
int dqlite_node_set_network_latency(dqlite_node *t,
unsigned long long nanoseconds)
{
unsigned milliseconds;
if (t->running) {
return DQLITE_MISUSE;
}
/* 1 hour latency should be more than sufficient, also avoids overflow
* issues when converting to unsigned milliseconds later on */
if (nanoseconds > 3600000000000ULL) {
return DQLITE_MISUSE;
}
milliseconds = (unsigned)(nanoseconds / (1000000ULL));
return dqlite_node_set_network_latency_ms(t, milliseconds);
}
int dqlite_node_set_network_latency_ms(dqlite_node *t, unsigned milliseconds)
{
if (t->running) {
return DQLITE_MISUSE;
}
/* Currently we accept at least 1 millisecond latency and maximum 3600 s
* of latency */
if (milliseconds == 0 || milliseconds > 3600U * 1000U) {
return DQLITE_MISUSE;
}
raft_set_heartbeat_timeout(&t->raft, (milliseconds * 15) / 10);
raft_set_election_timeout(&t->raft, milliseconds * 15);
return 0;
}
int dqlite_node_set_failure_domain(dqlite_node *n, unsigned long long code)
{
n->config.failure_domain = code;
return 0;
}
int dqlite_node_set_snapshot_params(dqlite_node *n,
unsigned snapshot_threshold,
unsigned snapshot_trailing)
{
if (n->running) {
return DQLITE_MISUSE;
}
if (snapshot_trailing < 4) {
return DQLITE_MISUSE;
}
/* This is a safety precaution and allows to recover data from the
* second last raft snapshot and segment files in case the last raft
* snapshot is unusable. */
if (snapshot_trailing < snapshot_threshold) {
return DQLITE_MISUSE;
}
raft_set_snapshot_threshold(&n->raft, snapshot_threshold);
raft_set_snapshot_trailing(&n->raft, snapshot_trailing);
return 0;
}
#define KB(N) (1024 * N)
int dqlite_node_set_block_size(dqlite_node *n, size_t size)
{
if (n->running) {
return DQLITE_MISUSE;
}
switch (size) {
case 512: // fallthrough
case KB(1): // fallthrough
case KB(2): // fallthrough
case KB(4): // fallthrough
case KB(8): // fallthrough
case KB(16): // fallthrough
case KB(32): // fallthrough
case KB(64): // fallthrough
case KB(128): // fallthrough
case KB(256):
break;
default:
return DQLITE_ERROR;
}
raft_uv_set_block_size(&n->raft_io, size);
return 0;
}
int dqlite_node_enable_disk_mode(dqlite_node *n)
{
int rv;
if (n->running) {
return DQLITE_MISUSE;
}
rv = dqlite_vfs_enable_disk(&n->vfs);
if (rv != 0) {
return rv;
}
n->registry.config->disk = true;
/* Close the default fsm and initialize the disk one. */
fsm__close(&n->raft_fsm);
rv = fsm__init_disk(&n->raft_fsm, &n->config, &n->registry);
if (rv != 0) {
return rv;
}
return 0;
}
static int maybeBootstrap(dqlite_node *d,
dqlite_node_id id,
const char *address)
{
struct raft_configuration configuration;
int rv;
if (id != 1 && id != BOOTSTRAP_ID) {
return 0;
}
raft_configuration_init(&configuration);
rv = raft_configuration_add(&configuration, id, address, RAFT_VOTER);
if (rv != 0) {
assert(rv == RAFT_NOMEM);
rv = DQLITE_NOMEM;
goto out;
};
rv = raft_bootstrap(&d->raft, &configuration);
if (rv != 0) {
if (rv == RAFT_CANTBOOTSTRAP) {
rv = 0;
} else {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"raft_bootstrap(): %s", raft_errmsg(&d->raft));
rv = DQLITE_ERROR;
}
goto out;
}
out:
raft_configuration_close(&configuration);
return rv;
}
/* Callback invoked when the stop async handle gets fired.
*
* This callback will walk through all active handles and close them. After the
* last handle is closed, the loop gets stopped.
*/
static void raftCloseCb(struct raft *raft)
{
struct dqlite_node *s = raft->data;
raft_uv_close(&s->raft_io);
uv_close((struct uv_handle_s *)&s->stop, NULL);
uv_close((struct uv_handle_s *)&s->handover, NULL);
uv_close((struct uv_handle_s *)&s->startup, NULL);
uv_close((struct uv_handle_s *)&s->monitor, NULL);
uv_close((struct uv_handle_s *)s->listener, NULL);
uv_close((struct uv_handle_s *)&s->timer, NULL);
}
static void destroy_conn(struct conn *conn)
{
QUEUE__REMOVE(&conn->queue);
sqlite3_free(conn);
}
static void handoverDoneCb(struct dqlite_node *d, int status)
{
d->handover_status = status;
sem_post(&d->handover_done);
}
static void handoverCb(uv_async_t *handover)
{
struct dqlite_node *d = handover->data;
int rv;
/* Nothing to do. */
if (!d->running) {
return;
}
if (d->role_management) {
rv = uv_timer_stop(&d->timer);
assert(rv == 0);
RolesCancelPendingChanges(d);
}
RolesHandover(d, handoverDoneCb);
}
static void stopCb(uv_async_t *stop)
{
struct dqlite_node *d = stop->data;
queue *head;
struct conn *conn;
int rv;
/* Nothing to do. */
if (!d->running) {
tracef("not running or already stopped");
return;
}
if (d->role_management) {
rv = uv_timer_stop(&d->timer);
assert(rv == 0);
RolesCancelPendingChanges(d);
}
d->running = false;
QUEUE__FOREACH(head, &d->conns)
{
conn = QUEUE__DATA(head, struct conn, queue);
conn__stop(conn);
}
raft_close(&d->raft, raftCloseCb);
}
/* Callback invoked as soon as the loop as started.
*
* It unblocks the s->ready semaphore.
*/
static void startup_cb(uv_timer_t *startup)
{
struct dqlite_node *d = startup->data;
int rv;
d->running = true;
rv = sem_post(&d->ready);
assert(rv == 0); /* No reason for which posting should fail */
}
static void listenCb(uv_stream_t *listener, int status)
{
struct dqlite_node *t = listener->data;
struct uv_stream_s *stream;
struct conn *conn;
struct id_state seed;
int rv;
if (!t->running) {
tracef("not running");
return;
}
if (status != 0) {
/* TODO: log the error. */
return;
}
switch (listener->type) {
case UV_TCP:
stream = raft_malloc(sizeof(struct uv_tcp_s));
if (stream == NULL) {
return;
}
rv = uv_tcp_init(&t->loop, (struct uv_tcp_s *)stream);
assert(rv == 0);
break;
case UV_NAMED_PIPE:
stream = raft_malloc(sizeof(struct uv_pipe_s));
if (stream == NULL) {
return;
}
rv = uv_pipe_init(&t->loop, (struct uv_pipe_s *)stream,
0);
assert(rv == 0);
break;
default:
assert(0);
}
rv = uv_accept(listener, stream);
if (rv != 0) {
goto err;
}
/* We accept unix socket connections only from the same process. */
if (listener->type == UV_NAMED_PIPE) {
int fd = stream->io_watcher.fd;
#if defined(SO_PEERCRED) // Linux
struct ucred cred;
socklen_t len = sizeof(cred);
rv = getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &cred, &len);
if (rv != 0) {
goto err;
}
if (cred.pid != getpid()) {
goto err;
}
#elif defined(LOCAL_PEERPID) // BSD
pid_t pid = -1;
socklen_t len = sizeof(pid);
rv = getsockopt(fd, SOL_LOCAL, LOCAL_PEERPID, &pid, &len);
if (rv != 0) {
goto err;
}
if (pid != getpid()) {
goto err;
}
#else
// The unix socket connection can't be verified and from
// security perspective it's better to block it entirely
goto err;
#endif
}
seed = t->random_state;
idJump(&t->random_state);
conn = sqlite3_malloc(sizeof *conn);
if (conn == NULL) {
goto err;
}
rv = conn__start(conn, &t->config, &t->loop, &t->registry, &t->raft,
stream, &t->raft_transport, seed, destroy_conn);
if (rv != 0) {
goto err_after_conn_alloc;
}
QUEUE__PUSH(&t->conns, &conn->queue);
return;
err_after_conn_alloc:
sqlite3_free(conn);
err:
uv_close((struct uv_handle_s *)stream, (uv_close_cb)raft_free);
}
static void monitor_cb(uv_prepare_t *monitor)
{
struct dqlite_node *d = monitor->data;
int state = raft_state(&d->raft);
queue *head;
struct conn *conn;
if (state == RAFT_UNAVAILABLE) {
return;
}
if (d->raft_state == RAFT_LEADER && state != RAFT_LEADER) {
tracef("node %llu@%s: leadership lost", d->raft.id,
d->raft.address);
QUEUE__FOREACH(head, &d->conns)
{
conn = QUEUE__DATA(head, struct conn, queue);
gateway__leader_close(&conn->gateway,
RAFT_LEADERSHIPLOST);
}
}
d->raft_state = state;
}
/* Runs every tick on the main thread to kick off roles adjustment. */
static void roleManagementTimerCb(uv_timer_t *handle)
{
struct dqlite_node *d = handle->data;
RolesAdjust(d);
}
static int taskRun(struct dqlite_node *d)
{
int rv;
/* TODO: implement proper cleanup upon error by spinning the loop a few
* times. */
assert(d->listener != NULL);
rv = uv_listen(d->listener, 128, listenCb);
if (rv != 0) {
return rv;
}
d->listener->data = d;
d->handover.data = d;
rv = uv_async_init(&d->loop, &d->handover, handoverCb);
assert(rv == 0);
/* Initialize notification handles. */
d->stop.data = d;
rv = uv_async_init(&d->loop, &d->stop, stopCb);
assert(rv == 0);
/* Schedule startup_cb to be fired as soon as the loop starts. It will
* unblock clients of taskReady. */
d->startup.data = d;
rv = uv_timer_init(&d->loop, &d->startup);
assert(rv == 0);
rv = uv_timer_start(&d->startup, startup_cb, 0, 0);
assert(rv == 0);
/* Schedule raft state change monitor. */
d->monitor.data = d;
rv = uv_prepare_init(&d->loop, &d->monitor);
assert(rv == 0);
rv = uv_prepare_start(&d->monitor, monitor_cb);
assert(rv == 0);
/* Schedule the role management callback. */
d->timer.data = d;
rv = uv_timer_init(&d->loop, &d->timer);
assert(rv == 0);
if (d->role_management) {
/* TODO make the interval configurable */
rv = uv_timer_start(&d->timer, roleManagementTimerCb, 1000,
1000);
assert(rv == 0);
}
d->raft.data = d;
rv = raft_start(&d->raft);
if (rv != 0) {
snprintf(d->errmsg, DQLITE_ERRMSG_BUF_SIZE, "raft_start(): %s",
raft_errmsg(&d->raft));
/* Unblock any client of taskReady */
sem_post(&d->ready);
return rv;
}
rv = uv_run(&d->loop, UV_RUN_DEFAULT);
assert(rv == 0);
/* Unblock any client of taskReady */
rv = sem_post(&d->ready);
assert(rv == 0); /* no reason for which posting should fail */
return 0;
}
int dqlite_node_set_target_voters(dqlite_node *n, int voters)
{
n->config.voters = voters;
return 0;
}
int dqlite_node_set_target_standbys(dqlite_node *n, int standbys)
{
n->config.standbys = standbys;
return 0;
}
int dqlite_node_enable_role_management(dqlite_node *n)
{
n->role_management = true;
return 0;
}
int dqlite_node_set_auto_recovery(dqlite_node *n, bool enabled)
{
raft_uv_set_auto_recovery(&n->raft_io, enabled);
return 0;
}
const char *dqlite_node_errmsg(dqlite_node *n)
{
if (n != NULL) {
return n->errmsg;
}
return "node is NULL";
}
static void *taskStart(void *arg)
{
struct dqlite_node *t = arg;
int rv;
rv = taskRun(t);
if (rv != 0) {
uintptr_t result = (uintptr_t)rv;
return (void *)result;
}
return NULL;
}
void dqlite_node_destroy(dqlite_node *d)
{
dqlite__close(d);
sqlite3_free(d);
}
/* Wait until a dqlite server is ready and can handle connections.
**
** Returns true if the server has been successfully started, false otherwise.
**
** This is a thread-safe API, but must be invoked before any call to
** dqlite_stop or dqlite_handle.
*/
static bool taskReady(struct dqlite_node *d)
{
/* Wait for the ready semaphore */
sem_wait(&d->ready);
return d->running;
}
static int dqliteDatabaseDirSetup(dqlite_node *t)
{
int rv;
if (!t->config.disk) {
// nothing to do
return 0;
}
rv = FsEnsureDir(t->config.dir);
if (rv != 0) {
snprintf(t->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"Error creating database dir: %d", rv);
return rv;
}
rv = FsRemoveDirFiles(t->config.dir);
if (rv != 0) {
snprintf(t->errmsg, DQLITE_ERRMSG_BUF_SIZE,
"Error removing files in database dir: %d", rv);
return rv;
}
return rv;
}
int dqlite_node_start(dqlite_node *t)
{
int rv;
tracef("dqlite node start");
dqliteTracingMaybeEnable(true);
rv = dqliteDatabaseDirSetup(t);
if (rv != 0) {
tracef("database dir setup failed %s", t->errmsg);
goto err;
}
rv = maybeBootstrap(t, t->config.id, t->config.address);
if (rv != 0) {
tracef("bootstrap failed %d", rv);
goto err;
}
rv = pthread_create(&t->thread, 0, &taskStart, t);
if (rv != 0) {
tracef("pthread create failed %d", rv);
rv = DQLITE_ERROR;
goto err;
}
if (!taskReady(t)) {
tracef("!taskReady");
rv = DQLITE_ERROR;
goto err;
}
return 0;
err:
return rv;
}
int dqlite_node_handover(dqlite_node *d)
{
int rv;
rv = uv_async_send(&d->handover);
assert(rv == 0);
sem_wait(&d->handover_done);
return d->handover_status;
}
int dqlite_node_stop(dqlite_node *d)
{
tracef("dqlite node stop");
void *result;
int rv;
rv = uv_async_send(&d->stop);
assert(rv == 0);
rv = pthread_join(d->thread, &result);
assert(rv == 0);
return (int)((uintptr_t)result);
}
int dqlite_node_recover(dqlite_node *n,
struct dqlite_node_info infos[],
int n_info)
{
tracef("dqlite node recover");
int i;
int ret;
struct dqlite_node_info_ext *infos_ext =
calloc((size_t)n_info, sizeof(*infos_ext));
if (infos_ext == NULL) {
return DQLITE_NOMEM;
}
for (i = 0; i < n_info; i++) {
infos_ext[i].size = sizeof(*infos_ext);
infos_ext[i].id = infos[i].id;
infos_ext[i].address = PTR_TO_UINT64(infos[i].address);
infos_ext[i].dqlite_role = DQLITE_VOTER;
}
ret = dqlite_node_recover_ext(n, infos_ext, n_info);
free(infos_ext);
return ret;
}
static bool node_info_valid(struct dqlite_node_info_ext *info)
{
/* Reject any size smaller than the original definition of the
* extensible struct. */
if (info->size < DQLITE_NODE_INFO_EXT_SZ_ORIG) {
return false;
}
/* Require 8 byte allignment */
if (info->size % sizeof(uint64_t)) {
return false;
}
/* If the user uses a newer, and larger version of the struct, make sure
* the unknown fields are zeroed out. */
uint64_t known_size = sizeof(struct dqlite_node_info_ext);
if (info->size > known_size) {
const uint64_t num_known_fields = known_size / sizeof(uint64_t);
const uint64_t num_extra_fields =
(info->size - known_size) / sizeof(uint64_t);
const uint64_t *extra_fields =
((const uint64_t *)info) + num_known_fields;
for (uint64_t i = 0; i < num_extra_fields; i++) {
if (extra_fields[i] != (uint64_t)0) {
return false;
}
}
}
return true;
}
int dqlite_node_recover_ext(dqlite_node *n,
struct dqlite_node_info_ext infos[],
int n_info)
{
tracef("dqlite node recover ext");
struct raft_configuration configuration;
int i;
int rv;
raft_configuration_init(&configuration);
for (i = 0; i < n_info; i++) {
struct dqlite_node_info_ext *info = &infos[i];
if (!node_info_valid(info)) {
rv = DQLITE_MISUSE;
goto out;
}
int raft_role = translateDqliteRole((int)info->dqlite_role);
const char *address =
UINT64_TO_PTR(info->address, const char *);
rv = raft_configuration_add(&configuration, info->id, address,
raft_role);
if (rv != 0) {
assert(rv == RAFT_NOMEM);
rv = DQLITE_NOMEM;
goto out;
};
}
rv = raft_recover(&n->raft, &configuration);
if (rv != 0) {
rv = DQLITE_ERROR;
goto out;
}
out:
raft_configuration_close(&configuration);
return rv;
}
dqlite_node_id dqlite_generate_node_id(const char *address)
{
tracef("generate node id");
struct timespec ts;
int rv;
unsigned long long n;
rv = clock_gettime(CLOCK_REALTIME, &ts);
assert(rv == 0);
n = (unsigned long long)(ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec);
return raft_digest(address, n);
}
static void pushNodeInfo(struct node_store_cache *cache,
struct client_node_info info)
{
unsigned cap = cache->cap;
struct client_node_info *new;
if (cache->len == cap) {
if (cap == 0) {
cap = 5;
}
cap *= 2;
new = callocChecked(cap, sizeof *new);
memcpy(new, cache->nodes, cache->len * sizeof *new);
free(cache->nodes);
cache->nodes = new;
cache->cap = cap;
}
cache->nodes[cache->len] = info;
cache->len += 1;
}
static void emptyCache(struct node_store_cache *cache)
{
unsigned i;
for (i = 0; i < cache->len; i += 1) {
free(cache->nodes[i].addr);
}
free(cache->nodes);
cache->nodes = NULL;
cache->len = 0;
cache->cap = 0;
}
static const struct client_node_info *findNodeInCache(
const struct node_store_cache *cache,
uint64_t id)
{
unsigned i;
for (i = 0; i < cache->len; i += 1) {
if (cache->nodes[i].id == id) {
return &cache->nodes[i];
}
}
return NULL;
}
/* Called at startup to parse the node store read from disk into an in-memory
* representation. */
static int parseNodeStore(char *buf, size_t len, struct node_store_cache *cache)
{
const char *p = buf;
const char *end = buf + len;
char *nl;
const char *version_str;
const char *addr;
const char *id_str;
const char *dig;
unsigned long long id;
const char *role_str;
int role;
struct client_node_info info;
version_str = p;
nl = memchr(p, '\n', (size_t)(end - version_str));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
if (strcmp(version_str, NODE_STORE_INFO_FORMAT_V1) != 0) {
return 1;
}
while (p != end) {
addr = p;
nl = memchr(p, '\n', (size_t)(end - addr));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
id_str = p;
nl = memchr(p, '\n', (size_t)(end - id_str));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
/* Be stricter than strtoull: digits only! */
for (dig = id_str; dig != nl; dig += 1) {
if (*dig < '0' || *dig > '9') {
return 1;
}
}
errno = 0;
id = strtoull(id_str, NULL, 10);
if (errno != 0) {
return 1;
}
role_str = p;
nl = memchr(p, '\n', (size_t)(end - role_str));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
if (strcmp(role_str, "spare") == 0) {
role = DQLITE_SPARE;
} else if (strcmp(role_str, "standby") == 0) {
role = DQLITE_STANDBY;
} else if (strcmp(role_str, "voter") == 0) {
role = DQLITE_VOTER;
} else {
return 1;
}
info.addr = strdupChecked(addr);
info.id = (uint64_t)id;
info.role = role;
pushNodeInfo(cache, info);
}
return 0;
}
/* Write the in-memory node store to disk. This discards errors, because:
*
* - we can't do much to handle any of these error cases
* - we don't want to stop everything when this encounters an error, since the
* persisted node store is an optimization, so it's not disastrous for it to
* be missing or out of date
* - there is already a "retry" mechanism in the form of the refreshTask thread,
* which periodically tries to write the node store file
*/
static void writeNodeStore(struct dqlite_server *server)
{
int store_fd;
FILE *f;
unsigned i;
ssize_t k;
const char *role_name;
int rv;
store_fd = openat(server->dir_fd, "node-store-tmp",
O_RDWR | O_CREAT | O_TRUNC, 0644);
if (store_fd < 0) {
return;
}
f = fdopen(store_fd, "w+");
if (f == NULL) {
close(store_fd);
return;
}
k = fprintf(f, "%s\n", NODE_STORE_INFO_FORMAT_V1);
if (k < 0) {
fclose(f);
return;
}
for (i = 0; i < server->cache.len; i += 1) {
role_name =
(server->cache.nodes[i].role == DQLITE_SPARE)
? "spare"
: ((server->cache.nodes[i].role == DQLITE_STANDBY)
? "standby"
: "voter");
k = fprintf(f, "%s\n%" PRIu64 "\n%s\n",
server->cache.nodes[i].addr,
server->cache.nodes[i].id, role_name);
if (k < 0) {
fclose(f);
return;
}
}
fclose(f);
rv = renameat(server->dir_fd, "node-store-tmp", server->dir_fd,
"node-store");
(void)rv;
}
/* Called at startup to parse the node store read from disk into an in-memory
* representation. */
static int parseLocalInfo(char *buf,
size_t len,
char **local_addr,
uint64_t *local_id)
{
const char *p = buf;
const char *end = buf + len;
char *nl;
const char *version_str;
const char *addr;
const char *id_str;
const char *dig;
unsigned long long id;
version_str = p;
nl = memchr(version_str, '\n', (size_t)(end - version_str));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
if (strcmp(version_str, NODE_STORE_INFO_FORMAT_V1) != 0) {
return 1;
}
addr = p;
nl = memchr(addr, '\n', (size_t)(end - addr));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
id_str = p;
nl = memchr(id_str, '\n', (size_t)(end - id_str));
if (nl == NULL) {
return 1;
}
*nl = '\0';
p = nl + 1;
for (dig = id_str; dig != nl; dig += 1) {
if (*dig < '0' || *dig > '9') {
return 1;
}
}
errno = 0;
id = strtoull(id_str, NULL, 10);
if (errno != 0) {
return 1;
}
if (p != end) {
return 1;
}
*local_addr = strdupChecked(addr);
*local_id = (uint64_t)id;
return 0;
}
/* Write the local node's info to disk. */
static int writeLocalInfo(struct dqlite_server *server)
{
int info_fd;
FILE *f;
ssize_t k;
int rv;
info_fd = openat(server->dir_fd, "server-info-tmp",
O_RDWR | O_CREAT | O_TRUNC, 0664);
if (info_fd < 0) {
return 1;
}
f = fdopen(info_fd, "w+");
if (f == NULL) {
close(info_fd);
return 1;
}
k = fprintf(f, "%s\n%s\n%" PRIu64 "\n", NODE_STORE_INFO_FORMAT_V1,
server->local_addr, server->local_id);
if (k < 0) {
fclose(f);
return 1;
}
rv = renameat(server->dir_fd, "server-info-tmp", server->dir_fd,
"server-info");
if (rv != 0) {
fclose(f);
return 1;
}
fclose(f);
return 0;
}
int dqlite_server_create(const char *path, dqlite_server **server)
{
int rv;
*server = callocChecked(1, sizeof **server);
rv = pthread_cond_init(&(*server)->cond, NULL);
assert(rv == 0);
rv = pthread_mutex_init(&(*server)->mutex, NULL);
assert(rv == 0);
(*server)->dir_path = strdupChecked(path);
(*server)->connect = transportDefaultConnect;
(*server)->proto.connect = transportDefaultConnect;
(*server)->dir_fd = -1;
(*server)->refresh_period = 30 * 1000;
return 0;
}
int dqlite_server_set_address(dqlite_server *server, const char *address)
{
free(server->local_addr);
server->local_addr = strdupChecked(address);
return 0;
}
int dqlite_server_set_auto_bootstrap(dqlite_server *server, bool on)
{
server->bootstrap = on;
return 0;
}
int dqlite_server_set_auto_join(dqlite_server *server,
const char *const *addrs,
unsigned n)
{
/* We don't know the ID or role of this server, so leave those fields
* zeroed. In dqlite_server_start, we must take care not to use this
* initial node store cache to do anything except find a server to
* connect to. Once we've done that, we immediately fetch a fresh list
* of cluster members that includes ID and role information, and clear
* away the temporary node store cache. */
struct client_node_info info = {0};
unsigned i;
for (i = 0; i < n; i += 1) {
info.addr = strdupChecked(addrs[i]);
pushNodeInfo(&server->cache, info);
}
return 0;
}
int dqlite_server_set_bind_address(dqlite_server *server, const char *addr)
{
free(server->bind_addr);
server->bind_addr = strdupChecked(addr);
return 0;
}
int dqlite_server_set_connect_func(dqlite_server *server,
dqlite_connect_func f,
void *arg)
{
server->connect = f;
server->connect_arg = arg;
server->proto.connect = f;
server->proto.connect_arg = arg;
return 0;
}
static int openAndHandshake(struct client_proto *proto,
const char *addr,
uint64_t id,
struct client_context *context)
{
int rv;
rv = clientOpen(proto, addr, id);
if (rv != 0) {
return 1;
}
rv = clientSendHandshake(proto, context);
if (rv != 0) {
clientClose(proto);
return 1;
}
/* TODO client identification? */
return 0;
}
/* TODO prioritize voters > standbys > spares */
static int connectToSomeServer(struct dqlite_server *server,
struct client_context *context)
{
unsigned i;
int rv;
for (i = 0; i < server->cache.len; i += 1) {
rv = openAndHandshake(&server->proto,
server->cache.nodes[i].addr,
server->cache.nodes[i].id, context);
if (rv == 0) {
return 0;
}
}
return 1;
}
/* Given an open connection, make an honest effort to reopen it as a connection
* to the current cluster leader. This bails out rather than retrying on
* client/server/network errors, leaving the retry policy up to the caller. On
* failure (rv != 0) the given client object may be closed or not: the caller
* must check this by comparing proto->fd to -1. */
static int tryReconnectToLeader(struct client_proto *proto,
struct client_context *context)
{
char *addr;
uint64_t id;
int rv;
rv = clientSendLeader(proto, context);
if (rv != 0) {
clientClose(proto);
return 1;
}
rv = clientRecvServer(proto, &id, &addr, context);
if (rv == DQLITE_CLIENT_PROTO_RECEIVED_FAILURE) {
return 1;
} else if (rv != 0) {
clientClose(proto);
return 1;
}
if (id == 0) {
free(addr);
return 1;
} else if (id == proto->server_id) {
free(addr);
return 0;
}
clientClose(proto);
rv = openAndHandshake(proto, addr, id, context);
free(addr);
if (rv != 0) {
return 1;
}
return 0;
}
static int refreshNodeStoreCache(struct dqlite_server *server,
struct client_context *context)
{
struct client_node_info *servers;
uint64_t n_servers;
int rv;
rv = clientSendCluster(&server->proto, context);
if (rv != 0) {
clientClose(&server->proto);
return 1;
}
rv = clientRecvServers(&server->proto, &servers, &n_servers, context);
if (rv != 0) {
clientClose(&server->proto);
return 1;
}
emptyCache(&server->cache);
server->cache.nodes = servers;
server->cache.len = (unsigned)n_servers;
assert((uint64_t)server->cache.len == n_servers);
server->cache.cap = (unsigned)n_servers;
return 0;
}
static int maybeJoinCluster(struct dqlite_server *server,
struct client_context *context)
{
int rv;
if (findNodeInCache(&server->cache, server->local_id) != NULL) {
return 0;
}
rv = clientSendAdd(&server->proto, server->local_id, server->local_addr,
context);
if (rv != 0) {
clientClose(&server->proto);
return 1;
}
rv = clientRecvEmpty(&server->proto, context);
if (rv != 0) {
clientClose(&server->proto);
return 1;
}
rv = refreshNodeStoreCache(server, context);
if (rv != 0) {
return 1;
}
return 0;
}
static int bootstrapOrJoinCluster(struct dqlite_server *server,
struct client_context *context)
{
struct client_node_info info;
int rv;
if (server->is_new && server->bootstrap) {
rv = openAndHandshake(&server->proto, server->local_addr,
server->local_id, context);
if (rv != 0) {
return 1;
}
info.addr = strdupChecked(server->local_addr);
info.id = server->local_id;
info.role = DQLITE_VOTER;
pushNodeInfo(&server->cache, info);
} else {
rv = connectToSomeServer(server, context);
if (rv != 0) {
return 1;
}
rv = tryReconnectToLeader(&server->proto, context);
if (rv != 0) {
return 1;
}
rv = refreshNodeStoreCache(server, context);
if (rv != 0) {
return 1;
}
rv = maybeJoinCluster(server, context);
if (rv != 0) {
return 1;
}
}
writeNodeStore(server);
return 0;
}
static void *refreshTask(void *arg)
{
struct dqlite_server *server = arg;
struct client_context context;
struct timespec ts;
unsigned long long nsec;
int rv;
rv = pthread_mutex_lock(&server->mutex);
assert(rv == 0);
for (;;) {
rv = clock_gettime(CLOCK_REALTIME, &ts);
assert(rv == 0);
nsec = (unsigned long long)ts.tv_nsec;
nsec += server->refresh_period * 1000 * 1000;
while (nsec > 1000 * 1000 * 1000) {
nsec -= 1000 * 1000 * 1000;
ts.tv_sec += 1;
}
/* The type of tv_nsec is "an implementation-defined signed type
* capable of holding [the range 0..=999,999,999]". int is the
* narrowest such type (on all the targets we care about), so
* cast to that before doing the assignment to avoid warnings.
*/
ts.tv_nsec = (int)nsec;
rv = pthread_cond_timedwait(&server->cond, &server->mutex, &ts);
if (server->shutdown) {
rv = pthread_mutex_unlock(&server->mutex);
assert(rv == 0);
break;
}
assert(rv == 0 || rv == ETIMEDOUT);
clientContextMillis(&context, 5000);
if (server->proto.fd == -1) {
rv = connectToSomeServer(server, &context);
if (rv != 0) {
continue;
}
(void)tryReconnectToLeader(&server->proto, &context);
if (server->proto.fd == -1) {
continue;
}
}
rv = refreshNodeStoreCache(server, &context);
if (rv != 0) {
continue;
}
writeNodeStore(server);
}
return NULL;
}
int dqlite_server_start(dqlite_server *server)
{
int info_fd;
int store_fd;
off_t full_size;
ssize_t size;
char *buf;
ssize_t n_read;
struct client_context context;
int rv;
if (server->started) {
goto err;
}
if (server->bootstrap && server->cache.len > 0) {
goto err;
}
server->is_new = true;
server->dir_fd = open(server->dir_path, O_RDONLY | O_DIRECTORY);
if (server->dir_fd < 0) {
goto err;
}
info_fd = openat(server->dir_fd, "server-info", O_RDWR | O_CREAT, 0664);
if (info_fd < 0) {
goto err_after_open_dir;
}
store_fd = openat(server->dir_fd, "node-store", O_RDWR | O_CREAT, 0664);
if (store_fd < 0) {
goto err_after_open_info;
}
full_size = lseek(info_fd, 0, SEEK_END);
assert(full_size >= 0);
if (full_size > (off_t)SSIZE_MAX) {
goto err_after_open_store;
}
size = (ssize_t)full_size;
if (size > 0) {
server->is_new = false;
/* TODO mmap it? */
buf = mallocChecked((size_t)size);
n_read = pread(info_fd, buf, (size_t)size, 0);
if (n_read < size) {
free(buf);
goto err_after_open_store;
}
free(server->local_addr);
server->local_addr = NULL;
rv = parseLocalInfo(buf, (size_t)size, &server->local_addr,
&server->local_id);
free(buf);
if (rv != 0) {
goto err_after_open_store;
}
}
full_size = lseek(store_fd, 0, SEEK_END);
assert(full_size >= 0);
if (full_size > (off_t)SSIZE_MAX) {
goto err_after_open_store;
}
size = (ssize_t)full_size;
if (size > 0) {
if (server->is_new) {
goto err_after_open_store;
}
/* TODO mmap it? */
buf = mallocChecked((size_t)size);
n_read = pread(store_fd, buf, (size_t)size, 0);
if (n_read < size) {
free(buf);
goto err_after_open_store;
}
emptyCache(&server->cache);
rv = parseNodeStore(buf, (size_t)size, &server->cache);
free(buf);
if (rv != 0) {
goto err_after_open_store;
}
}
if (server->is_new) {
server->local_id =
server->bootstrap
? BOOTSTRAP_ID
: dqlite_generate_node_id(server->local_addr);
}
rv = dqlite_node_create(server->local_id, server->local_addr,
server->dir_path, &server->local);
if (rv != 0) {
goto err_after_create_node;
}
rv = dqlite_node_set_bind_address(
server->local, (server->bind_addr != NULL) ? server->bind_addr
: server->local_addr);
if (rv != 0) {
goto err_after_create_node;
}
rv = dqlite_node_set_connect_func(server->local, server->connect,
server->connect_arg);
if (rv != 0) {
goto err_after_create_node;
}
rv = dqlite_node_start(server->local);
if (rv != 0) {
goto err_after_create_node;
}
/* TODO set weight and failure domain here */
rv = writeLocalInfo(server);
if (rv != 0) {
goto err_after_start_node;
}
clientContextMillis(&context, 5000);
rv = bootstrapOrJoinCluster(server, &context);
if (rv != 0) {
goto err_after_start_node;
}
rv = pthread_create(&server->refresh_thread, NULL, refreshTask, server);
assert(rv == 0);
close(store_fd);
close(info_fd);
server->started = true;
return 0;
err_after_start_node:
dqlite_node_stop(server->local);
err_after_create_node:
dqlite_node_destroy(server->local);
server->local = NULL;
err_after_open_store:
close(store_fd);
err_after_open_info:
close(info_fd);
err_after_open_dir:
close(server->dir_fd);
server->dir_fd = -1;
err:
return 1;
}
dqlite_node_id dqlite_server_get_id(dqlite_server *server)
{
return server->local_id;
}
int dqlite_server_handover(dqlite_server *server)
{
int rv = dqlite_node_handover(server->local);
if (rv != 0) {
return 1;
}
return 0;
}
int dqlite_server_stop(dqlite_server *server)
{
void *ret;
int rv;
if (!server->started) {
return 1;
}
rv = pthread_mutex_lock(&server->mutex);
assert(rv == 0);
server->shutdown = true;
rv = pthread_mutex_unlock(&server->mutex);
assert(rv == 0);
rv = pthread_cond_signal(&server->cond);
assert(rv == 0);
rv = pthread_join(server->refresh_thread, &ret);
assert(rv == 0);
emptyCache(&server->cache);
clientClose(&server->proto);
server->started = false;
rv = dqlite_node_stop(server->local);
if (rv != 0) {
return 1;
}
return 0;
}
void dqlite_server_destroy(dqlite_server *server)
{
pthread_cond_destroy(&server->cond);
pthread_mutex_destroy(&server->mutex);
emptyCache(&server->cache);
free(server->dir_path);
if (server->local != NULL) {
dqlite_node_destroy(server->local);
}
free(server->local_addr);
free(server->bind_addr);
close(server->dir_fd);
free(server);
}
dqlite-1.16.0/src/server.h 0000664 0000000 0000000 00000006151 14512203222 0015323 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_SERVER_H
#define DQLITE_SERVER_H
#include
#include
#include
#include
#include "client/protocol.h"
#include "config.h"
#include "id.h"
#include "lib/assert.h"
#include "logger.h"
#include "registry.h"
#define DQLITE_ERRMSG_BUF_SIZE 300
/**
* A single dqlite server instance.
*/
struct dqlite_node
{
bool initialized; /* dqlite__init succeeded */
pthread_t thread; /* Main run loop thread. */
struct config config; /* Config values */
struct sqlite3_vfs vfs; /* In-memory VFS */
struct registry registry; /* Databases */
struct uv_loop_s loop; /* UV loop */
struct raft_uv_transport raft_transport; /* Raft libuv transport */
struct raft_io raft_io; /* libuv I/O */
struct raft_fsm raft_fsm; /* dqlite FSM */
sem_t ready; /* Server is ready */
sem_t stopped; /* Notify loop stopped */
sem_t handover_done;
queue queue; /* Incoming connections */
queue conns; /* Active connections */
queue roles_changes;
bool running; /* Loop is running */
struct raft raft; /* Raft instance */
struct uv_stream_s *listener; /* Listening socket */
struct uv_async_s handover;
int handover_status;
void (*handover_done_cb)(struct dqlite_node *, int);
struct uv_async_s stop; /* Trigger UV loop stop */
struct uv_timer_s startup; /* Unblock ready sem */
struct uv_prepare_s monitor; /* Raft state change monitor */
struct uv_timer_s timer;
int raft_state; /* Previous raft state */
char *bind_address; /* Listen address */
bool role_management;
int (*connect_func)(
void *,
const char *,
int *); /* Connection function for role management */
void *connect_func_arg; /* User data for connection function */
char errmsg[DQLITE_ERRMSG_BUF_SIZE]; /* Last error occurred */
struct id_state random_state; /* For seeding ID generation */
};
/* Dynamic array of node info objects. This is the in-memory representation of
* the node store. */
struct node_store_cache
{
struct client_node_info *nodes; /* owned */
unsigned len;
unsigned cap;
};
struct dqlite_server
{
/* Threading stuff: */
pthread_cond_t cond;
pthread_mutex_t mutex;
pthread_t refresh_thread;
/* These fields are protected by the mutex: */
bool shutdown;
struct node_store_cache cache;
/* We try to keep this pointing at the leader, but it might be out of
* date or not open. */
struct client_proto proto;
/* These fields are only accessed on the main thread: */
bool started;
bool is_new;
bool bootstrap;
char *dir_path; /* owned */
dqlite_node *local;
uint64_t local_id;
char *local_addr; /* owned */
char *bind_addr; /* owned */
dqlite_connect_func connect;
void *connect_arg;
unsigned long long refresh_period; /* in milliseconds */
int dir_fd;
};
int dqlite__init(struct dqlite_node *d,
dqlite_node_id id,
const char *address,
const char *dir);
void dqlite__close(struct dqlite_node *d);
int dqlite__run(struct dqlite_node *d);
#endif
dqlite-1.16.0/src/stmt.c 0000664 0000000 0000000 00000001157 14512203222 0015000 0 ustar 00root root 0000000 0000000 #include
#include "./lib/assert.h"
#include "./tuple.h"
#include "stmt.h"
/* The maximum number of columns we expect (for bindings or rows) is 255, which
* can fit in one byte. */
#define STMT__MAX_COLUMNS (1 << 8) - 1
void stmt__init(struct stmt *s)
{
s->stmt = NULL;
}
void stmt__close(struct stmt *s)
{
if (s->stmt != NULL) {
/* Ignore the return code, since it will be non-zero in case the
* most rececent evaluation of the statement failed. */
sqlite3_finalize(s->stmt);
}
}
const char *stmt__hash(struct stmt *stmt)
{
(void)stmt;
return NULL;
}
REGISTRY_METHODS(stmt__registry, stmt);
dqlite-1.16.0/src/stmt.h 0000664 0000000 0000000 00000005566 14512203222 0015015 0 ustar 00root root 0000000 0000000 /******************************************************************************
*
* APIs to decode parameters and bind them to SQLite statement, and to fetch
* rows and encode them.
*
* The dqlite wire format for a list of parameters to be bound to a statement
* is divided in header and body. The format of the header is:
*
* 8 bits: Number of parameters to bind (min is 1, max is 255).
* 4 bits: Type code of the 1st parameter to bind.
* 4 bits: Type code of the 2nd parameter to bind, or 0.
* 4 bits: Type code of the 3rn parameter to bind, or 0.
* ...
*
* This repeats until reaching a full 64-bit word. If there are more than 14
* parameters, the header will grow additional 64-bit words as needed, following
* the same pattern: a sequence of 4-bit slots with type codes of the parameters
* to bind, followed by a sequence of zero bits, until word boundary is reached.
*
* After the parameters header follows the parameters body, which contain one
* value for each parameter to bind, following the normal encoding rules.
*
* The dqlite wire format for a set of query rows is divided in header and
* body. The format of the header is:
*
* 64 bits: Number of columns in the result set (min is 1).
* 64 bits: Name of the first column. If the name is longer, additional words
* of 64 bits can be used, like for normal string encoding.
* ... If present, name of the 2nd, 3rd, ..., nth column.
*
* After the result set header follows the result set body, which is a sequence
* of zero or more rows. Each row has the following format:
*
* 4 bits: Type code of the 1st column of the row.
* 4 bits: Type code of the 2nd column of row, or 0.
* 4 bits: Type code of the 2nd column of row, or 0.
*
* This repeats until reaching a full 64-bit word. If there are more than 16 row
* columns, the header will grow additional 64-bit words as needed, following
* the same pattern. After this row preamble, the values of all columns of the
* row follow, using the normal dqlite enconding conventions.
*
*****************************************************************************/
#ifndef DQLITE_STMT_H
#define DQLITE_STMT_H
#include
#include "lib/registry.h"
/* Hold state for a single open SQLite database */
struct stmt
{
size_t id; /* Statement ID */
sqlite3_stmt *stmt; /* Underlying SQLite statement handle */
};
/* Initialize a statement state object */
void stmt__init(struct stmt *s);
/* Close a statement state object, releasing all associated resources. */
void stmt__close(struct stmt *s);
/* No-op hash function (hashing is not supported for stmt). This is
* required by the registry interface. */
const char *stmt__hash(struct stmt *stmt);
/* TODO: change registry naming pattern */
#define stmt_init stmt__init
#define stmt_close stmt__close
#define stmt_hash stmt__hash
REGISTRY(stmt__registry, stmt);
#endif /* DQLITE_STMT_H */
dqlite-1.16.0/src/tracing.c 0000664 0000000 0000000 00000000367 14512203222 0015442 0 ustar 00root root 0000000 0000000 #include "tracing.h"
#include
#define LIBDQLITE_TRACE "LIBDQLITE_TRACE"
bool _dqliteTracingEnabled = false;
void dqliteTracingMaybeEnable(bool enable)
{
if (getenv(LIBDQLITE_TRACE) != NULL) {
_dqliteTracingEnabled = enable;
}
}
dqlite-1.16.0/src/tracing.h 0000664 0000000 0000000 00000002517 14512203222 0015446 0 ustar 00root root 0000000 0000000 /* Tracing functionality for dqlite */
#ifndef DQLITE_TRACING_H_
#define DQLITE_TRACING_H_
#include
#include
#include
#include
#include "../include/dqlite.h"
#include "utils.h"
/* This global variable is only written once at startup and is only read
* from there on. Users should not manipulate the value of this variable. */
DQLITE_VISIBLE_TO_TESTS extern bool _dqliteTracingEnabled;
#define tracef(...) \
do { \
if (UNLIKELY(_dqliteTracingEnabled)) { \
static char _msg[1024]; \
snprintf(_msg, sizeof(_msg), __VA_ARGS__); \
struct timespec ts = {0}; \
/* Ignore errors */ \
clock_gettime(CLOCK_REALTIME, &ts); \
int64_t ns = ts.tv_sec * 1000000000 + ts.tv_nsec; \
fprintf(stderr, "LIBDQLITE %" PRId64 " %s:%d %s\n", \
ns, __func__, __LINE__, _msg); \
} \
} while (0)
/* Enable tracing if the appropriate env variable is set, or disable tracing. */
DQLITE_VISIBLE_TO_TESTS void dqliteTracingMaybeEnable(bool enabled);
#endif /* DQLITE_TRACING_H_ */
dqlite-1.16.0/src/translate.c 0000664 0000000 0000000 00000002050 14512203222 0015777 0 ustar 00root root 0000000 0000000 #include "translate.h"
#include
#include "assert.h"
#include "leader.h"
#include "protocol.h"
/* Translate a raft error to a dqlite one. */
int translateRaftErrCode(int code)
{
switch (code) {
case RAFT_NOTLEADER:
return SQLITE_IOERR_NOT_LEADER;
case RAFT_LEADERSHIPLOST:
return SQLITE_IOERR_LEADERSHIP_LOST;
case RAFT_CANTCHANGE:
return SQLITE_BUSY;
default:
return SQLITE_ERROR;
}
}
/* Translate a dqlite role code to its raft equivalent. */
int translateDqliteRole(int role)
{
switch (role) {
case DQLITE_VOTER:
return RAFT_VOTER;
case DQLITE_STANDBY:
return RAFT_STANDBY;
case DQLITE_SPARE:
return RAFT_SPARE;
default:
/* For backward compat with clients that don't set a
* role. */
return DQLITE_VOTER;
}
}
/* Translate a raft role code to its dqlite equivalent. */
int translateRaftRole(int role)
{
switch (role) {
case RAFT_VOTER:
return DQLITE_VOTER;
case RAFT_STANDBY:
return DQLITE_STANDBY;
case RAFT_SPARE:
return DQLITE_SPARE;
default:
assert(0);
return -1;
}
}
dqlite-1.16.0/src/translate.h 0000664 0000000 0000000 00000000617 14512203222 0016013 0 ustar 00root root 0000000 0000000 /* Translate to/from dqlite types */
#ifndef DQLITE_TRANSLATE_H_
#define DQLITE_TRANSLATE_H_
/* Translate a raft error to a dqlite one. */
int translateRaftErrCode(int code);
/* Translate a dqlite role code to its raft equivalent. */
int translateDqliteRole(int role);
/* Translate a raft role code to its dqlite equivalent. */
int translateRaftRole(int role);
#endif /* DQLITE_TRANSLATE_H_ */
dqlite-1.16.0/src/transport.c 0000664 0000000 0000000 00000013574 14512203222 0016053 0 ustar 00root root 0000000 0000000 #include "lib/transport.h"
#include
#include
#include
#include
#include
#include "lib/addr.h"
#include "message.h"
#include "protocol.h"
#include "request.h"
#include "tracing.h"
#include "transport.h"
struct impl
{
struct uv_loop_s *loop;
struct
{
int (*f)(void *arg, const char *address, int *fd);
void *arg;
} connect;
raft_id id;
const char *address;
raft_uv_accept_cb accept_cb;
};
struct connect
{
struct impl *impl;
struct raft_uv_connect *req;
struct uv_work_s work;
raft_id id;
const char *address;
int fd;
int status;
};
static int impl_init(struct raft_uv_transport *transport,
raft_id id,
const char *address)
{
tracef("impl init");
struct impl *i = transport->impl;
i->id = id;
i->address = address;
return 0;
}
static int impl_listen(struct raft_uv_transport *transport,
raft_uv_accept_cb cb)
{
tracef("impl listen");
struct impl *i = transport->impl;
i->accept_cb = cb;
return 0;
}
static void connect_work_cb(uv_work_t *work)
{
tracef("connect work cb");
struct connect *r = work->data;
struct impl *i = r->impl;
struct message message = {0};
struct request_connect request = {0};
uint64_t protocol;
void *buf;
void *cursor;
size_t n;
size_t n1;
size_t n2;
int rv;
/* Establish a connection to the other node using the provided connect
* function. */
rv = i->connect.f(i->connect.arg, r->address, &r->fd);
if (rv != 0) {
tracef("connect failed to %llu@%s", r->id, r->address);
rv = RAFT_NOCONNECTION;
goto err;
}
/* Send the initial dqlite protocol handshake. */
protocol = ByteFlipLe64(DQLITE_PROTOCOL_VERSION);
rv = (int)write(r->fd, &protocol, sizeof protocol);
if (rv != sizeof protocol) {
tracef("write failed");
rv = RAFT_NOCONNECTION;
goto err_after_connect;
}
/* Send a CONNECT dqlite protocol command, which will transfer control
* to the underlying raft UV backend. */
request.id = i->id;
request.address = i->address;
n1 = message__sizeof(&message);
n2 = request_connect__sizeof(&request);
message.type = DQLITE_REQUEST_CONNECT;
message.words = (uint32_t)(n2 / 8);
n = n1 + n2;
buf = sqlite3_malloc64(n);
if (buf == NULL) {
tracef("malloc failed");
rv = RAFT_NOCONNECTION;
goto err_after_connect;
}
cursor = buf;
message__encode(&message, &cursor);
request_connect__encode(&request, &cursor);
rv = (int)write(r->fd, buf, n);
sqlite3_free(buf);
if (rv != (int)n) {
tracef("write failed");
rv = RAFT_NOCONNECTION;
goto err_after_connect;
}
r->status = 0;
return;
err_after_connect:
close(r->fd);
err:
r->status = rv;
return;
}
static void connect_after_work_cb(uv_work_t *work, int status)
{
tracef("connect after work cb status %d", status);
struct connect *r = work->data;
struct impl *i = r->impl;
struct uv_stream_s *stream = NULL;
int rv;
assert(status == 0);
if (r->status != 0) {
goto out;
}
rv = transport__stream(i->loop, r->fd, &stream);
if (rv != 0) {
tracef("transport stream failed %d", rv);
r->status = RAFT_NOCONNECTION;
close(r->fd);
goto out;
}
out:
r->req->cb(r->req, stream, r->status);
sqlite3_free(r);
}
static int impl_connect(struct raft_uv_transport *transport,
struct raft_uv_connect *req,
raft_id id,
const char *address,
raft_uv_connect_cb cb)
{
tracef("impl connect id:%llu address:%s", id, address);
struct impl *i = transport->impl;
struct connect *r;
int rv;
r = sqlite3_malloc(sizeof *r);
if (r == NULL) {
tracef("malloc failed");
rv = DQLITE_NOMEM;
goto err;
}
r->impl = i;
r->req = req;
r->work.data = r;
r->id = id;
r->address = address;
req->cb = cb;
rv = uv_queue_work(i->loop, &r->work, connect_work_cb,
connect_after_work_cb);
if (rv != 0) {
tracef("queue work failed");
rv = RAFT_NOCONNECTION;
goto err_after_connect_alloc;
}
return 0;
err_after_connect_alloc:
sqlite3_free(r);
err:
return rv;
}
static void impl_close(struct raft_uv_transport *transport,
raft_uv_transport_close_cb cb)
{
tracef("impl close");
cb(transport);
}
int transportDefaultConnect(void *arg, const char *address, int *fd)
{
struct sockaddr_in addr_in;
struct sockaddr *addr = (struct sockaddr *)&addr_in;
socklen_t addr_len = sizeof addr_in;
int rv;
(void)arg;
rv = AddrParse(address, addr, &addr_len, "8080", 0);
if (rv != 0) {
return RAFT_NOCONNECTION;
}
assert(addr->sa_family == AF_INET || addr->sa_family == AF_INET6);
*fd = socket(addr->sa_family, SOCK_STREAM, 0);
if (*fd == -1) {
return RAFT_NOCONNECTION;
}
rv = connect(*fd, addr, addr_len);
if (rv == -1) {
close(*fd);
return RAFT_NOCONNECTION;
}
return 0;
}
int raftProxyInit(struct raft_uv_transport *transport, struct uv_loop_s *loop)
{
tracef("raft proxy init");
struct impl *i = sqlite3_malloc(sizeof *i);
if (i == NULL) {
return DQLITE_NOMEM;
}
i->loop = loop;
i->connect.f = transportDefaultConnect;
i->connect.arg = NULL;
i->accept_cb = NULL;
transport->version = 1;
transport->impl = i;
transport->init = impl_init;
transport->listen = impl_listen;
transport->connect = impl_connect;
transport->close = impl_close;
return 0;
}
void raftProxyClose(struct raft_uv_transport *transport)
{
tracef("raft proxy close");
struct impl *i = transport->impl;
sqlite3_free(i);
}
void raftProxyAccept(struct raft_uv_transport *transport,
raft_id id,
const char *address,
struct uv_stream_s *stream)
{
tracef("raft proxy accept");
struct impl *i = transport->impl;
/* If the accept callback is NULL it means we were stopped. */
if (i->accept_cb == NULL) {
tracef("raft proxy accept closed");
uv_close((struct uv_handle_s *)stream, (uv_close_cb)raft_free);
} else {
i->accept_cb(transport, id, address, stream);
}
}
void raftProxySetConnectFunc(struct raft_uv_transport *transport,
int (*f)(void *arg, const char *address, int *fd),
void *arg)
{
struct impl *i = transport->impl;
i->connect.f = f;
i->connect.arg = arg;
}
dqlite-1.16.0/src/transport.h 0000664 0000000 0000000 00000002270 14512203222 0016047 0 ustar 00root root 0000000 0000000 /* Implementation of the raft_uv_transport interface, proxied by a dqlite
* connection.
*
* Instead of having raft instances connect to each other directly, we pass a
* custom connect function that causes dqlite to send a CONNECT request to the
* dqlite server where the destination raft instance is running. That server
* responds to the CONNECT request by forwarding the dqlite connection to its
* raft instance, after which the raft-to-raft connection is transparent. */
#ifndef TRANSPORT_H_
#define TRANSPORT_H_
#include
#include "../include/dqlite.h"
int transportDefaultConnect(void *arg, const char *address, int *fd);
int raftProxyInit(struct raft_uv_transport *transport, struct uv_loop_s *loop);
void raftProxyClose(struct raft_uv_transport *transport);
/* Invoke the accept callback configured on the transport object. */
void raftProxyAccept(struct raft_uv_transport *transport,
raft_id id,
const char *address,
struct uv_stream_s *stream);
/* Set a custom connect function. */
void raftProxySetConnectFunc(struct raft_uv_transport *transport,
int (*f)(void *arg, const char *address, int *fd),
void *arg);
#endif /* TRANSPORT_H_*/
dqlite-1.16.0/src/tuple.c 0000664 0000000 0000000 00000015075 14512203222 0015146 0 ustar 00root root 0000000 0000000 #include
#include "assert.h"
#include "tuple.h"
/* Return the tuple header size in bytes, for a tuple of @n values.
*
* If the tuple is a row, then each slot is 4 bits, otherwise if the tuple is a
* sequence of parameters each slot is 8 bits. */
static size_t calc_header_size(unsigned long n, int format)
{
size_t size;
switch (format) {
case TUPLE__ROW:
/* Half a byte for each slot, rounded up... */
size = (n + 1) / 2;
/* ...and padded to a multiple of 8 bytes. */
size = BytePad64(size);
break;
case TUPLE__PARAMS:
/* 1-byte params count at the beginning of the first
* word */
size = n + 1;
size = BytePad64(size);
/* Params count is not included in the header */
size -= 1;
break;
case TUPLE__PARAMS32:
/* 4-byte params count at the beginning of the first
* word */
size = n + 4;
size = BytePad64(size);
/* Params count is not included in the header */
size -= 4;
break;
default:
assert(0);
}
return size;
}
int tuple_decoder__init(struct tuple_decoder *d,
unsigned n,
int format,
struct cursor *cursor)
{
size_t header_size;
uint8_t byte = 0;
uint32_t val = 0;
int rc = 0;
switch (format) {
case TUPLE__ROW:
assert(n > 0);
d->n = n;
break;
case TUPLE__PARAMS:
assert(n == 0);
rc = uint8__decode(cursor, &byte);
d->n = byte;
break;
case TUPLE__PARAMS32:
assert(n == 0);
rc = uint32__decode(cursor, &val);
d->n = val;
break;
default:
assert(0);
}
if (rc != 0) {
return rc;
}
d->format = format;
d->i = 0;
d->header = cursor->p;
/* Check that there is enough room to hold n type code slots. */
header_size = calc_header_size(d->n, d->format);
if (header_size > cursor->cap) {
return DQLITE_PARSE;
}
d->cursor = cursor;
d->cursor->p += header_size;
d->cursor->cap -= header_size;
return 0;
}
/* Return the number of values in the decoder's tuple. */
unsigned long tuple_decoder__n(struct tuple_decoder *d)
{
return d->n;
}
/* Return the type of the i'th value of the tuple. */
static int get_type(struct tuple_decoder *d, unsigned long i)
{
int type;
/* In row format the type slot size is 4 bits, while in params format
* the slot is 8 bits. */
if (d->format == TUPLE__ROW) {
type = d->header[i / 2];
if (i % 2 == 0) {
type &= 0x0f;
} else {
type = type >> 4;
}
} else {
type = d->header[i];
}
return type;
}
int tuple_decoder__next(struct tuple_decoder *d, struct value *value)
{
int rc;
assert(d->i < d->n);
value->type = get_type(d, d->i);
switch (value->type) {
case SQLITE_INTEGER:
rc = int64__decode(d->cursor, &value->integer);
break;
case SQLITE_FLOAT:
rc = float__decode(d->cursor, &value->float_);
break;
case SQLITE_BLOB:
rc = blob__decode(d->cursor, &value->blob);
break;
case SQLITE_NULL:
/* TODO: allow null to be encoded with 0 bytes? */
rc = uint64__decode(d->cursor, &value->null);
break;
case SQLITE_TEXT:
rc = text__decode(d->cursor, &value->text);
break;
case DQLITE_ISO8601:
rc = text__decode(d->cursor, &value->iso8601);
break;
case DQLITE_BOOLEAN:
rc = uint64__decode(d->cursor, &value->boolean);
break;
default:
rc = DQLITE_PARSE;
break;
};
if (rc != 0) {
return rc;
}
d->i++;
return 0;
}
/* Return a pointer to the tuple header. */
static uint8_t *encoder__header(struct tuple_encoder *e)
{
return buffer__cursor(e->buffer, e->header);
}
int tuple_encoder__init(struct tuple_encoder *e,
unsigned long n,
int format,
struct buffer *buffer)
{
void *cursor;
size_t n_header;
e->n = n;
e->format = format;
e->buffer = buffer;
e->i = 0;
/* When encoding a tuple of parameters, we need to write the
* number of values at the beginning of the header. */
if (e->format == TUPLE__PARAMS) {
assert(n <= UINT8_MAX);
uint8_t *header = buffer__advance(buffer, 1);
if (header == NULL) {
return DQLITE_NOMEM;
}
header[0] = (uint8_t)n;
} else if (e->format == TUPLE__PARAMS32) {
uint32_t val = (uint32_t)n;
assert((unsigned long long)val == (unsigned long long)n);
void *header = buffer__advance(buffer, 4);
if (header == NULL) {
return DQLITE_NOMEM;
}
uint32__encode(&val, &header);
}
e->header = buffer__offset(buffer);
/* Reset the header */
n_header = calc_header_size(n, format);
memset(encoder__header(e), 0, n_header);
/* Advance the buffer write pointer past the tuple header. */
cursor = buffer__advance(buffer, n_header);
if (cursor == NULL) {
return DQLITE_NOMEM;
}
return 0;
}
/* Set the type of the i'th value of the tuple. */
static void set_type(struct tuple_encoder *e, unsigned long i, int type)
{
uint8_t *header = encoder__header(e);
/* In row format the type slot size is 4 bits, while in params format
* the slot is 8 bits. */
if (e->format == TUPLE__ROW) {
uint8_t *slot;
slot = &header[i / 2];
if (i % 2 == 0) {
*slot = (uint8_t)type;
} else {
*slot |= (uint8_t)(type << 4);
}
} else {
header[i] = (uint8_t)type;
}
}
int tuple_encoder__next(struct tuple_encoder *e, struct value *value)
{
void *cursor;
size_t size;
assert(e->i < e->n);
set_type(e, e->i, value->type);
switch (value->type) {
case SQLITE_INTEGER:
size = int64__sizeof(&value->integer);
break;
case SQLITE_FLOAT:
size = float__sizeof(&value->float_);
break;
case SQLITE_BLOB:
size = blob__sizeof(&value->blob);
break;
case SQLITE_NULL:
/* TODO: allow null to be encoded with 0 bytes */
size = uint64__sizeof(&value->null);
break;
case SQLITE_TEXT:
size = text__sizeof(&value->text);
break;
case DQLITE_UNIXTIME:
size = int64__sizeof(&value->unixtime);
break;
case DQLITE_ISO8601:
size = text__sizeof(&value->iso8601);
break;
case DQLITE_BOOLEAN:
size = uint64__sizeof(&value->boolean);
break;
default:
assert(0);
};
/* Advance the buffer write pointer. */
cursor = buffer__advance(e->buffer, size);
if (cursor == NULL) {
return DQLITE_NOMEM;
}
switch (value->type) {
case SQLITE_INTEGER:
int64__encode(&value->integer, &cursor);
break;
case SQLITE_FLOAT:
float__encode(&value->float_, &cursor);
break;
case SQLITE_BLOB:
blob__encode(&value->blob, &cursor);
break;
case SQLITE_NULL:
/* TODO: allow null to be encoded with 0 bytes */
uint64__encode(&value->null, &cursor);
break;
case SQLITE_TEXT:
text__encode(&value->text, &cursor);
break;
case DQLITE_UNIXTIME:
int64__encode(&value->unixtime, &cursor);
break;
case DQLITE_ISO8601:
text__encode(&value->iso8601, &cursor);
break;
case DQLITE_BOOLEAN:
uint64__encode(&value->boolean, &cursor);
break;
};
e->i++;
return 0;
}
dqlite-1.16.0/src/tuple.h 0000664 0000000 0000000 00000010470 14512203222 0015145 0 ustar 00root root 0000000 0000000 /**
* Encode and decode tuples of database values.
*
* A tuple is composed by a header and a body.
*
* The format of the header changes depending on whether the tuple is a sequence
* of parameters to bind to a statement, or a sequence of values of a single row
* yielded by a query.
*
* For a tuple of parameters the format of the header is:
*
* 8 bits: Number of values in the tuple.
* 8 bits: Type code of the 1st value of the tuple.
* 8 bits: Type code of the 2nd value of the tuple, or 0.
* 8 bits: Type code of the 3rd value of the tuple, or 0.
* ...
*
* This repeats until reaching a full 64-bit word. If there are more than 7
* parameters to bind, the header will grow additional 64-bit words as needed,
* following the same pattern: a sequence of 8-bit slots with type codes of the
* parameters followed by a sequence of zero bits, until word boundary is
* reached.
*
* For a tuple of row values the format of the header is:
*
* 4 bits: Type code of the 1st value of the tuple.
* 4 bits: Type code of the 2nd value of the tuple, or 0.
* 4 bits: Type code of the 3rd value of the tuple, or 0.
* ...
*
* This repeats until reaching a full 64-bit word. If there are more than 16
* values, the header will grow additional 64-bit words as needed, following the
* same pattern: a sequence of 4-bit slots with type codes of the values
* followed by a sequence of zero bits, until word boundary is reached.
*
* After the header the body follows immediately, which contains all parameters
* or values in sequence, encoded using type-specific rules.
*/
#ifndef DQLITE_TUPLE_H_
#define DQLITE_TUPLE_H_
#include
#include
#include
#include "lib/buffer.h"
#include "lib/serialize.h"
#include "protocol.h"
/* Formats for tuple encoding and decoding. */
enum {
/* Used for coding a row from the database: length field is implicit,
* type codes are 4 bits each. */
TUPLE__ROW = 1,
/* Used for coding a short tuple of statement parameters: length field
* is 1 byte, type codes are 1 byte each. */
TUPLE__PARAMS,
/* Used for coding a longer tuple of statement parameters: length field
* is 4 bytes, type codes are 1 byte each. */
TUPLE__PARAMS32
};
/**
* Hold a single database value.
*/
struct value
{
int type;
union {
int64_t integer;
double float_;
uv_buf_t blob;
uint64_t null;
const char *text;
const char *iso8601; /* INT8601 date string */
int64_t unixtime; /* Unix time in seconds since epoch */
uint64_t boolean;
};
};
/**
* Maintain state while decoding a single tuple.
*/
struct tuple_decoder
{
unsigned long n; /* Number of values in the tuple */
struct cursor *cursor; /* Reading cursor */
int format; /* Tuple format */
unsigned long i; /* Index of next value to decode */
const uint8_t *header; /* Pointer to tuple header */
};
/**
* Initialize the state of the decoder, before starting to decode a new
* tuple.
*
* If @n is zero, it means that the tuple is a sequence of statement
* parameters. In that case the d->n field will be read from the first byte of
* @cursor.
*/
int tuple_decoder__init(struct tuple_decoder *d,
unsigned n,
int format,
struct cursor *cursor);
/**
* Return the number of values in the tuple being decoded.
*
* In row format this will be the same @n passed to the constructor. In
* parameters format this is the value contained in the first byte of the tuple
* header.
*/
unsigned long tuple_decoder__n(struct tuple_decoder *d);
/**
* Decode the next value of the tuple.
*/
int tuple_decoder__next(struct tuple_decoder *d, struct value *value);
/**
* Maintain state while encoding a single tuple.
*/
struct tuple_encoder
{
unsigned long n; /* Number of values in the tuple */
int format; /* Tuple format */
struct buffer *buffer; /* Write buffer */
unsigned long i; /* Index of next value to encode */
size_t header; /* Buffer offset of tuple header */
};
/**
* Initialize the state of the encoder, before starting to encode a new
* tuple. The @n parameter must always be greater than zero.
*/
int tuple_encoder__init(struct tuple_encoder *e,
unsigned long n,
int format,
struct buffer *buffer);
/**
* Encode the next value of the tuple.
*/
int tuple_encoder__next(struct tuple_encoder *e, struct value *value);
#endif /* DQLITE_TUPLE_H_ */
dqlite-1.16.0/src/utils.h 0000664 0000000 0000000 00000000641 14512203222 0015153 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_UTILS_H_
#define DQLITE_UTILS_H_
#include
/* Various utility functions and macros */
#define PTR_TO_UINT64(p) ((uint64_t)((uintptr_t)(p)))
#define UINT64_TO_PTR(u, ptr_type) ((ptr_type)((uintptr_t)(u)))
#define LIKELY(x) __builtin_expect(!!(x), 1)
#define UNLIKELY(x) __builtin_expect(!!(x), 0)
#define DBG() fprintf(stderr, "%s:%d\n", __func__, __LINE__)
#endif /* DQLITE_UTILS_H_ */
dqlite-1.16.0/src/vfs.c 0000664 0000000 0000000 00000251053 14512203222 0014611 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "../include/dqlite.h"
#include "lib/assert.h"
#include "lib/byte.h"
#include "format.h"
#include "tracing.h"
#include "vfs.h"
/* tinycc doesn't have this builtin, nor the warning that it's meant to silence.
*/
#ifdef __TINYC__
#define __builtin_assume_aligned(x, y) x
#endif
/* Byte order */
#if defined(DQLITE_LITTLE_ENDIAN)
#define VFS__BIGENDIAN 0
#elif defined(DQLITE_BIG_ENDIAN)
#define VFS__BIGENDIAN 1
#else
const int vfsOne = 1;
#define VFS__BIGENDIAN (*(char *)(&vfsOne) == 0)
#endif
/* Maximum pathname length supported by this VFS. */
#define VFS__MAX_PATHNAME 512
/* WAL magic value. Either this value, or the same value with the least
* significant bit also set (FORMAT__WAL_MAGIC | 0x00000001) is stored in 32-bit
* big-endian format in the first 4 bytes of a WAL file.
*
* If the LSB is set, then the checksums for each frame within the WAL file are
* calculated by treating all data as an array of 32-bit big-endian
* words. Otherwise, they are calculated by interpreting all data as 32-bit
* little-endian words. */
#define VFS__WAL_MAGIC 0x377f0682
/* WAL format version (same for WAL index). */
#define VFS__WAL_VERSION 3007000
/* Index of the write lock in the WAL-index header locks area. */
#define VFS__WAL_WRITE_LOCK 0
/* Write ahead log header size. */
#define VFS__WAL_HEADER_SIZE 32
/* Write ahead log frame header size. */
#define VFS__FRAME_HEADER_SIZE 24
/* Size of the first part of the WAL index header. */
#define VFS__WAL_INDEX_HEADER_SIZE 48
/* Size of a single memory-mapped WAL index region. */
#define VFS__WAL_INDEX_REGION_SIZE 32768
#define vfsFrameSize(PAGE_SIZE) (VFS__FRAME_HEADER_SIZE + PAGE_SIZE)
/* Hold content for a shared memory mapping. */
struct vfsShm
{
void **regions; /* Pointers to shared memory regions. */
unsigned n_regions; /* Number of shared memory regions. */
unsigned refcount; /* Number of outstanding mappings. */
unsigned shared[SQLITE_SHM_NLOCK]; /* Count of shared locks */
unsigned exclusive[SQLITE_SHM_NLOCK]; /* Count of exclusive locks */
};
/* Hold the content of a single WAL frame. */
struct vfsFrame
{
uint8_t header[VFS__FRAME_HEADER_SIZE];
uint8_t *page; /* Content of the page. */
};
/* WAL-specific content.
* Watch out when changing the members of this struct, see
* comment in `formatWalChecksumBytes`. */
struct vfsWal
{
uint8_t hdr[VFS__WAL_HEADER_SIZE]; /* Header. */
struct vfsFrame **frames; /* All frames committed. */
unsigned n_frames; /* Number of committed frames. */
struct vfsFrame **tx; /* Frames added by a transaction. */
unsigned n_tx; /* Number of added frames. */
};
/* Database-specific content */
struct vfsDatabase
{
char *name; /* Database name. */
void **pages; /* All database. */
unsigned page_size; /* Only used for on-disk db */
unsigned n_pages; /* Number of pages. */
struct vfsShm shm; /* Shared memory. */
struct vfsWal wal; /* Associated WAL. */
};
/*
* Generate or extend an 8 byte checksum based on the data in array data[] and
* the initial values of in[0] and in[1] (or initial values of 0 and 0 if
* in==NULL).
*
* The checksum is written back into out[] before returning.
*
* n must be a positive multiple of 8. */
static void vfsChecksum(
uint8_t *data, /* Content to be checksummed */
unsigned n, /* Bytes of content in a[]. Must be a multiple of 8. */
const uint32_t in[2], /* Initial checksum value input */
uint32_t out[2] /* OUT: Final checksum value output */
)
{
assert((((uintptr_t)data) % sizeof(uint32_t)) == 0);
uint32_t s1, s2;
uint32_t *cur =
(uint32_t *)__builtin_assume_aligned(data, sizeof(uint32_t));
uint32_t *end =
(uint32_t *)__builtin_assume_aligned(&data[n], sizeof(uint32_t));
if (in) {
s1 = in[0];
s2 = in[1];
} else {
s1 = s2 = 0;
}
assert(n >= 8);
assert((n & 0x00000007) == 0);
assert(n <= 65536);
do {
s1 += *cur++ + s2;
s2 += *cur++ + s1;
} while (cur < end);
out[0] = s1;
out[1] = s2;
}
/* Create a new frame of a WAL file. */
static struct vfsFrame *vfsFrameCreate(unsigned size)
{
struct vfsFrame *f;
assert(size > 0);
f = sqlite3_malloc(sizeof *f);
if (f == NULL) {
goto oom;
}
f->page = sqlite3_malloc64(size);
if (f->page == NULL) {
goto oom_after_page_alloc;
}
memset(f->header, 0, FORMAT__WAL_FRAME_HDR_SIZE);
memset(f->page, 0, (size_t)size);
return f;
oom_after_page_alloc:
sqlite3_free(f);
oom:
return NULL;
}
/* Destroy a WAL frame */
static void vfsFrameDestroy(struct vfsFrame *f)
{
assert(f != NULL);
assert(f->page != NULL);
sqlite3_free(f->page);
sqlite3_free(f);
}
/* Initialize the shared memory mapping of a database file. */
static void vfsShmInit(struct vfsShm *s)
{
int i;
s->regions = NULL;
s->n_regions = 0;
s->refcount = 0;
for (i = 0; i < SQLITE_SHM_NLOCK; i++) {
s->shared[i] = 0;
s->exclusive[i] = 0;
}
}
/* Release all resources used by a shared memory mapping. */
static void vfsShmClose(struct vfsShm *s)
{
void *region;
unsigned i;
assert(s != NULL);
/* Free all regions. */
for (i = 0; i < s->n_regions; i++) {
region = *(s->regions + i);
assert(region != NULL);
sqlite3_free(region);
}
/* Free the shared memory region array. */
if (s->regions != NULL) {
sqlite3_free(s->regions);
}
}
/* Revert the shared mamory to its initial state. */
static void vfsShmReset(struct vfsShm *s)
{
vfsShmClose(s);
vfsShmInit(s);
}
/* Initialize a new WAL object. */
static void vfsWalInit(struct vfsWal *w)
{
memset(w->hdr, 0, VFS__WAL_HEADER_SIZE);
w->frames = NULL;
w->n_frames = 0;
w->tx = NULL;
w->n_tx = 0;
}
/* Initialize a new database object. */
static void vfsDatabaseInit(struct vfsDatabase *d)
{
d->pages = NULL;
d->n_pages = 0;
d->page_size = 0;
vfsShmInit(&d->shm);
vfsWalInit(&d->wal);
}
/* Release all memory used by a WAL object. */
static void vfsWalClose(struct vfsWal *w)
{
unsigned i;
for (i = 0; i < w->n_frames; i++) {
vfsFrameDestroy(w->frames[i]);
}
if (w->frames != NULL) {
sqlite3_free(w->frames);
}
for (i = 0; i < w->n_tx; i++) {
vfsFrameDestroy(w->tx[i]);
}
if (w->tx != NULL) {
sqlite3_free(w->tx);
}
}
/* Release all memory used by a database object. */
static void vfsDatabaseClose(struct vfsDatabase *d)
{
unsigned i;
for (i = 0; d->pages != NULL && i < d->n_pages; i++) {
sqlite3_free(d->pages[i]);
}
if (d->pages != NULL) {
sqlite3_free(d->pages);
}
vfsShmClose(&d->shm);
vfsWalClose(&d->wal);
}
/* Destroy the content of a database object. */
static void vfsDatabaseDestroy(struct vfsDatabase *d)
{
assert(d != NULL);
sqlite3_free(d->name);
vfsDatabaseClose(d);
sqlite3_free(d);
}
/*
* Comment copied entirely for sqlite source code, it is safe to assume
* the value 0x40000000 will never change. dq_sqlite_pending_byte is global
* to be able to adapt it in the unittest, the value must never be changed.
*
* ==BEGIN COPY==
* The value of the "pending" byte must be 0x40000000 (1 byte past the
* 1-gibabyte boundary) in a compatible database. SQLite never uses
* the database page that contains the pending byte. It never attempts
* to read or write that page. The pending byte page is set aside
* for use by the VFS layers as space for managing file locks.
*
* During testing, it is often desirable to move the pending byte to
* a different position in the file. This allows code that has to
* deal with the pending byte to run on files that are much smaller
* than 1 GiB. The sqlite3_test_control() interface can be used to
* move the pending byte.
*
* IMPORTANT: Changing the pending byte to any value other than
* 0x40000000 results in an incompatible database file format!
* Changing the pending byte during operation will result in undefined
* and incorrect behavior.
* ==END COPY==
*/
DQLITE_VISIBLE_TO_TESTS unsigned dq_sqlite_pending_byte = 0x40000000;
/* Get a page from the given database, possibly creating a new one. */
static int vfsDatabaseGetPage(struct vfsDatabase *d,
uint32_t page_size,
unsigned pgno,
void **page)
{
int rc;
assert(d != NULL);
assert(pgno > 0);
/* SQLite should access pages progressively, without jumping more than
* one page after the end unless one would attempt to access a page at
* `sqlite_pending_byte` offset, skipping a page is permitted then. */
bool pending_byte_page_reached =
(page_size * d->n_pages == dq_sqlite_pending_byte);
if ((pgno > d->n_pages + 1) && !pending_byte_page_reached) {
rc = SQLITE_IOERR_WRITE;
goto err;
}
if (pgno <= d->n_pages) {
/* Return the existing page. */
assert(d->pages != NULL);
*page = d->pages[pgno - 1];
return SQLITE_OK;
}
/* Create a new page, grow the page array, and append the
* new page to it. */
*page = sqlite3_malloc64(page_size);
if (*page == NULL) {
rc = SQLITE_NOMEM;
goto err;
}
void **pages = sqlite3_realloc64(d->pages, sizeof *pages * pgno);
if (pages == NULL) {
rc = SQLITE_NOMEM;
goto err_after_vfs_page_create;
}
pages[pgno - 1] = *page;
/* Allocate a page to store the pending_byte */
if (pending_byte_page_reached) {
void *pending_byte_page = sqlite3_malloc64(page_size);
if (pending_byte_page == NULL) {
rc = SQLITE_NOMEM;
goto err_after_pending_byte_page;
}
pages[d->n_pages] = pending_byte_page;
}
/* Update the page array. */
d->pages = pages;
d->n_pages = pgno;
return SQLITE_OK;
err_after_pending_byte_page:
d->pages = pages;
err_after_vfs_page_create:
sqlite3_free(*page);
err:
*page = NULL;
return rc;
}
/* Get a frame from the current transaction, possibly creating a new one. */
static int vfsWalFrameGet(struct vfsWal *w,
unsigned index,
uint32_t page_size,
struct vfsFrame **frame)
{
int rv;
assert(w != NULL);
assert(index > 0);
/* SQLite should access pages progressively, without jumping more than
* one page after the end. */
if (index > w->n_frames + w->n_tx + 1) {
rv = SQLITE_IOERR_WRITE;
goto err;
}
if (index == w->n_frames + w->n_tx + 1) {
/* Create a new frame, grow the transaction array, and append
* the new frame to it. */
struct vfsFrame **tx;
/* We assume that the page size has been set, either by
* intervepting the first main database file write, or by
* handling a 'PRAGMA page_size=N' command in
* vfs__file_control(). This assumption is enforved in
* vfsFileWrite(). */
assert(page_size > 0);
*frame = vfsFrameCreate(page_size);
if (*frame == NULL) {
rv = SQLITE_NOMEM;
goto err;
}
tx = sqlite3_realloc64(w->tx, sizeof *tx * w->n_tx + 1);
if (tx == NULL) {
rv = SQLITE_NOMEM;
goto err_after_vfs_frame_create;
}
/* Append the new page to the new page array. */
tx[index - w->n_frames - 1] = *frame;
/* Update the page array. */
w->tx = tx;
w->n_tx++;
} else {
/* Return the existing page. */
assert(w->tx != NULL);
*frame = w->tx[index - w->n_frames - 1];
}
return SQLITE_OK;
err_after_vfs_frame_create:
vfsFrameDestroy(*frame);
err:
*frame = NULL;
return rv;
}
/* Lookup a page from the given database, returning NULL if it doesn't exist. */
static void *vfsDatabasePageLookup(struct vfsDatabase *d, unsigned pgno)
{
void *page;
assert(d != NULL);
assert(pgno > 0);
if (pgno > d->n_pages) {
/* This page hasn't been written yet. */
return NULL;
}
page = d->pages[pgno - 1];
assert(page != NULL);
return page;
}
/* Lookup a frame from the WAL, returning NULL if it doesn't exist. */
static struct vfsFrame *vfsWalFrameLookup(struct vfsWal *w, unsigned n)
{
struct vfsFrame *frame;
assert(w != NULL);
assert(n > 0);
if (n > w->n_frames + w->n_tx) {
/* This page hasn't been written yet. */
return NULL;
}
if (n <= w->n_frames) {
frame = w->frames[n - 1];
} else {
frame = w->tx[n - w->n_frames - 1];
}
assert(frame != NULL);
return frame;
}
/* Parse the page size ("Must be a power of two between 512 and 32768
* inclusive, or the value 1 representing a page size of 65536").
*
* Return 0 if the page size is out of bound. */
static uint32_t vfsParsePageSize(uint32_t page_size)
{
if (page_size == 1) {
page_size = FORMAT__PAGE_SIZE_MAX;
} else if (page_size < FORMAT__PAGE_SIZE_MIN) {
page_size = 0;
} else if (page_size > (FORMAT__PAGE_SIZE_MAX / 2)) {
page_size = 0;
} else if (((page_size - 1) & page_size) != 0) {
page_size = 0;
}
return page_size;
}
static uint32_t vfsDatabaseGetPageSize(struct vfsDatabase *d)
{
uint8_t *page;
/* Only set in disk-mode */
if (d->page_size != 0) {
return d->page_size;
}
assert(d->n_pages > 0);
page = d->pages[0];
/* The page size is stored in the 16th and 17th bytes of the first
* database page (big-endian) */
return vfsParsePageSize(ByteGetBe16(&page[16]));
}
/* Truncate a database file to be exactly the given number of pages. */
static int vfsDatabaseTruncate(struct vfsDatabase *d, sqlite_int64 size)
{
void **cursor;
uint32_t page_size;
unsigned n_pages;
unsigned i;
if (d->n_pages == 0) {
if (size > 0) {
return SQLITE_IOERR_TRUNCATE;
}
return SQLITE_OK;
}
/* Since the file size is not zero, some content must
* have been written and the page size must be known. */
page_size = vfsDatabaseGetPageSize(d);
assert(page_size > 0);
if ((size % page_size) != 0) {
return SQLITE_IOERR_TRUNCATE;
}
n_pages = (unsigned)(size / page_size);
/* We expect callers to only invoke us if some actual content has been
* written already. */
assert(d->n_pages > 0);
/* Truncate should always shrink a file. */
assert(n_pages <= d->n_pages);
assert(d->pages != NULL);
/* Destroy pages beyond pages_len. */
cursor = d->pages + n_pages;
for (i = 0; i < (d->n_pages - n_pages); i++) {
sqlite3_free(*cursor);
cursor++;
}
/* Shrink the page array, possibly to 0.
*
* TODO: in principle realloc could fail also when shrinking. */
d->pages = sqlite3_realloc64(d->pages, sizeof *d->pages * n_pages);
/* Update the page count. */
d->n_pages = n_pages;
return SQLITE_OK;
}
/* Truncate a WAL file to zero. */
static int vfsWalTruncate(struct vfsWal *w, sqlite3_int64 size)
{
unsigned i;
/* We expect SQLite to only truncate to zero, after a
* full checkpoint.
*
* TODO: figure out other case where SQLite might
* truncate to a different size.
*/
if (size != 0) {
return SQLITE_PROTOCOL;
}
if (w->n_frames == 0) {
return SQLITE_OK;
}
assert(w->frames != NULL);
/* Restart the header. */
formatWalRestartHeader(w->hdr);
/* Destroy all frames. */
for (i = 0; i < w->n_frames; i++) {
vfsFrameDestroy(w->frames[i]);
}
sqlite3_free(w->frames);
w->frames = NULL;
w->n_frames = 0;
return SQLITE_OK;
}
enum vfsFileType {
VFS__DATABASE, /* Main database file */
VFS__JOURNAL, /* Default SQLite journal file */
VFS__WAL /* Write-Ahead Log */
};
/* Implementation of the abstract sqlite3_file base class. */
struct vfsFile
{
sqlite3_file base; /* Base class. Must be first. */
struct vfs *vfs; /* Pointer to volatile VFS data. */
enum vfsFileType type; /* Associated file (main db or WAL). */
struct vfsDatabase *database; /* Underlying database content. */
int flags; /* Flags passed to xOpen */
sqlite3_file *temp; /* For temp-files, actual VFS. */
sqlite3_file *db; /* For on-disk DB files, actual VFS. */
};
/* Custom dqlite VFS. Contains pointers to all databases that were created. */
struct vfs
{
struct vfsDatabase **databases; /* Database objects */
unsigned n_databases; /* Number of databases */
int error; /* Last error occurred. */
bool disk; /* True if the database is kept on disk. */
struct sqlite3_vfs *base_vfs; /* Base VFS. */
};
/* Create a new vfs object. */
static struct vfs *vfsCreate(void)
{
struct vfs *v;
v = sqlite3_malloc(sizeof *v);
if (v == NULL) {
return NULL;
}
v->databases = NULL;
v->n_databases = 0;
v->error = 0;
v->disk = false;
v->base_vfs = sqlite3_vfs_find("unix");
assert(v->base_vfs != NULL);
return v;
}
/* Release the memory used internally by the VFS object.
*
* All file content will be de-allocated, so dangling open FDs against
* those files will be broken.
*/
static void vfsDestroy(struct vfs *r)
{
unsigned i;
assert(r != NULL);
for (i = 0; i < r->n_databases; i++) {
struct vfsDatabase *database = r->databases[i];
vfsDatabaseDestroy(database);
}
if (r->databases != NULL) {
sqlite3_free(r->databases);
}
}
static bool vfsFilenameEndsWith(const char *filename, const char *suffix)
{
size_t n_filename = strlen(filename);
size_t n_suffix = strlen(suffix);
if (n_suffix > n_filename) {
return false;
}
return strncmp(filename + n_filename - n_suffix, suffix, n_suffix) == 0;
}
/* Find the database object associated with the given filename. */
static struct vfsDatabase *vfsDatabaseLookup(struct vfs *v,
const char *filename)
{
size_t n = strlen(filename);
unsigned i;
assert(v != NULL);
assert(filename != NULL);
if (vfsFilenameEndsWith(filename, "-wal")) {
n -= strlen("-wal");
}
if (vfsFilenameEndsWith(filename, "-journal")) {
n -= strlen("-journal");
}
for (i = 0; i < v->n_databases; i++) {
struct vfsDatabase *database = v->databases[i];
if (strlen(database->name) == n &&
strncmp(database->name, filename, n) == 0) {
// Found matching file.
return database;
}
}
return NULL;
}
static int vfsDeleteDatabase(struct vfs *r, const char *name)
{
unsigned i;
for (i = 0; i < r->n_databases; i++) {
struct vfsDatabase *database = r->databases[i];
unsigned j;
if (strcmp(database->name, name) != 0) {
continue;
}
/* Free all memory allocated for this file. */
vfsDatabaseDestroy(database);
/* Shift all other contents objects. */
for (j = i + 1; j < r->n_databases; j++) {
r->databases[j - 1] = r->databases[j];
}
r->n_databases--;
return SQLITE_OK;
}
r->error = ENOENT;
return SQLITE_IOERR_DELETE_NOENT;
}
static int vfsFileClose(sqlite3_file *file)
{
int rc = SQLITE_OK;
struct vfsFile *f = (struct vfsFile *)file;
struct vfs *v = (struct vfs *)(f->vfs);
if (f->temp != NULL) {
/* Close the actual temporary file. */
rc = f->temp->pMethods->xClose(f->temp);
sqlite3_free(f->temp);
return rc;
}
if (f->flags & SQLITE_OPEN_DELETEONCLOSE) {
rc = vfsDeleteDatabase(v, f->database->name);
}
return rc;
}
/* Read data from the main database. */
static int vfsDatabaseRead(struct vfsDatabase *d,
void *buf,
int amount,
sqlite_int64 offset)
{
unsigned page_size;
unsigned pgno;
void *page;
if (d->n_pages == 0) {
return SQLITE_IOERR_SHORT_READ;
}
/* If the main database file is not empty, we expect the
* page size to have been set by an initial write. */
page_size = vfsDatabaseGetPageSize(d);
assert(page_size > 0);
if (offset < (int)page_size) {
/* Reading from page 1. We expect the read to be
* at most page_size bytes. */
assert(amount <= (int)page_size);
pgno = 1;
} else {
/* For pages greater than 1, we expect a full
* page read, with an offset that starts exectly
* at the page boundary. */
assert(amount == (int)page_size);
assert((offset % (int)page_size) == 0);
pgno = (unsigned)(offset / (int)page_size) + 1;
}
assert(pgno > 0);
page = vfsDatabasePageLookup(d, pgno);
if (pgno == 1) {
/* Read the desired part of page 1. */
memcpy(buf, (char *)page + offset, (size_t)amount);
} else {
/* Read the full page. */
memcpy(buf, page, (size_t)amount);
}
return SQLITE_OK;
}
/* Get the page size stored in the WAL header. */
static uint32_t vfsWalGetPageSize(struct vfsWal *w)
{
/* The page size is stored in the 4 bytes starting at 8
* (big-endian) */
return vfsParsePageSize(ByteGetBe32(&w->hdr[8]));
}
/* Read data from the WAL. */
static int vfsWalRead(struct vfsWal *w,
void *buf,
int amount,
sqlite_int64 offset)
{
uint32_t page_size;
unsigned index;
struct vfsFrame *frame;
if (offset == 0) {
/* Read the header. */
assert(amount == VFS__WAL_HEADER_SIZE);
memcpy(buf, w->hdr, VFS__WAL_HEADER_SIZE);
return SQLITE_OK;
}
page_size = vfsWalGetPageSize(w);
assert(page_size > 0);
/* For any other frame, we expect either a header read,
* a checksum read, a page read or a full frame read. */
if (amount == FORMAT__WAL_FRAME_HDR_SIZE) {
assert(((offset - VFS__WAL_HEADER_SIZE) %
((int)page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0);
index =
(unsigned)formatWalCalcFrameIndex((int)page_size, offset);
} else if (amount == sizeof(uint32_t) * 2) {
if (offset == FORMAT__WAL_FRAME_HDR_SIZE) {
/* Read the checksum from the WAL
* header. */
memcpy(buf, w->hdr + offset, (size_t)amount);
return SQLITE_OK;
}
assert(((offset - 16 - VFS__WAL_HEADER_SIZE) %
((int)page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0);
index =
(unsigned)((offset - 16 - VFS__WAL_HEADER_SIZE) /
((int)page_size + FORMAT__WAL_FRAME_HDR_SIZE)) +
1;
} else if (amount == (int)page_size) {
assert(((offset - VFS__WAL_HEADER_SIZE -
FORMAT__WAL_FRAME_HDR_SIZE) %
((int)page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0);
index =
(unsigned)formatWalCalcFrameIndex((int)page_size, offset);
} else {
assert(amount == (FORMAT__WAL_FRAME_HDR_SIZE + (int)page_size));
index =
(unsigned)formatWalCalcFrameIndex((int)page_size, offset);
}
if (index == 0) {
// This is an attempt to read a page that was
// never written.
memset(buf, 0, (size_t)amount);
return SQLITE_IOERR_SHORT_READ;
}
frame = vfsWalFrameLookup(w, index);
if (frame == NULL) {
// Again, the requested page doesn't exist.
memset(buf, 0, (size_t)amount);
return SQLITE_IOERR_SHORT_READ;
}
if (amount == FORMAT__WAL_FRAME_HDR_SIZE) {
memcpy(buf, frame->header, (size_t)amount);
} else if (amount == sizeof(uint32_t) * 2) {
memcpy(buf, frame->header + 16, (size_t)amount);
} else if (amount == (int)page_size) {
memcpy(buf, frame->page, (size_t)amount);
} else {
memcpy(buf, frame->header, FORMAT__WAL_FRAME_HDR_SIZE);
memcpy(buf + FORMAT__WAL_FRAME_HDR_SIZE, frame->page,
page_size);
}
return SQLITE_OK;
}
static int vfsFileRead(sqlite3_file *file,
void *buf,
int amount,
sqlite_int64 offset)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(buf != NULL);
assert(amount > 0);
assert(offset >= 0);
assert(f != NULL);
if (f->temp != NULL) {
/* Read from the actual temporary file. */
return f->temp->pMethods->xRead(f->temp, buf, amount, offset);
}
switch (f->type) {
case VFS__DATABASE:
rv = vfsDatabaseRead(f->database, buf, amount, offset);
break;
case VFS__WAL:
rv = vfsWalRead(&f->database->wal, buf, amount, offset);
break;
default:
rv = SQLITE_IOERR_READ;
break;
}
/* From SQLite docs:
*
* If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill
* in the unread portions of the buffer with zeros. A VFS that
* fails to zero-fill short reads might seem to work. However,
* failure to zero-fill short reads will eventually lead to
* database corruption.
*/
if (rv == SQLITE_IOERR_SHORT_READ) {
memset(buf, 0, (size_t)amount);
}
return rv;
}
static int vfsDatabaseWrite(struct vfsDatabase *d,
const void *buf,
int amount,
sqlite_int64 offset)
{
unsigned pgno;
uint32_t page_size;
void *page;
int rc;
if (offset == 0) {
const uint8_t *header = buf;
/* This is the first database page. We expect
* the data to contain at least the header. */
assert(amount >= FORMAT__DB_HDR_SIZE);
/* Extract the page size from the header. */
page_size = vfsParsePageSize(ByteGetBe16(&header[16]));
if (page_size == 0) {
return SQLITE_CORRUPT;
}
pgno = 1;
} else {
page_size = vfsDatabaseGetPageSize(d);
/* The header must have been written and the page size set. */
assert(page_size > 0);
/* For pages beyond the first we expect offset to be a multiple
* of the page size. */
assert((offset % (int)page_size) == 0);
/* We expect that SQLite writes a page at time. */
assert(amount == (int)page_size);
pgno = ((unsigned)(offset / (int)page_size)) + 1;
}
rc = vfsDatabaseGetPage(d, page_size, pgno, &page);
if (rc != SQLITE_OK) {
return rc;
}
assert(page != NULL);
memcpy(page, buf, (size_t)amount);
return SQLITE_OK;
}
static int vfsWalWrite(struct vfsWal *w,
const void *buf,
int amount,
sqlite_int64 offset)
{
uint32_t page_size;
unsigned index;
struct vfsFrame *frame;
/* WAL header. */
if (offset == 0) {
/* We expect the data to contain exactly 32
* bytes. */
assert(amount == VFS__WAL_HEADER_SIZE);
memcpy(w->hdr, buf, (size_t)amount);
return SQLITE_OK;
}
page_size = vfsWalGetPageSize(w);
assert(page_size > 0);
/* This is a WAL frame write. We expect either a frame
* header or page write. */
if (amount == FORMAT__WAL_FRAME_HDR_SIZE) {
/* Frame header write. */
assert(((offset - VFS__WAL_HEADER_SIZE) %
((int)page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0);
index =
(unsigned)formatWalCalcFrameIndex((int)page_size, offset);
vfsWalFrameGet(w, index, page_size, &frame);
if (frame == NULL) {
return SQLITE_NOMEM;
}
memcpy(frame->header, buf, (size_t)amount);
} else {
/* Frame page write. */
assert(amount == (int)page_size);
assert(((offset - VFS__WAL_HEADER_SIZE -
FORMAT__WAL_FRAME_HDR_SIZE) %
((int)page_size + FORMAT__WAL_FRAME_HDR_SIZE)) == 0);
index =
(unsigned)formatWalCalcFrameIndex((int)page_size, offset);
/* The header for the this frame must already
* have been written, so the page is there. */
frame = vfsWalFrameLookup(w, index);
assert(frame != NULL);
memcpy(frame->page, buf, (size_t)amount);
}
return SQLITE_OK;
}
static int vfsFileWrite(sqlite3_file *file,
const void *buf,
int amount,
sqlite_int64 offset)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(buf != NULL);
assert(amount > 0);
assert(f != NULL);
if (f->temp != NULL) {
/* Write to the actual temporary file. */
return f->temp->pMethods->xWrite(f->temp, buf, amount, offset);
}
switch (f->type) {
case VFS__DATABASE:
rv = vfsDatabaseWrite(f->database, buf, amount, offset);
break;
case VFS__WAL:
rv =
vfsWalWrite(&f->database->wal, buf, amount, offset);
break;
case VFS__JOURNAL:
/* Silently swallow writes to the journal */
rv = SQLITE_OK;
break;
default:
rv = SQLITE_IOERR_WRITE;
break;
}
return rv;
}
static int vfsFileTruncate(sqlite3_file *file, sqlite_int64 size)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(f != NULL);
switch (f->type) {
case VFS__DATABASE:
rv = vfsDatabaseTruncate(f->database, size);
break;
case VFS__WAL:
rv = vfsWalTruncate(&f->database->wal, size);
break;
default:
rv = SQLITE_IOERR_TRUNCATE;
break;
}
return rv;
}
static int vfsFileSync(sqlite3_file *file, int flags)
{
(void)file;
(void)flags;
return SQLITE_IOERR_FSYNC;
}
/* Return the size of the database file in bytes. */
static size_t vfsDatabaseFileSize(struct vfsDatabase *d)
{
uint64_t size = 0;
if (d->n_pages > 0) {
size =
(uint64_t)d->n_pages * (uint64_t)vfsDatabaseGetPageSize(d);
}
/* TODO dqlite is limited to a max database size of SIZE_MAX */
assert(size <= SIZE_MAX);
return (size_t)size;
}
/* Return the size of the WAL file in bytes. */
static size_t vfsWalFileSize(struct vfsWal *w)
{
uint64_t size = 0;
if (w->n_frames > 0) {
uint32_t page_size;
page_size = vfsWalGetPageSize(w);
size += VFS__WAL_HEADER_SIZE;
size += (uint64_t)w->n_frames *
(uint64_t)(FORMAT__WAL_FRAME_HDR_SIZE + page_size);
}
/* TODO dqlite is limited to a max database size of SIZE_MAX */
assert(size <= SIZE_MAX);
return (size_t)size;
}
static int vfsFileSize(sqlite3_file *file, sqlite_int64 *size)
{
struct vfsFile *f = (struct vfsFile *)file;
size_t n;
switch (f->type) {
case VFS__DATABASE:
n = vfsDatabaseFileSize(f->database);
break;
case VFS__WAL:
/* TODO? here we assume that FileSize() is never invoked
* between a header write and a page write. */
n = vfsWalFileSize(&f->database->wal);
break;
default:
n = 0;
break;
}
*size = (sqlite3_int64)n;
return SQLITE_OK;
}
/* Locking a file is a no-op, since no other process has visibility on it. */
static int vfsFileLock(sqlite3_file *file, int lock)
{
(void)file;
(void)lock;
return SQLITE_OK;
}
/* Unlocking a file is a no-op, since no other process has visibility on it. */
static int vfsFileUnlock(sqlite3_file *file, int lock)
{
(void)file;
(void)lock;
return SQLITE_OK;
}
/* We always report that a lock is held. This routine should be used only in
* journal mode, so it doesn't matter. */
static int vfsFileCheckReservedLock(sqlite3_file *file, int *result)
{
(void)file;
*result = 1;
return SQLITE_OK;
}
/* Handle pragma a pragma file control. See the xFileControl
* docstring in sqlite.h.in for more details. */
static int vfsFileControlPragma(struct vfsFile *f, char **fnctl)
{
const char *left;
const char *right;
assert(f != NULL);
assert(fnctl != NULL);
left = fnctl[1];
right = fnctl[2];
assert(left != NULL);
if (strcmp(left, "page_size") == 0 && right) {
/* When the user executes 'PRAGMA page_size=N' we save the
* size internally.
*
* The page size must be between 512 and 65536, and be a
* power of two. The check below was copied from
* sqlite3BtreeSetPageSize in btree.c.
*
* Invalid sizes are simply ignored, SQLite will do the same.
*
* It's not possible to change the size after it's set.
*/
int page_size = atoi(right);
if (page_size >= FORMAT__PAGE_SIZE_MIN &&
page_size <= FORMAT__PAGE_SIZE_MAX &&
((page_size - 1) & page_size) == 0) {
if (f->database->n_pages > 0 &&
page_size !=
(int)vfsDatabaseGetPageSize(f->database)) {
fnctl[0] = sqlite3_mprintf(
"changing page size is not supported");
return SQLITE_IOERR;
}
}
} else if (strcmp(left, "journal_mode") == 0 && right) {
/* When the user executes 'PRAGMA journal_mode=x' we ensure
* that the desired mode is 'wal'. */
if (strcasecmp(right, "wal") != 0) {
fnctl[0] =
sqlite3_mprintf("only WAL mode is supported");
return SQLITE_IOERR;
}
}
/* We're returning NOTFOUND here to tell SQLite that we wish it to go on
* with its own handling as well. If we returned SQLITE_OK the page size
* of the journal mode wouldn't be effectively set, as the processing of
* the PRAGMA would stop here. */
return SQLITE_NOTFOUND;
}
/* Return the page number field stored in the header of the given frame. */
static uint32_t vfsFrameGetPageNumber(struct vfsFrame *f)
{
return ByteGetBe32(&f->header[0]);
}
/* Return the database size field stored in the header of the given frame. */
static uint32_t vfsFrameGetDatabaseSize(struct vfsFrame *f)
{
return ByteGetBe32(&f->header[4]);
}
/* Return the checksum-1 field stored in the header of the given frame. */
static uint32_t vfsFrameGetChecksum1(struct vfsFrame *f)
{
return ByteGetBe32(&f->header[16]);
}
/* Return the checksum-2 field stored in the header of the given frame. */
static uint32_t vfsFrameGetChecksum2(struct vfsFrame *f)
{
return ByteGetBe32(&f->header[20]);
}
/* Fill the header and the content of a WAL frame. The given checksum is the
* rolling one of all preceeding frames and is updated by this function. */
static void vfsFrameFill(struct vfsFrame *f,
uint32_t page_number,
uint32_t database_size,
uint32_t salt[2],
uint32_t checksum[2],
uint8_t *page,
uint32_t page_size)
{
BytePutBe32(page_number, &f->header[0]);
BytePutBe32(database_size, &f->header[4]);
vfsChecksum(f->header, 8, checksum, checksum);
vfsChecksum(page, page_size, checksum, checksum);
memcpy(&f->header[8], &salt[0], sizeof salt[0]);
memcpy(&f->header[12], &salt[1], sizeof salt[1]);
BytePutBe32(checksum[0], &f->header[16]);
BytePutBe32(checksum[1], &f->header[20]);
memcpy(f->page, page, page_size);
}
/* This function modifies part of the WAL index header to reflect the current
* content of the WAL.
*
* It is called in two cases. First, after a write transaction gets completed
* and the SQLITE_FCNTL_COMMIT_PHASETWO file control op code is triggered, in
* order to "rewind" the mxFrame and szPage fields of the WAL index header back
* to when the write transaction started, effectively "shadowing" the
* transaction, which will be replicated asynchronously. Second, when the
* replication actually succeeds and dqlite_vfs_apply() is called on the VFS
* that originated the transaction, in order to make the transaction visible.
*
* Note that the hash table contained in the WAL index does not get modified,
* and even after a rewind following a write transaction it will still contain
* entries for the frames committed by the transaction. That's safe because
* mxFrame will make clients ignore those hash table entries. However it means
* that in case the replication is not actually successful and
* dqlite_vfs_abort() is called the WAL index must be invalidated.
**/
static void vfsAmendWalIndexHeader(struct vfsDatabase *d)
{
struct vfsShm *shm = &d->shm;
struct vfsWal *wal = &d->wal;
uint8_t *index;
uint32_t frame_checksum[2] = {0, 0};
uint32_t n_pages = (uint32_t)d->n_pages;
uint32_t checksum[2] = {0, 0};
if (wal->n_frames > 0) {
struct vfsFrame *last = wal->frames[wal->n_frames - 1];
frame_checksum[0] = vfsFrameGetChecksum1(last);
frame_checksum[1] = vfsFrameGetChecksum2(last);
n_pages = vfsFrameGetDatabaseSize(last);
}
assert(shm->n_regions > 0);
index = shm->regions[0];
/* index is an alias for shm->regions[0] which is a void* that points to
* memory allocated by `sqlite3_malloc64` and has the required alignment
*/
assert(*(uint32_t *)(__builtin_assume_aligned(&index[0],
sizeof(uint32_t))) ==
VFS__WAL_VERSION); /* iVersion */
assert(index[12] == 1); /* isInit */
assert(index[13] == VFS__BIGENDIAN); /* bigEndCksum */
*(uint32_t *)(__builtin_assume_aligned(&index[16], sizeof(uint32_t))) =
wal->n_frames;
*(uint32_t *)(__builtin_assume_aligned(&index[20], sizeof(uint32_t))) =
n_pages;
*(uint32_t *)(__builtin_assume_aligned(&index[24], sizeof(uint32_t))) =
frame_checksum[0];
*(uint32_t *)(__builtin_assume_aligned(&index[28], sizeof(uint32_t))) =
frame_checksum[1];
vfsChecksum(index, 40, checksum, checksum);
*(uint32_t *)__builtin_assume_aligned(&index[40], sizeof(uint32_t)) =
checksum[0];
*(uint32_t *)__builtin_assume_aligned(&index[44], sizeof(uint32_t)) =
checksum[1];
/* Update the second copy of the first part of the WAL index header. */
memcpy(index + VFS__WAL_INDEX_HEADER_SIZE, index,
VFS__WAL_INDEX_HEADER_SIZE);
}
/* The SQLITE_FCNTL_COMMIT_PHASETWO file control op code is trigged by the
* SQLite pager after completing a transaction. */
static int vfsFileControlCommitPhaseTwo(struct vfsFile *f)
{
struct vfsDatabase *database = f->database;
struct vfsWal *wal = &database->wal;
if (wal->n_tx > 0) {
vfsAmendWalIndexHeader(database);
}
return 0;
}
static int vfsFileControl(sqlite3_file *file, int op, void *arg)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(f->type == VFS__DATABASE);
switch (op) {
case SQLITE_FCNTL_PRAGMA:
rv = vfsFileControlPragma(f, arg);
break;
case SQLITE_FCNTL_COMMIT_PHASETWO:
rv = vfsFileControlCommitPhaseTwo(f);
break;
case SQLITE_FCNTL_PERSIST_WAL:
/* This prevents SQLite from deleting the WAL after the
* last connection is closed. */
*(int *)(arg) = 1;
rv = SQLITE_OK;
break;
default:
rv = SQLITE_OK;
break;
}
return rv;
}
static int vfsFileSectorSize(sqlite3_file *file)
{
(void)file;
return 0;
}
static int vfsFileDeviceCharacteristics(sqlite3_file *file)
{
(void)file;
return 0;
}
static int vfsShmMap(struct vfsShm *s,
unsigned region_index,
unsigned region_size,
bool extend,
void volatile **out)
{
void *region;
int rv;
if (s->regions != NULL && region_index < s->n_regions) {
/* The region was already allocated. */
region = s->regions[region_index];
assert(region != NULL);
} else {
if (extend) {
void **regions;
/* We should grow the map one region at a time. */
assert(region_size == VFS__WAL_INDEX_REGION_SIZE);
assert(region_index == s->n_regions);
region = sqlite3_malloc64(region_size);
if (region == NULL) {
rv = SQLITE_NOMEM;
goto err;
}
memset(region, 0, region_size);
regions = sqlite3_realloc64(
s->regions,
sizeof *s->regions * (s->n_regions + 1));
if (regions == NULL) {
rv = SQLITE_NOMEM;
goto err_after_region_malloc;
}
s->regions = regions;
s->regions[region_index] = region;
s->n_regions++;
} else {
/* The region was not allocated and we don't have to
* extend the map. */
region = NULL;
}
}
*out = region;
if (region_index == 0 && region != NULL) {
s->refcount++;
}
return SQLITE_OK;
err_after_region_malloc:
sqlite3_free(region);
err:
assert(rv != SQLITE_OK);
*out = NULL;
return rv;
}
/* Simulate shared memory by allocating on the C heap. */
static int vfsFileShmMap(sqlite3_file *file, /* Handle open on database file */
int region_index, /* Region to retrieve */
int region_size, /* Size of regions */
int extend, /* True to extend file if necessary */
void volatile **out /* OUT: Mapped memory */
)
{
struct vfsFile *f = (struct vfsFile *)file;
assert(f->type == VFS__DATABASE);
return vfsShmMap(&f->database->shm, (unsigned)region_index,
(unsigned)region_size, extend != 0, out);
}
static int vfsShmLock(struct vfsShm *s, int ofst, int n, int flags)
{
int i;
if (flags & SQLITE_SHM_EXCLUSIVE) {
/* No shared or exclusive lock must be held in the region. */
for (i = ofst; i < ofst + n; i++) {
if (s->shared[i] > 0 || s->exclusive[i] > 0) {
tracef(
"EXCLUSIVE lock contention ofst:%d n:%d "
"exclusive[%d]=%d shared[%d]=%d",
ofst, n, i, s->exclusive[i], i,
s->shared[i]);
return SQLITE_BUSY;
}
}
for (i = ofst; i < ofst + n; i++) {
assert(s->exclusive[i] == 0);
s->exclusive[i] = 1;
}
} else {
/* No exclusive lock must be held in the region. */
for (i = ofst; i < ofst + n; i++) {
if (s->exclusive[i] > 0) {
tracef(
"SHARED lock contention ofst:%d n:%d "
"exclusive[%d]=%d shared[%d]=%d",
ofst, n, i, s->exclusive[i], i,
s->shared[i]);
return SQLITE_BUSY;
}
}
for (i = ofst; i < ofst + n; i++) {
s->shared[i]++;
}
}
return SQLITE_OK;
}
static int vfsShmUnlock(struct vfsShm *s, int ofst, int n, int flags)
{
unsigned *these_locks;
unsigned *other_locks;
int i;
if (flags & SQLITE_SHM_SHARED) {
these_locks = s->shared;
other_locks = s->exclusive;
} else {
these_locks = s->exclusive;
other_locks = s->shared;
}
for (i = ofst; i < ofst + n; i++) {
/* Coherence check that no lock of the other type is held in
* this region. */
assert(other_locks[i] == 0);
/* Only decrease the lock count if it's positive. In other words
* releasing a never acquired lock is legal and idemponent. */
if (these_locks[i] > 0) {
these_locks[i]--;
}
}
return SQLITE_OK;
}
/* If there's a uncommitted transaction, roll it back. */
static void vfsWalRollbackIfUncommitted(struct vfsWal *w)
{
struct vfsFrame *last;
uint32_t commit;
unsigned i;
if (w->n_tx == 0) {
return;
}
tracef("rollback n_tx:%d", w->n_tx);
last = w->tx[w->n_tx - 1];
commit = vfsFrameGetDatabaseSize(last);
if (commit > 0) {
tracef("rollback commit:%u", commit);
return;
}
for (i = 0; i < w->n_tx; i++) {
vfsFrameDestroy(w->tx[i]);
}
w->n_tx = 0;
}
static int vfsFileShmLock(sqlite3_file *file, int ofst, int n, int flags)
{
struct vfsFile *f;
struct vfsShm *shm;
struct vfsWal *wal;
int rv;
assert(file != NULL);
assert(ofst >= 0);
assert(n >= 0);
/* Legal values for the offset and the range */
assert(ofst >= 0 && ofst + n <= SQLITE_SHM_NLOCK);
assert(n >= 1);
assert(n == 1 || (flags & SQLITE_SHM_EXCLUSIVE) != 0);
/* Legal values for the flags.
*
* See https://sqlite.org/c3ref/c_shm_exclusive.html. */
assert(flags == (SQLITE_SHM_LOCK | SQLITE_SHM_SHARED) ||
flags == (SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE) ||
flags == (SQLITE_SHM_UNLOCK | SQLITE_SHM_SHARED) ||
flags == (SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE));
/* This is a no-op since shared-memory locking is relevant only for
* inter-process concurrency. See also the unix-excl branch from
* upstream (git commit cda6b3249167a54a0cf892f949d52760ee557129). */
f = (struct vfsFile *)file;
assert(f->type == VFS__DATABASE);
assert(f->database != NULL);
shm = &f->database->shm;
if (flags & SQLITE_SHM_UNLOCK) {
rv = vfsShmUnlock(shm, ofst, n, flags);
} else {
rv = vfsShmLock(shm, ofst, n, flags);
}
wal = &f->database->wal;
if (rv == SQLITE_OK && ofst == VFS__WAL_WRITE_LOCK) {
assert(n == 1);
/* When acquiring the write lock, make sure there's no
* transaction that hasn't been rolled back or polled. */
if (flags == (SQLITE_SHM_LOCK | SQLITE_SHM_EXCLUSIVE)) {
assert(wal->n_tx == 0);
}
/* When releasing the write lock, if we find a pending
* uncommitted transaction then a rollback must have occurred.
* In that case we delete the pending transaction. */
if (flags == (SQLITE_SHM_UNLOCK | SQLITE_SHM_EXCLUSIVE)) {
vfsWalRollbackIfUncommitted(wal);
}
}
return rv;
}
static void vfsFileShmBarrier(sqlite3_file *file)
{
(void)file;
/* This is a no-op since we expect SQLite to be compiled with mutex
* support (i.e. SQLITE_MUTEX_OMIT or SQLITE_MUTEX_NOOP are *not*
* defined, see sqliteInt.h). */
}
static void vfsShmUnmap(struct vfsShm *s)
{
s->refcount--;
if (s->refcount == 0) {
vfsShmReset(s);
}
}
static int vfsFileShmUnmap(sqlite3_file *file, int delete_flag)
{
struct vfsFile *f = (struct vfsFile *)file;
(void)delete_flag;
vfsShmUnmap(&f->database->shm);
return SQLITE_OK;
}
static const sqlite3_io_methods vfsFileMethods = {
2, // iVersion
vfsFileClose, // xClose
vfsFileRead, // xRead
vfsFileWrite, // xWrite
vfsFileTruncate, // xTruncate
vfsFileSync, // xSync
vfsFileSize, // xFileSize
vfsFileLock, // xLock
vfsFileUnlock, // xUnlock
vfsFileCheckReservedLock, // xCheckReservedLock
vfsFileControl, // xFileControl
vfsFileSectorSize, // xSectorSize
vfsFileDeviceCharacteristics, // xDeviceCharacteristics
vfsFileShmMap, // xShmMap
vfsFileShmLock, // xShmLock
vfsFileShmBarrier, // xShmBarrier
vfsFileShmUnmap, // xShmUnmap
0,
0,
};
/* Create a database object and add it to the databases array. */
static struct vfsDatabase *vfsCreateDatabase(struct vfs *v, const char *name)
{
unsigned n = v->n_databases + 1;
struct vfsDatabase **databases;
struct vfsDatabase *d;
assert(name != NULL);
/* Create a new entry. */
databases = sqlite3_realloc64(v->databases, sizeof *databases * n);
if (databases == NULL) {
goto oom;
}
v->databases = databases;
d = sqlite3_malloc(sizeof *d);
if (d == NULL) {
goto oom;
}
d->name = sqlite3_malloc64(strlen(name) + 1);
if (d->name == NULL) {
goto oom_after_database_malloc;
}
strcpy(d->name, name);
vfsDatabaseInit(d);
v->databases[n - 1] = d;
v->n_databases = n;
return d;
oom_after_database_malloc:
sqlite3_free(d);
oom:
return NULL;
}
static int vfsOpen(sqlite3_vfs *vfs,
const char *filename,
sqlite3_file *file,
int flags,
int *out_flags)
{
struct vfs *v;
struct vfsFile *f;
struct vfsDatabase *database;
enum vfsFileType type;
bool exists;
int exclusive = flags & SQLITE_OPEN_EXCLUSIVE;
int create = flags & SQLITE_OPEN_CREATE;
int rc;
(void)out_flags;
assert(vfs != NULL);
assert(vfs->pAppData != NULL);
assert(file != NULL);
/* From sqlite3.h.in:
*
* The SQLITE_OPEN_EXCLUSIVE flag is always used in conjunction with
* the SQLITE_OPEN_CREATE flag, which are both directly analogous to
* the O_EXCL and O_CREAT flags of the POSIX open() API. The
* SQLITE_OPEN_EXCLUSIVE flag, when paired with the
* SQLITE_OPEN_CREATE, is used to indicate that file should always be
* created, and that it is an error if it already exists. It is not
* used to indicate the file should be opened for exclusive access.
*/
assert(!exclusive || create);
v = (struct vfs *)(vfs->pAppData);
f = (struct vfsFile *)file;
/* This tells SQLite to not call Close() in case we return an error. */
f->base.pMethods = 0;
f->temp = NULL;
/* Save the flags */
f->flags = flags;
/* From SQLite documentation:
*
* If the zFilename parameter to xOpen is a NULL pointer then xOpen
* must invent its own temporary name for the file. Whenever the
* xFilename parameter is NULL it will also be the case that the
* flags parameter will include SQLITE_OPEN_DELETEONCLOSE.
*/
if (filename == NULL) {
assert(flags & SQLITE_OPEN_DELETEONCLOSE);
/* Open an actual temporary file. */
vfs = sqlite3_vfs_find("unix");
assert(vfs != NULL);
f->temp = sqlite3_malloc(vfs->szOsFile);
if (f->temp == NULL) {
v->error = ENOENT;
return SQLITE_CANTOPEN;
}
rc = vfs->xOpen(vfs, NULL, f->temp, flags, out_flags);
if (rc != SQLITE_OK) {
sqlite3_free(f->temp);
return rc;
}
f->base.pMethods = &vfsFileMethods;
f->vfs = NULL;
f->database = NULL;
return SQLITE_OK;
}
/* Search if the database object exists already. */
database = vfsDatabaseLookup(v, filename);
exists = database != NULL;
if (flags & SQLITE_OPEN_MAIN_DB) {
type = VFS__DATABASE;
} else if (flags & SQLITE_OPEN_MAIN_JOURNAL) {
type = VFS__JOURNAL;
} else if (flags & SQLITE_OPEN_WAL) {
type = VFS__WAL;
} else {
v->error = ENOENT;
return SQLITE_CANTOPEN;
}
/* If file exists, and the exclusive flag is on, return an error. */
if (exists && exclusive && create && type == VFS__DATABASE) {
v->error = EEXIST;
rc = SQLITE_CANTOPEN;
goto err;
}
if (!exists) {
/* When opening a WAL or journal file we expect the main
* database file to have already been created. */
if (type == VFS__WAL || type == VFS__JOURNAL) {
v->error = ENOENT;
rc = SQLITE_CANTOPEN;
goto err;
}
assert(type == VFS__DATABASE);
/* Check the create flag. */
if (!create) {
v->error = ENOENT;
rc = SQLITE_CANTOPEN;
goto err;
}
database = vfsCreateDatabase(v, filename);
if (database == NULL) {
v->error = ENOMEM;
rc = SQLITE_CANTOPEN;
goto err;
}
}
/* Populate the new file handle. */
f->base.pMethods = &vfsFileMethods;
f->vfs = v;
f->type = type;
f->database = database;
return SQLITE_OK;
err:
assert(rc != SQLITE_OK);
return rc;
}
static int vfsDelete(sqlite3_vfs *vfs, const char *filename, int dir_sync)
{
struct vfs *v;
(void)dir_sync;
assert(vfs != NULL);
assert(vfs->pAppData != NULL);
if (vfsFilenameEndsWith(filename, "-journal")) {
return SQLITE_OK;
}
if (vfsFilenameEndsWith(filename, "-wal")) {
return SQLITE_OK;
}
v = (struct vfs *)(vfs->pAppData);
return vfsDeleteDatabase(v, filename);
}
static int vfsAccess(sqlite3_vfs *vfs,
const char *filename,
int flags,
int *result)
{
struct vfs *v;
struct vfsDatabase *database;
(void)flags;
assert(vfs != NULL);
assert(vfs->pAppData != NULL);
v = (struct vfs *)(vfs->pAppData);
/* If the database object exists, we consider all associated files as
* existing and accessible. */
database = vfsDatabaseLookup(v, filename);
if (database == NULL) {
*result = 0;
} else {
*result = 1;
}
return SQLITE_OK;
}
static int vfsFullPathname(sqlite3_vfs *vfs,
const char *filename,
int pathname_len,
char *pathname)
{
(void)vfs;
/* Just return the path unchanged. */
sqlite3_snprintf(pathname_len, pathname, "%s", filename);
return SQLITE_OK;
}
static void *vfsDlOpen(sqlite3_vfs *vfs, const char *filename)
{
(void)vfs;
(void)filename;
return 0;
}
static void vfsDlError(sqlite3_vfs *vfs, int nByte, char *zErrMsg)
{
(void)vfs;
sqlite3_snprintf(nByte, zErrMsg,
"Loadable extensions are not supported");
zErrMsg[nByte - 1] = '\0';
}
static void (*vfsDlSym(sqlite3_vfs *vfs, void *pH, const char *z))(void)
{
(void)vfs;
(void)pH;
(void)z;
return 0;
}
static void vfsDlClose(sqlite3_vfs *vfs, void *pHandle)
{
(void)vfs;
(void)pHandle;
return;
}
static int vfsRandomness(sqlite3_vfs *vfs, int nByte, char *zByte)
{
(void)vfs;
(void)nByte;
(void)zByte;
/* TODO (is this needed?) */
return SQLITE_OK;
}
static int vfsSleep(sqlite3_vfs *vfs, int microseconds)
{
(void)vfs;
/* TODO (is this needed?) */
return microseconds;
}
static int vfsCurrentTimeInt64(sqlite3_vfs *vfs, sqlite3_int64 *piNow)
{
static const sqlite3_int64 unixEpoch =
24405875 * (sqlite3_int64)8640000;
struct timeval now;
(void)vfs;
gettimeofday(&now, 0);
*piNow =
unixEpoch + 1000 * (sqlite3_int64)now.tv_sec + now.tv_usec / 1000;
return SQLITE_OK;
}
static int vfsCurrentTime(sqlite3_vfs *vfs, double *piNow)
{
// TODO: check if it's always safe to cast a double* to a
// sqlite3_int64*.
return vfsCurrentTimeInt64(vfs, (sqlite3_int64 *)piNow);
}
static int vfsGetLastError(sqlite3_vfs *vfs, int x, char *y)
{
struct vfs *v = (struct vfs *)(vfs->pAppData);
int rc;
(void)vfs;
(void)x;
(void)y;
rc = v->error;
return rc;
}
int VfsInit(struct sqlite3_vfs *vfs, const char *name)
{
tracef("vfs init");
vfs->iVersion = 2;
vfs->szOsFile = sizeof(struct vfsFile);
vfs->mxPathname = VFS__MAX_PATHNAME;
vfs->pNext = NULL;
vfs->pAppData = vfsCreate();
if (vfs->pAppData == NULL) {
return DQLITE_NOMEM;
}
vfs->xOpen = vfsOpen;
vfs->xDelete = vfsDelete;
vfs->xAccess = vfsAccess;
vfs->xFullPathname = vfsFullPathname;
vfs->xDlOpen = vfsDlOpen;
vfs->xDlError = vfsDlError;
vfs->xDlSym = vfsDlSym;
vfs->xDlClose = vfsDlClose;
vfs->xRandomness = vfsRandomness;
vfs->xSleep = vfsSleep;
vfs->xCurrentTime = vfsCurrentTime;
vfs->xGetLastError = vfsGetLastError;
vfs->xCurrentTimeInt64 = vfsCurrentTimeInt64;
vfs->zName = name;
return 0;
}
void VfsClose(struct sqlite3_vfs *vfs)
{
tracef("vfs close");
struct vfs *v = vfs->pAppData;
vfsDestroy(v);
sqlite3_free(v);
}
static int vfsWalPoll(struct vfsWal *w, dqlite_vfs_frame **frames, unsigned *n)
{
struct vfsFrame *last;
uint32_t commit;
unsigned i;
if (w->n_tx == 0) {
*frames = NULL;
*n = 0;
return 0;
}
/* Check if the last frame in the transaction has the commit marker. */
last = w->tx[w->n_tx - 1];
commit = vfsFrameGetDatabaseSize(last);
if (commit == 0) {
*frames = NULL;
*n = 0;
return 0;
}
*frames = sqlite3_malloc64(sizeof **frames * w->n_tx);
if (*frames == NULL) {
return DQLITE_NOMEM;
}
*n = w->n_tx;
for (i = 0; i < w->n_tx; i++) {
dqlite_vfs_frame *frame = &(*frames)[i];
uint32_t page_number = vfsFrameGetPageNumber(w->tx[i]);
frame->data = w->tx[i]->page;
frame->page_number = page_number;
/* Release the vfsFrame object, but not its buf attribute, since
* responsibility for that memory has been transferred to the
* caller. */
sqlite3_free(w->tx[i]);
}
w->n_tx = 0;
return 0;
}
int VfsPoll(sqlite3_vfs *vfs,
const char *filename,
dqlite_vfs_frame **frames,
unsigned *n)
{
tracef("vfs poll filename:%s", filename);
struct vfs *v;
struct vfsDatabase *database;
struct vfsShm *shm;
struct vfsWal *wal;
int rv;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, filename);
if (database == NULL) {
tracef("not found");
return DQLITE_ERROR;
}
shm = &database->shm;
wal = &database->wal;
if (wal == NULL) {
*frames = NULL;
*n = 0;
return 0;
}
rv = vfsWalPoll(wal, frames, n);
if (rv != 0) {
tracef("wal poll failed %d", rv);
return rv;
}
/* If some frames have been written take the write lock. */
if (*n > 0) {
rv = vfsShmLock(shm, 0, 1, SQLITE_SHM_EXCLUSIVE);
if (rv != 0) {
tracef("shm lock failed %d", rv);
return rv;
}
vfsAmendWalIndexHeader(database);
}
return 0;
}
/* Return the salt-1 field stored in the WAL header.*/
static uint32_t vfsWalGetSalt1(struct vfsWal *w)
{
/* `hdr` field is pointer aligned, cast is safe */
return *(uint32_t *)__builtin_assume_aligned(&w->hdr[16],
sizeof(uint32_t));
}
/* Return the salt-2 field stored in the WAL header.*/
static uint32_t vfsWalGetSalt2(struct vfsWal *w)
{
/* `hdr` field is pointer aligned, cast is safe */
return *(uint32_t *)__builtin_assume_aligned(&w->hdr[20],
sizeof(uint32_t));
}
/* Return the checksum-1 field stored in the WAL header.*/
static uint32_t vfsWalGetChecksum1(struct vfsWal *w)
{
return ByteGetBe32(&w->hdr[24]);
}
/* Return the checksum-2 field stored in the WAL header.*/
static uint32_t vfsWalGetChecksum2(struct vfsWal *w)
{
return ByteGetBe32(&w->hdr[28]);
}
/* Append the given pages as new frames. */
static int vfsWalAppend(struct vfsWal *w,
unsigned database_n_pages,
unsigned n,
unsigned long *page_numbers,
uint8_t *pages)
{
struct vfsFrame **frames; /* New frames array. */
uint32_t page_size;
uint32_t database_size;
unsigned i;
unsigned j;
uint32_t salt[2];
uint32_t checksum[2];
/* No pending transactions. */
assert(w->n_tx == 0);
page_size = vfsWalGetPageSize(w);
assert(page_size > 0);
/* Get the salt from the WAL header. */
salt[0] = vfsWalGetSalt1(w);
salt[1] = vfsWalGetSalt2(w);
/* If there are currently no frames in the WAL, the starting database
* size will be equal to the current number of pages in the main
* database, and the starting checksum should be set to the one stored
* in the WAL header. Otherwise, the starting database size and checksum
* will be the ones stored in the last frame of the WAL. */
if (w->n_frames == 0) {
database_size = (uint32_t)database_n_pages;
checksum[0] = vfsWalGetChecksum1(w);
checksum[1] = vfsWalGetChecksum2(w);
} else {
struct vfsFrame *frame = w->frames[w->n_frames - 1];
checksum[0] = vfsFrameGetChecksum1(frame);
checksum[1] = vfsFrameGetChecksum2(frame);
database_size = vfsFrameGetDatabaseSize(frame);
}
frames =
sqlite3_realloc64(w->frames, sizeof *frames * (w->n_frames + n));
if (frames == NULL) {
goto oom;
}
w->frames = frames;
for (i = 0; i < n; i++) {
struct vfsFrame *frame = vfsFrameCreate(page_size);
uint32_t page_number = (uint32_t)page_numbers[i];
uint32_t commit = 0;
uint8_t *page = &pages[i * page_size];
if (frame == NULL) {
goto oom_after_frames_alloc;
}
if (page_number > database_size) {
database_size = page_number;
}
/* For commit records, the size of the database file in pages
* after the commit. For all other records, zero. */
if (i == n - 1) {
commit = database_size;
}
vfsFrameFill(frame, page_number, commit, salt, checksum, page,
page_size);
frames[w->n_frames + i] = frame;
}
w->n_frames += n;
return 0;
oom_after_frames_alloc:
for (j = 0; j < i; j++) {
vfsFrameDestroy(frames[w->n_frames + j]);
}
oom:
return DQLITE_NOMEM;
}
/* Write the header of a brand new WAL file image. */
static void vfsWalStartHeader(struct vfsWal *w, uint32_t page_size)
{
assert(page_size > 0);
uint32_t checksum[2] = {0, 0};
/* SQLite calculates checksums for the WAL header and frames either
* using little endian or big endian byte order when adding up 32-bit
* words. The byte order that should be used is recorded in the WAL file
* header by setting the least significant bit of the magic value stored
* in the first 32 bits. This allows portability of the WAL file across
* hosts with different native byte order.
*
* When creating a brand new WAL file, SQLite will set the byte order
* bit to match the host's native byte order, so checksums are a bit
* more efficient.
*
* In Dqlite the WAL file image is always generated at run time on the
* host, so we can always use the native byte order. */
BytePutBe32(VFS__WAL_MAGIC | VFS__BIGENDIAN, &w->hdr[0]);
BytePutBe32(VFS__WAL_VERSION, &w->hdr[4]);
BytePutBe32(page_size, &w->hdr[8]);
BytePutBe32(0, &w->hdr[12]);
sqlite3_randomness(8, &w->hdr[16]);
vfsChecksum(w->hdr, 24, checksum, checksum);
BytePutBe32(checksum[0], w->hdr + 24);
BytePutBe32(checksum[1], w->hdr + 28);
}
/* Invalidate the WAL index header, forcing the next connection that tries to
* start a read transaction to rebuild the WAL index by reading the WAL.
*
* No read or write lock must be currently held. */
static void vfsInvalidateWalIndexHeader(struct vfsDatabase *d)
{
struct vfsShm *shm = &d->shm;
uint8_t *header = shm->regions[0];
unsigned i;
for (i = 0; i < SQLITE_SHM_NLOCK; i++) {
assert(shm->shared[i] == 0);
assert(shm->exclusive[i] == 0);
}
/* The walIndexTryHdr function in sqlite/wal.c (which is indirectly
* called by sqlite3WalBeginReadTransaction), compares the first and
* second copy of the WAL index header to see if it is valid. Changing
* the first byte of each of the two copies is enough to make the check
* fail. */
header[0] = 1;
header[VFS__WAL_INDEX_HEADER_SIZE] = 0;
}
int VfsApply(sqlite3_vfs *vfs,
const char *filename,
unsigned n,
unsigned long *page_numbers,
void *frames)
{
tracef("vfs apply filename %s n %u", filename, n);
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
struct vfsShm *shm;
int rv;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, filename);
assert(database != NULL);
wal = &database->wal;
shm = &database->shm;
/* If there's no page size set in the WAL header, it must mean that WAL
* file was never written. In that case we need to initialize the WAL
* header. */
if (vfsWalGetPageSize(wal) == 0) {
vfsWalStartHeader(wal, vfsDatabaseGetPageSize(database));
}
rv = vfsWalAppend(wal, database->n_pages, n, page_numbers, frames);
if (rv != 0) {
tracef("wal append failed rv:%d n_pages:%u n:%u", rv,
database->n_pages, n);
return rv;
}
/* If a write lock is held it means that this is the VFS that orginated
* this commit and on which dqlite_vfs_poll() was called. In that case
* we release the lock and update the WAL index.
*
* Otherwise, if the WAL index header is mapped it means that this VFS
* has one or more open connections even if it's not the one that
* originated the transaction (this can happen for example when applying
* a Raft barrier and replaying the Raft log in order to serve a request
* of a newly connected client). */
if (shm->exclusive[0] == 1) {
shm->exclusive[0] = 0;
vfsAmendWalIndexHeader(database);
} else {
if (shm->n_regions > 0) {
vfsInvalidateWalIndexHeader(database);
}
}
return 0;
}
int VfsAbort(sqlite3_vfs *vfs, const char *filename)
{
tracef("vfs abort filename %s", filename);
struct vfs *v;
struct vfsDatabase *database;
int rv;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, filename);
if (database == NULL) {
tracef("database: %s does not exist", filename);
return DQLITE_ERROR;
}
rv = vfsShmUnlock(&database->shm, 0, 1, SQLITE_SHM_EXCLUSIVE);
if (rv != 0) {
tracef("shm unlock failed %d", rv);
return rv;
}
return 0;
}
/* Extract the number of pages field from the database header. */
static uint32_t vfsDatabaseGetNumberOfPages(struct vfsDatabase *d)
{
uint8_t *page;
assert(d->n_pages > 0);
page = d->pages[0];
/* The page size is stored in the 16th and 17th bytes of the first
* database page (big-endian) */
return ByteGetBe32(&page[28]);
}
int VfsDatabaseNumPages(sqlite3_vfs *vfs, const char *filename, uint32_t *n)
{
struct vfs *v;
struct vfsDatabase *d;
v = (struct vfs *)(vfs->pAppData);
d = vfsDatabaseLookup(v, filename);
if (d == NULL) {
return -1;
}
*n = vfsDatabaseGetNumberOfPages(d);
return 0;
}
static void vfsDatabaseSnapshot(struct vfsDatabase *d, uint8_t **cursor)
{
uint32_t page_size;
unsigned i;
page_size = vfsDatabaseGetPageSize(d);
assert(page_size > 0);
assert(d->n_pages == vfsDatabaseGetNumberOfPages(d));
for (i = 0; i < d->n_pages; i++) {
memcpy(*cursor, d->pages[i], page_size);
*cursor += page_size;
}
}
static void vfsWalSnapshot(struct vfsWal *w, uint8_t **cursor)
{
uint32_t page_size;
unsigned i;
if (w->n_frames == 0) {
return;
}
memcpy(*cursor, w->hdr, VFS__WAL_HEADER_SIZE);
*cursor += VFS__WAL_HEADER_SIZE;
page_size = vfsWalGetPageSize(w);
assert(page_size > 0);
for (i = 0; i < w->n_frames; i++) {
struct vfsFrame *frame = w->frames[i];
memcpy(*cursor, frame->header, FORMAT__WAL_FRAME_HDR_SIZE);
*cursor += FORMAT__WAL_FRAME_HDR_SIZE;
memcpy(*cursor, frame->page, page_size);
*cursor += page_size;
}
}
int VfsSnapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n)
{
tracef("vfs snapshot filename %s", filename);
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
uint8_t *cursor;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, filename);
if (database == NULL) {
tracef("not found");
*data = NULL;
*n = 0;
return 0;
}
if (database->n_pages != vfsDatabaseGetNumberOfPages(database)) {
tracef("corrupt");
return SQLITE_CORRUPT;
}
wal = &database->wal;
*n = vfsDatabaseFileSize(database) + vfsWalFileSize(wal);
/* TODO: we should fix the tests and use sqlite3_malloc instead. */
*data = raft_malloc(*n);
if (*data == NULL) {
tracef("malloc");
return DQLITE_NOMEM;
}
cursor = *data;
vfsDatabaseSnapshot(database, &cursor);
vfsWalSnapshot(wal, &cursor);
return 0;
}
static void vfsDatabaseShallowSnapshot(struct vfsDatabase *d,
struct dqlite_buffer *bufs)
{
uint32_t page_size;
page_size = vfsDatabaseGetPageSize(d);
assert(page_size > 0);
/* Fill the buffers with pointers to all of the database pages */
for (unsigned i = 0; i < d->n_pages; ++i) {
bufs[i].base = d->pages[i];
bufs[i].len = page_size;
}
}
int VfsShallowSnapshot(sqlite3_vfs *vfs,
const char *filename,
struct dqlite_buffer bufs[],
uint32_t n)
{
tracef("vfs snapshot filename %s", filename);
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
uint8_t *cursor;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, filename);
if (database == NULL) {
tracef("not found");
return -1;
}
if (database->n_pages != vfsDatabaseGetNumberOfPages(database)) {
tracef("corrupt");
return SQLITE_CORRUPT;
}
if (database->n_pages != n - 1) {
tracef("not enough buffers provided");
return SQLITE_MISUSE;
}
/* Copy WAL to last buffer. */
wal = &database->wal;
bufs[n - 1].len = vfsWalFileSize(wal);
bufs[n - 1].base = sqlite3_malloc64(bufs[n - 1].len);
/* WAL can have 0 length! */
if (bufs[n - 1].base == NULL && bufs[n - 1].len != 0) {
return SQLITE_NOMEM;
}
cursor = bufs[n - 1].base;
vfsWalSnapshot(wal, &cursor);
/* Copy page pointers to first n-1 buffers */
vfsDatabaseShallowSnapshot(database, bufs);
return 0;
}
static int vfsDatabaseRestore(struct vfsDatabase *d,
const uint8_t *data,
size_t n)
{
uint32_t page_size = vfsParsePageSize(ByteGetBe16(&data[16]));
unsigned n_pages;
void **pages;
unsigned i;
size_t offset;
int rv;
assert(page_size > 0);
/* Check that the page size of the snapshot is consistent with what we
* have here. */
assert(vfsDatabaseGetPageSize(d) == page_size);
n_pages = (unsigned)ByteGetBe32(&data[28]);
if (n < (uint64_t)n_pages * (uint64_t)page_size) {
return DQLITE_ERROR;
}
pages = sqlite3_malloc64(sizeof *pages * n_pages);
if (pages == NULL) {
goto oom;
}
for (i = 0; i < n_pages; i++) {
void *page = sqlite3_malloc64(page_size);
if (page == NULL) {
unsigned j;
for (j = 0; j < i; j++) {
sqlite3_free(pages[j]);
}
goto oom_after_pages_alloc;
}
pages[i] = page;
offset = (size_t)i * (size_t)page_size;
memcpy(page, &data[offset], page_size);
}
/* Truncate any existing content. */
rv = vfsDatabaseTruncate(d, 0);
assert(rv == 0);
d->pages = pages;
d->n_pages = n_pages;
return 0;
oom_after_pages_alloc:
sqlite3_free(pages);
oom:
return DQLITE_NOMEM;
}
static int vfsWalRestore(struct vfsWal *w,
const uint8_t *data,
size_t n,
uint32_t page_size)
{
struct vfsFrame **frames;
unsigned n_frames;
unsigned i;
size_t offset;
int rv;
if (n == 0) {
return 0;
}
assert(w->n_tx == 0);
assert(n > VFS__WAL_HEADER_SIZE);
assert(((n - (size_t)VFS__WAL_HEADER_SIZE) %
((size_t)vfsFrameSize(page_size))) == 0);
n_frames = (unsigned)((n - (size_t)VFS__WAL_HEADER_SIZE) /
((size_t)vfsFrameSize(page_size)));
frames = sqlite3_malloc64(sizeof *frames * n_frames);
if (frames == NULL) {
goto oom;
}
for (i = 0; i < n_frames; i++) {
struct vfsFrame *frame = vfsFrameCreate(page_size);
const uint8_t *p;
if (frame == NULL) {
unsigned j;
for (j = 0; j < i; j++) {
vfsFrameDestroy(frames[j]);
}
goto oom_after_frames_alloc;
}
frames[i] = frame;
offset = (size_t)VFS__WAL_HEADER_SIZE +
((size_t)i * (size_t)vfsFrameSize(page_size));
p = &data[offset];
memcpy(frame->header, p, VFS__FRAME_HEADER_SIZE);
memcpy(frame->page, p + VFS__FRAME_HEADER_SIZE, page_size);
}
memcpy(w->hdr, data, VFS__WAL_HEADER_SIZE);
rv = vfsWalTruncate(w, 0);
assert(rv == 0);
w->frames = frames;
w->n_frames = n_frames;
return 0;
oom_after_frames_alloc:
sqlite3_free(frames);
oom:
return DQLITE_NOMEM;
}
int VfsRestore(sqlite3_vfs *vfs,
const char *filename,
const void *data,
size_t n)
{
tracef("vfs restore filename %s size %zd", filename, n);
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
uint32_t page_size;
size_t offset;
int rv;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, filename);
assert(database != NULL);
wal = &database->wal;
/* Truncate any existing content. */
rv = vfsWalTruncate(wal, 0);
if (rv != 0) {
tracef("wal truncate failed %d", rv);
return rv;
}
/* Restore the content of the main database and of the WAL. */
rv = vfsDatabaseRestore(database, data, n);
if (rv != 0) {
tracef("database restore failed %d", rv);
return rv;
}
page_size = vfsDatabaseGetPageSize(database);
offset = (size_t)database->n_pages * (size_t)page_size;
rv = vfsWalRestore(wal, data + offset, n - offset, page_size);
if (rv != 0) {
tracef("wal restore failed %d", rv);
return rv;
}
return 0;
}
/******************************************************************************
Disk-based VFS
*****************************************************************************/
static int vfsDiskFileClose(sqlite3_file *file)
{
int rc = SQLITE_OK;
struct vfsFile *f = (struct vfsFile *)file;
struct vfs *v = (struct vfs *)(f->vfs);
if (f->temp != NULL) {
/* Close the actual temporary file. */
rc = f->temp->pMethods->xClose(f->temp);
sqlite3_free(f->temp);
return rc;
}
if (f->db != NULL) {
rc = f->db->pMethods->xClose(f->db);
sqlite3_free(f->db);
f->db = NULL;
if (rc != SQLITE_OK) {
return rc;
}
}
if (f->flags & SQLITE_OPEN_DELETEONCLOSE) {
rc = vfsDeleteDatabase(v, f->database->name);
}
return rc;
}
static int vfsDiskFileRead(sqlite3_file *file,
void *buf,
int amount,
sqlite_int64 offset)
{
struct vfsFile *f = (struct vfsFile *)file;
struct vfs *v;
int rv;
assert(buf != NULL);
assert(amount > 0);
assert(f != NULL);
if (f->temp != NULL) {
/* Read from the actual temporary file. */
return f->temp->pMethods->xRead(f->temp, buf, amount, offset);
}
if (f->db != NULL) {
/* Read from the actual database file. */
return f->db->pMethods->xRead(f->db, buf, amount, offset);
}
switch (f->type) {
case VFS__WAL:
rv = vfsWalRead(&f->database->wal, buf, amount, offset);
break;
case VFS__JOURNAL:
rv = SQLITE_IOERR_READ;
v = f->vfs;
if (v->disk) {
rv = SQLITE_OK;
}
break;
default:
rv = SQLITE_IOERR_READ;
break;
}
/* From SQLite docs:
*
* If xRead() returns SQLITE_IOERR_SHORT_READ it must also fill
* in the unread portions of the buffer with zeros. A VFS that
* fails to zero-fill short reads might seem to work. However,
* failure to zero-fill short reads will eventually lead to
* database corruption.
*/
if (rv == SQLITE_IOERR_SHORT_READ) {
memset(buf, 0, (size_t)amount);
}
return rv;
}
/* Need to keep track of the number of database pages to allow creating correct
* WAL headers when in on-disk mode. */
static int vfsDiskDatabaseTrackNumPages(struct vfsDatabase *d,
sqlite_int64 offset)
{
unsigned pgno;
if (offset == 0) {
pgno = 1;
} else {
assert(d->page_size != 0);
if (d->page_size == 0) {
return SQLITE_ERROR;
}
pgno = ((unsigned)offset / d->page_size) + 1;
}
if (pgno > d->n_pages) {
d->n_pages = pgno;
}
return SQLITE_OK;
}
static int vfsDiskFileWrite(sqlite3_file *file,
const void *buf,
int amount,
sqlite_int64 offset)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(buf != NULL);
assert(amount > 0);
assert(f != NULL);
if (f->temp != NULL) {
/* Write to the actual temporary file. */
return f->temp->pMethods->xWrite(f->temp, buf, amount, offset);
}
if (f->db != NULL) {
/* Write to the actual database file. */
vfsDiskDatabaseTrackNumPages(f->database, offset);
rv = f->db->pMethods->xWrite(f->db, buf, amount, offset);
tracef("vfsDiskFileWrite %s amount:%d rv:%d", "db", amount, rv);
return rv;
}
switch (f->type) {
case VFS__WAL:
rv =
vfsWalWrite(&f->database->wal, buf, amount, offset);
break;
case VFS__JOURNAL:
/* Silently swallow writes to the journal */
rv = SQLITE_OK;
break;
default:
rv = SQLITE_IOERR_WRITE;
break;
}
return rv;
}
static int vfsDiskFileTruncate(sqlite3_file *file, sqlite_int64 size)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(f != NULL);
if (f->db != NULL) {
return f->db->pMethods->xTruncate(f->db, size);
}
switch (f->type) {
case VFS__WAL:
rv = vfsWalTruncate(&f->database->wal, size);
break;
default:
rv = SQLITE_IOERR_TRUNCATE;
break;
}
return rv;
}
static int vfsDiskFileSync(sqlite3_file *file, int flags)
{
int rv;
struct vfsFile *f = (struct vfsFile *)file;
if (f->db != NULL) {
rv = f->db->pMethods->xSync(f->db, flags);
return rv;
}
return SQLITE_IOERR_FSYNC;
}
static int vfsDiskFileSize(sqlite3_file *file, sqlite_int64 *size)
{
struct vfsFile *f = (struct vfsFile *)file;
size_t n;
if (f->db != NULL) {
return f->db->pMethods->xFileSize(f->db, size);
}
switch (f->type) {
case VFS__WAL:
/* TODO? here we assume that FileSize() is never invoked
* between a header write and a page write. */
n = vfsWalFileSize(&f->database->wal);
break;
default:
n = 0;
break;
}
*size = (sqlite3_int64)n;
return SQLITE_OK;
}
/* Locking a file is a no-op, since no other process has visibility on it,
* unless the database resides on disk. */
static int vfsDiskFileLock(sqlite3_file *file, int lock)
{
struct vfsFile *f = (struct vfsFile *)file;
if (f->db != NULL) {
return f->db->pMethods->xLock(f->db, lock);
}
return SQLITE_OK;
}
/* Unlocking a file is a no-op, since no other process has visibility on it,
* unless the database resides on disk. */
static int vfsDiskFileUnlock(sqlite3_file *file, int lock)
{
struct vfsFile *f = (struct vfsFile *)file;
if (f->db != NULL) {
return f->db->pMethods->xUnlock(f->db, lock);
}
return SQLITE_OK;
}
/* We always report that a lock is held. This routine should be used only in
* journal mode, so it doesn't matter.
* TODO does this need to be treated differently in disk-mode?
* */
static int vfsDiskFileCheckReservedLock(sqlite3_file *file, int *result)
{
(void)file;
*result = 1;
return SQLITE_OK;
}
/* Handle pragma a pragma file control. See the xFileControl
* docstring in sqlite.h.in for more details. */
static int vfsDiskFileControlPragma(struct vfsFile *f, char **fnctl)
{
int rv;
const char *left;
const char *right;
assert(f != NULL);
assert(fnctl != NULL);
left = fnctl[1];
right = fnctl[2];
assert(left != NULL);
if (strcmp(left, "page_size") == 0 && right) {
int page_size = atoi(right);
/* The first page_size pragma sets page_size member of the db
* and is called by dqlite based on the page_size configuration.
* Only used for on-disk databases.
* */
if (f->db == NULL) {
fnctl[0] = sqlite3_mprintf("no DB file found");
return SQLITE_IOERR;
}
if (page_size > UINT16_MAX) {
fnctl[0] = sqlite3_mprintf("max page_size exceeded");
return SQLITE_IOERR;
}
if (f->database->page_size == 0) {
rv = f->db->pMethods->xFileControl(
f->db, SQLITE_FCNTL_PRAGMA, fnctl);
if (rv == SQLITE_NOTFOUND || rv == SQLITE_OK) {
f->database->page_size = (uint16_t)page_size;
}
return rv;
} else if ((uint16_t)page_size != f->database->page_size) {
fnctl[0] = sqlite3_mprintf(
"changing page size is not supported");
return SQLITE_IOERR;
}
} else if (strcmp(left, "journal_mode") == 0 && right) {
/* When the user executes 'PRAGMA journal_mode=x' we ensure
* that the desired mode is 'wal'. */
if (strcasecmp(right, "wal") != 0) {
fnctl[0] =
sqlite3_mprintf("only WAL mode is supported");
return SQLITE_IOERR;
}
}
/* We're returning NOTFOUND here to tell SQLite that we wish it to go on
* with its own handling as well. If we returned SQLITE_OK the page size
* of the journal mode wouldn't be effectively set, as the processing of
* the PRAGMA would stop here. */
return SQLITE_NOTFOUND;
}
static int vfsDiskFileControl(sqlite3_file *file, int op, void *arg)
{
struct vfsFile *f = (struct vfsFile *)file;
int rv;
assert(f->type == VFS__DATABASE);
switch (op) {
case SQLITE_FCNTL_PRAGMA:
rv = vfsDiskFileControlPragma(f, arg);
break;
case SQLITE_FCNTL_COMMIT_PHASETWO:
rv = vfsFileControlCommitPhaseTwo(f);
break;
case SQLITE_FCNTL_PERSIST_WAL:
/* This prevents SQLite from deleting the WAL after the
* last connection is closed. */
*(int *)(arg) = 1;
rv = SQLITE_OK;
break;
default:
rv = SQLITE_OK;
break;
}
return rv;
}
static int vfsDiskFileSectorSize(sqlite3_file *file)
{
struct vfsFile *f = (struct vfsFile *)file;
if (f->db != NULL) {
return f->db->pMethods->xSectorSize(f->db);
}
return 0;
}
static int vfsDiskFileDeviceCharacteristics(sqlite3_file *file)
{
struct vfsFile *f = (struct vfsFile *)file;
if (f->db != NULL) {
return f->db->pMethods->xDeviceCharacteristics(f->db);
}
return 0;
}
static const sqlite3_io_methods vfsDiskFileMethods = {
2, // iVersion
vfsDiskFileClose, // xClose
vfsDiskFileRead, // xRead
vfsDiskFileWrite, // xWrite
vfsDiskFileTruncate, // xTruncate
vfsDiskFileSync, // xSync
vfsDiskFileSize, // xFileSize
vfsDiskFileLock, // xLock
vfsDiskFileUnlock, // xUnlock
vfsDiskFileCheckReservedLock, // xCheckReservedLock
vfsDiskFileControl, // xFileControl
vfsDiskFileSectorSize, // xSectorSize
vfsDiskFileDeviceCharacteristics, // xDeviceCharacteristics
vfsFileShmMap, // xShmMap
vfsFileShmLock, // xShmLock
vfsFileShmBarrier, // xShmBarrier
vfsFileShmUnmap, // xShmUnmap
0,
0,
};
static int vfsDiskOpen(sqlite3_vfs *vfs,
const char *filename,
sqlite3_file *file,
int flags,
int *out_flags)
{
struct vfs *v;
struct vfsFile *f;
struct vfsDatabase *database;
enum vfsFileType type;
bool exists;
int exclusive = flags & SQLITE_OPEN_EXCLUSIVE;
int create = flags & SQLITE_OPEN_CREATE;
int rc;
tracef("filename:%s", filename);
(void)out_flags;
assert(vfs != NULL);
assert(vfs->pAppData != NULL);
assert(file != NULL);
/* From sqlite3.h.in:
*
* The SQLITE_OPEN_EXCLUSIVE flag is always used in conjunction with
* the SQLITE_OPEN_CREATE flag, which are both directly analogous to
* the O_EXCL and O_CREAT flags of the POSIX open() API. The
* SQLITE_OPEN_EXCLUSIVE flag, when paired with the
* SQLITE_OPEN_CREATE, is used to indicate that file should always be
* created, and that it is an error if it already exists. It is not
* used to indicate the file should be opened for exclusive access.
*/
assert(!exclusive || create);
v = (struct vfs *)(vfs->pAppData);
f = (struct vfsFile *)file;
/* This tells SQLite to not call Close() in case we return an error. */
f->base.pMethods = 0;
f->temp = NULL;
f->db = NULL;
/* Save the flags */
f->flags = flags;
/* From SQLite documentation:
*
* If the zFilename parameter to xOpen is a NULL pointer then xOpen
* must invent its own temporary name for the file. Whenever the
* xFilename parameter is NULL it will also be the case that the
* flags parameter will include SQLITE_OPEN_DELETEONCLOSE.
*/
if (filename == NULL) {
assert(flags & SQLITE_OPEN_DELETEONCLOSE);
/* Open an actual temporary file. */
vfs = v->base_vfs;
f->temp = sqlite3_malloc(vfs->szOsFile);
if (f->temp == NULL) {
v->error = ENOENT;
return SQLITE_CANTOPEN;
}
rc = vfs->xOpen(vfs, NULL, f->temp, flags, out_flags);
if (rc != SQLITE_OK) {
sqlite3_free(f->temp);
return rc;
}
f->base.pMethods = &vfsDiskFileMethods;
f->vfs = NULL;
f->database = NULL;
return SQLITE_OK;
}
/* Search if the database object exists already. */
database = vfsDatabaseLookup(v, filename);
exists = database != NULL;
if (flags & SQLITE_OPEN_MAIN_DB) {
type = VFS__DATABASE;
} else if (flags & SQLITE_OPEN_MAIN_JOURNAL) {
type = VFS__JOURNAL;
} else if (flags & SQLITE_OPEN_WAL) {
type = VFS__WAL;
} else {
v->error = ENOENT;
return SQLITE_CANTOPEN;
}
/* If file exists, and the exclusive flag is on, return an error. */
if (exists && exclusive && create && type == VFS__DATABASE) {
v->error = EEXIST;
rc = SQLITE_CANTOPEN;
goto err;
}
if (!exists) {
/* When opening a WAL or journal file we expect the main
* database file to have already been created. */
if (type == VFS__WAL || type == VFS__JOURNAL) {
v->error = ENOENT;
rc = SQLITE_CANTOPEN;
goto err;
}
assert(type == VFS__DATABASE);
/* Check the create flag. */
if (!create) {
v->error = ENOENT;
rc = SQLITE_CANTOPEN;
goto err;
}
database = vfsCreateDatabase(v, filename);
if (database == NULL) {
v->error = ENOMEM;
rc = SQLITE_CANTOPEN;
goto err;
}
}
if (type == VFS__DATABASE && v->disk) {
/* Open an actual database file. */
vfs = v->base_vfs;
f->db = sqlite3_malloc(vfs->szOsFile);
if (f->db == NULL) {
return SQLITE_NOMEM;
}
rc = vfs->xOpen(vfs, filename, f->db, flags, out_flags);
if (rc != SQLITE_OK) {
sqlite3_free(f->db);
f->db = NULL;
return rc;
}
}
/* Populate the new file handle. */
f->base.pMethods = &vfsDiskFileMethods;
f->vfs = v;
f->type = type;
f->database = database;
return SQLITE_OK;
err:
assert(rc != SQLITE_OK);
return rc;
}
static int vfsDiskDelete(sqlite3_vfs *vfs, const char *filename, int dir_sync)
{
int rv;
struct vfs *v;
(void)dir_sync;
assert(vfs != NULL);
assert(vfs->pAppData != NULL);
if (vfsFilenameEndsWith(filename, "-journal")) {
return SQLITE_OK;
}
if (vfsFilenameEndsWith(filename, "-wal")) {
return SQLITE_OK;
}
v = (struct vfs *)(vfs->pAppData);
rv = vfsDeleteDatabase(v, filename);
if (rv != 0) {
return rv;
}
if (!v->disk) {
return rv;
}
return v->base_vfs->xDelete(v->base_vfs, filename, dir_sync);
}
static int vfsDiskAccess(sqlite3_vfs *vfs,
const char *filename,
int flags,
int *result)
{
struct vfs *v;
struct vfsDatabase *database;
(void)flags;
assert(vfs != NULL);
assert(vfs->pAppData != NULL);
v = (struct vfs *)(vfs->pAppData);
/* If the database object exists, we consider all associated files as
* existing and accessible. */
database = vfsDatabaseLookup(v, filename);
if (database == NULL) {
*result = 0;
} else if (vfsFilenameEndsWith(filename, "-journal")) {
*result = 1;
} else if (vfsFilenameEndsWith(filename, "-wal")) {
*result = 1;
} else {
/* dqlite database object exists, now check if the regular
* SQLite file exists. */
return v->base_vfs->xAccess(vfs, filename, flags, result);
}
return SQLITE_OK;
}
int VfsEnableDisk(struct sqlite3_vfs *vfs)
{
if (vfs->pAppData == NULL) {
return -1;
}
struct vfs *v = vfs->pAppData;
v->disk = true;
vfs->xOpen = vfsDiskOpen;
vfs->xDelete = vfsDiskDelete;
vfs->xAccess = vfsDiskAccess;
/* TODO check if below functions need alteration for on-disk case. */
vfs->xFullPathname = vfsFullPathname;
vfs->xDlOpen = vfsDlOpen;
vfs->xDlError = vfsDlError;
vfs->xDlSym = vfsDlSym;
vfs->xDlClose = vfsDlClose;
vfs->xRandomness = vfsRandomness;
vfs->xSleep = vfsSleep;
vfs->xCurrentTime = vfsCurrentTime;
vfs->xGetLastError = vfsGetLastError;
vfs->xCurrentTimeInt64 = vfsCurrentTimeInt64;
return 0;
}
int VfsDiskSnapshotWal(sqlite3_vfs *vfs,
const char *path,
struct dqlite_buffer *buf)
{
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
uint8_t *cursor;
int rv;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, path);
if (database == NULL) {
tracef("not found");
rv = SQLITE_NOTFOUND;
goto err;
}
/* Copy WAL to last buffer. */
wal = &database->wal;
buf->len = vfsWalFileSize(wal);
buf->base = sqlite3_malloc64(buf->len);
/* WAL can have 0 length! */
if (buf->base == NULL && buf->len != 0) {
rv = SQLITE_NOMEM;
goto err;
}
cursor = buf->base;
vfsWalSnapshot(wal, &cursor);
return 0;
err:
return rv;
}
int VfsDiskSnapshotDb(sqlite3_vfs *vfs,
const char *path,
struct dqlite_buffer *buf)
{
struct vfs *v;
struct vfsDatabase *database;
int fd;
int rv;
char *addr;
struct stat sb;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, path);
if (database == NULL) {
tracef("not found");
rv = SQLITE_NOTFOUND;
goto err;
}
/* mmap the database file */
fd = open(path, O_RDONLY);
if (fd == -1) {
tracef("failed to open %s", path);
rv = SQLITE_IOERR;
goto err;
}
rv = fstat(fd, &sb);
if (rv == -1) {
tracef("fstat failed path:%s fd:%d", path, fd);
close(fd);
rv = SQLITE_IOERR;
goto err;
}
/* TODO database size limited to whatever fits in a size_t. Multiple
* mmap's needed. This limitation also exists in various other places
* throughout the codebase. */
addr = mmap(NULL, (size_t)sb.st_size, PROT_READ, MAP_SHARED, fd, 0);
close(fd);
if (addr == MAP_FAILED) {
rv = SQLITE_IOERR;
goto err;
}
buf->base = addr;
buf->len = (size_t)sb.st_size;
return 0;
err:
return rv;
}
static int vfsDiskDatabaseRestore(struct vfsDatabase *d,
const char *filename,
const uint8_t *data,
size_t n)
{
int rv = 0;
int fd;
ssize_t sz; /* rv of write */
uint32_t page_size;
unsigned n_pages;
const uint8_t *cursor;
size_t n_left; /* amount of data left to write */
fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600);
if (fd == -1) {
tracef("fopen failed filename:%s", filename);
return -1;
}
n_left = n;
cursor = data;
while (n_left > 0) {
sz = write(fd, cursor, n_left);
/* sz == 0 should not be possible when writing a positive amount
* of bytes. */
if (sz <= 0) {
tracef("fwrite failed n:%zd sz:%zd errno:%d", n_left,
sz, errno);
rv = DQLITE_ERROR;
goto out;
}
n_left -= (size_t)sz;
cursor += sz;
}
page_size = vfsParsePageSize(ByteGetBe16(&data[16]));
assert(page_size > 0);
/* Check that the page size of the snapshot is consistent with what we
* have here. */
assert(vfsDatabaseGetPageSize(d) == page_size);
n_pages = (unsigned)ByteGetBe32(&data[28]);
d->n_pages = n_pages;
d->page_size = page_size;
out:
close(fd);
return rv;
}
int VfsDiskRestore(sqlite3_vfs *vfs,
const char *path,
const void *data,
size_t main_size,
size_t wal_size)
{
tracef("vfs restore path %s main_size %zd wal_size %zd", path,
main_size, wal_size);
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
uint32_t page_size;
int rv;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, path);
assert(database != NULL);
wal = &database->wal;
/* Truncate any existing content. */
rv = vfsWalTruncate(wal, 0);
if (rv != 0) {
tracef("wal truncate failed %d", rv);
return rv;
}
/* Restore the content of the main database and of the WAL. */
rv = vfsDiskDatabaseRestore(database, path, data, main_size);
if (rv != 0) {
tracef("database restore failed %d", rv);
return rv;
}
page_size = vfsDatabaseGetPageSize(database);
rv = vfsWalRestore(wal, data + main_size, wal_size, page_size);
if (rv != 0) {
tracef("wal restore failed %d", rv);
return rv;
}
return 0;
}
uint64_t VfsDatabaseSize(sqlite3_vfs *vfs,
const char *path,
unsigned n,
unsigned page_size)
{
struct vfs *v;
struct vfsDatabase *database;
struct vfsWal *wal;
uint64_t new_wal_size;
v = (struct vfs *)(vfs->pAppData);
database = vfsDatabaseLookup(v, path);
assert(database != NULL);
wal = &database->wal;
new_wal_size = (uint64_t)vfsWalFileSize(wal);
if (new_wal_size == 0) {
new_wal_size += (uint64_t)VFS__WAL_HEADER_SIZE;
}
new_wal_size += (uint64_t)n * (uint64_t)vfsFrameSize(page_size);
return (uint64_t)vfsDatabaseFileSize(database) + new_wal_size;
}
uint64_t VfsDatabaseSizeLimit(sqlite3_vfs *vfs)
{
(void)vfs;
return (uint64_t)SIZE_MAX;
}
dqlite-1.16.0/src/vfs.h 0000664 0000000 0000000 00000005104 14512203222 0014610 0 ustar 00root root 0000000 0000000 #ifndef VFS_H_
#define VFS_H_
#include
#include "config.h"
/* Initialize the given SQLite VFS interface with dqlite's custom
* implementation. */
int VfsInit(struct sqlite3_vfs *vfs, const char *name);
int VfsEnableDisk(struct sqlite3_vfs *vfs);
/* Release all memory associated with the given dqlite in-memory VFS
* implementation.
*
* This function also automatically unregister the implementation from the
* SQLite global registry. */
void VfsClose(struct sqlite3_vfs *vfs);
/* Check if the last sqlite3_step() call triggered a write transaction, and
* return its content if so. */
int VfsPoll(sqlite3_vfs *vfs,
const char *database,
dqlite_vfs_frame **frames,
unsigned *n);
/* Append the given frames to the WAL. */
int VfsApply(sqlite3_vfs *vfs,
const char *filename,
unsigned n,
unsigned long *page_numbers,
void *frames);
/* Cancel a pending transaction. */
int VfsAbort(sqlite3_vfs *vfs, const char *filename);
/* Make a full snapshot of a database. */
int VfsSnapshot(sqlite3_vfs *vfs, const char *filename, void **data, size_t *n);
/* Makes a full, shallow snapshot of a database. The first n-1 buffers will each
* contain a pointer to the actual database pages, while the n'th buffer
* will contain a copy of the WAL. `bufs` MUST point to an array of n
* `dqlite_buffer` structs and n MUST equal 1 + the number of pages in
* the database. */
int VfsShallowSnapshot(sqlite3_vfs *vfs,
const char *filename,
struct dqlite_buffer bufs[],
uint32_t n);
/* Copies the WAL into buf */
int VfsDiskSnapshotWal(sqlite3_vfs *vfs,
const char *path,
struct dqlite_buffer *buf);
/* `mmap` the database into buf. */
int VfsDiskSnapshotDb(sqlite3_vfs *vfs,
const char *path,
struct dqlite_buffer *buf);
/* Restore a database snapshot. */
int VfsRestore(sqlite3_vfs *vfs,
const char *filename,
const void *data,
size_t n);
/* Restore a disk database snapshot. */
int VfsDiskRestore(sqlite3_vfs *vfs,
const char *path,
const void *data,
size_t main_size,
size_t wal_size);
/* Number of pages in the database. */
int VfsDatabaseNumPages(sqlite3_vfs *vfs, const char *filename, uint32_t *n);
/* Returns the resulting size of the main file, wal file and n additional WAL
* frames with the specified page_size. */
uint64_t VfsDatabaseSize(sqlite3_vfs *vfs,
const char *path,
unsigned n,
unsigned page_size);
/* Returns the the maximum size of the main file and wal file. */
uint64_t VfsDatabaseSizeLimit(sqlite3_vfs *vfs);
#endif /* VFS_H_ */
dqlite-1.16.0/test/ 0000775 0000000 0000000 00000000000 14512203222 0014031 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/test/integration/ 0000775 0000000 0000000 00000000000 14512203222 0016354 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/test/integration/main.c 0000664 0000000 0000000 00000000062 14512203222 0017442 0 ustar 00root root 0000000 0000000 #include "../lib/runner.h"
RUNNER("integration")
dqlite-1.16.0/test/integration/test_client.c 0000664 0000000 0000000 00000006770 14512203222 0021047 0 ustar 00root root 0000000 0000000 #include "../lib/client.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/server.h"
#include "../lib/sqlite.h"
/******************************************************************************
*
* Handle client requests
*
******************************************************************************/
SUITE(client);
static char *bools[] = {"0", "1", NULL};
static MunitParameterEnum client_params[] = {
{"disk_mode", bools},
{NULL, NULL},
};
struct fixture
{
struct test_server server;
struct client_proto *client;
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
(void)user_data;
test_heap_setup(params, user_data);
test_sqlite_setup(params);
test_server_setup(&f->server, 1, params);
test_server_start(&f->server, params);
f->client = test_server_client(&f->server);
HANDSHAKE;
OPEN;
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
test_server_tear_down(&f->server);
test_sqlite_tear_down();
test_heap_tear_down(data);
free(f);
}
TEST(client, exec, setUp, tearDown, 0, client_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
(void)params;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
return MUNIT_OK;
}
TEST(client, execWithOneParam, setUp, tearDown, 0, client_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct value param = {0};
int rv;
(void)params;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test (n) VALUES(?)", &stmt_id);
param.type = SQLITE_INTEGER;
param.integer = 17;
rv = clientSendExec(f->client, stmt_id, ¶m, 1, NULL);
munit_assert_int(rv, ==, 0);
rv = clientRecvResult(f->client, &last_insert_id, &rows_affected, NULL);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(client, execSql, setUp, tearDown, 0, client_params)
{
struct fixture *f = data;
uint64_t last_insert_id;
uint64_t rows_affected;
(void)params;
EXEC_SQL("CREATE TABLE test (n INT)", &last_insert_id, &rows_affected);
return MUNIT_OK;
}
TEST(client, query, setUp, tearDown, 0, client_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
unsigned i;
struct rows rows;
(void)params;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("BEGIN", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test (n) VALUES(123)", &stmt_id);
for (i = 0; i < 256; i++) {
EXEC(stmt_id, &last_insert_id, &rows_affected);
}
PREPARE("COMMIT", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("SELECT n FROM test", &stmt_id);
QUERY(stmt_id, &rows);
clientCloseRows(&rows);
return MUNIT_OK;
}
TEST(client, querySql, setUp, tearDown, 0, client_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
unsigned i;
struct rows rows;
(void)params;
EXEC_SQL("CREATE TABLE test (n INT)", &last_insert_id, &rows_affected);
EXEC_SQL("BEGIN", &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test (n) VALUES(123)", &stmt_id);
for (i = 0; i < 256; i++) {
EXEC(stmt_id, &last_insert_id, &rows_affected);
}
EXEC_SQL("COMMIT", &last_insert_id, &rows_affected);
QUERY_SQL("SELECT n FROM test", &rows);
clientCloseRows(&rows);
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_cluster.c 0000664 0000000 0000000 00000017036 14512203222 0021247 0 ustar 00root root 0000000 0000000 #include "../../src/client/protocol.h"
#include "../../src/server.h"
#include "../lib/client.h"
#include "../lib/endpoint.h"
#include "../lib/fs.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/server.h"
#include "../lib/sqlite.h"
/******************************************************************************
*
* Fixture
*
******************************************************************************/
#define N_SERVERS 3
#define FIXTURE \
struct test_server servers[N_SERVERS]; \
struct client_proto *client
#define SETUP \
unsigned i_; \
test_heap_setup(params, user_data); \
test_sqlite_setup(params); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_setup(server, i_ + 1, params); \
} \
test_server_network(f->servers, N_SERVERS); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_start(server, params); \
} \
SELECT(1)
#define TEAR_DOWN \
unsigned i_; \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
test_server_tear_down(&f->servers[i_]); \
} \
test_sqlite_tear_down(); \
test_heap_tear_down(data)
/* Use the client connected to the server with the given ID. */
#define SELECT(ID) f->client = test_server_client(&f->servers[ID - 1])
/******************************************************************************
*
* cluster
*
******************************************************************************/
SUITE(cluster)
struct fixture
{
FIXTURE;
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
SETUP;
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
TEAR_DOWN;
free(f);
}
static char *bools[] = {"0", "1", NULL};
static char *num_records[] = {
"0", "1", "256",
/* WAL will just have been checkpointed after 993 writes. */
"993",
/* Non-empty WAL, checkpointed twice, 2 snapshots taken */
"2200", NULL};
static MunitParameterEnum cluster_params[] = {
{"num_records", num_records},
{"disk_mode", bools},
{NULL, NULL},
};
/* Restart a node and check if all data is there */
TEST(cluster, restart, setUp, tearDown, 0, cluster_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct rows rows;
long n_records =
strtol(munit_parameters_get(params, "num_records"), NULL, 0);
char sql[128];
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
for (int i = 0; i < n_records; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
PREPARE(sql, &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
}
struct test_server *server = &f->servers[0];
test_server_stop(server);
test_server_start(server, params);
/* The table is visible after restart. */
HANDSHAKE;
OPEN;
PREPARE("SELECT COUNT(*) from test", &stmt_id);
QUERY(stmt_id, &rows);
munit_assert_long(rows.next->values->integer, ==, n_records);
clientCloseRows(&rows);
return MUNIT_OK;
}
/* Add data to a node, add a new node and make sure data is there. */
TEST(cluster, dataOnNewNode, setUp, tearDown, 0, cluster_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct rows rows;
long n_records =
strtol(munit_parameters_get(params, "num_records"), NULL, 0);
char sql[128];
unsigned id = 2;
const char *address = "@2";
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
for (int i = 0; i < n_records; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
PREPARE(sql, &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
}
/* Add a second voting server, this one will receive all data from the
* original leader. */
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
/* Remove original server so second server becomes leader after election
* timeout */
REMOVE(1);
sleep(1);
/* The full table is visible from the new node */
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("SELECT COUNT(*) from test", &stmt_id);
QUERY(stmt_id, &rows);
munit_assert_long(rows.next->values->integer, ==, n_records);
clientCloseRows(&rows);
return MUNIT_OK;
}
/* Insert a huge row, causing SQLite to allocate overflow pages. Then
* insert the same row again. (Reproducer for
* https://github.com/canonical/raft/issues/432.) */
TEST(cluster, hugeRow, setUp, tearDown, 0, NULL)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
char *sql;
ssize_t n;
size_t huge = 20000000;
(void)params;
HANDSHAKE;
OPEN;
PREPARE(
"CREATE TABLE IF NOT EXISTS model(key TEXT, value TEXT, "
"UNIQUE(key))",
&stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
sql = munit_malloc(huge);
n = snprintf(
sql, huge,
"INSERT OR REPLACE INTO model (key, value) VALUES('my-key-1', '");
memset(sql + n, 'A', huge - n);
memcpy(sql + huge - 3, "')", 3);
PREPARE(sql, &stmt_id);
free(sql);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Again */
EXEC(stmt_id, &last_insert_id, &rows_affected);
return MUNIT_OK;
}
TEST(cluster, modifyingQuery, setUp, tearDown, 0, cluster_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct rows rows;
long n_records =
strtol(munit_parameters_get(params, "num_records"), NULL, 0);
char sql[128];
unsigned id = 2;
const char *address = "@2";
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
for (int i = 0; i < n_records; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
PREPARE(sql, &stmt_id);
QUERY(stmt_id, &rows);
munit_assert_uint64(rows.column_count, ==, 0);
munit_assert_ptr(rows.next, ==, NULL);
clientCloseRows(&rows);
}
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
REMOVE(1);
sleep(1);
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("SELECT COUNT(*) from test", &stmt_id);
QUERY(stmt_id, &rows);
munit_assert_long(rows.next->values->integer, ==, n_records);
clientCloseRows(&rows);
return MUNIT_OK;
}
TEST(cluster, modifyingQuerySql, setUp, tearDown, 0, cluster_params)
{
struct fixture *f = data;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct rows rows;
long n_records =
strtol(munit_parameters_get(params, "num_records"), NULL, 0);
char sql[128];
unsigned id = 2;
const char *address = "@2";
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
for (int i = 0; i < n_records; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
QUERY_SQL(sql, &rows);
munit_assert_uint64(rows.column_count, ==, 0);
munit_assert_ptr(rows.next, ==, NULL);
clientCloseRows(&rows);
}
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
REMOVE(1);
sleep(1);
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("SELECT COUNT(*) from test", &stmt_id);
QUERY(stmt_id, &rows);
munit_assert_long(rows.next->values->integer, ==, n_records);
clientCloseRows(&rows);
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_fsm.c 0000664 0000000 0000000 00000054223 14512203222 0020352 0 ustar 00root root 0000000 0000000 #include "../../src/client/protocol.h"
#include "../../src/command.h"
#include "../../src/server.h"
#include "../lib/client.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/server.h"
#include "../lib/sqlite.h"
/******************************************************************************
*
* Fixture
*
******************************************************************************/
#define N_SERVERS 1
#define FIXTURE \
struct test_server servers[N_SERVERS]; \
struct client_proto *client
#define SETUP \
unsigned i_; \
test_heap_setup(params, user_data); \
test_sqlite_setup(params); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_setup(server, i_ + 1, params); \
} \
test_server_network(f->servers, N_SERVERS); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_start(server, params); \
} \
SELECT(1)
#define TEAR_DOWN \
unsigned i_; \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
test_server_tear_down(&f->servers[i_]); \
} \
test_sqlite_tear_down(); \
test_heap_tear_down(data)
/******************************************************************************
*
* Helper macros.
*
******************************************************************************/
/* Use the client connected to the server with the given ID. */
#define SELECT(ID) f->client = test_server_client(&f->servers[ID - 1])
static char *bools[] = {"0", "1", NULL};
/* Make sure the snapshots scheduled by raft don't interfere with the snapshots
* scheduled by the tests. */
static char *snapshot_threshold[] = {"8192", NULL};
static MunitParameterEnum snapshot_params[] = {
{SNAPSHOT_THRESHOLD_PARAM, snapshot_threshold},
{"disk_mode", bools},
{NULL, NULL},
};
/******************************************************************************
*
* snapshot
*
******************************************************************************/
SUITE(fsm)
struct fixture
{
FIXTURE;
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
SETUP;
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
TEAR_DOWN;
free(f);
}
TEST(fsm, snapshotFreshDb, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_uint(n_bufs, ==, 1); /* Snapshot header */
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_ptr_null(bufs);
munit_assert_uint(n_bufs, ==, 0);
return MUNIT_OK;
}
TEST(fsm, snapshotWrittenDb, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Add some data to database */
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_uint(n_bufs, >, 1);
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_ptr_null(bufs);
munit_assert_uint(n_bufs, ==, 0);
return MUNIT_OK;
}
TEST(fsm, snapshotHeapFaultSingleDB, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Add some data to database */
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Inject heap faults at different stages of fsm__snapshot */
test_heap_fault_config(0, 1);
test_heap_fault_enable();
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
test_heap_fault_config(1, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
test_heap_fault_config(2, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
/* disk_mode does fewer allocations */
if (!disk_mode) {
test_heap_fault_config(3, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
}
return MUNIT_OK;
}
/* Inject faults into the async stage of the snapshot process */
TEST(fsm,
snapshotHeapFaultSingleDBAsyncDisk,
setUp,
tearDown,
0,
snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
if (!disk_mode) {
return MUNIT_SKIP;
}
/* Add some data to database */
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Sync stage succeeds */
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
/* Inject heap fault in first call to encodeDiskDatabaseAsync */
test_heap_fault_config(0, 1);
test_heap_fault_enable();
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
/* Cleanup should succeed */
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(fsm, snapshotHeapFaultTwoDB, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Open 2 databases and add data to them */
HANDSHAKE;
OPEN_NAME("test");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Close and reopen the client and open a second database */
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN_NAME("test2");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Inject heap faults at different stages of fsm__snapshot */
test_heap_fault_config(0, 1);
test_heap_fault_enable();
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
test_heap_fault_config(1, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
test_heap_fault_config(2, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
test_heap_fault_config(3, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
/* disk_mode does fewer allocations */
if (!disk_mode) {
test_heap_fault_config(4, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
test_heap_fault_config(5, 1);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
}
return MUNIT_OK;
}
TEST(fsm, snapshotHeapFaultTwoDBAsync, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
if (!disk_mode) {
return MUNIT_SKIP;
}
/* Open 2 databases and add data to them */
HANDSHAKE;
OPEN_NAME("test");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Close and reopen the client and open a second database */
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN_NAME("test2");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* sync fsm__snapshot succeeds. */
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
/* async step fails at different stages. */
test_heap_fault_enable();
test_heap_fault_config(0, 1);
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
/* Inject fault when encoding second Database */
/* sync fsm__snapshot succeeds. */
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
test_heap_fault_config(1, 1);
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, !=, 0);
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(fsm, snapshotNewDbAddedBeforeFinalize, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
int rv;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Add some data to database */
HANDSHAKE;
OPEN_NAME("test");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_uint(n_bufs, >, 1);
/* Close and reopen the client and open a second database,
* and ensure finalize succeeds. */
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN_NAME("test2");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_ptr_null(bufs);
munit_assert_uint(n_bufs, ==, 0);
return MUNIT_OK;
}
TEST(fsm, snapshotWritesBeforeFinalize, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
unsigned n_bufs = 0;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
char sql[128];
int rv;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Add some data to database */
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(0)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_uint(n_bufs, >, 1);
/* Add (a lot) more data to the database */
for (unsigned i = 0; i < 1000; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
PREPARE(sql, &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
if (disk_mode && i == 512) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
}
/* Finalize succeeds */
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
munit_assert_ptr_null(bufs);
munit_assert_uint(n_bufs, ==, 0);
/* Triggers a checkpoint */
PREPARE("INSERT INTO test(n) VALUES(1001)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
return MUNIT_OK;
}
TEST(fsm, concurrentSnapshots, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
struct raft_buffer *bufs2;
unsigned n_bufs = 0;
unsigned n_bufs2 = 0;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
int rv;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Add some data to database */
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Second snapshot fails when first isn't finalized */
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
rv = fsm->snapshot(fsm, &bufs2, &n_bufs2);
munit_assert_int(rv, ==, RAFT_BUSY);
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
/* Second snapshot succeeds after first is finalized */
rv = fsm->snapshot(fsm, &bufs2, &n_bufs2);
munit_assert_int(rv, ==, 0);
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs2, &n_bufs2);
munit_assert_int(rv, ==, 0);
}
rv = fsm->snapshot_finalize(fsm, &bufs2, &n_bufs2);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
/* Copies n raft buffers to a single raft buffer */
static struct raft_buffer n_bufs_to_buf(struct raft_buffer bufs[], unsigned n)
{
uint8_t *cursor;
struct raft_buffer buf = {0};
/* Allocate a suitable buffer */
for (unsigned i = 0; i < n; ++i) {
buf.len += bufs[i].len;
}
buf.base = raft_malloc(buf.len);
munit_assert_ptr_not_null(buf.base);
/* Copy all data */
cursor = buf.base;
for (unsigned i = 0; i < n; ++i) {
memcpy(cursor, bufs[i].base, bufs[i].len);
cursor += bufs[i].len;
}
munit_assert_ullong((uintptr_t)(cursor - (uint8_t *)buf.base), ==,
buf.len);
return buf;
}
static char *num_records[] = {
"0", "1", "256",
/* WAL will just have been checkpointed after 993 writes. */
"993",
/* Non-empty WAL, checkpointed twice */
"2200", NULL};
static MunitParameterEnum restore_params[] = {
{"num_records", num_records},
{SNAPSHOT_THRESHOLD_PARAM, snapshot_threshold},
{"disk_mode", bools},
{NULL, NULL},
};
TEST(fsm, snapshotRestore, setUp, tearDown, 0, restore_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
struct raft_buffer snapshot;
long n_records =
strtol(munit_parameters_get(params, "num_records"), NULL, 0);
unsigned n_bufs = 0;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct rows rows;
int rv;
char sql[128];
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Add some data to database */
HANDSHAKE;
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
for (int i = 0; i < n_records; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
PREPARE(sql, &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
}
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
/* Deep copy snapshot */
snapshot = n_bufs_to_buf(bufs, n_bufs);
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
/* Additionally frees snapshot.base */
rv = fsm->restore(fsm, &snapshot);
munit_assert_int(rv, ==, 0);
/* Table is there on fresh connection. */
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN;
PREPARE("SELECT COUNT(*) from test", &stmt_id);
QUERY(stmt_id, &rows);
munit_assert_long(rows.next->values->integer, ==, n_records);
clientCloseRows(&rows);
/* Still possible to insert entries */
for (int i = 0; i < n_records; ++i) {
sprintf(sql, "INSERT INTO test(n) VALUES(%ld)",
n_records + i + 1);
PREPARE(sql, &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
}
return MUNIT_OK;
}
TEST(fsm, snapshotRestoreMultipleDBs, setUp, tearDown, 0, snapshot_params)
{
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
struct raft_buffer *bufs;
struct raft_buffer snapshot;
unsigned n_bufs = 0;
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct rows rows;
uint64_t code;
char *msg;
int rv;
bool disk_mode = false;
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
disk_mode = (bool)atoi(disk_mode_param);
}
/* Create 2 databases and add data to them. */
HANDSHAKE;
OPEN_NAME("test");
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN_NAME("test2");
PREPARE("CREATE TABLE test2a (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test2a(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Snapshot both databases and restore the data. */
rv = fsm->snapshot(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
if (disk_mode) {
rv = fsm->snapshot_async(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
}
/* Copy the snapshot to restore it */
snapshot = n_bufs_to_buf(bufs, n_bufs);
rv = fsm->snapshot_finalize(fsm, &bufs, &n_bufs);
munit_assert_int(rv, ==, 0);
/* Create a new table in test2 that shouldn't be visible after
* restoring the snapshot. */
PREPARE("CREATE TABLE test2b (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test2b(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Restore snapshot */
rv = fsm->restore(fsm, &snapshot);
munit_assert_int(rv, ==, 0);
/* Reopen connection */
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN_NAME("test2");
/* Table before snapshot is there on second DB */
PREPARE("SELECT * from test2a", &stmt_id);
QUERY(stmt_id, &rows);
clientCloseRows(&rows);
/* Table after snapshot is not there on second DB */
PREPARE_FAIL("SELECT * from test2b", &stmt_id, &code, &msg);
munit_assert_uint64(code, ==, DQLITE_ERROR);
munit_assert_string_equal(msg, "no such table: test2b");
free(msg);
/* Table is there on first DB */
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
HANDSHAKE;
OPEN_NAME("test");
PREPARE("SELECT * from test", &stmt_id);
QUERY(stmt_id, &rows);
clientCloseRows(&rows);
return MUNIT_OK;
}
/******************************************************************************
*
* apply
*
******************************************************************************/
TEST(fsm, applyFail, setUp, tearDown, 0, NULL)
{
int rv;
struct command_frames c;
struct raft_buffer buf;
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
void *result = (void *)(uintptr_t)0xDEADBEEF;
/* Create a frames command without data. */
c.filename = "test";
c.tx_id = 0;
c.truncate = 0;
c.is_commit = 0;
c.frames.n_pages = 0;
c.frames.page_size = 4096;
c.frames.data = NULL;
rv = command__encode(COMMAND_FRAMES, &c, &buf);
/* Apply the command and expect it to fail. */
rv = fsm->apply(fsm, &buf, &result);
munit_assert_int(rv, !=, 0);
munit_assert_ptr_null(result);
raft_free(buf.base);
return MUNIT_OK;
}
TEST(fsm, applyUnknownTypeFail, setUp, tearDown, 0, NULL)
{
int rv;
struct command_frames c;
struct raft_buffer buf;
struct fixture *f = data;
struct raft_fsm *fsm = &f->servers[0].dqlite->raft_fsm;
void *result = (void *)(uintptr_t)0xDEADBEEF;
/* Create a frames command without data. */
c.filename = "test";
c.tx_id = 0;
c.truncate = 0;
c.is_commit = 0;
c.frames.n_pages = 0;
c.frames.page_size = 4096;
c.frames.data = NULL;
rv = command__encode(COMMAND_FRAMES, &c, &buf);
/* Command type does not exist. */
((uint8_t *)(buf.base))[1] = COMMAND_CHECKPOINT + 8;
/* Apply the command and expect it to fail. */
rv = fsm->apply(fsm, &buf, &result);
munit_assert_int(rv, ==, DQLITE_PROTO);
munit_assert_ptr_null(result);
raft_free(buf.base);
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_membership.c 0000664 0000000 0000000 00000023745 14512203222 0021725 0 ustar 00root root 0000000 0000000 #include "../../src/client/protocol.h"
#include "../../src/server.h"
#include "../lib/client.h"
#include "../lib/endpoint.h"
#include "../lib/fs.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/server.h"
#include "../lib/sqlite.h"
#include "../lib/util.h"
/******************************************************************************
*
* Fixture
*
******************************************************************************/
#define N_SERVERS 3
#define FIXTURE \
struct test_server servers[N_SERVERS]; \
struct client_proto *client; \
struct rows rows;
#define SETUP \
unsigned i_; \
test_heap_setup(params, user_data); \
test_sqlite_setup(params); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_setup(server, i_ + 1, params); \
} \
test_server_network(f->servers, N_SERVERS); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_start(server, params); \
} \
SELECT(1)
#define TEAR_DOWN \
unsigned i_; \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
test_server_tear_down(&f->servers[i_]); \
} \
test_sqlite_tear_down(); \
test_heap_tear_down(data)
/******************************************************************************
*
* Helper macros.
*
******************************************************************************/
/* Use the client connected to the server with the given ID. */
#define SELECT(ID) f->client = test_server_client(&f->servers[ID - 1])
/******************************************************************************
*
* join
*
******************************************************************************/
static char *bools[] = {"0", "1", NULL};
static MunitParameterEnum membership_params[] = {
{"disk_mode", bools},
{NULL, NULL},
};
SUITE(membership)
struct fixture
{
FIXTURE;
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
SETUP;
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
TEAR_DOWN;
free(f);
}
TEST(membership, join, setUp, tearDown, 0, membership_params)
{
struct fixture *f = data;
unsigned id = 2;
const char *address = "@2";
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
HANDSHAKE;
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* The table is visible from the new node */
TRANSFER(id, f->client);
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("SELECT * FROM test", &stmt_id);
/* TODO: fix the standalone test for remove */
REMOVE(1);
return MUNIT_OK;
}
struct id_last_applied
{
struct fixture *f;
int id;
raft_index last_applied;
};
static bool last_applied_cond(struct id_last_applied arg)
{
return arg.f->servers[arg.id].dqlite->raft.last_applied >=
arg.last_applied;
}
TEST(membership, transfer, setUp, tearDown, 0, membership_params)
{
struct fixture *f = data;
unsigned id = 2;
const char *address = "@2";
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
raft_index last_applied;
struct client_proto c_transfer; /* Client used for transfer requests */
struct id_last_applied await_arg;
HANDSHAKE;
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Transfer leadership and wait until first leader has applied a new
* entry replicated from the new leader. */
test_server_client_connect(&f->servers[0], &c_transfer);
HANDSHAKE_C(&c_transfer);
TRANSFER(2, &c_transfer);
test_server_client_close(&f->servers[0], &c_transfer);
last_applied = f->servers[0].dqlite->raft.last_applied;
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
await_arg.f = f;
await_arg.id = 0;
await_arg.last_applied = last_applied + 1;
AWAIT_TRUE(last_applied_cond, await_arg, 2);
return MUNIT_OK;
}
/* Transfer leadership away from a member that has a pending transaction */
TEST(membership,
transferPendingTransaction,
setUp,
tearDown,
0,
membership_params)
{
struct fixture *f = data;
unsigned id = 2;
const char *address = "@2";
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
raft_index last_applied;
struct client_proto c_transfer; /* Client used for transfer requests */
struct id_last_applied await_arg;
HANDSHAKE;
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Pending transaction */
PREPARE("BEGIN", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("SELECT * FROM test", &stmt_id);
QUERY(stmt_id, &f->rows);
clientCloseRows(&f->rows);
/* Transfer leadership and wait until first leader has applied a new
* entry replicated from the new leader. */
test_server_client_connect(&f->servers[0], &c_transfer);
HANDSHAKE_C(&c_transfer);
TRANSFER(2, &c_transfer);
test_server_client_close(&f->servers[0], &c_transfer);
last_applied = f->servers[0].dqlite->raft.last_applied;
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("INSERT INTO test(n) VALUES(2)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
await_arg.f = f;
await_arg.id = 0;
await_arg.last_applied = last_applied + 1;
AWAIT_TRUE(last_applied_cond, await_arg, 2);
return MUNIT_OK;
}
struct fixture_id
{
struct fixture *f;
int id;
};
static bool transfer_started_cond(struct fixture_id arg)
{
return arg.f->servers[arg.id].dqlite->raft.transfer != NULL;
}
/* Transfer leadership away from a member and immediately try to EXEC a
* prepared SQL statement that needs a barrier */
TEST(membership, transferAndSqlExecWithBarrier, setUp, tearDown, 0, NULL)
{
int rv;
struct fixture *f = data;
unsigned id = 2;
const char *address = "@2";
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
struct client_proto c_transfer; /* Client used for transfer requests */
struct fixture_id arg;
HANDSHAKE;
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
/* Iniate transfer of leadership. This will cause a raft_barrier
* failure while the node is technically still the leader, so the
* gateway functionality that checks for leadership still succeeds. */
test_server_client_connect(&f->servers[0], &c_transfer);
HANDSHAKE_C(&c_transfer);
rv = clientSendTransfer(&c_transfer, 2, NULL);
munit_assert_int(rv, ==, 0);
/* Wait until transfer is started by raft so the barrier can fail. */
arg.f = f;
arg.id = 0;
AWAIT_TRUE(transfer_started_cond, arg, 2);
/* Force a barrier.
* TODO this is hacky, but I can't seem to hit the codepath otherwise */
f->servers[0].dqlite->raft.last_applied = 0;
rv = clientSendExec(f->client, stmt_id, NULL, 0, NULL);
munit_assert_int(rv, ==, 0);
rv = clientRecvResult(f->client, &last_insert_id, &rows_affected, NULL);
munit_assert_int(rv, ==, DQLITE_CLIENT_PROTO_ERROR);
test_server_client_close(&f->servers[1], &c_transfer);
return MUNIT_OK;
}
/* Transfer leadership back and forth from a member that has a pending
* transaction */
TEST(membership,
transferTwicePendingTransaction,
setUp,
tearDown,
0,
membership_params)
{
struct fixture *f = data;
unsigned id = 2;
const char *address = "@2";
uint32_t stmt_id;
uint64_t last_insert_id;
uint64_t rows_affected;
raft_index last_applied;
struct client_proto c_transfer; /* Client used for transfer requests */
struct id_last_applied await_arg;
HANDSHAKE;
ADD(id, address);
ASSIGN(id, DQLITE_VOTER);
OPEN;
PREPARE("CREATE TABLE test (n INT)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("INSERT INTO test(n) VALUES(1)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
/* Pending transaction */
PREPARE("BEGIN", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
PREPARE("SELECT * FROM test", &stmt_id);
QUERY(stmt_id, &f->rows);
clientCloseRows(&f->rows);
/* Transfer leadership and wait until first leader has applied a new
* entry replicated from the new leader. */
test_server_client_connect(&f->servers[0], &c_transfer);
HANDSHAKE_C(&c_transfer);
TRANSFER(2, &c_transfer);
test_server_client_close(&f->servers[0], &c_transfer);
last_applied = f->servers[0].dqlite->raft.last_applied;
SELECT(2);
HANDSHAKE;
OPEN;
PREPARE("INSERT INTO test(n) VALUES(2)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
await_arg.f = f;
await_arg.id = 0;
await_arg.last_applied = last_applied + 1;
AWAIT_TRUE(last_applied_cond, await_arg, 2);
/* Transfer leadership back to original node, reconnect the client and
* ensure queries can be executed. */
test_server_client_connect(&f->servers[1], &c_transfer);
HANDSHAKE_C(&c_transfer);
TRANSFER(1, &c_transfer);
test_server_client_close(&f->servers[1], &c_transfer);
last_applied = f->servers[1].dqlite->raft.last_applied;
test_server_client_reconnect(&f->servers[0], &f->servers[0].client);
SELECT(1);
HANDSHAKE;
OPEN;
PREPARE("INSERT INTO test(n) VALUES(3)", &stmt_id);
EXEC(stmt_id, &last_insert_id, &rows_affected);
await_arg.id = 1;
AWAIT_TRUE(last_applied_cond, await_arg, 2);
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_node.c 0000664 0000000 0000000 00000027230 14512203222 0020510 0 ustar 00root root 0000000 0000000 #include "../lib/fs.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/sqlite.h"
#include "../../include/dqlite.h"
#include "../../src/protocol.h"
#include "../../src/utils.h"
/******************************************************************************
*
* Fixture
*
******************************************************************************/
static char *bools[] = {"0", "1", NULL};
static MunitParameterEnum node_params[] = {
{"disk_mode", bools},
{NULL, NULL},
};
struct fixture
{
char *dir; /* Data directory. */
dqlite_node *node; /* Node instance. */
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
int rv;
test_heap_setup(params, user_data);
test_sqlite_setup(params);
f->dir = test_dir_setup();
rv = dqlite_node_create(1, "1", f->dir, &f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_bind_address(f->node, "@123");
munit_assert_int(rv, ==, 0);
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
bool disk_mode = (bool)atoi(disk_mode_param);
if (disk_mode) {
rv = dqlite_node_enable_disk_mode(f->node);
munit_assert_int(rv, ==, 0);
}
}
return f;
}
static void *setUpInet(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
int rv;
test_heap_setup(params, user_data);
test_sqlite_setup(params);
f->dir = test_dir_setup();
rv = dqlite_node_create(1, "1", f->dir, &f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_bind_address(f->node, "127.0.0.1:9001");
munit_assert_int(rv, ==, 0);
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
bool disk_mode = (bool)atoi(disk_mode_param);
if (disk_mode) {
rv = dqlite_node_enable_disk_mode(f->node);
munit_assert_int(rv, ==, 0);
}
}
return f;
}
/* Tests if node starts/stops successfully and also performs some memory cleanup
*/
static void startStopNode(struct fixture *f)
{
munit_assert_int(dqlite_node_start(f->node), ==, 0);
munit_assert_int(dqlite_node_stop(f->node), ==, 0);
}
/* Recovery only works if a node has been started regularly for a first time. */
static void *setUpForRecovery(const MunitParameter params[], void *user_data)
{
int rv;
struct fixture *f = setUp(params, user_data);
startStopNode(f);
dqlite_node_destroy(f->node);
rv = dqlite_node_create(1, "1", f->dir, &f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_bind_address(f->node, "@123");
munit_assert_int(rv, ==, 0);
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
bool disk_mode = (bool)atoi(disk_mode_param);
if (disk_mode) {
rv = dqlite_node_enable_disk_mode(f->node);
munit_assert_int(rv, ==, 0);
}
}
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
dqlite_node_destroy(f->node);
test_dir_tear_down(f->dir);
test_sqlite_tear_down();
test_heap_tear_down(data);
free(f);
}
SUITE(node);
/******************************************************************************
*
* dqlite_node_start
*
******************************************************************************/
TEST(node, start, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(node, startInet, setUpInet, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(node, snapshotParams, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_snapshot_params(f->node, 2048, 2048);
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, snapshotParamsRunning, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_snapshot_params(f->node, 2048, 2048);
munit_assert_int(rv, !=, 0);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(node, snapshotParamsTrailingTooSmall, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_snapshot_params(f->node, 2, 2);
munit_assert_int(rv, !=, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node,
snapshotParamsThresholdLargerThanTrailing,
setUp,
tearDown,
0,
node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_snapshot_params(f->node, 2049, 2048);
munit_assert_int(rv, !=, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, networkLatency, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_network_latency(f->node, 3600000000000ULL);
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, networkLatencyRunning, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_network_latency(f->node, 3600000000000ULL);
munit_assert_int(rv, ==, DQLITE_MISUSE);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(node, networkLatencyTooLarge, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_network_latency(f->node, 3600000000000ULL + 1ULL);
munit_assert_int(rv, ==, DQLITE_MISUSE);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, networkLatencyMs, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_network_latency_ms(f->node, 5);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_network_latency_ms(f->node, (3600U * 1000U));
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, networkLatencyMsRunning, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_network_latency_ms(f->node, 2);
munit_assert_int(rv, ==, DQLITE_MISUSE);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(node, networkLatencyMsTooSmall, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_network_latency_ms(f->node, 0);
munit_assert_int(rv, ==, DQLITE_MISUSE);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, networkLatencyMsTooLarge, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_network_latency_ms(f->node, (3600U * 1000U) + 1);
munit_assert_int(rv, ==, DQLITE_MISUSE);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, blockSize, setUp, tearDown, 0, NULL)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_set_block_size(f->node, 0);
munit_assert_int(rv, ==, DQLITE_ERROR);
rv = dqlite_node_set_block_size(f->node, 1);
munit_assert_int(rv, ==, DQLITE_ERROR);
rv = dqlite_node_set_block_size(f->node, 511);
munit_assert_int(rv, ==, DQLITE_ERROR);
rv = dqlite_node_set_block_size(f->node, 1024 * 512);
munit_assert_int(rv, ==, DQLITE_ERROR);
rv = dqlite_node_set_block_size(f->node, 64 * 1024);
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, blockSizeRunning, setUp, tearDown, 0, NULL)
{
struct fixture *f = data;
int rv;
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_block_size(f->node, 64 * 1024);
munit_assert_int(rv, ==, DQLITE_MISUSE);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite_node_recover
*
******************************************************************************/
TEST(node, recover, setUpForRecovery, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
/* Setup the infos structs */
static struct dqlite_node_info infos[2] = {0};
infos[0].id = 1;
infos[0].address = "1";
infos[1].id = 2;
infos[1].address = "2";
rv = dqlite_node_recover(f->node, infos, 2);
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, recoverExt, setUpForRecovery, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
/* Setup the infos structs */
static struct dqlite_node_info_ext infos[2] = {0};
infos[0].size = sizeof(*infos);
infos[0].id = dqlite_generate_node_id("1");
infos[0].address = PTR_TO_UINT64("1");
infos[0].dqlite_role = DQLITE_VOTER;
infos[1].size = sizeof(*infos);
infos[1].id = dqlite_generate_node_id("2");
;
infos[1].address = PTR_TO_UINT64("2");
infos[1].dqlite_role = DQLITE_SPARE;
rv = dqlite_node_recover_ext(f->node, infos, 2);
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, recoverExtUnaligned, setUpForRecovery, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
/* Setup the infos structs */
static struct dqlite_node_info_ext infos[1] = {0};
infos[0].size = sizeof(*infos) + 1; /* Unaligned */
infos[0].id = 1;
infos[0].address = PTR_TO_UINT64("1");
infos[0].dqlite_role = DQLITE_VOTER;
rv = dqlite_node_recover_ext(f->node, infos, 1);
munit_assert_int(rv, ==, DQLITE_MISUSE);
startStopNode(f);
return MUNIT_OK;
}
TEST(node, recoverExtTooSmall, setUpForRecovery, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
/* Setup the infos structs */
static struct dqlite_node_info_ext infos[1] = {0};
infos[0].size = DQLITE_NODE_INFO_EXT_SZ_ORIG - 1;
infos[0].id = 1;
infos[0].address = PTR_TO_UINT64("1");
infos[0].dqlite_role = DQLITE_VOTER;
rv = dqlite_node_recover_ext(f->node, infos, 1);
munit_assert_int(rv, ==, DQLITE_MISUSE);
startStopNode(f);
return MUNIT_OK;
}
struct dqlite_node_info_ext_new
{
struct dqlite_node_info_ext orig;
uint64_t new1;
uint64_t new2;
};
TEST(node, recoverExtNewFields, setUpForRecovery, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
/* Setup the infos structs */
static struct dqlite_node_info_ext_new infos[1] = {0};
infos[0].orig.size = sizeof(*infos);
infos[0].orig.id = 1;
infos[0].orig.address = PTR_TO_UINT64("1");
infos[0].orig.dqlite_role = DQLITE_VOTER;
infos[0].new1 = 0;
infos[0].new2 = 0;
rv = dqlite_node_recover_ext(f->node,
(struct dqlite_node_info_ext *)infos, 1);
munit_assert_int(rv, ==, 0);
startStopNode(f);
return MUNIT_OK;
}
TEST(node,
recoverExtNewFieldsNotZero,
setUpForRecovery,
tearDown,
0,
node_params)
{
struct fixture *f = data;
int rv;
/* Setup the infos structs */
static struct dqlite_node_info_ext_new infos[1] = {0};
infos[0].orig.size = sizeof(*infos);
infos[0].orig.id = 1;
infos[0].orig.address = PTR_TO_UINT64("1");
infos[0].orig.dqlite_role = DQLITE_VOTER;
infos[0].new1 = 0;
infos[0].new2 = 1; /* This will cause a failure */
rv = dqlite_node_recover_ext(f->node,
(struct dqlite_node_info_ext *)infos, 1);
munit_assert_int(rv, ==, DQLITE_MISUSE);
startStopNode(f);
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite_node_errmsg
*
******************************************************************************/
TEST(node, errMsgNodeNull, NULL, NULL, 0, NULL)
{
munit_assert_string_equal(dqlite_node_errmsg(NULL), "node is NULL");
return MUNIT_OK;
}
TEST(node, errMsg, setUp, tearDown, 0, node_params)
{
struct fixture *f = data;
int rv;
munit_assert_string_equal(dqlite_node_errmsg(f->node), "");
rv = dqlite_node_start(f->node);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_stop(f->node);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_role_management.c 0000664 0000000 0000000 00000007333 14512203222 0022722 0 ustar 00root root 0000000 0000000 #include "../../src/client/protocol.h"
#include "../../src/server.h"
#include "../lib/client.h"
#include "../lib/endpoint.h"
#include "../lib/fs.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/server.h"
#include "../lib/sqlite.h"
#include "../lib/util.h"
#define N_SERVERS 5
#define FIXTURE \
struct test_server servers[N_SERVERS]; \
struct client_proto *client; \
struct rows rows;
#define SETUP \
unsigned i_; \
test_heap_setup(params, user_data); \
test_sqlite_setup(params); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_setup(server, i_ + 1, params); \
} \
test_server_network(f->servers, N_SERVERS); \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
struct test_server *server = &f->servers[i_]; \
test_server_start(server, params); \
} \
SELECT(1)
#define TEAR_DOWN \
unsigned i_; \
for (i_ = 0; i_ < N_SERVERS; i_++) { \
tracef("test_server_tear_down(%u)", i_); \
test_server_tear_down(&f->servers[i_]); \
} \
test_sqlite_tear_down(); \
test_heap_tear_down(data)
#define SELECT(ID) f->client = test_server_client(&f->servers[ID - 1])
#define TRIES 5
static char *trueonly[] = {"1", NULL};
static char *threeonly[] = {"3", NULL};
static MunitParameterEnum role_management_params[] = {
{"role_management", trueonly},
{"target_voters", threeonly},
{"target_standbys", threeonly},
{NULL, NULL},
};
SUITE(role_management)
struct fixture
{
FIXTURE;
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
SETUP;
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
TEAR_DOWN;
free(f);
}
static bool hasRole(struct fixture *f, dqlite_node_id id, int role)
{
struct client_node_info *servers;
uint64_t n_servers;
struct client_context context;
unsigned i;
bool ret = false;
int rv;
clientContextMillis(&context, 5000);
rv = clientSendCluster(f->client, &context);
munit_assert_int(rv, ==, 0);
rv = clientRecvServers(f->client, &servers, &n_servers, &context);
munit_assert_int(rv, ==, 0);
for (i = 0; i < n_servers; i += 1) {
if (servers[i].id == id) {
ret = servers[i].role == role;
break;
}
}
for (i = 0; i < n_servers; i += 1) {
free(servers[i].addr);
}
free(servers);
return ret;
}
TEST(role_management, promote, setUp, tearDown, 0, role_management_params)
{
struct fixture *f = data;
unsigned id = 2;
const char *address = "@2";
int tries;
HANDSHAKE;
id = 2;
address = "@2";
ADD(id, address);
for (tries = 0; tries < TRIES && !hasRole(f, 2, DQLITE_VOTER);
tries += 1) {
sleep(1);
}
if (tries == TRIES) {
return MUNIT_FAIL;
};
id = 3;
address = "@3";
ADD(id, address);
for (tries = 0; tries < TRIES && !hasRole(f, 3, DQLITE_VOTER);
tries += 1) {
sleep(1);
}
if (tries == TRIES) {
return MUNIT_FAIL;
};
id = 4;
address = "@4";
ADD(id, address);
for (tries = 0; tries < TRIES && !hasRole(f, 4, DQLITE_STANDBY);
tries += 1) {
sleep(1);
}
if (tries == TRIES) {
return MUNIT_FAIL;
};
id = 5;
address = "@5";
ADD(id, address);
for (tries = 0; tries < TRIES && !hasRole(f, 5, DQLITE_STANDBY);
tries += 1) {
sleep(1);
}
if (tries == TRIES) {
return MUNIT_FAIL;
};
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_server.c 0000664 0000000 0000000 00000014746 14512203222 0021101 0 ustar 00root root 0000000 0000000 #include "../../include/dqlite.h"
#include "../../src/server.h"
#include "../lib/fs.h"
#include "../lib/munit.h"
#include "../lib/runner.h"
#include
#include
SUITE(server);
#define N_SERVERS 3
struct fixture
{
char *dirs[N_SERVERS];
dqlite_server *servers[N_SERVERS];
};
static void *setup(const MunitParameter params[], void *user_data)
{
(void)params;
(void)user_data;
struct fixture *f = munit_malloc(sizeof *f);
unsigned i;
int rv;
for (i = 0; i < N_SERVERS; i += 1) {
f->dirs[i] = test_dir_setup();
rv = dqlite_server_create(f->dirs[i], &f->servers[i]);
munit_assert_int(rv, ==, 0);
}
return f;
}
static void teardown(void *data)
{
struct fixture *f = data;
unsigned i;
for (i = 0; i < N_SERVERS; i += 1) {
dqlite_server_destroy(f->servers[i]);
test_dir_tear_down(f->dirs[i]);
}
free(f);
}
#define PREPARE_FILE(i, name, ...) \
do { \
char path[100]; \
snprintf(path, 100, "%s/%s", f->dirs[i], name); \
FILE *fp = fopen(path, "w+"); \
fprintf(fp, __VA_ARGS__); \
fclose(fp); \
} while (0)
#define NODE(x) x
#define NODE0_ID "3297041220608546238"
void start_each_server(struct fixture *f)
{
const char *addrs[] = {"127.0.0.1:8880", "127.0.0.1:8881"};
int rv;
rv = dqlite_server_set_address(f->servers[0], "127.0.0.1:8880");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_bootstrap(f->servers[0], true);
munit_assert_int(rv, ==, 0);
f->servers[0]->refresh_period = 100;
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_address(f->servers[1], "127.0.0.1:8881");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_join(f->servers[1], addrs, 1);
munit_assert_int(rv, ==, 0);
f->servers[1]->refresh_period = 100;
rv = dqlite_server_start(f->servers[1]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_address(f->servers[2], "127.0.0.1:8882");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_join(f->servers[2], addrs, 2);
munit_assert_int(rv, ==, 0);
f->servers[2]->refresh_period = 100;
rv = dqlite_server_start(f->servers[2]);
munit_assert_int(rv, ==, 0);
}
void stop_each_server(struct fixture *f)
{
int rv;
rv = dqlite_server_stop(f->servers[2]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_stop(f->servers[1]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_stop(f->servers[0]);
munit_assert_int(rv, ==, 0);
}
TEST(server, restart_follower, setup, teardown, 0, NULL)
{
struct fixture *f = data;
struct timespec ts = {0};
int rv;
/* Between operations we sleep for 200 milliseconds, twice
* the configured refresh period, so that the refresh task
* has a chance to be triggered. */
ts.tv_nsec = 200 * 1000 * 1000;
start_each_server(f);
nanosleep(&ts, NULL);
rv = dqlite_server_stop(f->servers[1]);
munit_assert_int(rv, ==, 0);
nanosleep(&ts, NULL);
rv = dqlite_server_start(f->servers[1]);
munit_assert_int(rv, ==, 0);
nanosleep(&ts, NULL);
stop_each_server(f);
return MUNIT_OK;
}
TEST(server, restart_leader, setup, teardown, 0, NULL)
{
struct fixture *f = data;
struct timespec ts = {0};
int rv;
/* Between operations we sleep for 200 milliseconds, twice
* the configured refresh period, so that the refresh task
* has a chance to be triggered. */
ts.tv_nsec = 200 * 1000 * 1000;
start_each_server(f);
nanosleep(&ts, NULL);
rv = dqlite_server_stop(f->servers[0]);
munit_assert_int(rv, ==, 0);
nanosleep(&ts, NULL);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, ==, 0);
nanosleep(&ts, NULL);
stop_each_server(f);
return MUNIT_OK;
}
TEST(server, bad_info_file, setup, teardown, 0, NULL)
{
struct fixture *f = data;
int rv;
PREPARE_FILE(NODE(0), "server-info", "blah");
rv = dqlite_server_set_address(f->servers[0], "127.0.0.1:8880");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_bootstrap(f->servers[0], true);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, !=, 0);
return MUNIT_OK;
}
TEST(server, bad_node_store, setup, teardown, 0, NULL)
{
struct fixture *f = data;
int rv;
PREPARE_FILE(NODE(0), "server-info",
"v1\n127.0.0.1:8880\n" NODE0_ID "\n");
PREPARE_FILE(NODE(0), "node-store", "blah");
rv = dqlite_server_set_address(f->servers[0], "127.0.0.1:8880");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_bootstrap(f->servers[0], true);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, !=, 0);
return MUNIT_OK;
}
TEST(server, node_store_but_no_info, setup, teardown, 0, NULL)
{
struct fixture *f = data;
int rv;
PREPARE_FILE(NODE(0), "node-store",
"v1\n127.0.0.1:8880\n" NODE0_ID "\nvoter\n");
rv = dqlite_server_set_address(f->servers[0], "127.0.0.1:8880");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_bootstrap(f->servers[0], true);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, !=, 0);
return MUNIT_OK;
}
TEST(server, missing_bootstrap, setup, teardown, 0, NULL)
{
struct fixture *f = data;
const char *addrs[] = {"127.0.0.1:8880"};
int rv;
rv = dqlite_server_set_address(f->servers[1], "127.0.0.1:8881");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_join(f->servers[1], addrs, 1);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[1]);
munit_assert_int(rv, !=, 0);
return MUNIT_OK;
}
TEST(server, start_twice, setup, teardown, 0, NULL)
{
struct fixture *f = data;
int rv;
rv = dqlite_server_set_address(f->servers[0], "127.0.0.1:8880");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_bootstrap(f->servers[0], true);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, !=, 0);
rv = dqlite_server_stop(f->servers[0]);
munit_assert_int(rv, ==, 0);
return MUNIT_OK;
}
TEST(server, stop_twice, setup, teardown, 0, NULL)
{
struct fixture *f = data;
int rv;
rv = dqlite_server_set_address(f->servers[0], "127.0.0.1:8880");
munit_assert_int(rv, ==, 0);
rv = dqlite_server_set_auto_bootstrap(f->servers[0], true);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_start(f->servers[0]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_stop(f->servers[0]);
munit_assert_int(rv, ==, 0);
rv = dqlite_server_stop(f->servers[0]);
munit_assert_int(rv, !=, 0);
return MUNIT_OK;
}
dqlite-1.16.0/test/integration/test_vfs.c 0000664 0000000 0000000 00000124256 14512203222 0020367 0 ustar 00root root 0000000 0000000 #include
#include
#include "../lib/fs.h"
#include "../lib/heap.h"
#include "../lib/runner.h"
#include "../lib/sqlite.h"
#include "../../include/dqlite.h"
#include
SUITE(vfs);
#define N_VFS 2
static char *bools[] = {"0", "1", NULL};
#define SNAPSHOT_SHALLOW_PARAM "snapshot-shallow-param"
static MunitParameterEnum vfs_params[] = {
{SNAPSHOT_SHALLOW_PARAM, bools},
{"disk_mode", bools},
{NULL, NULL},
};
struct fixture
{
struct sqlite3_vfs vfs[N_VFS]; /* A "cluster" of VFS objects. */
char names[8][N_VFS]; /* Registration names */
char *dirs[N_VFS]; /* For the disk vfs. */
};
static void *setUp(const MunitParameter params[], void *user_data)
{
struct fixture *f = munit_malloc(sizeof *f);
unsigned i;
int rv;
SETUP_HEAP;
SETUP_SQLITE;
for (i = 0; i < N_VFS; i++) {
f->dirs[i] = NULL;
sprintf(f->names[i], "%u", i + 1);
rv = dqlite_vfs_init(&f->vfs[i], f->names[i]);
munit_assert_int(rv, ==, 0);
const char *disk_mode_param =
munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
bool disk_mode = (bool)atoi(disk_mode_param);
if (disk_mode) {
f->dirs[i] = test_dir_setup();
rv = dqlite_vfs_enable_disk(&f->vfs[i]);
munit_assert_int(rv, ==, 0);
}
}
rv = sqlite3_vfs_register(&f->vfs[i], 0);
munit_assert_int(rv, ==, 0);
}
return f;
}
static void tearDown(void *data)
{
struct fixture *f = data;
unsigned i;
int rv;
for (i = 0; i < N_VFS; i++) {
rv = sqlite3_vfs_unregister(&f->vfs[i]);
munit_assert_int(rv, ==, 0);
dqlite_vfs_close(&f->vfs[i]);
test_dir_tear_down(f->dirs[i]);
}
TEAR_DOWN_SQLITE;
TEAR_DOWN_HEAP;
free(f);
}
extern unsigned dq_sqlite_pending_byte;
static void tearDownRestorePendingByte(void *data)
{
sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, 0x40000000);
dq_sqlite_pending_byte = 0x40000000;
tearDown(data);
}
#define PAGE_SIZE 512
#define PRAGMA(DB, COMMAND) \
_rv = sqlite3_exec(DB, "PRAGMA " COMMAND, NULL, NULL, NULL); \
if (_rv != SQLITE_OK) { \
munit_errorf("PRAGMA " COMMAND ": %s (%d)", \
sqlite3_errmsg(DB), _rv); \
}
#define VFS_PATH_SZ 512
static void vfsFillDbPath(struct fixture *f,
char *vfs,
char *filename,
char *path)
{
int rv;
char *dir = f->dirs[atoi(vfs) - 1];
if (dir != NULL) {
rv = snprintf(path, VFS_PATH_SZ, "%s/%s", dir, filename);
} else {
rv = snprintf(path, VFS_PATH_SZ, "%s", filename);
}
munit_assert_int(rv, >, 0);
munit_assert_int(rv, <, VFS_PATH_SZ);
}
/* Open a new database connection on the given VFS. */
#define OPEN(VFS, DB) \
do { \
int _flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; \
int _rv; \
char path[VFS_PATH_SZ]; \
struct fixture *f = data; \
vfsFillDbPath(f, VFS, "test.db", path); \
_rv = sqlite3_open_v2(path, &DB, _flags, VFS); \
munit_assert_int(_rv, ==, SQLITE_OK); \
_rv = sqlite3_extended_result_codes(DB, 1); \
munit_assert_int(_rv, ==, SQLITE_OK); \
PRAGMA(DB, "page_size=512"); \
PRAGMA(DB, "synchronous=OFF"); \
PRAGMA(DB, "journal_mode=WAL"); \
PRAGMA(DB, "cache_size=1"); \
_rv = sqlite3_db_config(DB, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, \
1, NULL); \
munit_assert_int(_rv, ==, SQLITE_OK); \
} while (0)
/* Close a database connection. */
#define CLOSE(DB) \
do { \
int _rv; \
_rv = sqlite3_close(DB); \
munit_assert_int(_rv, ==, SQLITE_OK); \
} while (0)
/* Prepare a statement. */
#define PREPARE(DB, STMT, SQL) \
do { \
int _rv; \
_rv = sqlite3_prepare_v2(DB, SQL, -1, &STMT, NULL); \
if (_rv != SQLITE_OK) { \
munit_errorf("prepare '%s': %s (%d)", SQL, \
sqlite3_errmsg(DB), _rv); \
} \
} while (0)
/* Reset a statement. */
#define RESET(STMT, RV) \
do { \
int _rv; \
_rv = sqlite3_reset(STMT); \
munit_assert_int(_rv, ==, RV); \
} while (0)
/* Finalize a statement. */
#define FINALIZE(STMT) \
do { \
int _rv; \
_rv = sqlite3_finalize(STMT); \
munit_assert_int(_rv, ==, SQLITE_OK); \
} while (0)
/* Shortcut for PREPARE, STEP, FINALIZE. */
#define EXEC(DB, SQL) \
do { \
sqlite3_stmt *_stmt; \
PREPARE(DB, _stmt, SQL); \
STEP(_stmt, SQLITE_DONE); \
FINALIZE(_stmt); \
} while (0)
/* Step through a statement and assert that the given value is returned. */
#define STEP(STMT, RV) \
do { \
int _rv; \
_rv = sqlite3_step(STMT); \
if (_rv != RV) { \
munit_errorf("step: %s (%d)", \
sqlite3_errmsg(sqlite3_db_handle(STMT)), \
_rv); \
} \
} while (0)
/* Hold WAL replication information about a single transaction. */
struct tx
{
unsigned n;
unsigned long *page_numbers;
void *frames;
};
/* Poll the given VFS object and serialize the transaction data into the given
* tx object. */
#define POLL(VFS, TX) \
do { \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
dqlite_vfs_frame *_frames; \
unsigned _i; \
int _rv; \
memset(&TX, 0, sizeof TX); \
char path[VFS_PATH_SZ]; \
struct fixture *f = data; \
vfsFillDbPath(f, VFS, "test.db", path); \
_rv = dqlite_vfs_poll(vfs, path, &_frames, &TX.n); \
munit_assert_int(_rv, ==, 0); \
if (_frames != NULL) { \
TX.page_numbers = \
munit_malloc(sizeof *TX.page_numbers * TX.n); \
TX.frames = munit_malloc(PAGE_SIZE * TX.n); \
for (_i = 0; _i < TX.n; _i++) { \
dqlite_vfs_frame *_frame = &_frames[_i]; \
TX.page_numbers[_i] = _frame->page_number; \
memcpy(TX.frames + _i * PAGE_SIZE, \
_frame->data, PAGE_SIZE); \
sqlite3_free(_frame->data); \
} \
sqlite3_free(_frames); \
} \
} while (0)
/* Apply WAL frames to the given VFS. */
#define APPLY(VFS, TX) \
do { \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
int _rv; \
char path[VFS_PATH_SZ]; \
struct fixture *f = data; \
vfsFillDbPath(f, VFS, "test.db", path); \
_rv = dqlite_vfs_apply(vfs, path, TX.n, TX.page_numbers, \
TX.frames); \
munit_assert_int(_rv, ==, 0); \
} while (0)
/* Abort a transaction on the given VFS. */
#define ABORT(VFS) \
do { \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
int _rv; \
char path[VFS_PATH_SZ]; \
struct fixture *f = data; \
vfsFillDbPath(f, VFS, "test.db", path); \
_rv = dqlite_vfs_abort(vfs, path); \
munit_assert_int(_rv, ==, 0); \
} while (0)
/* Release all memory used by a struct tx object. */
#define DONE(TX) \
do { \
free(TX.frames); \
free(TX.page_numbers); \
} while (0)
/* Peform a full checkpoint on the given database. */
#define CHECKPOINT(DB) \
do { \
int _size; \
int _ckpt; \
int _rv; \
_rv = sqlite3_wal_checkpoint_v2( \
DB, "main", SQLITE_CHECKPOINT_TRUNCATE, &_size, &_ckpt); \
if (_rv != SQLITE_OK) { \
munit_errorf("checkpoint: %s (%d)", \
sqlite3_errmsg(DB), _rv); \
} \
munit_assert_int(_size, ==, 0); \
munit_assert_int(_ckpt, ==, 0); \
} while (0)
/* Perform a full checkpoint on a fresh connection, mimicking dqlite's
* checkpoint behavior. */
#define CHECKPOINT_FRESH(VFS) \
do { \
sqlite3 *_db; \
OPEN(VFS, _db); \
CHECKPOINT(_db); \
CLOSE(_db); \
} while (0)
/* Attempt to perform a full checkpoint on the given database, but fail. */
#define CHECKPOINT_FAIL(DB, RV) \
do { \
int _size; \
int _ckpt; \
int _rv; \
_rv = sqlite3_wal_checkpoint_v2( \
DB, "main", SQLITE_CHECKPOINT_TRUNCATE, &_size, &_ckpt); \
munit_assert_int(_rv, ==, RV); \
} while (0)
struct snapshot
{
void *data;
size_t n;
size_t main_size;
size_t wal_size;
};
/* Copies n dqlite_buffers to a single dqlite buffer */
static struct dqlite_buffer n_bufs_to_buf(struct dqlite_buffer bufs[],
unsigned n)
{
uint8_t *cursor;
struct dqlite_buffer buf = {0};
/* Allocate a suitable buffer */
for (unsigned i = 0; i < n; ++i) {
buf.len += bufs[i].len;
tracef("buf.len %zu", buf.len);
}
buf.base = raft_malloc(buf.len);
munit_assert_ptr_not_null(buf.base);
/* Copy all data */
cursor = buf.base;
for (unsigned i = 0; i < n; ++i) {
memcpy(cursor, bufs[i].base, bufs[i].len);
cursor += bufs[i].len;
}
munit_assert_ullong((uintptr_t)(cursor - (uint8_t *)buf.base), ==,
buf.len);
return buf;
}
#define SNAPSHOT_DISK(VFS, SNAPSHOT) \
do { \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
int _rv; \
unsigned _n; \
struct dqlite_buffer *_bufs; \
struct dqlite_buffer _all_data; \
_n = 2; \
_bufs = sqlite3_malloc64(_n * sizeof(*_bufs)); \
char path[VFS_PATH_SZ]; \
struct fixture *f = data; \
vfsFillDbPath(f, VFS, "test.db", path); \
_rv = dqlite_vfs_snapshot_disk(vfs, path, _bufs, _n); \
munit_assert_int(_rv, ==, 0); \
_all_data = n_bufs_to_buf(_bufs, _n); \
/* Free WAL buffer after copy. */ \
SNAPSHOT.main_size = _bufs[0].len; \
SNAPSHOT.wal_size = _bufs[1].len; \
sqlite3_free(_bufs[1].base); \
munmap(_bufs[0].base, _bufs[0].len); \
sqlite3_free(_bufs); \
SNAPSHOT.data = _all_data.base; \
SNAPSHOT.n = _all_data.len; \
} while (0)
/* Take a snapshot of the database on the given VFS. */
#define SNAPSHOT_DEEP(VFS, SNAPSHOT) \
do { \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
int _rv; \
_rv = dqlite_vfs_snapshot(vfs, "test.db", &SNAPSHOT.data, \
&SNAPSHOT.n); \
munit_assert_int(_rv, ==, 0); \
} while (0)
/* Take a shallow snapshot of the database on the given VFS. */
#define SNAPSHOT_SHALLOW(VFS, SNAPSHOT) \
do { \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
int _rv; \
unsigned _n; \
unsigned _n_pages; \
struct dqlite_buffer *_bufs; \
struct dqlite_buffer _all_data; \
_rv = dqlite_vfs_num_pages(vfs, "test.db", &_n_pages); \
munit_assert_int(_rv, ==, 0); \
_n = _n_pages + 1; /* + 1 for WAL */ \
_bufs = sqlite3_malloc64(_n * sizeof(*_bufs)); \
_rv = dqlite_vfs_shallow_snapshot(vfs, "test.db", _bufs, _n); \
munit_assert_int(_rv, ==, 0); \
_all_data = n_bufs_to_buf(_bufs, _n); \
/* Free WAL buffer after copy. */ \
sqlite3_free(_bufs[_n - 1].base); \
sqlite3_free(_bufs); \
SNAPSHOT.data = _all_data.base; \
SNAPSHOT.n = _all_data.len; \
} while (0)
#define SNAPSHOT(VFS, SNAPSHOT) \
do { \
bool _shallow = false; \
bool _disk_mode = false; \
if (munit_parameters_get(params, SNAPSHOT_SHALLOW_PARAM) != \
NULL) { \
_shallow = atoi(munit_parameters_get( \
params, SNAPSHOT_SHALLOW_PARAM)); \
} \
if (munit_parameters_get(params, "disk_mode") != NULL) { \
_disk_mode = \
atoi(munit_parameters_get(params, "disk_mode")); \
} \
if (_shallow && !_disk_mode) { \
SNAPSHOT_SHALLOW(VFS, SNAPSHOT); \
} else if (!_shallow && !_disk_mode) { \
SNAPSHOT_DEEP(VFS, SNAPSHOT); \
} else { \
SNAPSHOT_DISK(VFS, SNAPSHOT); \
} \
} while (0)
/* Restore a snapshot onto the given VFS. */
#define RESTORE(VFS, SNAPSHOT) \
do { \
bool _disk_mode = false; \
if (munit_parameters_get(params, "disk_mode") != NULL) { \
_disk_mode = \
atoi(munit_parameters_get(params, "disk_mode")); \
} \
sqlite3_vfs *vfs = sqlite3_vfs_find(VFS); \
int _rv; \
char path[VFS_PATH_SZ]; \
struct fixture *f = data; \
vfsFillDbPath(f, VFS, "test.db", path); \
if (_disk_mode) { \
_rv = dqlite_vfs_restore_disk( \
vfs, path, SNAPSHOT.data, SNAPSHOT.main_size, \
SNAPSHOT.wal_size); \
} else { \
_rv = dqlite_vfs_restore(vfs, path, SNAPSHOT.data, \
SNAPSHOT.n); \
} \
munit_assert_int(_rv, ==, 0); \
} while (0)
/* Open and close a new connection using the dqlite VFS. */
TEST(vfs, open, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
OPEN("1", db);
CLOSE(db);
return MUNIT_OK;
}
/* New frames appended to the WAL file by a sqlite3_step() call that has
* triggered a write transactions are not immediately visible to other
* connections after sqlite3_step() has returned. */
TEST(vfs, writeTransactionNotImmediatelyVisible, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
int rv;
OPEN("1", db1);
EXEC(db1, "CREATE TABLE test(n INT)");
OPEN("1", db2);
rv = sqlite3_prepare_v2(db2, "SELECT * FROM test", -1, &stmt, NULL);
munit_assert_int(rv, ==, SQLITE_ERROR);
munit_assert_string_equal(sqlite3_errmsg(db2), "no such table: test");
CLOSE(db1);
CLOSE(db2);
return MUNIT_OK;
}
/* Invoking dqlite_vfs_poll() after a call to sqlite3_step() has triggered a
* write transaction returns the newly appended WAL frames. */
TEST(vfs, pollAfterWriteTransaction, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
unsigned i;
OPEN("1", db);
PREPARE(db, stmt, "CREATE TABLE test(n INT)");
STEP(stmt, SQLITE_DONE);
POLL("1", tx);
munit_assert_ptr_not_null(tx.frames);
munit_assert_int(tx.n, ==, 2);
for (i = 0; i < tx.n; i++) {
munit_assert_int(tx.page_numbers[i], ==, i + 1);
}
DONE(tx);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Invoking dqlite_vfs_poll() after a call to sqlite3_step() has triggered a
* write transaction sets a write lock on the WAL, so calls to sqlite3_step()
* from other connections return SQLITE_BUSY if they try to start a write
* transaction. */
TEST(vfs, pollAcquireWriteLock, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt1;
sqlite3_stmt *stmt2;
struct tx tx;
OPEN("1", db1);
OPEN("1", db2);
PREPARE(db1, stmt1, "CREATE TABLE test(n INT)");
PREPARE(db2, stmt2, "CREATE TABLE test2(n INT)");
STEP(stmt1, SQLITE_DONE);
POLL("1", tx);
STEP(stmt2, SQLITE_BUSY);
RESET(stmt2, SQLITE_BUSY);
FINALIZE(stmt1);
FINALIZE(stmt2);
CLOSE(db1);
CLOSE(db2);
DONE(tx);
return MUNIT_OK;
}
/* If the page cache limit is exceeded during a call to sqlite3_step() that has
* triggered a write transaction, some WAL frames will be written and then
* overwritten before the final commit. Only the final version of the frame is
* included in the set returned by dqlite_vfs_poll(). */
TEST(vfs, pollAfterPageStress, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
unsigned i;
char sql[64];
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "BEGIN");
for (i = 0; i < 163; i++) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
EXEC(db, sql);
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
}
for (i = 0; i < 163; i++) {
sprintf(sql, "UPDATE test SET n=%d WHERE n=%d", i, i + 1);
EXEC(db, sql);
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
}
EXEC(db, "COMMIT");
POLL("1", tx);
/* Five frames were replicated and the first frame actually contains a
* spill of the third page. */
munit_assert_int(tx.n, ==, 6);
munit_assert_int(tx.page_numbers[0], ==, 3);
munit_assert_int(tx.page_numbers[1], ==, 4);
munit_assert_int(tx.page_numbers[2], ==, 5);
munit_assert_int(tx.page_numbers[3], ==, 1);
munit_assert_int(tx.page_numbers[4], ==, 2);
APPLY("1", tx);
DONE(tx);
/* All records have been inserted. */
PREPARE(db, stmt, "SELECT * FROM test");
for (i = 0; i < 163; i++) {
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, i);
}
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Set the SQLite PENDING_BYTE at the start of the second page and make sure
* all data entry is successful.
*/
TEST(vfs, adaptPendingByte, setUp, tearDownRestorePendingByte, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
int i;
int n;
char sql[64];
/* Set the pending byte at the start of the second page */
const unsigned new_pending_byte = 512;
dq_sqlite_pending_byte = new_pending_byte;
sqlite3_test_control(SQLITE_TESTCTRL_PENDING_BYTE, new_pending_byte);
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "BEGIN");
n = 65536;
for (i = 0; i < n; i++) {
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i);
EXEC(db, sql);
POLL("1", tx);
munit_assert_uint(tx.n, ==, 0);
}
EXEC(db, "COMMIT");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
/* All records have been inserted. */
PREPARE(db, stmt, "SELECT * FROM test");
for (i = 0; i < n; i++) {
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, i);
}
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction
* was triggered by a call to sqlite3_step(), then perform a read transaction
* and check that it can see the transaction changes. */
TEST(vfs, applyMakesTransactionVisible, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction
* was triggered by an explicit "COMMIT" statement and check that changes are
* visible. */
TEST(vfs, applyExplicitTransaction, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db);
PREPARE(db, stmt, "BEGIN");
STEP(stmt, SQLITE_DONE);
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
FINALIZE(stmt);
PREPARE(db, stmt, "CREATE TABLE test(n INT)");
STEP(stmt, SQLITE_DONE);
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
FINALIZE(stmt);
PREPARE(db, stmt, "COMMIT");
STEP(stmt, SQLITE_DONE);
POLL("1", tx);
munit_assert_int(tx.n, ==, 2);
APPLY("1", tx);
DONE(tx);
FINALIZE(stmt);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Perform two consecutive full write transactions using sqlite3_step(),
* dqlite_vfs_poll() and dqlite_vfs_apply(), then run a read transaction and
* check that it can see all committed changes. */
TEST(vfs, consecutiveWriteTransactions, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Perform three consecutive write transactions, then re-open the database and
* finally run a read transaction and check that it can see all committed
* changes. */
TEST(vfs,
reopenAfterConsecutiveWriteTransactions,
setUp,
tearDown,
0,
vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db);
EXEC(db, "CREATE TABLE foo(id INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "CREATE TABLE bar (id INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "INSERT INTO foo(id) VALUES(1)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
CLOSE(db);
OPEN("1", db);
PREPARE(db, stmt, "SELECT * FROM sqlite_master");
STEP(stmt, SQLITE_ROW);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction
* was triggered by sqlite3_step(), and verify that the transaction is visible
* from another existing connection. */
TEST(vfs,
transactionIsVisibleFromExistingConnection,
setUp,
tearDown,
0,
vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db1);
OPEN("1", db2);
EXEC(db1, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
PREPARE(db2, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db1);
CLOSE(db2);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction
* was triggered by sqlite3_step(), and verify that the transaction is visible
* from a brand new connection. */
TEST(vfs, transactionIsVisibleFromNewConnection, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db1);
EXEC(db1, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
OPEN("1", db2);
PREPARE(db2, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db1);
CLOSE(db2);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to actually modify the WAL after a write transaction
* was triggered by sqlite3_step(), then close the connection and open a new
* one. A read transaction started in the new connection can see the changes
* committed by the first one. */
TEST(vfs,
transactionIsVisibleFromReopenedConnection,
setUp,
tearDown,
0,
vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
CLOSE(db);
OPEN("1", db);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to replicate the very first write transaction on a
* different VFS than the one that initially generated it. In that case it's
* necessary to initialize the database file on the other VFS by opening and
* closing a connection. */
TEST(vfs, firstApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db1);
PREPARE(db1, stmt, "CREATE TABLE test(n INT)");
STEP(stmt, SQLITE_DONE);
POLL("1", tx);
APPLY("1", tx);
OPEN("2", db2);
CLOSE(db2);
APPLY("2", tx);
DONE(tx);
FINALIZE(stmt);
CLOSE(db1);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to replicate a second write transaction on a different
* VFS than the one that initially generated it. In that case it's not necessary
* to do anything special before calling dqlite_vfs_apply(). */
TEST(vfs, secondApplyOnDifferentVfs, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
struct tx tx;
OPEN("1", db1);
EXEC(db1, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
OPEN("2", db2);
CLOSE(db2);
APPLY("2", tx);
DONE(tx);
EXEC(db1, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx);
APPLY("1", tx);
APPLY("2", tx);
DONE(tx);
CLOSE(db1);
return MUNIT_OK;
}
/* Use dqlite_vfs_apply() to replicate a second write transaction on a different
* VFS than the one that initially generated it and that has an open connection
* which has built the WAL index header by preparing a statement. */
TEST(vfs, applyOnDifferentVfsWithOpenConnection, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db1);
PREPARE(db1, stmt, "CREATE TABLE test(n INT)");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
POLL("1", tx);
APPLY("1", tx);
OPEN("2", db2);
CLOSE(db2);
APPLY("2", tx);
DONE(tx);
EXEC(db1, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx);
CLOSE(db1);
OPEN("2", db2);
PREPARE(db2, stmt, "PRAGMA cache_size=-5000");
FINALIZE(stmt);
APPLY("2", tx);
PREPARE(db2, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_ROW);
FINALIZE(stmt);
DONE(tx);
CLOSE(db2);
return MUNIT_OK;
}
/* A write transaction that gets replicated to a different VFS is visible to a
* new connection opened on that VFS. */
TEST(vfs, transactionVisibleOnDifferentVfs, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db1);
EXEC(db1, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
OPEN("2", db2);
CLOSE(db2);
APPLY("2", tx);
DONE(tx);
CLOSE(db1);
OPEN("2", db1);
PREPARE(db1, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db1);
return MUNIT_OK;
}
/* Calling dqlite_vfs_abort() to cancel a transaction releases the write
* lock on the WAL. */
TEST(vfs, abort, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt1;
sqlite3_stmt *stmt2;
struct tx tx;
OPEN("1", db1);
OPEN("1", db2);
PREPARE(db1, stmt1, "CREATE TABLE test(n INT)");
PREPARE(db2, stmt2, "CREATE TABLE test2(n INT)");
STEP(stmt1, SQLITE_DONE);
POLL("1", tx);
ABORT("1");
STEP(stmt2, SQLITE_DONE);
FINALIZE(stmt1);
FINALIZE(stmt2);
CLOSE(db1);
CLOSE(db2);
DONE(tx);
return MUNIT_OK;
}
/* Perform a checkpoint after a write transaction has completed, then perform
* another write transaction and check that changes both before and after the
* checkpoint are visible. */
TEST(vfs, checkpoint, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
sqlite3 *db2;
sqlite3_stmt *stmt;
struct tx tx;
OPEN("1", db1);
EXEC(db1, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db1, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
OPEN("1", db2);
CHECKPOINT(db2);
CLOSE(db2);
EXEC(db1, "INSERT INTO test(n) VALUES(456)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
PREPARE(db1, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123);
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 456);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db1);
return MUNIT_OK;
}
/* Replicate a write transaction that happens after a checkpoint. */
TEST(vfs, applyOnDifferentVfsAfterCheckpoint, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx1;
struct tx tx2;
struct tx tx3;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx1);
APPLY("1", tx1);
EXEC(db, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx2);
APPLY("1", tx2);
CHECKPOINT(db);
EXEC(db, "INSERT INTO test(n) VALUES(456)");
POLL("1", tx3);
APPLY("1", tx3);
CLOSE(db);
OPEN("2", db);
CLOSE(db);
APPLY("2", tx1);
APPLY("2", tx2);
OPEN("2", db);
CHECKPOINT(db);
CLOSE(db);
APPLY("2", tx3);
OPEN("2", db);
PREPARE(db, stmt, "SELECT * FROM test ORDER BY n");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123);
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 456);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
DONE(tx1);
DONE(tx2);
DONE(tx3);
return MUNIT_OK;
}
/* Replicate a write transaction that happens after a checkpoint, without
* performing the checkpoint on the replicated DB. */
TEST(vfs,
applyOnDifferentVfsAfterCheckpointOtherVfsNoCheckpoint,
setUp,
tearDown,
0,
vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx1;
struct tx tx2;
struct tx tx3;
struct tx tx4;
/* Create transactions and checkpoint the DB after every transaction */
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx1);
APPLY("1", tx1);
CHECKPOINT_FRESH("1");
EXEC(db, "CREATE TABLE test2(n INT)");
POLL("1", tx2);
APPLY("1", tx2);
CHECKPOINT_FRESH("1");
EXEC(db, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx3);
APPLY("1", tx3);
CHECKPOINT_FRESH("1");
EXEC(db, "INSERT INTO test2(n) VALUES(456)");
POLL("1", tx4);
APPLY("1", tx4);
CHECKPOINT_FRESH("1");
CLOSE(db);
/* Create a second VFS and Apply the transactions without checkpointing
* the DB in between. */
OPEN("2", db);
APPLY("2", tx1);
APPLY("2", tx2);
APPLY("2", tx3);
APPLY("2", tx4);
/* Ensure data is there. */
PREPARE(db, stmt, "SELECT * FROM test ORDER BY n");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
PREPARE(db, stmt, "SELECT * FROM test2 ORDER BY n");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 456);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
/* Make sure checkpoint succeeds */
CHECKPOINT_FRESH("2");
CLOSE(db);
DONE(tx1);
DONE(tx2);
DONE(tx3);
DONE(tx4);
return MUNIT_OK;
}
/* Replicate a write transaction that happens before a checkpoint, and is
* replicated on a DB that has been checkpointed. */
TEST(vfs,
applyOnDifferentVfsExtraCheckpointsOnOtherVfs,
setUp,
tearDown,
0,
vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx1;
struct tx tx2;
struct tx tx3;
struct tx tx4;
/* Create transactions */
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx1);
APPLY("1", tx1);
EXEC(db, "CREATE TABLE test2(n INT)");
POLL("1", tx2);
APPLY("1", tx2);
EXEC(db, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx3);
APPLY("1", tx3);
EXEC(db, "INSERT INTO test2(n) VALUES(456)");
POLL("1", tx4);
APPLY("1", tx4);
CLOSE(db);
/* Create a second VFS and Apply the transactions while checkpointing
* after every transaction. */
OPEN("2", db);
CLOSE(db);
APPLY("2", tx1);
CHECKPOINT_FRESH("2");
APPLY("2", tx2);
CHECKPOINT_FRESH("2");
APPLY("2", tx3);
CHECKPOINT_FRESH("2");
APPLY("2", tx4);
CHECKPOINT_FRESH("2");
/* Ensure all the data is there. */
OPEN("2", db);
PREPARE(db, stmt, "SELECT * FROM test ORDER BY n");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 123);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
PREPARE(db, stmt, "SELECT * FROM test2 ORDER BY n");
STEP(stmt, SQLITE_ROW);
munit_assert_int(sqlite3_column_int(stmt, 0), ==, 456);
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
DONE(tx1);
DONE(tx2);
DONE(tx3);
DONE(tx4);
return MUNIT_OK;
}
/* Replicate to another VFS a series of changes including a checkpoint, then
* perform a new write transaction on that other VFS. */
TEST(vfs, checkpointThenPerformTransaction, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db1;
struct tx tx1;
struct tx tx2;
struct tx tx3;
OPEN("1", db1);
EXEC(db1, "CREATE TABLE test(n INT)");
POLL("1", tx1);
APPLY("1", tx1);
EXEC(db1, "INSERT INTO test(n) VALUES(123)");
POLL("1", tx2);
APPLY("1", tx2);
CHECKPOINT(db1);
EXEC(db1, "INSERT INTO test(n) VALUES(456)");
POLL("1", tx3);
APPLY("1", tx3);
CLOSE(db1);
OPEN("2", db1);
APPLY("2", tx1);
APPLY("2", tx2);
CHECKPOINT_FRESH("2");
APPLY("2", tx3);
DONE(tx1);
DONE(tx2);
DONE(tx3);
EXEC(db1, "INSERT INTO test(n) VALUES(789)");
POLL("2", tx1);
APPLY("2", tx1);
DONE(tx1);
CLOSE(db1);
return MUNIT_OK;
}
/* Rollback a transaction that didn't hit the page cache limit and hence didn't
* perform any pre-commit WAL writes. */
TEST(vfs, rollbackTransactionWithoutPageStress, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
struct tx tx;
sqlite3_stmt *stmt;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "BEGIN");
EXEC(db, "INSERT INTO test(n) VALUES(1)");
EXEC(db, "ROLLBACK");
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
RESET(stmt, SQLITE_OK);
EXEC(db, "INSERT INTO test(n) VALUES(1)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
STEP(stmt, SQLITE_ROW);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Rollback a transaction that hit the page cache limit and hence performed some
* pre-commit WAL writes. */
TEST(vfs, rollbackTransactionWithPageStress, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct tx tx;
unsigned i;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "BEGIN");
for (i = 0; i < 163; i++) {
char sql[64];
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
EXEC(db, sql);
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
}
EXEC(db, "ROLLBACK");
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
RESET(stmt, SQLITE_OK);
EXEC(db, "INSERT INTO test(n) VALUES(1)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
STEP(stmt, SQLITE_ROW);
FINALIZE(stmt);
CLOSE(db);
return MUNIT_OK;
}
/* Try and fail to checkpoint a WAL that performed some pre-commit WAL writes.
*/
TEST(vfs, checkpointTransactionWithPageStress, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
struct tx tx;
unsigned i;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
EXEC(db, "BEGIN");
for (i = 0; i < 163; i++) {
char sql[64];
sprintf(sql, "INSERT INTO test(n) VALUES(%d)", i + 1);
EXEC(db, sql);
POLL("1", tx);
munit_assert_int(tx.n, ==, 0);
}
CHECKPOINT_FAIL(db, SQLITE_LOCKED);
CLOSE(db);
return MUNIT_OK;
}
/* A snapshot of a brand new database that has been just initialized contains
* just the first page of the main database file. */
TEST(vfs, snapshotInitialDatabase, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
struct snapshot snapshot;
uint8_t *page;
uint8_t page_size[2] = {2, 0}; /* Big-endian page size */
uint8_t database_size[4] = {0, 0, 0, 1}; /* Big-endian database size */
OPEN("1", db);
CLOSE(db);
SNAPSHOT("1", snapshot);
munit_assert_int(snapshot.n, ==, PAGE_SIZE);
page = snapshot.data;
munit_assert_int(memcmp(&page[16], page_size, 2), ==, 0);
munit_assert_int(memcmp(&page[28], database_size, 4), ==, 0);
raft_free(snapshot.data);
return MUNIT_OK;
}
/* A snapshot of a database after the first write transaction gets applied
* contains the first page of the database plus the WAL file containing the
* transaction frames. */
TEST(vfs, snapshotAfterFirstTransaction, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
struct snapshot snapshot;
struct tx tx;
uint8_t *page;
uint8_t page_size[2] = {2, 0}; /* Big-endian page size */
uint8_t database_size[4] = {0, 0, 0, 1}; /* Big-endian database size */
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
CLOSE(db);
SNAPSHOT("1", snapshot);
munit_assert_int(snapshot.n, ==, PAGE_SIZE + 32 + (24 + PAGE_SIZE) * 2);
page = snapshot.data;
munit_assert_int(memcmp(&page[16], page_size, 2), ==, 0);
munit_assert_int(memcmp(&page[28], database_size, 4), ==, 0);
raft_free(snapshot.data);
return MUNIT_OK;
}
/* A snapshot of a database after a checkpoint contains all checkpointed pages
* and no WAL frames. */
TEST(vfs, snapshotAfterCheckpoint, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
struct snapshot snapshot;
struct tx tx;
uint8_t *page;
uint8_t page_size[2] = {2, 0}; /* Big-endian page size */
uint8_t database_size[4] = {0, 0, 0, 2}; /* Big-endian database size */
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
CHECKPOINT(db);
CLOSE(db);
SNAPSHOT("1", snapshot);
munit_assert_int(snapshot.n, ==, PAGE_SIZE * 2);
page = snapshot.data;
munit_assert_int(memcmp(&page[16], page_size, 2), ==, 0);
munit_assert_int(memcmp(&page[28], database_size, 4), ==, 0);
raft_free(snapshot.data);
return MUNIT_OK;
}
/* Restore a snapshot taken after a brand new database has been just
* initialized. */
TEST(vfs, restoreInitialDatabase, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
struct snapshot snapshot;
OPEN("1", db);
CLOSE(db);
SNAPSHOT("1", snapshot);
OPEN("2", db);
CLOSE(db);
RESTORE("2", snapshot);
raft_free(snapshot.data);
return MUNIT_OK;
}
/* Restore a snapshot of a database taken after the first write transaction gets
* applied. */
TEST(vfs, restoreAfterFirstTransaction, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct snapshot snapshot;
struct tx tx;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
CLOSE(db);
SNAPSHOT("1", snapshot);
OPEN("2", db);
CLOSE(db);
RESTORE("2", snapshot);
OPEN("2", db);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
raft_free(snapshot.data);
return MUNIT_OK;
}
/* Restore a snapshot of a database while a connection is open. */
TEST(vfs, restoreWithOpenConnection, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
sqlite3_stmt *stmt;
struct snapshot snapshot;
struct tx tx;
OPEN("1", db);
EXEC(db, "CREATE TABLE test(n INT)");
POLL("1", tx);
APPLY("1", tx);
DONE(tx);
CLOSE(db);
SNAPSHOT("1", snapshot);
OPEN("2", db);
RESTORE("2", snapshot);
PREPARE(db, stmt, "SELECT * FROM test");
STEP(stmt, SQLITE_DONE);
FINALIZE(stmt);
CLOSE(db);
raft_free(snapshot.data);
return MUNIT_OK;
}
/* Changing page_size to non-default value fails. */
TEST(vfs, changePageSize, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
int rv;
OPEN("1", db);
rv = sqlite3_exec(db, "PRAGMA page_size=1024", NULL, NULL, NULL);
munit_assert_int(rv, !=, 0);
CLOSE(db);
return MUNIT_OK;
}
/* Changing page_size to current value succeeds. */
TEST(vfs, changePageSizeSameValue, setUp, tearDown, 0, vfs_params)
{
sqlite3 *db;
int rv;
OPEN("1", db);
rv = sqlite3_exec(db, "PRAGMA page_size=512", NULL, NULL, NULL);
munit_assert_int(rv, ==, 0);
CLOSE(db);
return MUNIT_OK;
}
dqlite-1.16.0/test/lib/ 0000775 0000000 0000000 00000000000 14512203222 0014577 5 ustar 00root root 0000000 0000000 dqlite-1.16.0/test/lib/client.h 0000664 0000000 0000000 00000016140 14512203222 0016230 0 ustar 00root root 0000000 0000000 /* Setup a test dqlite client. */
#include "endpoint.h"
#ifndef TEST_CLIENT_H
#define TEST_CLIENT_H
#define FIXTURE_CLIENT \
struct client_proto client; \
struct test_endpoint endpoint; \
int server
#define SETUP_CLIENT \
{ \
int _rv; \
int _client; \
test_endpoint_setup(&f->endpoint, params); \
_rv = listen(f->endpoint.fd, 16); \
munit_assert_int(_rv, ==, 0); \
test_endpoint_pair(&f->endpoint, &f->server, &_client); \
memset(&f->client, 0, sizeof f->client); \
buffer__init(&f->client.read); \
buffer__init(&f->client.write); \
f->client.fd = _client; \
}
#define TEAR_DOWN_CLIENT \
clientClose(&f->client); \
test_endpoint_tear_down(&f->endpoint)
/******************************************************************************
*
* Helper macros.
*
******************************************************************************/
/* Send the initial client handshake. */
#define HANDSHAKE \
{ \
int rv_; \
rv_ = clientSendHandshake(f->client, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Send the initial client handshake for a specific client. */
#define HANDSHAKE_C(CLIENT) \
{ \
int rv_; \
rv_ = clientSendHandshake(CLIENT, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Send an add request. */
#define ADD(ID, ADDRESS) \
{ \
int rv_; \
rv_ = clientSendAdd(f->client, ID, ADDRESS, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvEmpty(f->client, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Send an assign role request. */
#define ASSIGN(ID, ROLE) \
{ \
int rv_; \
rv_ = clientSendAssign(f->client, ID, ROLE, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvEmpty(f->client, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Send a remove request. */
#define REMOVE(ID) \
{ \
int rv_; \
rv_ = clientSendRemove(f->client, ID, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvEmpty(f->client, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Send a transfer request. */
#define TRANSFER(ID, CLIENT) \
{ \
int rv_; \
rv_ = clientSendTransfer(CLIENT, ID, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvEmpty(CLIENT, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Open a test database. */
#define OPEN \
{ \
int rv_; \
rv_ = clientSendOpen(f->client, "test", NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvDb(f->client, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Open a test database with a specific name. */
#define OPEN_NAME(NAME) \
{ \
int rv_; \
rv_ = clientSendOpen(f->client, NAME, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvDb(f->client, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Prepare a statement. */
#define PREPARE(SQL, STMT_ID) \
{ \
int rv_; \
rv_ = clientSendPrepare(f->client, SQL, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvStmt(f->client, STMT_ID, NULL, NULL, NULL); \
munit_assert_int(rv_, ==, 0); \
}
#define PREPARE_FAIL(SQL, STMT_ID, RV, MSG) \
{ \
int rv_; \
rv_ = clientSendPrepare(f->client, SQL, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvFailure(f->client, RV, MSG, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Execute a statement. */
#define EXEC(STMT_ID, LAST_INSERT_ID, ROWS_AFFECTED) \
{ \
int rv_; \
rv_ = clientSendExec(f->client, STMT_ID, NULL, 0, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvResult(f->client, LAST_INSERT_ID, \
ROWS_AFFECTED, NULL); \
munit_assert_int(rv_, ==, 0); \
}
#define EXEC_SQL(SQL, LAST_INSERT_ID, ROWS_AFFECTED) \
{ \
int rv_; \
rv_ = clientSendExecSQL(f->client, SQL, NULL, 0, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvResult(f->client, LAST_INSERT_ID, \
ROWS_AFFECTED, NULL); \
munit_assert_int(rv_, ==, 0); \
}
/* Perform a query. */
#define QUERY(STMT_ID, ROWS) \
{ \
int rv_; \
rv_ = clientSendQuery(f->client, STMT_ID, NULL, 0, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvRows(f->client, ROWS, NULL, NULL); \
munit_assert_int(rv_, ==, 0); \
}
#define QUERY_SQL(SQL, ROWS) \
{ \
int rv_; \
rv_ = clientSendQuerySQL(f->client, SQL, NULL, 0, NULL); \
munit_assert_int(rv_, ==, 0); \
rv_ = clientRecvRows(f->client, ROWS, NULL, NULL); \
munit_assert_int(rv_, ==, 0); \
}
#endif /* TEST_CLIENT_H */
dqlite-1.16.0/test/lib/cluster.h 0000664 0000000 0000000 00000014244 14512203222 0016436 0 ustar 00root root 0000000 0000000 /**
* Helpers to setup a raft cluster in test fixtures.
*
* Each raft instance will use its own dqlite FSM, which in turn will be created
* using its own config, registry and logger.
*
* The fixture will also register a VFS and a SQLite replication object for each
* raft instance, using "test" as registration name, where is the raft
* instance index.
*
* This fixture is meant to be used as base-line fixture for most higher-level
* tests.
*/
#ifndef TEST_CLUSTER_H
#define TEST_CLUSTER_H
#include
#include
#include "../../src/config.h"
#include "../../src/fsm.h"
#include "../../src/registry.h"
#include "../../src/vfs.h"
#include "../lib/fs.h"
#include "../lib/heap.h"
#include "../lib/logger.h"
#include "../lib/sqlite.h"
#define N_SERVERS 3
#define V1 0
#define V2 1
struct server
{
struct logger logger;
struct config config;
sqlite3_vfs vfs;
struct registry registry;
char *dir;
};
#define FIXTURE_CLUSTER \
struct server servers[N_SERVERS]; \
struct raft_fsm fsms[N_SERVERS]; \
struct raft_fixture cluster;
#define SETUP_CLUSTER(VERSION) \
{ \
struct raft_configuration _configuration; \
unsigned _i; \
int _rv; \
SETUP_HEAP; \
SETUP_SQLITE; \
_rv = raft_fixture_init(&f->cluster); \
munit_assert_int(_rv, ==, 0); \
for (_i = 0; _i < N_SERVERS; _i++) { \
SETUP_SERVER(_i, VERSION); \
raft_fixture_grow(&f->cluster, &f->fsms[_i]); \
} \
_rv = raft_fixture_configuration(&f->cluster, N_SERVERS, \
&_configuration); \
munit_assert_int(_rv, ==, 0); \
_rv = raft_fixture_bootstrap(&f->cluster, &_configuration); \
munit_assert_int(_rv, ==, 0); \
raft_configuration_close(&_configuration); \
_rv = raft_fixture_start(&f->cluster); \
munit_assert_int(_rv, ==, 0); \
}
#define SETUP_SERVER(I, VERSION) \
{ \
struct server *_s = &f->servers[I]; \
struct raft_fsm *_fsm = &f->fsms[I]; \
char address[16]; \
int _rc; \
\
test_logger_setup(params, &_s->logger); \
\
sprintf(address, "%d", I + 1); \
\
char *dir = test_dir_setup(); \
_s->dir = dir; \
\
_rc = config__init(&_s->config, I + 1, address, dir); \
munit_assert_int(_rc, ==, 0); \
\
registry__init(&_s->registry, &_s->config); \
\
_rc = VfsInit(&_s->vfs, _s->config.name); \
munit_assert_int(_rc, ==, 0); \
_rc = sqlite3_vfs_register(&_s->vfs, 0); \
munit_assert_int(_rc, ==, 0); \
\
_rc = fsm__init(_fsm, &_s->config, &_s->registry); \
munit_assert_int(_rc, ==, 0); \
}
#define TEAR_DOWN_CLUSTER \
{ \
int _i; \
for (_i = 0; _i < N_SERVERS; _i++) { \
TEAR_DOWN_SERVER(_i); \
} \
raft_fixture_close(&f->cluster); \
TEAR_DOWN_SQLITE; \
TEAR_DOWN_HEAP; \
}
#define TEAR_DOWN_SERVER(I) \
{ \
struct server *s = &f->servers[I]; \
struct raft_fsm *fsm = &f->fsms[I]; \
fsm__close(fsm); \
registry__close(&s->registry); \
sqlite3_vfs_unregister(&s->vfs); \
VfsClose(&s->vfs); \
config__close(&s->config); \
test_dir_tear_down(s->dir); \
test_logger_tear_down(&s->logger); \
}
#define CLUSTER_CONFIG(I) &f->servers[I].config
#define CLUSTER_LOGGER(I) &f->servers[I].logger
#define CLUSTER_LEADER(I) &f->servers[I].leader
#define CLUSTER_REGISTRY(I) &f->servers[I].registry
#define CLUSTER_RAFT(I) raft_fixture_get(&f->cluster, I)
#define CLUSTER_LAST_INDEX(I) raft_last_index(CLUSTER_RAFT(I))
#define CLUSTER_DISCONNECT(I, J) raft_fixture_disconnect(&f->cluster, I, J)
#define CLUSTER_RECONNECT(I, J) raft_fixture_reconnect(&f->cluster, I, J)
#define CLUSTER_ELECT(I) raft_fixture_elect(&f->cluster, I)
#define CLUSTER_DEPOSE raft_fixture_depose(&f->cluster)
#define CLUSTER_APPLIED(N) \
{ \
int _i; \
for (_i = 0; _i < N_SERVERS; _i++) { \
bool done; \
done = raft_fixture_step_until_applied(&f->cluster, \
_i, N, 1000); \
munit_assert_true(done); \
} \
}
#define CLUSTER_STEP raft_fixture_step(&f->cluster)
#define CLUSTER_SNAPSHOT_THRESHOLD(I, N) \
raft_set_snapshot_threshold(CLUSTER_RAFT(I), N)
#define CLUSTER_SNAPSHOT_TRAILING(I, N) \
raft_set_snapshot_trailing(CLUSTER_RAFT(I), N)
#endif /* TEST_CLUSTER_H */
dqlite-1.16.0/test/lib/config.h 0000664 0000000 0000000 00000001225 14512203222 0016215 0 ustar 00root root 0000000 0000000 /**
* Options object for tests.
*/
#ifndef TEST_OPTIONS_H
#define TEST_OPTIONS_H
#include "../../src/config.h"
#include "logger.h"
#define FIXTURE_CONFIG struct config config;
#define SETUP_CONFIG \
{ \
int rc; \
rc = config__init(&f->config, 1, "1", "dir"); \
munit_assert_int(rc, ==, 0); \
test_logger_setup(params, &f->config.logger); \
}
#define TEAR_DOWN_CONFIG \
test_logger_tear_down(&f->config.logger); \
config__close(&f->config)
#endif /* TEST_OPTIONS_H */
dqlite-1.16.0/test/lib/endpoint.c 0000664 0000000 0000000 00000010064 14512203222 0016564 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include "endpoint.h"
static int getFamily(const MunitParameter params[])
{
const char *family = NULL;
if (params != NULL) {
family = munit_parameters_get(params, TEST_ENDPOINT_FAMILY);
}
if (family == NULL) {
family = "unix";
}
if (strcmp(family, "tcp") == 0) {
return AF_INET;
} else if (strcmp(family, "unix") == 0) {
return AF_UNIX;
}
munit_errorf("unexpected socket family: %s", family);
return -1;
}
void test_endpoint_setup(struct test_endpoint *e, const MunitParameter params[])
{
struct sockaddr *address;
socklen_t size;
int rv;
e->family = getFamily(params);
/* Initialize the appropriate socket address structure, depending on the
* selected socket family. */
switch (e->family) {
case AF_INET:
/* TCP socket on loopback device */
memset(&e->in_address, 0, sizeof e->in_address);
e->in_address.sin_family = AF_INET;
e->in_address.sin_addr.s_addr = inet_addr("127.0.0.1");
e->in_address.sin_port = 0; /* Get a random free port */
address = (struct sockaddr *)(&e->in_address);
size = sizeof e->in_address;
break;
case AF_UNIX:
/* Abstract Unix socket */
memset(&e->un_address, 0, sizeof e->un_address);
e->un_address.sun_family = AF_UNIX;
strcpy(e->un_address.sun_path, ""); /* Random address */
address = (struct sockaddr *)(&e->un_address);
size = sizeof e->un_address;
break;
default:
munit_errorf("unexpected socket family: %d", e->family);
}
/* Create the listener fd. */
e->fd = socket(e->family, SOCK_STREAM, 0);
if (e->fd < 0) {
munit_errorf("socket(): %s", strerror(errno));
}
/* Bind the listener fd. */
rv = bind(e->fd, address, size);
if (rv != 0) {
munit_errorf("bind(): %s", strerror(errno));
}
/* Get the actual addressed assigned by the kernel and save it back in
* the relevant struct server field (pointed to by address). */
rv = getsockname(e->fd, address, &size);
if (rv != 0) {
munit_errorf("getsockname(): %s", strerror(errno));
}
/* Render the endpoint address. */
switch (e->family) {
case AF_INET:
sprintf(e->address, "127.0.0.1:%d",
htons(e->in_address.sin_port));
break;
case AF_UNIX:
/* TODO */
break;
}
}
void test_endpoint_tear_down(struct test_endpoint *e)
{
close(e->fd);
}
int test_endpoint_connect(struct test_endpoint *e)
{
struct sockaddr *address;
socklen_t size;
int fd;
int rv;
switch (e->family) {
case AF_INET:
address = (struct sockaddr *)&e->in_address;
size = sizeof e->in_address;
break;
case AF_UNIX:
address = (struct sockaddr *)&e->un_address;
size = sizeof e->un_address;
break;
}
/* Create the socket. */
fd = socket(e->family, SOCK_STREAM, 0);
if (fd < 0) {
munit_errorf("socket(): %s", strerror(errno));
}
/* Connect to the server */
rv = connect(fd, address, size);
if (rv != 0 && errno != ECONNREFUSED) {
munit_errorf("connect(): %s", strerror(errno));
}
return fd;
}
int test_endpoint_accept(struct test_endpoint *e)
{
struct sockaddr_in in_address;
struct sockaddr_un un_address;
struct sockaddr *address;
socklen_t size;
int fd;
int rv;
switch (e->family) {
case AF_INET:
address = (struct sockaddr *)&in_address;
size = sizeof in_address;
break;
case AF_UNIX:
address = (struct sockaddr *)&un_address;
size = sizeof un_address;
break;
}
/* Accept the client connection. */
fd = accept(e->fd, address, &size);
if (fd < 0) {
/* Check if the endpoint has been closed, so this is benign. */
if (errno == EBADF || errno == EINVAL || errno == ENOTSOCK) {
return -1;
}
munit_errorf("accept(): %s", strerror(errno));
}
/* Set non-blocking mode */
rv = fcntl(fd, F_SETFL, O_NONBLOCK);
if (rv != 0) {
munit_errorf("set non-blocking mode: %s", strerror(errno));
}
return fd;
}
void test_endpoint_pair(struct test_endpoint *e, int *server, int *client)
{
*client = test_endpoint_connect(e);
*server = test_endpoint_accept(e);
}
const char *test_endpoint_address(struct test_endpoint *e)
{
return e->address;
}
char *test_endpoint_family_values[] = {"tcp", "unix", NULL};
dqlite-1.16.0/test/lib/endpoint.h 0000664 0000000 0000000 00000003512 14512203222 0016571 0 ustar 00root root 0000000 0000000 /* Helpers to create and connect Unix or TCP sockets. */
#ifndef TEST_ENDPOINT_H
#define TEST_ENDPOINT_H
#include
#include
#include "munit.h"
/* A few tests depend on knowing that certain reads and writes will not be short
* and will happen immediately. */
#define TEST_SOCKET_MIN_BUF_SIZE 4096
/* Munit parameter defining the socket type to use in test_endpoint_setup.
*
* If set to "unix" a pair of unix abstract sockets will be created. If set to
* "tcp" a pair of TCP sockets using the loopback interface will be created. */
#define TEST_ENDPOINT_FAMILY "endpoint-family"
/* Null-terminated list of legal values for TEST_ENDPOINT_FAMILY. Currently
* "unix" and "tcp". */
extern char *test_endpoint_family_values[];
/* Listening socket endpoint. */
struct test_endpoint
{
char address[256]; /* Rendered address string. */
sa_family_t family; /* Address family (either AF_INET or AF_UNIX) */
int fd; /* Listening socket. */
union { /* Server address (either a TCP or Unix) */
struct sockaddr_in in_address;
struct sockaddr_un un_address;
};
};
/* Create a listening endpoint.
*
* This will bind a random address and start listening to it. */
void test_endpoint_setup(struct test_endpoint *e,
const MunitParameter params[]);
/* Tear down a listening endpoint. */
void test_endpoint_tear_down(struct test_endpoint *e);
/* Establish a new client connection. */
int test_endpoint_connect(struct test_endpoint *e);
/* Accept a new client connection. */
int test_endpoint_accept(struct test_endpoint *e);
/* Connect and accept a connection, returning the pair of connected sockets. */
void test_endpoint_pair(struct test_endpoint *e, int *server, int *client);
/* Return the endpoint address. */
const char *test_endpoint_address(struct test_endpoint *e);
#endif /* TEST_ENDPOINT_H */
dqlite-1.16.0/test/lib/fault.c 0000664 0000000 0000000 00000002227 14512203222 0016061 0 ustar 00root root 0000000 0000000 #include "fault.h"
#include "munit.h"
void test_fault_init(struct test_fault *f)
{
f->countdown = -1;
f->n = -1;
f->enabled = false;
}
bool test_fault_tick(struct test_fault *f)
{
if (MUNIT_UNLIKELY(!f->enabled)) {
return false;
}
/* If the initial delay parameter was set to -1, then never fail. This
* is the most common case. */
if (MUNIT_LIKELY(f->countdown < 0)) {
return false;
}
/* If we did not yet reach 'delay' ticks, then just decrease the
* countdown.
*/
if (f->countdown > 0) {
f->countdown--;
return false;
}
munit_assert_int(f->countdown, ==, 0);
/* We reached 'delay' ticks, let's see how many times we have to trigger
* the fault, if any. */
if (f->n < 0) {
/* Trigger the fault forever. */
return true;
}
if (f->n > 0) {
/* Trigger the fault at least this time. */
f->n--;
return true;
}
munit_assert_int(f->n, ==, 0);
/* We reached 'repeat' ticks, let's stop triggering the fault. */
f->countdown--;
return false;
}
void test_fault_config(struct test_fault *f, int delay, int repeat)
{
f->countdown = delay;
f->n = repeat;
}
void test_fault_enable(struct test_fault *f)
{
f->enabled = true;
}
dqlite-1.16.0/test/lib/fault.h 0000664 0000000 0000000 00000001564 14512203222 0016071 0 ustar 00root root 0000000 0000000 /**
* Helper for test components supporting fault injection.
*/
#ifndef TEST_FAULT_H
#define TEST_FAULT_H
#include
/**
* Information about a fault that should occurr in a component.
*/
struct test_fault
{
int countdown; /* Trigger the fault when this counter gets to zero. */
int n; /* Repeat the fault this many times. Default is -1. */
bool enabled; /* Enable fault triggering. */
};
/**
* Initialize a fault.
*/
void test_fault_init(struct test_fault *f);
/**
* Advance the counters of the fault. Return true if the fault should be
* triggered, false otherwise.
*/
bool test_fault_tick(struct test_fault *f);
/**
* Configure the fault with the given values.
*/
void test_fault_config(struct test_fault *f, int delay, int repeat);
/**
* Enable fault triggering.
*/
void test_fault_enable(struct test_fault *f);
#endif /* TEST_FAULT_H */
dqlite-1.16.0/test/lib/fs.c 0000664 0000000 0000000 00000001422 14512203222 0015352 0 ustar 00root root 0000000 0000000 #include
#include
#include
#include
#include
#include "fs.h"
#include "munit.h"
char *test_dir_setup()
{
char *dir = munit_malloc(strlen(TEST__DIR_TEMPLATE) + 1);
strcpy(dir, TEST__DIR_TEMPLATE);
munit_assert_ptr_not_null(mkdtemp(dir));
return dir;
}
static int test__dir_tear_down_nftw_fn(const char *path,
const struct stat *sb,
int type,
struct FTW *ftwb)
{
int rc;
(void)sb;
(void)type;
(void)ftwb;
rc = remove(path);
munit_assert_int(rc, ==, 0);
return 0;
}
void test_dir_tear_down(char *dir)
{
int rc;
if (dir == NULL) {
return;
}
rc = nftw(dir, test__dir_tear_down_nftw_fn, 10,
FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
munit_assert_int(rc, ==, 0);
free(dir);
}
dqlite-1.16.0/test/lib/fs.h 0000664 0000000 0000000 00000000422 14512203222 0015356 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_TEST_FS_H
#define DQLITE_TEST_FS_H
#define TEST__DIR_TEMPLATE "/tmp/dqlite-test-XXXXXX"
/* Setup a temporary directory. */
char *test_dir_setup(void);
/* Remove the temporary directory. */
void test_dir_tear_down(char *dir);
#endif /* DQLITE_TEST_FS_H */
dqlite-1.16.0/test/lib/heap.c 0000664 0000000 0000000 00000012310 14512203222 0015655 0 ustar 00root root 0000000 0000000 #include
#include "fault.h"
#include "heap.h"
/* This structure is used to encapsulate the global state variables used by
* malloc() fault simulation. */
struct mem_fault
{
struct test_fault fault; /* Fault trigger */
sqlite3_mem_methods m; /* Actual malloc implementation */
};
/* We need to use a global variable here because after a sqlite3_mem_methods
* instance has been installed using sqlite3_config(), and after
* sqlite3_initialize() has been called, there's no way to retrieve it back with
* sqlite3_config(). */
static struct mem_fault memFault;
/* A version of sqlite3_mem_methods.xMalloc() that includes fault simulation
* logic.*/
static void *mem_fault_malloc(int n)
{
void *p = NULL;
if (!test_fault_tick(&memFault.fault)) {
p = memFault.m.xMalloc(n);
}
return p;
}
/* A version of sqlite3_mem_methods.xRealloc() that includes fault simulation
* logic. */
static void *mem_fault_realloc(void *old, int n)
{
void *p = NULL;
if (!test_fault_tick(&memFault.fault)) {
p = memFault.m.xRealloc(old, n);
}
return p;
}
/* The following method calls are passed directly through to the underlying
* malloc system:
*
* xFree
* xSize
* xRoundup
* xInit
* xShutdown
*/
static void mem_fault_free(void *p)
{
memFault.m.xFree(p);
}
static int mem_fault_size(void *p)
{
return memFault.m.xSize(p);
}
static int mem_fault_roundup(int n)
{
return memFault.m.xRoundup(n);
}
static int mem_fault_init(void *p)
{
(void)p;
return memFault.m.xInit(memFault.m.pAppData);
}
static void mem_fault_shutdown(void *p)
{
(void)p;
memFault.m.xShutdown(memFault.m.pAppData);
}
/* Wrap the given SQLite memory management instance with the faulty memory
* management interface. By default no faults will be triggered. */
static void mem_wrap(sqlite3_mem_methods *m, sqlite3_mem_methods *wrap)
{
test_fault_init(&memFault.fault);
memFault.m = *m;
wrap->xMalloc = mem_fault_malloc;
wrap->xFree = mem_fault_free;
wrap->xRealloc = mem_fault_realloc;
wrap->xSize = mem_fault_size;
wrap->xRoundup = mem_fault_roundup;
wrap->xInit = mem_fault_init;
wrap->xShutdown = mem_fault_shutdown;
wrap->pAppData = &memFault;
}
/* Unwrap the given faulty memory management instance returning the original
* one. */
static void mem_unwrap(sqlite3_mem_methods *wrap, sqlite3_mem_methods *m)
{
(void)wrap;
*m = memFault.m;
}
/* Get the current number of outstanding malloc()'s without a matching free()
* and the total number of used memory. */
static void mem_stats(int *malloc_count, int *memory_used)
{
int rc;
int watermark;
rc = sqlite3_status(SQLITE_STATUS_MALLOC_COUNT, malloc_count,
&watermark, 1);
if (rc != SQLITE_OK) {
munit_errorf("can't get malloc count: %s", sqlite3_errstr(rc));
}
rc = sqlite3_status(SQLITE_STATUS_MEMORY_USED, memory_used, &watermark,
1);
if (rc != SQLITE_OK) {
munit_errorf("can't get memory: %s\n:", sqlite3_errstr(rc));
}
}
/* Ensure we're starting from a clean memory state with no allocations and
* optionally inject malloc failures. */
void test_heap_setup(const MunitParameter params[], void *user_data)
{
int malloc_count;
int memory_used;
const char *fault_delay;
const char *fault_repeat;
sqlite3_mem_methods mem;
sqlite3_mem_methods mem_fault;
int rc;
(void)params;
(void)user_data;
/* Install the faulty malloc implementation */
rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem);
if (rc != SQLITE_OK) {
munit_errorf("can't get default mem: %s", sqlite3_errstr(rc));
}
mem_wrap(&mem, &mem_fault);
rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &mem_fault);
if (rc != SQLITE_OK) {
munit_errorf("can't set faulty mem: %s", sqlite3_errstr(rc));
}
/* Check that memory is clean. */
mem_stats(&malloc_count, &memory_used);
if (malloc_count > 0 || memory_used > 0) {
munit_errorf(
"setup memory:\n bytes: %11d\n allocations: %5d\n",
malloc_count, memory_used);
}
/* Optionally inject memory allocation failures. */
fault_delay = munit_parameters_get(params, "mem-fault-delay");
fault_repeat = munit_parameters_get(params, "mem-fault-repeat");
munit_assert((fault_delay != NULL && fault_repeat != NULL) ||
(fault_delay == NULL && fault_repeat == NULL));
if (fault_delay != NULL) {
test_heap_fault_config(atoi(fault_delay), atoi(fault_repeat));
}
}
/* Ensure we're starting leaving a clean memory behind. */
void test_heap_tear_down(void *data)
{
sqlite3_mem_methods mem;
sqlite3_mem_methods mem_fault;
int rc;
(void)data;
int malloc_count;
int memory_used;
mem_stats(&malloc_count, &memory_used);
if (malloc_count > 0 || memory_used > 0) {
munit_errorf(
"teardown memory:\n bytes: %11d\n allocations: %5d\n",
memory_used, malloc_count);
}
/* Restore default memory management. */
rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &mem_fault);
if (rc != SQLITE_OK) {
munit_errorf("can't get faulty mem: %s", sqlite3_errstr(rc));
}
mem_unwrap(&mem_fault, &mem);
rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &mem);
if (rc != SQLITE_OK) {
munit_errorf("can't reset default mem: %s", sqlite3_errstr(rc));
}
}
void test_heap_fault_config(int delay, int repeat)
{
test_fault_config(&memFault.fault, delay, repeat);
}
void test_heap_fault_enable()
{
test_fault_enable(&memFault.fault);
}
dqlite-1.16.0/test/lib/heap.h 0000664 0000000 0000000 00000002343 14512203222 0015667 0 ustar 00root root 0000000 0000000 #ifndef DQLITE_TEST_HEAP_H
#define DQLITE_TEST_HEAP_H
#include "munit.h"
/* Munit parameter defining the delay of the faulty memory implementation. */
#define TEST_HEAP_FAULT_DELAY "mem-fault-delay"
/* Munit parameter defining the repeat of the faulty memory implementation. */
#define TEST_HEAP_FAULT_REPEAT "mem-fault-repeat"
void test_heap_setup(const MunitParameter params[], void *user_data);
void test_heap_tear_down(void *data);
/* Configure the faulty memory management implementation so malloc()-related
* functions start returning NULL pointers after 'delay' calls, and keep failing
* for 'repeat' consecutive times.
*
* Note that the faults won't automatically take place, an explicit call to
* test_mem_fault_enable() is needed. This allows configuration and actual
* behavior to happen at different times (e.g. configure at test setup time and
* enable at test case time). */
void test_heap_fault_config(int delay, int repeat);
/* Enable the faulty behavior, which from this point on will honor the
* parameters passed to test_mem_fault_config(). */
void test_heap_fault_enable(void);
#define SETUP_HEAP test_heap_setup(params, user_data);
#define TEAR_DOWN_HEAP test_heap_tear_down(data);
#endif /* DQLITE_TEST_HEAP_H */
dqlite-1.16.0/test/lib/leader.h 0000664 0000000 0000000 00000001366 14512203222 0016212 0 ustar 00root root 0000000 0000000 /**
* Setup a test struct leader object.
*/
#ifndef TEST_LEADER_H
#define TEST_LEADER_H
#include "../../src/leader.h"
#include "../../src/registry.h"
#define FIXTURE_LEADER struct leader leader
#define SETUP_LEADER \
{ \
struct db *db; \
int rv; \
rv = registry__db_get(&f->registry, "test.db", &db); \
munit_assert_int(rv, ==, 0); \
rv = leader__init(&f->leader, db, &f->raft); \
munit_assert_int(rv, ==, 0); \
}
#define TEAR_DOWN_LEADER leader__close(&f->leader)
#endif /* TEST_LEADER_H */
dqlite-1.16.0/test/lib/logger.c 0000664 0000000 0000000 00000002354 14512203222 0016226 0 ustar 00root root 0000000 0000000 #include
#include
#include "../../include/dqlite.h"
#include "logger.h"
#include "munit.h"
void test_logger_emit(void *data, int level, const char *format, va_list args)
{
struct test_logger *t = data;
char buf[1024];
const char *level_name;
int i;
(void)data;
switch (level) {
case DQLITE_DEBUG:
level_name = "DEBUG";
break;
case DQLITE_INFO:
level_name = "INFO ";
break;
case DQLITE_WARN:
level_name = "WARN ";
break;
case DQLITE_LOG_ERROR:
level_name = "ERROR";
break;
};
buf[0] = 0;
sprintf(buf + strlen(buf), "%2d -> [%s] ", t->id, level_name);
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
vsnprintf(buf + strlen(buf), 1024 - strlen(buf), format, args);
#pragma GCC diagnostic pop
munit_log(MUNIT_LOG_INFO, buf);
return;
snprintf(buf + strlen(buf), 1024 - strlen(buf), " ");
for (i = strlen(buf); i < 85; i++) {
buf[i] = ' ';
}
munit_log(MUNIT_LOG_INFO, buf);
}
void test_logger_setup(const MunitParameter params[], struct logger *l)
{
struct test_logger *t;
(void)params;
t = munit_malloc(sizeof *t);
t->data = NULL;
l->data = t;
l->emit = test_logger_emit;
}
void test_logger_tear_down(struct logger *l)
{
free(l->data);
}
dqlite-1.16.0/test/lib/logger.h 0000664 0000000 0000000 00000001064 14512203222 0016230 0 ustar 00root root 0000000 0000000 /**
* Test logger.
*/
#ifndef TEST_LOGGER_H
#define TEST_LOGGER_H
#include "../../src/logger.h"
#include "munit.h"
void test_logger_setup(const MunitParameter params[], struct logger *l);
void test_logger_tear_down(struct logger *l);
struct test_logger
{
unsigned id;
void *data;
};
void test_logger_emit(void *data, int level, const char *fmt, va_list args);
#define FIXTURE_LOGGER struct logger logger;
#define SETUP_LOGGER test_logger_setup(params, &f->logger);
#define TEAR_DOWN_LOGGER test_logger_tear_down(&f->logger);
#endif /* TEST_LOGGER_H */
dqlite-1.16.0/test/lib/munit.c 0000664 0000000 0000000 00000204565 14512203222 0016113 0 ustar 00root root 0000000 0000000 /* Copyright (c) 2013-2018 Evan Nemerson
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
/*** Configuration ***/
/* This is just where the output from the test goes. It's really just
* meant to let you choose stdout or stderr, but if anyone really want
* to direct it to a file let me know, it would be fairly easy to
* support. */
#if !defined(MUNIT_OUTPUT_FILE)
# define MUNIT_OUTPUT_FILE stdout
#endif
/* This is a bit more useful; it tells µnit how to format the seconds in
* timed tests. If your tests run for longer you might want to reduce
* it, and if your computer is really fast and your tests are tiny you
* can increase it. */
#if !defined(MUNIT_TEST_TIME_FORMAT)
# define MUNIT_TEST_TIME_FORMAT "0.8f"
#endif
/* If you have long test names you might want to consider bumping
* this. The result information takes 43 characters. */
#if !defined(MUNIT_TEST_NAME_LEN)
# define MUNIT_TEST_NAME_LEN 37
#endif
/* If you don't like the timing information, you can disable it by
* defining MUNIT_DISABLE_TIMING. */
#if !defined(MUNIT_DISABLE_TIMING)
# define MUNIT_ENABLE_TIMING
#endif
/*** End configuration ***/
#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE < 200809L)
# undef _POSIX_C_SOURCE
#endif
#if !defined(_POSIX_C_SOURCE)
# define _POSIX_C_SOURCE 200809L
#endif
/* Solaris freaks out if you try to use a POSIX or SUS standard without
* the "right" C standard. */
#if defined(_XOPEN_SOURCE)
# undef _XOPEN_SOURCE
#endif
#if defined(__STDC_VERSION__)
# if __STDC_VERSION__ >= 201112L
# define _XOPEN_SOURCE 700
# elif __STDC_VERSION__ >= 199901L
# define _XOPEN_SOURCE 600
# endif
#endif
/* Because, according to Microsoft, POSIX is deprecated. You've got
* to appreciate the chutzpah. */
#if defined(_MSC_VER) && !defined(_CRT_NONSTDC_NO_DEPRECATE)
# define _CRT_NONSTDC_NO_DEPRECATE
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
# include
#elif defined(_WIN32)
/* https://msdn.microsoft.com/en-us/library/tf4dy80a.aspx */
#endif
#include
#include
#include
#include
#include
#include
#include
#include
#if !defined(MUNIT_NO_NL_LANGINFO) && !defined(_WIN32)
#define MUNIT_NL_LANGINFO
#include
#include
#include
#endif
#if !defined(_WIN32)
# include
# include
# include
#else
# include
# include
# include
# if !defined(STDERR_FILENO)
# define STDERR_FILENO _fileno(stderr)
# endif
#endif
#include "munit.h"
#define MUNIT_STRINGIFY(x) #x
#define MUNIT_XSTRINGIFY(x) MUNIT_STRINGIFY(x)
#if defined(__GNUC__) || defined(__INTEL_COMPILER) || defined(__SUNPRO_CC) || defined(__IBMCPP__)
# define MUNIT_THREAD_LOCAL __thread
#elif (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201102L)) || defined(_Thread_local)
# define MUNIT_THREAD_LOCAL _Thread_local
#elif defined(_WIN32)
# define MUNIT_THREAD_LOCAL __declspec(thread)
#endif
/* MSVC 12.0 will emit a warning at /W4 for code like 'do { ... }
* while (0)', or 'do { ... } while (true)'. I'm pretty sure nobody
* at Microsoft compiles with /W4. */
#if defined(_MSC_VER) && (_MSC_VER <= 1800)
#pragma warning(disable: 4127)
#endif
#if defined(_WIN32) || defined(__EMSCRIPTEN__)
# define MUNIT_NO_FORK
#endif
#if defined(__EMSCRIPTEN__)
# define MUNIT_NO_BUFFER
#endif
/*** Logging ***/
static MunitLogLevel munit_log_level_visible = MUNIT_LOG_INFO;
static MunitLogLevel munit_log_level_fatal = MUNIT_LOG_ERROR;
#if defined(MUNIT_THREAD_LOCAL)
static MUNIT_THREAD_LOCAL bool munit_error_jmp_buf_valid = false;
static MUNIT_THREAD_LOCAL jmp_buf munit_error_jmp_buf;
#endif
/* At certain warning levels, mingw will trigger warnings about
* suggesting the format attribute, which we've explicity *not* set
* because it will then choke on our attempts to use the MS-specific
* I64 modifier for size_t (which we have to use since MSVC doesn't
* support the C99 z modifier). */
#if defined(__MINGW32__) || defined(__MINGW64__)
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wsuggest-attribute=format"
#endif
MUNIT_PRINTF(5,0)
static void
munit_logf_exv(MunitLogLevel level, FILE* fp, const char* filename, int line, const char* format, va_list ap) {
if (level < munit_log_level_visible)
return;
switch (level) {
case MUNIT_LOG_DEBUG:
fputs("Debug", fp);
break;
case MUNIT_LOG_INFO:
fputs("Info", fp);
break;
case MUNIT_LOG_WARNING:
fputs("Warning", fp);
break;
case MUNIT_LOG_ERROR:
fputs("Error", fp);
break;
default:
munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Invalid log level (%d)", level);
return;
}
fputs(": ", fp);
if (filename != NULL)
fprintf(fp, "%s:%d: ", filename, line);
vfprintf(fp, format, ap);
fputc('\n', fp);
}
MUNIT_PRINTF(3,4)
static void
munit_logf_internal(MunitLogLevel level, FILE* fp, const char* format, ...) {
va_list ap;
va_start(ap, format);
munit_logf_exv(level, fp, NULL, 0, format, ap);
va_end(ap);
}
static void
munit_log_internal(MunitLogLevel level, FILE* fp, const char* message) {
munit_logf_internal(level, fp, "%s", message);
}
void
munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...) {
va_list ap;
va_start(ap, format);
munit_logf_exv(level, stderr, filename, line, format, ap);
va_end(ap);
if (level >= munit_log_level_fatal) {
#if defined(MUNIT_THREAD_LOCAL)
if (munit_error_jmp_buf_valid)
longjmp(munit_error_jmp_buf, 1);
#endif
abort();
}
}
void
munit_errorf_ex(const char* filename, int line, const char* format, ...) {
va_list ap;
va_start(ap, format);
munit_logf_exv(MUNIT_LOG_ERROR, stderr, filename, line, format, ap);
va_end(ap);
#if defined(MUNIT_THREAD_LOCAL)
if (munit_error_jmp_buf_valid)
longjmp(munit_error_jmp_buf, 1);
#endif
abort();
}
#if defined(__MINGW32__) || defined(__MINGW64__)
#pragma GCC diagnostic pop
#endif
#if !defined(MUNIT_STRERROR_LEN)
# define MUNIT_STRERROR_LEN 80
#endif
static void
munit_log_errno(MunitLogLevel level, FILE* fp, const char* msg) {
#if defined(MUNIT_NO_STRERROR_R) || (defined(__MINGW32__) && !defined(MINGW_HAS_SECURE_API))
munit_logf_internal(level, fp, "%s: %s (%d)", msg, strerror(errno), errno);
#else
char munit_error_str[MUNIT_STRERROR_LEN];
munit_error_str[0] = '\0';
#if !defined(_WIN32)
strerror_r(errno, munit_error_str, MUNIT_STRERROR_LEN);
#else
strerror_s(munit_error_str, MUNIT_STRERROR_LEN, errno);
#endif
munit_logf_internal(level, fp, "%s: %s (%d)", msg, munit_error_str, errno);
#endif
}
/*** Memory allocation ***/
void*
munit_malloc_ex(const char* filename, int line, size_t size) {
void* ptr;
if (size == 0)
return NULL;
ptr = calloc(1, size);
if (MUNIT_UNLIKELY(ptr == NULL)) {
munit_logf_ex(MUNIT_LOG_ERROR, filename, line, "Failed to allocate %" MUNIT_SIZE_MODIFIER "u bytes.", size);
}
return ptr;
}
/*** Timer code ***/
#if defined(MUNIT_ENABLE_TIMING)
#define psnip_uint64_t munit_uint64_t
#define psnip_uint32_t munit_uint32_t
/* Code copied from portable-snippets
* . If you need to
* change something, please do it there so we can keep the code in
* sync. */
/* Clocks (v1)
* Portable Snippets - https://gitub.com/nemequ/portable-snippets
* Created by Evan Nemerson
*
* To the extent possible under law, the authors have waived all
* copyright and related or neighboring rights to this code. For
* details, see the Creative Commons Zero 1.0 Universal license at
* https://creativecommons.org/publicdomain/zero/1.0/
*/
#if !defined(PSNIP_CLOCK_H)
#define PSNIP_CLOCK_H
#if !defined(psnip_uint64_t)
# include "../exact-int/exact-int.h"
#endif
#if !defined(PSNIP_CLOCK_STATIC_INLINE)
# if defined(__GNUC__)
# define PSNIP_CLOCK__COMPILER_ATTRIBUTES __attribute__((__unused__))
# else
# define PSNIP_CLOCK__COMPILER_ATTRIBUTES
# endif
# define PSNIP_CLOCK__FUNCTION PSNIP_CLOCK__COMPILER_ATTRIBUTES static
#endif
enum PsnipClockType {
/* This clock provides the current time, in units since 1970-01-01
* 00:00:00 UTC not including leap seconds. In other words, UNIX
* time. Keep in mind that this clock doesn't account for leap
* seconds, and can go backwards (think NTP adjustments). */
PSNIP_CLOCK_TYPE_WALL = 1,
/* The CPU time is a clock which increases only when the current
* process is active (i.e., it doesn't increment while blocking on
* I/O). */
PSNIP_CLOCK_TYPE_CPU = 2,
/* Monotonic time is always running (unlike CPU time), but it only
ever moves forward unless you reboot the system. Things like NTP
adjustments have no effect on this clock. */
PSNIP_CLOCK_TYPE_MONOTONIC = 3
};
struct PsnipClockTimespec {
psnip_uint64_t seconds;
psnip_uint64_t nanoseconds;
};
/* Methods we support: */
#define PSNIP_CLOCK_METHOD_CLOCK_GETTIME 1
#define PSNIP_CLOCK_METHOD_TIME 2
#define PSNIP_CLOCK_METHOD_GETTIMEOFDAY 3
#define PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER 4
#define PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME 5
#define PSNIP_CLOCK_METHOD_CLOCK 6
#define PSNIP_CLOCK_METHOD_GETPROCESSTIMES 7
#define PSNIP_CLOCK_METHOD_GETRUSAGE 8
#define PSNIP_CLOCK_METHOD_GETSYSTEMTIMEPRECISEASFILETIME 9
#define PSNIP_CLOCK_METHOD_GETTICKCOUNT64 10
#include
#if defined(HEDLEY_UNREACHABLE)
# define PSNIP_CLOCK_UNREACHABLE() HEDLEY_UNREACHABLE()
#else
# define PSNIP_CLOCK_UNREACHABLE() assert(0)
#endif
/* Choose an implementation */
/* #undef PSNIP_CLOCK_WALL_METHOD */
/* #undef PSNIP_CLOCK_CPU_METHOD */
/* #undef PSNIP_CLOCK_MONOTONIC_METHOD */
/* We want to be able to detect the libc implementation, so we include
( isn't available everywhere). */
#if defined(__unix__) || defined(__unix) || defined(__linux__)
# include
# include
#endif
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
/* These are known to work without librt. If you know of others
* please let us know so we can add them. */
# if \
(defined(__GLIBC__) && (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17))) || \
(defined(__FreeBSD__))
# define PSNIP_CLOCK_HAVE_CLOCK_GETTIME
# elif !defined(PSNIP_CLOCK_NO_LIBRT)
# define PSNIP_CLOCK_HAVE_CLOCK_GETTIME
# endif
#endif
#if defined(_WIN32)
# if !defined(PSNIP_CLOCK_CPU_METHOD)
# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_GETPROCESSTIMES
# endif
# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER
# endif
#endif
#if defined(__MACH__) && !defined(__gnu_hurd__)
# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME
# endif
#endif
#if defined(PSNIP_CLOCK_HAVE_CLOCK_GETTIME)
# include
# if !defined(PSNIP_CLOCK_WALL_METHOD)
# if defined(CLOCK_REALTIME_PRECISE)
# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME_PRECISE
# elif !defined(__sun)
# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_WALL CLOCK_REALTIME
# endif
# endif
# if !defined(PSNIP_CLOCK_CPU_METHOD)
# if defined(_POSIX_CPUTIME) || defined(CLOCK_PROCESS_CPUTIME_ID)
# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_PROCESS_CPUTIME_ID
# elif defined(CLOCK_VIRTUAL)
# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_CPU CLOCK_VIRTUAL
# endif
# endif
# if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
# if defined(CLOCK_MONOTONIC_RAW)
# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC
# elif defined(CLOCK_MONOTONIC_PRECISE)
# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC_PRECISE
# elif defined(_POSIX_MONOTONIC_CLOCK) || defined(CLOCK_MONOTONIC)
# define PSNIP_CLOCK_MONOTONIC_METHOD PSNIP_CLOCK_METHOD_CLOCK_GETTIME
# define PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC CLOCK_MONOTONIC
# endif
# endif
#endif
#if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200112L)
# if !defined(PSNIP_CLOCK_WALL_METHOD)
# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_GETTIMEOFDAY
# endif
#endif
#if !defined(PSNIP_CLOCK_WALL_METHOD)
# define PSNIP_CLOCK_WALL_METHOD PSNIP_CLOCK_METHOD_TIME
#endif
#if !defined(PSNIP_CLOCK_CPU_METHOD)
# define PSNIP_CLOCK_CPU_METHOD PSNIP_CLOCK_METHOD_CLOCK
#endif
/* Primarily here for testing. */
#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD) && defined(PSNIP_CLOCK_REQUIRE_MONOTONIC)
# error No monotonic clock found.
#endif
/* Implementations */
#if \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK)) || \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_TIME))
# include
#endif
#if \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY))
# include
#endif
#if \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES)) || \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64))
# include
#endif
#if \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE))
# include
# include
#endif
#if \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME))
# include
# include
# include
#endif
/*** Implementations ***/
#define PSNIP_CLOCK_NSEC_PER_SEC ((psnip_uint32_t) (1000000000ULL))
#if \
(defined(PSNIP_CLOCK_CPU_METHOD) && (PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
(defined(PSNIP_CLOCK_WALL_METHOD) && (PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME)) || \
(defined(PSNIP_CLOCK_MONOTONIC_METHOD) && (PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME))
PSNIP_CLOCK__FUNCTION psnip_uint32_t
psnip_clock__clock_getres (clockid_t clk_id) {
struct timespec res;
int r;
r = clock_getres(clk_id, &res);
if (r != 0)
return 0;
return (psnip_uint32_t) (PSNIP_CLOCK_NSEC_PER_SEC / res.tv_nsec);
}
PSNIP_CLOCK__FUNCTION int
psnip_clock__clock_gettime (clockid_t clk_id, struct PsnipClockTimespec* res) {
struct timespec ts;
if (clock_gettime(clk_id, &ts) != 0)
return -10;
res->seconds = (psnip_uint64_t) (ts.tv_sec);
res->nanoseconds = (psnip_uint64_t) (ts.tv_nsec);
return 0;
}
#endif
PSNIP_CLOCK__FUNCTION psnip_uint32_t
psnip_clock_wall_get_precision (void) {
#if !defined(PSNIP_CLOCK_WALL_METHOD)
return 0;
#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_WALL);
#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY
return 1000000;
#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME
return 1;
#else
return 0;
#endif
}
PSNIP_CLOCK__FUNCTION int
psnip_clock_wall_get_time (struct PsnipClockTimespec* res) {
(void) res;
#if !defined(PSNIP_CLOCK_WALL_METHOD)
return -2;
#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_WALL, res);
#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_TIME
res->seconds = time(NULL);
res->nanoseconds = 0;
#elif defined(PSNIP_CLOCK_WALL_METHOD) && PSNIP_CLOCK_WALL_METHOD == PSNIP_CLOCK_METHOD_GETTIMEOFDAY
struct timeval tv;
if (gettimeofday(&tv, NULL) != 0)
return -6;
res->seconds = tv.tv_sec;
res->nanoseconds = tv.tv_usec * 1000;
#else
return -2;
#endif
return 0;
}
PSNIP_CLOCK__FUNCTION psnip_uint32_t
psnip_clock_cpu_get_precision (void) {
#if !defined(PSNIP_CLOCK_CPU_METHOD)
return 0;
#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_CPU);
#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK
return CLOCKS_PER_SEC;
#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES
return PSNIP_CLOCK_NSEC_PER_SEC / 100;
#else
return 0;
#endif
}
PSNIP_CLOCK__FUNCTION int
psnip_clock_cpu_get_time (struct PsnipClockTimespec* res) {
#if !defined(PSNIP_CLOCK_CPU_METHOD)
(void) res;
return -2;
#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_CPU, res);
#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_CLOCK
clock_t t = clock();
if (t == ((clock_t) -1))
return -5;
res->seconds = t / CLOCKS_PER_SEC;
res->nanoseconds = (t % CLOCKS_PER_SEC) * (PSNIP_CLOCK_NSEC_PER_SEC / CLOCKS_PER_SEC);
#elif defined(PSNIP_CLOCK_CPU_METHOD) && PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETPROCESSTIMES
FILETIME CreationTime, ExitTime, KernelTime, UserTime;
LARGE_INTEGER date, adjust;
if (!GetProcessTimes(GetCurrentProcess(), &CreationTime, &ExitTime, &KernelTime, &UserTime))
return -7;
/* http://www.frenk.com/2009/12/convert-filetime-to-unix-timestamp/ */
date.HighPart = UserTime.dwHighDateTime;
date.LowPart = UserTime.dwLowDateTime;
adjust.QuadPart = 11644473600000 * 10000;
date.QuadPart -= adjust.QuadPart;
res->seconds = date.QuadPart / 10000000;
res->nanoseconds = (date.QuadPart % 10000000) * (PSNIP_CLOCK_NSEC_PER_SEC / 100);
#elif PSNIP_CLOCK_CPU_METHOD == PSNIP_CLOCK_METHOD_GETRUSAGE
struct rusage usage;
if (getrusage(RUSAGE_SELF, &usage) != 0)
return -8;
res->seconds = usage.ru_utime.tv_sec;
res->nanoseconds = tv.tv_usec * 1000;
#else
(void) res;
return -2;
#endif
return 0;
}
PSNIP_CLOCK__FUNCTION psnip_uint32_t
psnip_clock_monotonic_get_precision (void) {
#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
return 0;
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
return psnip_clock__clock_getres(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC);
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME
static mach_timebase_info_data_t tbi = { 0, };
if (tbi.denom == 0)
mach_timebase_info(&tbi);
return (psnip_uint32_t) (tbi.numer / tbi.denom);
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64
return 1000;
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER
LARGE_INTEGER Frequency;
QueryPerformanceFrequency(&Frequency);
return (psnip_uint32_t) ((Frequency.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC) ? PSNIP_CLOCK_NSEC_PER_SEC : Frequency.QuadPart);
#else
return 0;
#endif
}
PSNIP_CLOCK__FUNCTION int
psnip_clock_monotonic_get_time (struct PsnipClockTimespec* res) {
#if !defined(PSNIP_CLOCK_MONOTONIC_METHOD)
(void) res;
return -2;
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_CLOCK_GETTIME
return psnip_clock__clock_gettime(PSNIP_CLOCK_CLOCK_GETTIME_MONOTONIC, res);
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_MACH_ABSOLUTE_TIME
psnip_uint64_t nsec = mach_absolute_time();
static mach_timebase_info_data_t tbi = { 0, };
if (tbi.denom == 0)
mach_timebase_info(&tbi);
nsec *= ((psnip_uint64_t) tbi.numer) / ((psnip_uint64_t) tbi.denom);
res->seconds = nsec / PSNIP_CLOCK_NSEC_PER_SEC;
res->nanoseconds = nsec % PSNIP_CLOCK_NSEC_PER_SEC;
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_QUERYPERFORMANCECOUNTER
LARGE_INTEGER t, f;
if (QueryPerformanceCounter(&t) == 0)
return -12;
QueryPerformanceFrequency(&f);
res->seconds = t.QuadPart / f.QuadPart;
res->nanoseconds = t.QuadPart % f.QuadPart;
if (f.QuadPart > PSNIP_CLOCK_NSEC_PER_SEC)
res->nanoseconds /= f.QuadPart / PSNIP_CLOCK_NSEC_PER_SEC;
else
res->nanoseconds *= PSNIP_CLOCK_NSEC_PER_SEC / f.QuadPart;
#elif defined(PSNIP_CLOCK_MONOTONIC_METHOD) && PSNIP_CLOCK_MONOTONIC_METHOD == PSNIP_CLOCK_METHOD_GETTICKCOUNT64
const ULONGLONG msec = GetTickCount64();
res->seconds = msec / 1000;
res->nanoseconds = sec % 1000;
#else
return -2;
#endif
return 0;
}
/* Returns the number of ticks per second for the specified clock.
* For example, a clock with millisecond precision would return 1000,
* and a clock with 1 second (such as the time() function) would
* return 1.
*
* If the requested clock isn't available, it will return 0.
* Hopefully this will be rare, but if it happens to you please let us
* know so we can work on finding a way to support your system.
*
* Note that different clocks on the same system often have a
* different precisions.
*/
PSNIP_CLOCK__FUNCTION psnip_uint32_t
psnip_clock_get_precision (enum PsnipClockType clock_type) {
switch (clock_type) {
case PSNIP_CLOCK_TYPE_MONOTONIC:
return psnip_clock_monotonic_get_precision ();
case PSNIP_CLOCK_TYPE_CPU:
return psnip_clock_cpu_get_precision ();
case PSNIP_CLOCK_TYPE_WALL:
return psnip_clock_wall_get_precision ();
}
PSNIP_CLOCK_UNREACHABLE();
return 0;
}
/* Set the provided timespec to the requested time. Returns 0 on
* success, or a negative value on failure. */
PSNIP_CLOCK__FUNCTION int
psnip_clock_get_time (enum PsnipClockType clock_type, struct PsnipClockTimespec* res) {
assert(res != NULL);
switch (clock_type) {
case PSNIP_CLOCK_TYPE_MONOTONIC:
return psnip_clock_monotonic_get_time (res);
case PSNIP_CLOCK_TYPE_CPU:
return psnip_clock_cpu_get_time (res);
case PSNIP_CLOCK_TYPE_WALL:
return psnip_clock_wall_get_time (res);
}
return -1;
}
#endif /* !defined(PSNIP_CLOCK_H) */
static psnip_uint64_t
munit_clock_get_elapsed(struct PsnipClockTimespec* start, struct PsnipClockTimespec* end) {
psnip_uint64_t r = (end->seconds - start->seconds) * PSNIP_CLOCK_NSEC_PER_SEC;
if (end->nanoseconds < start->nanoseconds) {
r -= (start->nanoseconds - end->nanoseconds);
} else {
r += (end->nanoseconds - start->nanoseconds);
}
return r;
}
#else
# include
#endif /* defined(MUNIT_ENABLE_TIMING) */
/*** PRNG stuff ***/
/* This is (unless I screwed up, which is entirely possible) the
* version of PCG with 32-bit state. It was chosen because it has a
* small enough state that we should reliably be able to use CAS
* instead of requiring a lock for thread-safety.
*
* If I did screw up, I probably will not bother changing it unless
* there is a significant bias. It's really not important this be
* particularly strong, as long as it is fairly random it's much more
* important that it be reproducible, so bug reports have a better
* chance of being reproducible. */
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__) && !defined(__EMSCRIPTEN__) && (!defined(__GNUC_MINOR__) || (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ > 8))
# define HAVE_STDATOMIC
#elif defined(__clang__)
# if __has_extension(c_atomic)
# define HAVE_CLANG_ATOMICS
# endif
#endif
/* Workaround for http://llvm.org/bugs/show_bug.cgi?id=26911 */
#if defined(__clang__) && defined(_WIN32)
# undef HAVE_STDATOMIC
# if defined(__c2__)
# undef HAVE_CLANG_ATOMICS
# endif
#endif
#if defined(_OPENMP)
# define ATOMIC_UINT32_T uint32_t
# define ATOMIC_UINT32_INIT(x) (x)
#elif defined(HAVE_STDATOMIC)
# include
# define ATOMIC_UINT32_T _Atomic uint32_t
# define ATOMIC_UINT32_INIT(x) ATOMIC_VAR_INIT(x)
#elif defined(HAVE_CLANG_ATOMICS)
# define ATOMIC_UINT32_T _Atomic uint32_t
# define ATOMIC_UINT32_INIT(x) (x)
#elif defined(_WIN32)
# define ATOMIC_UINT32_T volatile LONG
# define ATOMIC_UINT32_INIT(x) (x)
#else
# define ATOMIC_UINT32_T volatile uint32_t
# define ATOMIC_UINT32_INIT(x) (x)
#endif
static ATOMIC_UINT32_T munit_rand_state = ATOMIC_UINT32_INIT(42);
#if defined(_OPENMP)
static inline void
munit_atomic_store(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T value) {
#pragma omp critical (munit_atomics)
*dest = value;
}
static inline uint32_t
munit_atomic_load(ATOMIC_UINT32_T* src) {
int ret;
#pragma omp critical (munit_atomics)
ret = *src;
return ret;
}
static inline uint32_t
munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) {
bool ret;
#pragma omp critical (munit_atomics)
{
if (*dest == *expected) {
*dest = desired;
ret = true;
} else {
ret = false;
}
}
return ret;
}
#elif defined(HAVE_STDATOMIC)
# define munit_atomic_store(dest, value) atomic_store(dest, value)
# define munit_atomic_load(src) atomic_load(src)
# define munit_atomic_cas(dest, expected, value) atomic_compare_exchange_weak(dest, expected, value)
#elif defined(HAVE_CLANG_ATOMICS)
# define munit_atomic_store(dest, value) __c11_atomic_store(dest, value, __ATOMIC_SEQ_CST)
# define munit_atomic_load(src) __c11_atomic_load(src, __ATOMIC_SEQ_CST)
# define munit_atomic_cas(dest, expected, value) __c11_atomic_compare_exchange_weak(dest, expected, value, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
#elif defined(__GNUC__) && (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 7)
# define munit_atomic_store(dest, value) __atomic_store_n(dest, value, __ATOMIC_SEQ_CST)
# define munit_atomic_load(src) __atomic_load_n(src, __ATOMIC_SEQ_CST)
# define munit_atomic_cas(dest, expected, value) __atomic_compare_exchange_n(dest, expected, value, true, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
#elif defined(__GNUC__) && (__GNUC__ >= 4)
# define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0)
# define munit_atomic_load(src) (*(src))
# define munit_atomic_cas(dest, expected, value) __sync_bool_compare_and_swap(dest, *expected, value)
#elif defined(_WIN32) /* Untested */
# define munit_atomic_store(dest,value) do { *(dest) = (value); } while (0)
# define munit_atomic_load(src) (*(src))
# define munit_atomic_cas(dest, expected, value) InterlockedCompareExchange((dest), (value), *(expected))
#else
# warning No atomic implementation, PRNG will not be thread-safe
# define munit_atomic_store(dest, value) do { *(dest) = (value); } while (0)
# define munit_atomic_load(src) (*(src))
static inline bool
munit_atomic_cas(ATOMIC_UINT32_T* dest, ATOMIC_UINT32_T* expected, ATOMIC_UINT32_T desired) {
if (*dest == *expected) {
*dest = desired;
return true;
} else {
return false;
}
}
#endif
#define MUNIT_PRNG_MULTIPLIER (747796405U)
#define MUNIT_PRNG_INCREMENT (1729U)
static munit_uint32_t
munit_rand_next_state(munit_uint32_t state) {
return state * MUNIT_PRNG_MULTIPLIER + MUNIT_PRNG_INCREMENT;
}
static munit_uint32_t
munit_rand_from_state(munit_uint32_t state) {
munit_uint32_t res = ((state >> ((state >> 28) + 4)) ^ state) * (277803737U);
res ^= res >> 22;
return res;
}
void
munit_rand_seed(munit_uint32_t seed) {
munit_uint32_t state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT);
munit_atomic_store(&munit_rand_state, state);
}
static munit_uint32_t
munit_rand_generate_seed(void) {
munit_uint32_t seed, state;
#if defined(MUNIT_ENABLE_TIMING)
struct PsnipClockTimespec wc = { 0, 0 };
psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wc);
seed = (munit_uint32_t) wc.nanoseconds;
#else
seed = (munit_uint32_t) time(NULL);
#endif
state = munit_rand_next_state(seed + MUNIT_PRNG_INCREMENT);
return munit_rand_from_state(state);
}
static munit_uint32_t
munit_rand_state_uint32(munit_uint32_t* state) {
const munit_uint32_t old = *state;
*state = munit_rand_next_state(old);
return munit_rand_from_state(old);
}
munit_uint32_t
munit_rand_uint32(void) {
munit_uint32_t old, state;
do {
old = munit_atomic_load(&munit_rand_state);
state = munit_rand_next_state(old);
} while (!munit_atomic_cas(&munit_rand_state, &old, state));
return munit_rand_from_state(old);
}
static void
munit_rand_state_memory(munit_uint32_t* state, size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) {
size_t members_remaining = size / sizeof(munit_uint32_t);
size_t bytes_remaining = size % sizeof(munit_uint32_t);
munit_uint8_t* b = data;
munit_uint32_t rv;
while (members_remaining-- > 0) {
rv = munit_rand_state_uint32(state);
memcpy(b, &rv, sizeof(munit_uint32_t));
b += sizeof(munit_uint32_t);
}
if (bytes_remaining != 0) {
rv = munit_rand_state_uint32(state);
memcpy(b, &rv, bytes_remaining);
}
}
void
munit_rand_memory(size_t size, munit_uint8_t data[MUNIT_ARRAY_PARAM(size)]) {
munit_uint32_t old, state;
do {
state = old = munit_atomic_load(&munit_rand_state);
munit_rand_state_memory(&state, size, data);
} while (!munit_atomic_cas(&munit_rand_state, &old, state));
}
static munit_uint32_t
munit_rand_state_at_most(munit_uint32_t* state, munit_uint32_t salt, munit_uint32_t max) {
/* We want (UINT32_MAX + 1) % max, which in unsigned arithmetic is the same
* as (UINT32_MAX + 1 - max) % max = -max % max. We compute -max using not
* to avoid compiler warnings.
*/
const munit_uint32_t min = (~max + 1U) % max;
munit_uint32_t x;
if (max == (~((munit_uint32_t) 0U)))
return munit_rand_state_uint32(state) ^ salt;
max++;
do {
x = munit_rand_state_uint32(state) ^ salt;
} while (x < min);
return x % max;
}
static munit_uint32_t
munit_rand_at_most(munit_uint32_t salt, munit_uint32_t max) {
munit_uint32_t old, state;
munit_uint32_t retval;
do {
state = old = munit_atomic_load(&munit_rand_state);
retval = munit_rand_state_at_most(&state, salt, max);
} while (!munit_atomic_cas(&munit_rand_state, &old, state));
return retval;
}
int
munit_rand_int_range(int min, int max) {
munit_uint64_t range = (munit_uint64_t) max - (munit_uint64_t) min;
if (min > max)
return munit_rand_int_range(max, min);
if (range > (~((munit_uint32_t) 0U)))
range = (~((munit_uint32_t) 0U));
return min + munit_rand_at_most(0, (munit_uint32_t) range);
}
double
munit_rand_double(void) {
munit_uint32_t old, state;
double retval = 0.0;
do {
state = old = munit_atomic_load(&munit_rand_state);
/* See http://mumble.net/~campbell/tmp/random_real.c for how to do
* this right. Patches welcome if you feel that this is too
* biased. */
retval = munit_rand_state_uint32(&state) / ((~((munit_uint32_t) 0U)) + 1.0);
} while (!munit_atomic_cas(&munit_rand_state, &old, state));
return retval;
}
/*** Test suite handling ***/
typedef struct {
unsigned int successful;
unsigned int skipped;
unsigned int failed;
unsigned int errored;
#if defined(MUNIT_ENABLE_TIMING)
munit_uint64_t cpu_clock;
munit_uint64_t wall_clock;
#endif
} MunitReport;
typedef struct {
const char* prefix;
const MunitSuite* suite;
const char** tests;
munit_uint32_t seed;
unsigned int iterations;
MunitParameter* parameters;
bool single_parameter_mode;
void* user_data;
MunitReport report;
bool colorize;
bool fork;
bool show_stderr;
bool fatal_failures;
} MunitTestRunner;
const char*
munit_parameters_get(const MunitParameter params[], const char* key) {
const MunitParameter* param;
for (param = params ; param != NULL && param->name != NULL ; param++)
if (strcmp(param->name, key) == 0)
return param->value;
return NULL;
}
#if defined(MUNIT_ENABLE_TIMING)
static void
munit_print_time(FILE* fp, munit_uint64_t nanoseconds) {
fprintf(fp, "%" MUNIT_TEST_TIME_FORMAT, ((double) nanoseconds) / ((double) PSNIP_CLOCK_NSEC_PER_SEC));
}
#endif
/* Add a paramter to an array of parameters. */
static MunitResult
munit_parameters_add(size_t* params_size, MunitParameter* params[MUNIT_ARRAY_PARAM(*params_size)], char* name, char* value) {
*params = realloc(*params, sizeof(MunitParameter) * (*params_size + 2));
if (*params == NULL)
return MUNIT_ERROR;
(*params)[*params_size].name = name;
(*params)[*params_size].value = value;
(*params_size)++;
(*params)[*params_size].name = NULL;
(*params)[*params_size].value = NULL;
return MUNIT_OK;
}
/* Concatenate two strings, but just return one of the components
* unaltered if the other is NULL or "". */
static char*
munit_maybe_concat(size_t* len, char* prefix, char* suffix) {
char* res;
size_t res_l;
const size_t prefix_l = prefix != NULL ? strlen(prefix) : 0;
const size_t suffix_l = suffix != NULL ? strlen(suffix) : 0;
if (prefix_l == 0 && suffix_l == 0) {
res = NULL;
res_l = 0;
} else if (prefix_l == 0 && suffix_l != 0) {
res = suffix;
res_l = suffix_l;
} else if (prefix_l != 0 && suffix_l == 0) {
res = prefix;
res_l = prefix_l;
} else {
res_l = prefix_l + suffix_l;
res = malloc(res_l + 1);
memcpy(res, prefix, prefix_l);
memcpy(res + prefix_l, suffix, suffix_l);
res[res_l] = 0;
}
if (len != NULL)
*len = res_l;
return res;
}
/* Possbily free a string returned by munit_maybe_concat. */
static void
munit_maybe_free_concat(char* s, const char* prefix, const char* suffix) {
if (prefix != s && suffix != s)
free(s);
}
/* Cheap string hash function, just used to salt the PRNG. */
static munit_uint32_t
munit_str_hash(const char* name) {
const char *p;
munit_uint32_t h = 5381U;
for (p = name; *p != '\0'; p++)
h = (h << 5) + h + *p;
return h;
}
static void
munit_splice(int from, int to) {
munit_uint8_t buf[1024];
#if !defined(_WIN32)
ssize_t len;
ssize_t bytes_written;
ssize_t write_res;
#else
int len;
int bytes_written;
int write_res;
#endif
do {
len = read(from, buf, sizeof(buf));
if (len > 0) {
bytes_written = 0;
do {
write_res = write(to, buf + bytes_written, len - bytes_written);
if (write_res < 0)
break;
bytes_written += write_res;
} while (bytes_written < len);
}
else
break;
} while (true);
}
/* This is the part that should be handled in the child process */
static MunitResult
munit_test_runner_exec(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[], MunitReport* report) {
unsigned int iterations = runner->iterations;
MunitResult result = MUNIT_FAIL;
#if defined(MUNIT_ENABLE_TIMING)
struct PsnipClockTimespec wall_clock_begin = { 0, 0 }, wall_clock_end = { 0, 0 };
struct PsnipClockTimespec cpu_clock_begin = { 0, 0 }, cpu_clock_end = { 0, 0 };
#endif
unsigned int i = 0;
if ((test->options & MUNIT_TEST_OPTION_SINGLE_ITERATION) == MUNIT_TEST_OPTION_SINGLE_ITERATION)
iterations = 1;
else if (iterations == 0)
iterations = runner->suite->iterations;
munit_rand_seed(runner->seed);
do {
void* data = (test->setup == NULL) ? runner->user_data : test->setup(params, runner->user_data);
#if defined(MUNIT_ENABLE_TIMING)
psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_begin);
psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_begin);
#endif
result = test->test(params, data);
#if defined(MUNIT_ENABLE_TIMING)
psnip_clock_get_time(PSNIP_CLOCK_TYPE_WALL, &wall_clock_end);
psnip_clock_get_time(PSNIP_CLOCK_TYPE_CPU, &cpu_clock_end);
#endif
if (test->tear_down != NULL)
test->tear_down(data);
if (MUNIT_LIKELY(result == MUNIT_OK)) {
report->successful++;
#if defined(MUNIT_ENABLE_TIMING)
report->wall_clock += munit_clock_get_elapsed(&wall_clock_begin, &wall_clock_end);
report->cpu_clock += munit_clock_get_elapsed(&cpu_clock_begin, &cpu_clock_end);
#endif
} else {
switch ((int) result) {
case MUNIT_SKIP:
report->skipped++;
break;
case MUNIT_FAIL:
report->failed++;
break;
case MUNIT_ERROR:
report->errored++;
break;
default:
break;
}
break;
}
} while (++i < iterations);
return result;
}
#if defined(MUNIT_EMOTICON)
# define MUNIT_RESULT_STRING_OK ":)"
# define MUNIT_RESULT_STRING_SKIP ":|"
# define MUNIT_RESULT_STRING_FAIL ":("
# define MUNIT_RESULT_STRING_ERROR ":o"
# define MUNIT_RESULT_STRING_TODO ":/"
#else
# define MUNIT_RESULT_STRING_OK "OK "
# define MUNIT_RESULT_STRING_SKIP "SKIP "
# define MUNIT_RESULT_STRING_FAIL "FAIL "
# define MUNIT_RESULT_STRING_ERROR "ERROR"
# define MUNIT_RESULT_STRING_TODO "TODO "
#endif
static void
munit_test_runner_print_color(const MunitTestRunner* runner, const char* string, char color) {
if (runner->colorize)
fprintf(MUNIT_OUTPUT_FILE, "\x1b[3%cm%s\x1b[39m", color, string);
else
fputs(string, MUNIT_OUTPUT_FILE);
}
#if !defined(MUNIT_NO_BUFFER)
static int
munit_replace_stderr(FILE* stderr_buf) {
if (stderr_buf != NULL) {
const int orig_stderr = dup(STDERR_FILENO);
int errfd = fileno(stderr_buf);
if (MUNIT_UNLIKELY(errfd == -1)) {
exit(EXIT_FAILURE);
}
dup2(errfd, STDERR_FILENO);
return orig_stderr;
}
return -1;
}
static void
munit_restore_stderr(int orig_stderr) {
if (orig_stderr != -1) {
dup2(orig_stderr, STDERR_FILENO);
close(orig_stderr);
}
}
#endif /* !defined(MUNIT_NO_BUFFER) */
/* Run a test with the specified parameters. */
static void
munit_test_runner_run_test_with_params(MunitTestRunner* runner, const MunitTest* test, const MunitParameter params[]) {
MunitResult result = MUNIT_OK;
MunitReport report = {
0, 0, 0, 0,
#if defined(MUNIT_ENABLE_TIMING)
0, 0
#endif
};
unsigned int output_l;
bool first;
const MunitParameter* param;
FILE* stderr_buf;
#if !defined(MUNIT_NO_FORK)
int pipefd[2];
pid_t fork_pid;
ssize_t bytes_written = 0;
ssize_t write_res;
ssize_t bytes_read = 0;
ssize_t read_res;
int status = 0;
pid_t changed_pid;
#endif
if (params != NULL) {
output_l = 2;
fputs(" ", MUNIT_OUTPUT_FILE);
first = true;
for (param = params ; param != NULL && param->name != NULL ; param++) {
if (!first) {
fputs(", ", MUNIT_OUTPUT_FILE);
output_l += 2;
} else {
first = false;
}
output_l += fprintf(MUNIT_OUTPUT_FILE, "%s=%s", param->name, param->value);
}
while (output_l++ < MUNIT_TEST_NAME_LEN) {
fputc(' ', MUNIT_OUTPUT_FILE);
}
}
fflush(MUNIT_OUTPUT_FILE);
stderr_buf = NULL;
#if !defined(_WIN32) || defined(__MINGW32__)
stderr_buf = tmpfile();
#else
tmpfile_s(&stderr_buf);
#endif
if (stderr_buf == NULL) {
munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create buffer for stderr");
result = MUNIT_ERROR;
goto print_result;
}
#if !defined(MUNIT_NO_FORK)
if (runner->fork) {
pipefd[0] = -1;
pipefd[1] = -1;
if (pipe(pipefd) != 0) {
munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to create pipe");
result = MUNIT_ERROR;
goto print_result;
}
fork_pid = fork();
if (fork_pid == 0) {
#if !defined(MUNIT_NO_BUFFER)
int orig_stderr;
#endif
close(pipefd[0]);
#if !defined(MUNIT_NO_BUFFER)
orig_stderr = munit_replace_stderr(stderr_buf);
#endif
munit_test_runner_exec(runner, test, params, &report);
#if !defined(MUNIT_NO_BUFFER)
/* Note that we don't restore stderr. This is so we can buffer
* things written to stderr later on (such as by
* asan/tsan/ubsan, valgrind, etc.) */
close(orig_stderr);
#endif
do {
write_res = write(pipefd[1], ((munit_uint8_t*) (&report)) + bytes_written, sizeof(report) - bytes_written);
if (write_res < 0) {
if (stderr_buf != NULL) {
munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to write to pipe");
}
exit(EXIT_FAILURE);
}
bytes_written += write_res;
} while ((size_t) bytes_written < sizeof(report));
if (stderr_buf != NULL)
fclose(stderr_buf);
close(pipefd[1]);
exit(EXIT_SUCCESS);
} else if (fork_pid == -1) {
close(pipefd[0]);
close(pipefd[1]);
if (stderr_buf != NULL) {
munit_log_errno(MUNIT_LOG_ERROR, stderr, "unable to fork");
}
report.errored++;
result = MUNIT_ERROR;
} else {
close(pipefd[1]);
do {
read_res = read(pipefd[0], ((munit_uint8_t*) (&report)) + bytes_read, sizeof(report) - bytes_read);
if (read_res < 1)
break;
bytes_read += read_res;
} while (bytes_read < (ssize_t) sizeof(report));
changed_pid = waitpid(fork_pid, &status, 0);
if (MUNIT_LIKELY(changed_pid == fork_pid) && MUNIT_LIKELY(WIFEXITED(status))) {
if (bytes_read != sizeof(report)) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited unexpectedly with status %d", WEXITSTATUS(status));
report.errored++;
} else if (WEXITSTATUS(status) != EXIT_SUCCESS) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child exited with status %d", WEXITSTATUS(status));
report.errored++;
}
} else {
if (WIFSIGNALED(status)) {
#if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 700)
munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d (%s)", WTERMSIG(status), strsignal(WTERMSIG(status)));
#else
munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child killed by signal %d", WTERMSIG(status));
#endif
} else if (WIFSTOPPED(status)) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr_buf, "child stopped by signal %d", WSTOPSIG(status));
}
report.errored++;
}
close(pipefd[0]);
waitpid(fork_pid, NULL, 0);
}
} else
#endif
{
#if !defined(MUNIT_NO_BUFFER)
const volatile int orig_stderr = munit_replace_stderr(stderr_buf);
#endif
#if defined(MUNIT_THREAD_LOCAL)
if (MUNIT_UNLIKELY(setjmp(munit_error_jmp_buf) != 0)) {
result = MUNIT_FAIL;
report.failed++;
} else {
munit_error_jmp_buf_valid = true;
result = munit_test_runner_exec(runner, test, params, &report);
}
#else
result = munit_test_runner_exec(runner, test, params, &report);
#endif
#if !defined(MUNIT_NO_BUFFER)
munit_restore_stderr(orig_stderr);
#endif
/* Here just so that the label is used on Windows and we don't get
* a warning */
goto print_result;
}
print_result:
fputs("[ ", MUNIT_OUTPUT_FILE);
if ((test->options & MUNIT_TEST_OPTION_TODO) == MUNIT_TEST_OPTION_TODO) {
if (report.failed != 0 || report.errored != 0 || report.skipped != 0) {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_TODO, '3');
result = MUNIT_OK;
} else {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1');
if (MUNIT_LIKELY(stderr_buf != NULL))
munit_log_internal(MUNIT_LOG_ERROR, stderr_buf, "Test marked TODO, but was successful.");
runner->report.failed++;
result = MUNIT_ERROR;
}
} else if (report.failed > 0) {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_FAIL, '1');
runner->report.failed++;
result = MUNIT_FAIL;
} else if (report.errored > 0) {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_ERROR, '1');
runner->report.errored++;
result = MUNIT_ERROR;
} else if (report.skipped > 0) {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_SKIP, '3');
runner->report.skipped++;
result = MUNIT_SKIP;
} else if (report.successful > 1) {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2');
#if defined(MUNIT_ENABLE_TIMING)
fputs(" ] [ ", MUNIT_OUTPUT_FILE);
munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock / report.successful);
fputs(" / ", MUNIT_OUTPUT_FILE);
munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock / report.successful);
fprintf(MUNIT_OUTPUT_FILE, " CPU ]\n %-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s Total: [ ", "");
munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock);
fputs(" / ", MUNIT_OUTPUT_FILE);
munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock);
fputs(" CPU", MUNIT_OUTPUT_FILE);
#endif
runner->report.successful++;
result = MUNIT_OK;
} else if (report.successful > 0) {
munit_test_runner_print_color(runner, MUNIT_RESULT_STRING_OK, '2');
#if defined(MUNIT_ENABLE_TIMING)
fputs(" ] [ ", MUNIT_OUTPUT_FILE);
munit_print_time(MUNIT_OUTPUT_FILE, report.wall_clock);
fputs(" / ", MUNIT_OUTPUT_FILE);
munit_print_time(MUNIT_OUTPUT_FILE, report.cpu_clock);
fputs(" CPU", MUNIT_OUTPUT_FILE);
#endif
runner->report.successful++;
result = MUNIT_OK;
}
fputs(" ]\n", MUNIT_OUTPUT_FILE);
if (stderr_buf != NULL) {
if (result == MUNIT_FAIL || result == MUNIT_ERROR || runner->show_stderr) {
fflush(MUNIT_OUTPUT_FILE);
rewind(stderr_buf);
munit_splice(fileno(stderr_buf), STDERR_FILENO);
fflush(stderr);
}
fclose(stderr_buf);
}
}
static void
munit_test_runner_run_test_wild(MunitTestRunner* runner,
const MunitTest* test,
const char* test_name,
MunitParameter* params,
MunitParameter* p) {
const MunitParameterEnum* pe;
char** values;
MunitParameter* next;
for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) {
if (p->name == pe->name)
break;
}
if (pe == NULL)
return;
for (values = pe->values ; *values != NULL ; values++) {
next = p + 1;
p->value = *values;
if (next->name == NULL) {
munit_test_runner_run_test_with_params(runner, test, params);
} else {
munit_test_runner_run_test_wild(runner, test, test_name, params, next);
}
if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0))
break;
}
}
/* Run a single test, with every combination of parameters
* requested. */
static void
munit_test_runner_run_test(MunitTestRunner* runner,
const MunitTest* test,
const char* prefix) {
char* test_name = munit_maybe_concat(NULL, (char*) prefix, (char*) test->name);
/* The array of parameters to pass to
* munit_test_runner_run_test_with_params */
MunitParameter* params = NULL;
size_t params_l = 0;
/* Wildcard parameters are parameters which have possible values
* specified in the test, but no specific value was passed to the
* CLI. That means we want to run the test once for every
* possible combination of parameter values or, if --single was
* passed to the CLI, a single time with a random set of
* parameters. */
MunitParameter* wild_params = NULL;
size_t wild_params_l = 0;
const MunitParameterEnum* pe;
const MunitParameter* cli_p;
bool filled;
unsigned int possible;
char** vals;
size_t first_wild;
const MunitParameter* wp;
int pidx;
munit_rand_seed(runner->seed);
fprintf(MUNIT_OUTPUT_FILE, "%-" MUNIT_XSTRINGIFY(MUNIT_TEST_NAME_LEN) "s", test_name);
if (test->parameters == NULL) {
/* No parameters. Simple, nice. */
munit_test_runner_run_test_with_params(runner, test, NULL);
} else {
fputc('\n', MUNIT_OUTPUT_FILE);
for (pe = test->parameters ; pe != NULL && pe->name != NULL ; pe++) {
/* Did we received a value for this parameter from the CLI? */
filled = false;
for (cli_p = runner->parameters ; cli_p != NULL && cli_p->name != NULL ; cli_p++) {
if (strcmp(cli_p->name, pe->name) == 0) {
if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, cli_p->value) != MUNIT_OK))
goto cleanup;
filled = true;
break;
}
}
if (filled)
continue;
/* Nothing from CLI, is the enum NULL/empty? We're not a
* fuzzer… */
if (pe->values == NULL || pe->values[0] == NULL)
continue;
/* If --single was passed to the CLI, choose a value from the
* list of possibilities randomly. */
if (runner->single_parameter_mode) {
possible = 0;
for (vals = pe->values ; *vals != NULL ; vals++)
possible++;
/* We want the tests to be reproducible, even if you're only
* running a single test, but we don't want every test with
* the same number of parameters to choose the same parameter
* number, so use the test name as a primitive salt. */
pidx = munit_rand_at_most(munit_str_hash(test_name), possible - 1);
if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[pidx]) != MUNIT_OK))
goto cleanup;
} else {
/* We want to try every permutation. Put in a placeholder
* entry, we'll iterate through them later. */
if (MUNIT_UNLIKELY(munit_parameters_add(&wild_params_l, &wild_params, pe->name, NULL) != MUNIT_OK))
goto cleanup;
}
}
if (wild_params_l != 0) {
first_wild = params_l;
for (wp = wild_params ; wp != NULL && wp->name != NULL ; wp++) {
for (pe = test->parameters ; pe != NULL && pe->name != NULL && pe->values != NULL ; pe++) {
if (strcmp(wp->name, pe->name) == 0) {
if (MUNIT_UNLIKELY(munit_parameters_add(¶ms_l, ¶ms, pe->name, pe->values[0]) != MUNIT_OK))
goto cleanup;
}
}
}
munit_test_runner_run_test_wild(runner, test, test_name, params, params + first_wild);
} else {
munit_test_runner_run_test_with_params(runner, test, params);
}
cleanup:
free(params);
free(wild_params);
}
munit_maybe_free_concat(test_name, prefix, test->name);
}
/* Recurse through the suite and run all the tests. If a list of
* tests to run was provied on the command line, run only those
* tests. */
static void
munit_test_runner_run_suite(MunitTestRunner* runner,
const MunitSuite* suite,
const char* prefix) {
size_t pre_l;
char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix);
const MunitTest* test;
const char** test_name;
const MunitSuite* child_suite;
/* Run the tests. */
for (test = suite->tests ; test != NULL && test->test != NULL ; test++) {
if (runner->tests != NULL) { /* Specific tests were requested on the CLI */
for (test_name = runner->tests ; test_name != NULL && *test_name != NULL ; test_name++) {
if ((pre_l == 0 || strncmp(pre, *test_name, pre_l) == 0) &&
strncmp(test->name, *test_name + pre_l, strlen(*test_name + pre_l)) == 0) {
munit_test_runner_run_test(runner, test, pre);
if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0))
goto cleanup;
}
}
} else { /* Run all tests */
munit_test_runner_run_test(runner, test, pre);
}
}
if (runner->fatal_failures && (runner->report.failed != 0 || runner->report.errored != 0))
goto cleanup;
/* Run any child suites. */
for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) {
munit_test_runner_run_suite(runner, child_suite, pre);
}
cleanup:
munit_maybe_free_concat(pre, prefix, suite->prefix);
}
static void
munit_test_runner_run(MunitTestRunner* runner) {
munit_test_runner_run_suite(runner, runner->suite, NULL);
}
static void
munit_print_help(int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)], void* user_data, const MunitArgument arguments[]) {
const MunitArgument* arg;
(void) argc;
printf("USAGE: %s [OPTIONS...] [TEST...]\n\n", argv[0]);
puts(" --seed SEED\n"
" Value used to seed the PRNG. Must be a 32-bit integer in decimal\n"
" notation with no separators (commas, decimals, spaces, etc.), or\n"
" hexidecimal prefixed by \"0x\".\n"
" --iterations N\n"
" Run each test N times. 0 means the default number.\n"
" --param name value\n"
" A parameter key/value pair which will be passed to any test with\n"
" takes a parameter of that name. If not provided, the test will be\n"
" run once for each possible parameter value.\n"
" --list Write a list of all available tests.\n"
" --list-params\n"
" Write a list of all available tests and their possible parameters.\n"
" --single Run each parameterized test in a single configuration instead of\n"
" every possible combination\n"
" --log-visible debug|info|warning|error\n"
" --log-fatal debug|info|warning|error\n"
" Set the level at which messages of different severities are visible,\n"
" or cause the test to terminate.\n"
#if !defined(MUNIT_NO_FORK)
" --no-fork Do not execute tests in a child process. If this option is supplied\n"
" and a test crashes (including by failing an assertion), no further\n"
" tests will be performed.\n"
#endif
" --fatal-failures\n"
" Stop executing tests as soon as a failure is found.\n"
" --show-stderr\n"
" Show data written to stderr by the tests, even if the test succeeds.\n"
" --color auto|always|never\n"
" Colorize (or don't) the output.\n"
/* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
" --help Print this help message and exit.\n");
#if defined(MUNIT_NL_LANGINFO)
setlocale(LC_ALL, "");
fputs((strcasecmp("UTF-8", nl_langinfo(CODESET)) == 0) ? "µnit" : "munit", stdout);
#else
puts("munit");
#endif
printf(" %d.%d.%d\n"
"Full documentation at: https://nemequ.github.io/munit/\n",
(MUNIT_CURRENT_VERSION >> 16) & 0xff,
(MUNIT_CURRENT_VERSION >> 8) & 0xff,
(MUNIT_CURRENT_VERSION >> 0) & 0xff);
for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++)
arg->write_help(arg, user_data);
}
static const MunitArgument*
munit_arguments_find(const MunitArgument arguments[], const char* name) {
const MunitArgument* arg;
for (arg = arguments ; arg != NULL && arg->name != NULL ; arg++)
if (strcmp(arg->name, name) == 0)
return arg;
return NULL;
}
static void
munit_suite_list_tests(const MunitSuite* suite, bool show_params, const char* prefix) {
size_t pre_l;
char* pre = munit_maybe_concat(&pre_l, (char*) prefix, (char*) suite->prefix);
const MunitTest* test;
const MunitParameterEnum* params;
bool first;
char** val;
const MunitSuite* child_suite;
for (test = suite->tests ;
test != NULL && test->name != NULL ;
test++) {
if (pre != NULL)
fputs(pre, stdout);
puts(test->name);
if (show_params) {
for (params = test->parameters ;
params != NULL && params->name != NULL ;
params++) {
fprintf(stdout, " - %s: ", params->name);
if (params->values == NULL) {
puts("Any");
} else {
first = true;
for (val = params->values ;
*val != NULL ;
val++ ) {
if(!first) {
fputs(", ", stdout);
} else {
first = false;
}
fputs(*val, stdout);
}
putc('\n', stdout);
}
}
}
}
for (child_suite = suite->suites ; child_suite != NULL && child_suite->prefix != NULL ; child_suite++) {
munit_suite_list_tests(child_suite, show_params, pre);
}
munit_maybe_free_concat(pre, prefix, suite->prefix);
}
static bool
munit_stream_supports_ansi(FILE *stream) {
#if !defined(_WIN32)
return isatty(fileno(stream));
#else
#if !defined(__MINGW32__)
size_t ansicon_size = 0;
#endif
if (isatty(fileno(stream))) {
#if !defined(__MINGW32__)
getenv_s(&ansicon_size, NULL, 0, "ANSICON");
return ansicon_size != 0;
#else
return getenv("ANSICON") != NULL;
#endif
}
return false;
#endif
}
int
munit_suite_main_custom(const MunitSuite* suite, void* user_data,
int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)],
const MunitArgument arguments[]) {
int result = EXIT_FAILURE;
MunitTestRunner runner;
size_t parameters_size = 0;
size_t tests_size = 0;
int arg;
char* envptr;
unsigned long ts;
char* endptr;
unsigned long long iterations;
MunitLogLevel level;
const MunitArgument* argument;
const char** runner_tests;
unsigned int tests_run;
unsigned int tests_total;
runner.prefix = NULL;
runner.suite = NULL;
runner.tests = NULL;
runner.seed = 0;
runner.iterations = 0;
runner.parameters = NULL;
runner.single_parameter_mode = false;
runner.user_data = NULL;
runner.report.successful = 0;
runner.report.skipped = 0;
runner.report.failed = 0;
runner.report.errored = 0;
#if defined(MUNIT_ENABLE_TIMING)
runner.report.cpu_clock = 0;
runner.report.wall_clock = 0;
#endif
runner.colorize = false;
#if !defined(_WIN32)
runner.fork = true;
#else
runner.fork = false;
#endif
runner.show_stderr = false;
runner.fatal_failures = false;
runner.suite = suite;
runner.user_data = user_data;
runner.seed = munit_rand_generate_seed();
runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE);
for (arg = 1 ; arg < argc ; arg++) {
if (strncmp("--", argv[arg], 2) == 0) {
if (strcmp("seed", argv[arg] + 2) == 0) {
if (arg + 1 >= argc) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
goto cleanup;
}
envptr = argv[arg + 1];
ts = strtoul(argv[arg + 1], &envptr, 0);
if (*envptr != '\0' || ts > (~((munit_uint32_t) 0U))) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
goto cleanup;
}
runner.seed = (munit_uint32_t) ts;
arg++;
} else if (strcmp("iterations", argv[arg] + 2) == 0) {
if (arg + 1 >= argc) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
goto cleanup;
}
endptr = argv[arg + 1];
iterations = strtoul(argv[arg + 1], &endptr, 0);
if (*endptr != '\0' || iterations > UINT_MAX) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
goto cleanup;
}
runner.iterations = (unsigned int) iterations;
arg++;
} else if (strcmp("param", argv[arg] + 2) == 0) {
if (arg + 2 >= argc) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires two arguments", argv[arg]);
goto cleanup;
}
runner.parameters = realloc(runner.parameters, sizeof(MunitParameter) * (parameters_size + 2));
if (runner.parameters == NULL) {
munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory");
goto cleanup;
}
runner.parameters[parameters_size].name = (char*) argv[arg + 1];
runner.parameters[parameters_size].value = (char*) argv[arg + 2];
parameters_size++;
runner.parameters[parameters_size].name = NULL;
runner.parameters[parameters_size].value = NULL;
arg += 2;
} else if (strcmp("color", argv[arg] + 2) == 0) {
if (arg + 1 >= argc) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
goto cleanup;
}
if (strcmp(argv[arg + 1], "always") == 0)
runner.colorize = true;
else if (strcmp(argv[arg + 1], "never") == 0)
runner.colorize = false;
else if (strcmp(argv[arg + 1], "auto") == 0)
runner.colorize = munit_stream_supports_ansi(MUNIT_OUTPUT_FILE);
else {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
goto cleanup;
}
arg++;
} else if (strcmp("help", argv[arg] + 2) == 0) {
munit_print_help(argc, argv, user_data, arguments);
result = EXIT_SUCCESS;
goto cleanup;
} else if (strcmp("single", argv[arg] + 2) == 0) {
runner.single_parameter_mode = true;
} else if (strcmp("show-stderr", argv[arg] + 2) == 0) {
runner.show_stderr = true;
#if !defined(_WIN32)
} else if (strcmp("no-fork", argv[arg] + 2) == 0) {
runner.fork = false;
#endif
} else if (strcmp("fatal-failures", argv[arg] + 2) == 0) {
runner.fatal_failures = true;
} else if (strcmp("log-visible", argv[arg] + 2) == 0 ||
strcmp("log-fatal", argv[arg] + 2) == 0) {
if (arg + 1 >= argc) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "%s requires an argument", argv[arg]);
goto cleanup;
}
if (strcmp(argv[arg + 1], "debug") == 0)
level = MUNIT_LOG_DEBUG;
else if (strcmp(argv[arg + 1], "info") == 0)
level = MUNIT_LOG_INFO;
else if (strcmp(argv[arg + 1], "warning") == 0)
level = MUNIT_LOG_WARNING;
else if (strcmp(argv[arg + 1], "error") == 0)
level = MUNIT_LOG_ERROR;
else {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "invalid value ('%s') passed to %s", argv[arg + 1], argv[arg]);
goto cleanup;
}
if (strcmp("log-visible", argv[arg] + 2) == 0)
munit_log_level_visible = level;
else
munit_log_level_fatal = level;
arg++;
} else if (strcmp("list", argv[arg] + 2) == 0) {
munit_suite_list_tests(suite, false, NULL);
result = EXIT_SUCCESS;
goto cleanup;
} else if (strcmp("list-params", argv[arg] + 2) == 0) {
munit_suite_list_tests(suite, true, NULL);
result = EXIT_SUCCESS;
goto cleanup;
} else {
argument = munit_arguments_find(arguments, argv[arg] + 2);
if (argument == NULL) {
munit_logf_internal(MUNIT_LOG_ERROR, stderr, "unknown argument ('%s')", argv[arg]);
goto cleanup;
}
if (!argument->parse_argument(suite, user_data, &arg, argc, argv))
goto cleanup;
}
} else {
runner_tests = realloc((void*) runner.tests, sizeof(char*) * (tests_size + 2));
if (runner_tests == NULL) {
munit_log_internal(MUNIT_LOG_ERROR, stderr, "failed to allocate memory");
goto cleanup;
}
runner.tests = runner_tests;
runner.tests[tests_size++] = argv[arg];
runner.tests[tests_size] = NULL;
}
}
fflush(stderr);
fprintf(MUNIT_OUTPUT_FILE, "Running test suite with seed 0x%08" PRIx32 "...\n", runner.seed);
munit_test_runner_run(&runner);
tests_run = runner.report.successful + runner.report.failed + runner.report.errored;
tests_total = tests_run + runner.report.skipped;
if (tests_run == 0) {
fprintf(stderr, "No tests run, %d (100%%) skipped.\n", runner.report.skipped);
} else {
fprintf(MUNIT_OUTPUT_FILE, "%d of %d (%0.0f%%) tests successful, %d (%0.0f%%) test skipped.\n",
runner.report.successful, tests_run,
(((double) runner.report.successful) / ((double) tests_run)) * 100.0,
runner.report.skipped,
(((double) runner.report.skipped) / ((double) tests_total)) * 100.0);
}
if (runner.report.failed == 0 && runner.report.errored == 0) {
result = EXIT_SUCCESS;
}
cleanup:
free(runner.parameters);
free((void*) runner.tests);
return result;
}
int
munit_suite_main(const MunitSuite* suite, void* user_data,
int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)]) {
return munit_suite_main_custom(suite, user_data, argc, argv, NULL);
}
dqlite-1.16.0/test/lib/munit.h 0000664 0000000 0000000 00000042213 14512203222 0016106 0 ustar 00root root 0000000 0000000 /* µnit Testing Framework
* Copyright (c) 2013-2017 Evan Nemerson
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies
* of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
* BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
#if !defined(MUNIT_H)
#define MUNIT_H
#include
#include
#define MUNIT_VERSION(major, minor, revision) \
(((major) << 16) | ((minor) << 8) | (revision))
#define MUNIT_CURRENT_VERSION MUNIT_VERSION(0, 4, 1)
#if defined(_MSC_VER) && (_MSC_VER < 1600)
# define munit_int8_t __int8
# define munit_uint8_t unsigned __int8
# define munit_int16_t __int16
# define munit_uint16_t unsigned __int16
# define munit_int32_t __int32
# define munit_uint32_t unsigned __int32
# define munit_int64_t __int64
# define munit_uint64_t unsigned __int64
#else
# include
# define munit_int8_t int8_t
# define munit_uint8_t uint8_t
# define munit_int16_t int16_t
# define munit_uint16_t uint16_t
# define munit_int32_t int32_t
# define munit_uint32_t uint32_t
# define munit_int64_t int64_t
# define munit_uint64_t uint64_t
#endif
#if defined(_MSC_VER) && (_MSC_VER < 1800)
# if !defined(PRIi8)
# define PRIi8 "i"
# endif
# if !defined(PRIi16)
# define PRIi16 "i"
# endif
# if !defined(PRIi32)
# define PRIi32 "i"
# endif
# if !defined(PRIi64)
# define PRIi64 "I64i"
# endif
# if !defined(PRId8)
# define PRId8 "d"
# endif
# if !defined(PRId16)
# define PRId16 "d"
# endif
# if !defined(PRId32)
# define PRId32 "d"
# endif
# if !defined(PRId64)
# define PRId64 "I64d"
# endif
# if !defined(PRIx8)
# define PRIx8 "x"
# endif
# if !defined(PRIx16)
# define PRIx16 "x"
# endif
# if !defined(PRIx32)
# define PRIx32 "x"
# endif
# if !defined(PRIx64)
# define PRIx64 "I64x"
# endif
# if !defined(PRIu8)
# define PRIu8 "u"
# endif
# if !defined(PRIu16)
# define PRIu16 "u"
# endif
# if !defined(PRIu32)
# define PRIu32 "u"
# endif
# if !defined(PRIu64)
# define PRIu64 "I64u"
# endif
# if !defined(bool)
# define bool int
# endif
# if !defined(true)
# define true (!0)
# endif
# if !defined(false)
# define false (!!0)
# endif
#else
# include
# include
#endif
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__GNUC__)
# define MUNIT_LIKELY(expr) (__builtin_expect ((expr), 1))
# define MUNIT_UNLIKELY(expr) (__builtin_expect ((expr), 0))
# define MUNIT_UNUSED __attribute__((__unused__))
#else
# define MUNIT_LIKELY(expr) (expr)
# define MUNIT_UNLIKELY(expr) (expr)
# define MUNIT_UNUSED
#endif
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && !defined(__PGI)
# define MUNIT_ARRAY_PARAM(name) name
#else
# define MUNIT_ARRAY_PARAM(name)
#endif
#if !defined(_WIN32)
# define MUNIT_SIZE_MODIFIER "z"
# define MUNIT_CHAR_MODIFIER "hh"
# define MUNIT_SHORT_MODIFIER "h"
#else
# if defined(_M_X64) || defined(__amd64__)
# define MUNIT_SIZE_MODIFIER "I64"
# else
# define MUNIT_SIZE_MODIFIER ""
# endif
# define MUNIT_CHAR_MODIFIER ""
# define MUNIT_SHORT_MODIFIER ""
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
# define MUNIT_NO_RETURN _Noreturn
#elif defined(__GNUC__)
# define MUNIT_NO_RETURN __attribute__((__noreturn__))
#elif defined(_MSC_VER)
# define MUNIT_NO_RETURN __declspec(noreturn)
#else
# define MUNIT_NO_RETURN
#endif
#if defined(_MSC_VER) && (_MSC_VER >= 1500)
# define MUNIT__PUSH_DISABLE_MSVC_C4127 __pragma(warning(push)) __pragma(warning(disable:4127))
# define MUNIT__POP_DISABLE_MSVC_C4127 __pragma(warning(pop))
#else
# define MUNIT__PUSH_DISABLE_MSVC_C4127
# define MUNIT__POP_DISABLE_MSVC_C4127
#endif
typedef enum {
MUNIT_LOG_DEBUG,
MUNIT_LOG_INFO,
MUNIT_LOG_WARNING,
MUNIT_LOG_ERROR
} MunitLogLevel;
#if defined(__GNUC__) && !defined(__MINGW32__)
# define MUNIT_PRINTF(string_index, first_to_check) __attribute__((format (printf, string_index, first_to_check)))
#else
# define MUNIT_PRINTF(string_index, first_to_check)
#endif
MUNIT_PRINTF(4, 5)
void munit_logf_ex(MunitLogLevel level, const char* filename, int line, const char* format, ...);
#define munit_logf(level, format, ...) \
munit_logf_ex(level, __FILE__, __LINE__, format, __VA_ARGS__)
#define munit_log(level, msg) \
munit_logf(level, "%s", msg)
MUNIT_NO_RETURN
MUNIT_PRINTF(3, 4)
void munit_errorf_ex(const char* filename, int line, const char* format, ...);
#define munit_errorf(format, ...) \
munit_errorf_ex(__FILE__, __LINE__, format, __VA_ARGS__)
#define munit_error(msg) \
munit_errorf("%s", msg)
#define munit_assert(expr) \
do { \
if (!MUNIT_LIKELY(expr)) { \
munit_error("assertion failed: " #expr); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_true(expr) \
do { \
if (!MUNIT_LIKELY(expr)) { \
munit_error("assertion failed: " #expr " is not true"); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_false(expr) \
do { \
if (!MUNIT_LIKELY(!(expr))) { \
munit_error("assertion failed: " #expr " is not false"); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_type_full(prefix, suffix, T, fmt, a, op, b) \
do { \
T munit_tmp_a_ = (a); \
T munit_tmp_b_ = (b); \
if (!(munit_tmp_a_ op munit_tmp_b_)) { \
munit_errorf("assertion failed: %s %s %s (" prefix "%" fmt suffix " %s " prefix "%" fmt suffix ")", \
#a, #op, #b, munit_tmp_a_, #op, munit_tmp_b_); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_type(T, fmt, a, op, b) \
munit_assert_type_full("", "", T, fmt, a, op, b)
#define munit_assert_char(a, op, b) \
munit_assert_type_full("'\\x", "'", char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b)
#define munit_assert_uchar(a, op, b) \
munit_assert_type_full("'\\x", "'", unsigned char, "02" MUNIT_CHAR_MODIFIER "x", a, op, b)
#define munit_assert_short(a, op, b) \
munit_assert_type(short, MUNIT_SHORT_MODIFIER "d", a, op, b)
#define munit_assert_ushort(a, op, b) \
munit_assert_type(unsigned short, MUNIT_SHORT_MODIFIER "u", a, op, b)
#define munit_assert_int(a, op, b) \
munit_assert_type(int, "d", a, op, b)
#define munit_assert_uint(a, op, b) \
munit_assert_type(unsigned int, "u", a, op, b)
#define munit_assert_long(a, op, b) \
munit_assert_type(long int, "ld", a, op, b)
#define munit_assert_ulong(a, op, b) \
munit_assert_type(unsigned long int, "lu", a, op, b)
#define munit_assert_llong(a, op, b) \
munit_assert_type(long long int, "lld", a, op, b)
#define munit_assert_ullong(a, op, b) \
munit_assert_type(unsigned long long int, "llu", a, op, b)
#define munit_assert_size(a, op, b) \
munit_assert_type(size_t, MUNIT_SIZE_MODIFIER "u", a, op, b)
#define munit_assert_float(a, op, b) \
munit_assert_type(float, "f", a, op, b)
#define munit_assert_double(a, op, b) \
munit_assert_type(double, "g", a, op, b)
#define munit_assert_ptr(a, op, b) \
munit_assert_type(const void*, "p", a, op, b)
#define munit_assert_int8(a, op, b) \
munit_assert_type(munit_int8_t, PRIi8, a, op, b)
#define munit_assert_uint8(a, op, b) \
munit_assert_type(munit_uint8_t, PRIu8, a, op, b)
#define munit_assert_int16(a, op, b) \
munit_assert_type(munit_int16_t, PRIi16, a, op, b)
#define munit_assert_uint16(a, op, b) \
munit_assert_type(munit_uint16_t, PRIu16, a, op, b)
#define munit_assert_int32(a, op, b) \
munit_assert_type(munit_int32_t, PRIi32, a, op, b)
#define munit_assert_uint32(a, op, b) \
munit_assert_type(munit_uint32_t, PRIu32, a, op, b)
#define munit_assert_int64(a, op, b) \
munit_assert_type(munit_int64_t, PRIi64, a, op, b)
#define munit_assert_uint64(a, op, b) \
munit_assert_type(munit_uint64_t, PRIu64, a, op, b)
#define munit_assert_double_equal(a, b, precision) \
do { \
const double munit_tmp_a_ = (a); \
const double munit_tmp_b_ = (b); \
const double munit_tmp_diff_ = ((munit_tmp_a_ - munit_tmp_b_) < 0) ? \
-(munit_tmp_a_ - munit_tmp_b_) : \
(munit_tmp_a_ - munit_tmp_b_); \
if (MUNIT_UNLIKELY(munit_tmp_diff_ > 1e-##precision)) { \
munit_errorf("assertion failed: %s == %s (%0." #precision "g == %0." #precision "g)", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#include
#define munit_assert_string_equal(a, b) \
do { \
const char* munit_tmp_a_ = a; \
const char* munit_tmp_b_ = b; \
if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) != 0)) { \
munit_errorf("assertion failed: string %s == %s (\"%s\" == \"%s\")", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_string_not_equal(a, b) \
do { \
const char* munit_tmp_a_ = a; \
const char* munit_tmp_b_ = b; \
if (MUNIT_UNLIKELY(strcmp(munit_tmp_a_, munit_tmp_b_) == 0)) { \
munit_errorf("assertion failed: string %s != %s (\"%s\" == \"%s\")", \
#a, #b, munit_tmp_a_, munit_tmp_b_); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_memory_equal(size, a, b) \
do { \
const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \
const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \
const size_t munit_tmp_size_ = (size); \
if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) != 0) { \
size_t munit_tmp_pos_; \
for (munit_tmp_pos_ = 0 ; munit_tmp_pos_ < munit_tmp_size_ ; munit_tmp_pos_++) { \
if (munit_tmp_a_[munit_tmp_pos_] != munit_tmp_b_[munit_tmp_pos_]) { \
munit_errorf("assertion failed: memory %s == %s, at offset %" MUNIT_SIZE_MODIFIER "u", \
#a, #b, munit_tmp_pos_); \
break; \
} \
} \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_memory_not_equal(size, a, b) \
do { \
const unsigned char* munit_tmp_a_ = (const unsigned char*) (a); \
const unsigned char* munit_tmp_b_ = (const unsigned char*) (b); \
const size_t munit_tmp_size_ = (size); \
if (MUNIT_UNLIKELY(memcmp(munit_tmp_a_, munit_tmp_b_, munit_tmp_size_)) == 0) { \
munit_errorf("assertion failed: memory %s != %s (%zu bytes)", \
#a, #b, munit_tmp_size_); \
} \
MUNIT__PUSH_DISABLE_MSVC_C4127 \
} while (0) \
MUNIT__POP_DISABLE_MSVC_C4127
#define munit_assert_ptr_equal(a, b) \
munit_assert_ptr(a, ==, b)
#define munit_assert_ptr_not_equal(a, b) \
munit_assert_ptr(a, !=, b)
#define munit_assert_null(ptr) \
munit_assert_ptr(ptr, ==, NULL)
#define munit_assert_not_null(ptr) \
munit_assert_ptr(ptr, !=, NULL)
#define munit_assert_ptr_null(ptr) \
munit_assert_ptr(ptr, ==, NULL)
#define munit_assert_ptr_not_null(ptr) \
munit_assert_ptr(ptr, !=, NULL)
/*** Memory allocation ***/
void* munit_malloc_ex(const char* filename, int line, size_t size);
#define munit_malloc(size) \
munit_malloc_ex(__FILE__, __LINE__, (size))
#define munit_new(type) \
((type*) munit_malloc(sizeof(type)))
#define munit_calloc(nmemb, size) \
munit_malloc((nmemb) * (size))
#define munit_newa(type, nmemb) \
((type*) munit_calloc((nmemb), sizeof(type)))
/*** Random number generation ***/
void munit_rand_seed(munit_uint32_t seed);
munit_uint32_t munit_rand_uint32(void);
int munit_rand_int_range(int min, int max);
double munit_rand_double(void);
void munit_rand_memory(size_t size, munit_uint8_t buffer[MUNIT_ARRAY_PARAM(size)]);
/*** Tests and Suites ***/
typedef enum {
/* Test successful */
MUNIT_OK,
/* Test failed */
MUNIT_FAIL,
/* Test was skipped */
MUNIT_SKIP,
/* Test failed due to circumstances not intended to be tested
* (things like network errors, invalid parameter value, failure to
* allocate memory in the test harness, etc.). */
MUNIT_ERROR
} MunitResult;
typedef struct {
char* name;
char** values;
} MunitParameterEnum;
typedef struct {
char* name;
char* value;
} MunitParameter;
const char* munit_parameters_get(const MunitParameter params[], const char* key);
typedef enum {
MUNIT_TEST_OPTION_NONE = 0,
MUNIT_TEST_OPTION_SINGLE_ITERATION = 1 << 0,
MUNIT_TEST_OPTION_TODO = 1 << 1
} MunitTestOptions;
typedef MunitResult (* MunitTestFunc)(const MunitParameter params[], void* user_data_or_fixture);
typedef void* (* MunitTestSetup)(const MunitParameter params[], void* user_data);
typedef void (* MunitTestTearDown)(void* fixture);
typedef struct {
char* name;
MunitTestFunc test;
MunitTestSetup setup;
MunitTestTearDown tear_down;
MunitTestOptions options;
MunitParameterEnum* parameters;
} MunitTest;
typedef enum {
MUNIT_SUITE_OPTION_NONE = 0
} MunitSuiteOptions;
typedef struct MunitSuite_ MunitSuite;
struct MunitSuite_ {
char* prefix;
MunitTest* tests;
MunitSuite* suites;
unsigned int iterations;
MunitSuiteOptions options;
};
int munit_suite_main(const MunitSuite* suite, void* user_data, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)]);
/* Note: I'm not very happy with this API; it's likely to change if I
* figure out something better. Suggestions welcome. */
typedef struct MunitArgument_ MunitArgument;
struct MunitArgument_ {
char* name;
bool (* parse_argument)(const MunitSuite* suite, void* user_data, int* arg, int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)]);
void (* write_help)(const MunitArgument* argument, void* user_data);
};
int munit_suite_main_custom(const MunitSuite* suite,
void* user_data,
int argc, char* const argv[MUNIT_ARRAY_PARAM(argc)],
const MunitArgument arguments[]);
#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
#define assert_true(expr) munit_assert_true(expr)
#define assert_false(expr) munit_assert_false(expr)
#define assert_char(a, op, b) munit_assert_char(a, op, b)
#define assert_uchar(a, op, b) munit_assert_uchar(a, op, b)
#define assert_short(a, op, b) munit_assert_short(a, op, b)
#define assert_ushort(a, op, b) munit_assert_ushort(a, op, b)
#define assert_int(a, op, b) munit_assert_int(a, op, b)
#define assert_uint(a, op, b) munit_assert_uint(a, op, b)
#define assert_long(a, op, b) munit_assert_long(a, op, b)
#define assert_ulong(a, op, b) munit_assert_ulong(a, op, b)
#define assert_llong(a, op, b) munit_assert_llong(a, op, b)
#define assert_ullong(a, op, b) munit_assert_ullong(a, op, b)
#define assert_size(a, op, b) munit_assert_size(a, op, b)
#define assert_float(a, op, b) munit_assert_float(a, op, b)
#define assert_double(a, op, b) munit_assert_double(a, op, b)
#define assert_ptr(a, op, b) munit_assert_ptr(a, op, b)
#define assert_int8(a, op, b) munit_assert_int8(a, op, b)
#define assert_uint8(a, op, b) munit_assert_uint8(a, op, b)
#define assert_int16(a, op, b) munit_assert_int16(a, op, b)
#define assert_uint16(a, op, b) munit_assert_uint16(a, op, b)
#define assert_int32(a, op, b) munit_assert_int32(a, op, b)
#define assert_uint32(a, op, b) munit_assert_uint32(a, op, b)
#define assert_int64(a, op, b) munit_assert_int64(a, op, b)
#define assert_uint64(a, op, b) munit_assert_uint64(a, op, b)
#define assert_double_equal(a, b, precision) munit_assert_double_equal(a, b, precision)
#define assert_string_equal(a, b) munit_assert_string_equal(a, b)
#define assert_string_not_equal(a, b) munit_assert_string_not_equal(a, b)
#define assert_memory_equal(size, a, b) munit_assert_memory_equal(size, a, b)
#define assert_memory_not_equal(size, a, b) munit_assert_memory_not_equal(size, a, b)
#define assert_ptr_equal(a, b) munit_assert_ptr_equal(a, b)
#define assert_ptr_not_equal(a, b) munit_assert_ptr_not_equal(a, b)
#define assert_ptr_null(ptr) munit_assert_null_equal(ptr)
#define assert_ptr_not_null(ptr) munit_assert_not_null(ptr)
#define assert_null(ptr) munit_assert_null(ptr)
#define assert_not_null(ptr) munit_assert_not_null(ptr)
#endif /* defined(MUNIT_ENABLE_ASSERT_ALIASES) */
#if defined(__cplusplus)
}
#endif
#endif /* !defined(MUNIT_H) */
#if defined(MUNIT_ENABLE_ASSERT_ALIASES)
# if defined(assert)
# undef assert
# endif
# define assert(expr) munit_assert(expr)
#endif
dqlite-1.16.0/test/lib/raft.h 0000664 0000000 0000000 00000005361 14512203222 0015711 0 ustar 00root root 0000000 0000000 /**
* Helpers for setting up a standalone raft instance with a libuv transport.
*/
#ifndef TEST_RAFT_H
#define TEST_RAFT_H
#include
#include
#include
#include "../../src/fsm.h"
#include "../../src/transport.h"
#include "fs.h"
#include "logger.h"
#include "munit.h"
#include "uv.h"
#define FIXTURE_RAFT \
char *dir; \
struct uv_loop_s loop; \
struct raft_uv_transport raft_transport; \
struct raft_io raft_io; \
struct raft_fsm fsm; \
struct raft raft
#define SETUP_RAFT \
{ \
int rv2; \
f->dir = test_dir_setup(); \
test_uv_setup(params, &f->loop); \
rv2 = raftProxyInit(&f->raft_transport, &f->loop); \
munit_assert_int(rv2, ==, 0); \
rv2 = raft_uv_init(&f->raft_io, &f->loop, f->dir, \
&f->raft_transport); \
munit_assert_int(rv2, ==, 0); \
rv2 = fsm__init(&f->fsm, &f->config, &f->registry); \
munit_assert_int(rv2, ==, 0); \
rv2 = raft_init(&f->raft, &f->raft_io, &f->fsm, 1, "1"); \
munit_assert_int(rv2, ==, 0); \
}
#define TEAR_DOWN_RAFT \
{ \
raft_close(&f->raft, NULL); \
test_uv_stop(&f->loop); \
raft_uv_close(&f->raft_io); \
fsm__close(&f->fsm); \
test_uv_tear_down(&f->loop); \
raftProxyClose(&f->raft_transport); \
test_dir_tear_down(f->dir); \
}
/**
* Bootstrap the fixture raft instance with a configuration containing only
* itself.
*/
#define RAFT_BOOTSTRAP \
{ \
struct raft_configuration configuration; \
int rv2; \
raft_configuration_init(&configuration); \
rv2 = raft_configuration_add(&configuration, 1, "1", \
RAFT_VOTER); \
munit_assert_int(rv2, ==, 0); \
rv2 = raft_bootstrap(&f->raft, &configuration); \
munit_assert_int(rv2, ==, 0); \
raft_configuration_close(&configuration); \
}
#define RAFT_START \
{ \
int rv2; \
rv2 = raft_start(&f->raft); \
munit_assert_int(rv2, ==, 0); \
}
#endif /* TEST_RAFT_H */
dqlite-1.16.0/test/lib/raft_heap.c 0000664 0000000 0000000 00000004343 14512203222 0016700 0 ustar 00root root 0000000 0000000 #include
#include "fault.h"
#include "raft_heap.h"
struct heapFault
{
struct test_fault fault;
const struct raft_heap *orig_heap;
};
static struct heapFault faulty;
static void *faultyMalloc(void *data, size_t size)
{
(void)data;
if (test_fault_tick(&faulty.fault)) {
return NULL;
} else {
return faulty.orig_heap->malloc(faulty.orig_heap->data, size);
}
}
static void faultyFree(void *data, void *ptr)
{
(void)data;
faulty.orig_heap->free(faulty.orig_heap->data, ptr);
}
static void *faultyCalloc(void *data, size_t nmemb, size_t size)
{
(void)data;
if (test_fault_tick(&faulty.fault)) {
return NULL;
} else {
return faulty.orig_heap->calloc(faulty.orig_heap->data, nmemb,
size);
}
}
static void *faultyRealloc(void *data, void *ptr, size_t size)
{
(void)data;
if (test_fault_tick(&faulty.fault)) {
return NULL;
} else {
return faulty.orig_heap->realloc(faulty.orig_heap->data, ptr,
size);
}
}
static void *faultyAlignedAlloc(void *data, size_t alignment, size_t size)
{
(void)data;
if (test_fault_tick(&faulty.fault)) {
return NULL;
} else {
return faulty.orig_heap->aligned_alloc(faulty.orig_heap->data,
alignment, size);
}
}
static void faultyAlignedFree(void *data, size_t alignment, void *ptr)
{
(void)data;
(void)alignment;
faulty.orig_heap->aligned_free(faulty.orig_heap->data, alignment, ptr);
}
void test_raft_heap_setup(const MunitParameter params[], void *user_data)
{
(void)params;
(void)user_data;
struct raft_heap *heap = munit_malloc(sizeof(*heap));
test_fault_init(&faulty.fault);
faulty.orig_heap = raft_heap_get();
heap->data = NULL;
heap->malloc = faultyMalloc;
heap->free = faultyFree;
heap->calloc = faultyCalloc;
heap->realloc = faultyRealloc;
heap->aligned_alloc = faultyAlignedAlloc;
heap->aligned_free = faultyAlignedFree;
raft_heap_set(heap);
}
void test_raft_heap_tear_down(void *data)
{
struct raft_heap *heap = (struct raft_heap *)raft_heap_get();
(void)data;
raft_heap_set((struct raft_heap *)faulty.orig_heap);
faulty.orig_heap = NULL;
free(heap);
}
void test_raft_heap_fault_config(int delay, int repeat)
{
test_fault_config(&faulty.fault, delay, repeat);
}
void test_raft_heap_fault_enable(void)
{
test_fault_enable(&faulty.fault);
}
dqlite-1.16.0/test/lib/raft_heap.h 0000664 0000000 0000000 00000000624 14512203222 0016703 0 ustar 00root root 0000000 0000000 /**
* Helpers for injecting failures into raft's allocator.
*/
#ifndef DQLITE_TEST_RAFT_HEAP_H
#define DQLITE_TEST_RAFT_HEAP_H
#include "munit.h"
void test_raft_heap_setup(const MunitParameter params[], void *user_data);
void test_raft_heap_tear_down(void *data);
void test_raft_heap_fault_config(int delay, int repeat);
void test_raft_heap_fault_enable(void);
#endif /* DQLITE_TEST_RAFT_HEAP_H */
dqlite-1.16.0/test/lib/registry.h 0000664 0000000 0000000 00000000434 14512203222 0016621 0 ustar 00root root 0000000 0000000 #ifndef TEST_REGISTRY_H
#define TEST_REGISTRY_H
#include "../../src/registry.h"
#define FIXTURE_REGISTRY struct registry registry
#define SETUP_REGISTRY registry__init(&f->registry, &f->config)
#define TEAR_DOWN_REGISTRY registry__close(&f->registry);
#endif /* TEST_REGISTRY_H */
dqlite-1.16.0/test/lib/runner.h 0000664 0000000 0000000 00000042345 14512203222 0016271 0 ustar 00root root 0000000 0000000 /* Convenience macros to reduce munit boiler plate. */
#ifndef TEST_RUNNER_H
#define TEST_RUNNER_H
#include
#include "munit.h"
#include "../../src/tracing.h"
/* Top-level suites array declaration.
*
* These top-level suites hold all module-level child suites and must be defined
* and then set as child suites of a root suite created at runtime by the test
* runner's main(). This can be done using the RUNNER macro. */
extern MunitSuite _main_suites[];
extern int _main_suites_n;
/* Maximum number of test cases for each suite */
#define SUITE__CAP 128
#define TEST__CAP SUITE__CAP
/* Define the top-level suites array and the main() function of the test. */
#define RUNNER(NAME) \
MunitSuite _main_suites[SUITE__CAP]; \
int _main_suites_n = 0; \
\
int main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc)]) \
{ \
signal(SIGPIPE, SIG_IGN); \
dqliteTracingMaybeEnable(true); \
MunitSuite suite = {(char *)"", NULL, _main_suites, 1, 0}; \
return munit_suite_main(&suite, (void *)NAME, argc, argv); \
}
/* Declare and register a new test suite #S belonging to the file's test module.
*
* A test suite is a pair of static variables:
*
* static MunitTest _##S##_suites[SUITE__CAP]
* static MunitTest _##S##_tests[SUITE__CAP]
*
* The tests and suites attributes of the next available MunitSuite slot in the
* _module_suites array will be set to the suite's tests and suites arrays, and
* the prefix attribute of the slot will be set to /S. */
#define SUITE(S) \
SUITE__DECLARE(S) \
SUITE__ADD_CHILD(main, #S, S)
/* Declare and register a new test. */
#define TEST(S, C, SETUP, TEAR_DOWN, OPTIONS, PARAMS) \
static MunitResult test_##S##_##C(const MunitParameter params[], \
void *data); \
TEST__ADD_TO_SUITE(S, C, SETUP, TEAR_DOWN, OPTIONS, PARAMS) \
static MunitResult test_##S##_##C( \
MUNIT_UNUSED const MunitParameter params[], \
MUNIT_UNUSED void *data)
#define SKIP_IF_NO_FIXTURE \
if (f == NULL) { \
return MUNIT_SKIP; \
}
/* Declare the MunitSuite[] and the MunitTest[] arrays that compose the test
* suite identified by S. */
#define SUITE__DECLARE(S) \
static MunitSuite _##S##_suites[SUITE__CAP]; \
static MunitTest _##S##_tests[SUITE__CAP]; \
static MunitTestSetup _##S##_setup = NULL; \
static MunitTestTearDown _##S##_tear_down = NULL; \
static int _##S##_suites_n = 0; \
static int _##S##_tests_n = 0; \
__attribute__((constructor(101))) static void _##S##_init(void) \
{ \
memset(_##S##_suites, 0, sizeof(_##S##_suites)); \
memset(_##S##_tests, 0, sizeof(_##S##_tests)); \
(void)_##S##_suites_n; \
(void)_##S##_tests_n; \
(void)_##S##_setup; \
(void)_##S##_tear_down; \
}
/* Set the tests and suites attributes of the next available slot of the
* MunitSuite[] array of S1 to the MunitTest[] and MunitSuite[] arrays of S2,
* using the given PREXIX. */
#define SUITE__ADD_CHILD(S1, PREFIX, S2) \
__attribute__((constructor(102))) static void _##S1##_##S2##_init( \
void) \
{ \
int n = _##S1##_suites_n; \
_##S1##_suites[n].prefix = PREFIX; \
_##S1##_suites[n].tests = _##S2##_tests; \
_##S1##_suites[n].suites = _##S2##_suites; \
_##S1##_suites[n].iterations = 0; \
_##S1##_suites[n].options = 0; \
_##S1##_suites_n = n + 1; \
}
/* Add a test case to the MunitTest[] array of suite S. */
#define TEST__ADD_TO_SUITE(S, C, SETUP, TEAR_DOWN, OPTIONS, PARAMS) \
__attribute__((constructor(103))) static void _##S##_tests_##C##_init( \
void) \
{ \
MunitTest *tests = _##S##_tests; \
int n = _##S##_tests_n; \
TEST__SET_IN_ARRAY(tests, n, "/" #C, test_##S##_##C, SETUP, \
TEAR_DOWN, OPTIONS, PARAMS); \
_##S##_tests_n = n + 1; \
}
/* Set the values of the I'th test case slot in the given test array */
#define TEST__SET_IN_ARRAY(TESTS, I, NAME, FUNC, SETUP, TEAR_DOWN, OPTIONS, \
PARAMS) \
TESTS[I].name = NAME; \
TESTS[I].test = FUNC; \
TESTS[I].setup = SETUP; \
TESTS[I].tear_down = TEAR_DOWN; \
TESTS[I].options = OPTIONS; \
TESTS[I].parameters = PARAMS
/**
* Declare and register a new test module #M.
*
* A test module is a test suite (i.e. a pair of MunitTest[] and MunitSuite[]
* arrays), directly or indirectly containing all test cases in a test file.
*
* This macro uses hard-coded names to declare the module's tests and suites
* arrays static, so they can be easly referenced in other static declarations
* generated by the macros below:
*
* static MunitTest _module_tests[TEST__CAP];
* static MunitSuite _module_suites[TEST__CAP];
*
* The tests and suites attributes of the next available MunitSuite slot in the
* top-level suites array will be set to the module's tests and suites arrays,
* and the prefix attribute of the slot will be set to #M.
*
* Each test file should declare one and only one test module.
*/
#define TEST_MODULE(M) \
TEST_SUITE__DECLARE(module); \
TEST_SUITE__ADD_CHILD(main, #M, module);
/**
* Declare and register a new test suite #S belonging to the file's test module.
*
* A test suite is a pair of static variables:
*
* static MunitTest _##S##_suites[TEST__CAP]
* static MunitTest _##S##_tests[TEST__CAP]
*
* The tests and suites attributes of the next available MunitSuite slot in the
* #_module_suites array will be set to the suite's tests and suites arrays, and
* the prefix attribute of the slot will be set to /S.
*
* All tests in the suite will use the same setup and tear down functions.
*/
#define TEST_SUITE(S) \
TEST_SUITE__DECLARE(S); \
TEST_SUITE__ADD_CHILD(module, "/" #S, S);
/**
* Declare a setup function.
*
* Possible signatures are:
*
* - TEST_SETUP(S): Declare the setup function for suite S inline.
* - TEST_SETUP(S, F): Set the setup function for suite S to F.
*/
#define TEST_SETUP(...) TEST_SETUP__MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
#define TEST_SETUP_(S) \
static void *S##_setup(const MunitParameter[], void *); \
_##S##_setup = S##_setup; \
static void *S##_setup(const MunitParameter params[], void *user_data)
/**
* Declare a tear down function.
*
* Possible signatures are:
*
* - TEST_SETUP(S): Declare the tear down function for suite S inline.
* - TEST_SETUP(S, F): Set the tear down function for suite S to F.
*/
#define TEST_TEAR_DOWN(...) \
TEST_TEAR_DOWN__MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
/**
* Declare and register a new group of tests #G, belonging to suite #S in the
* file's test module.
*/
#define TEST_GROUP(C, T) \
static MunitTest _##C##_##T##_tests[TEST__CAP]; \
static int _##C##_##T##_tests_n = 0; \
TEST_SUITE__ADD_GROUP(C, T);
/**
* Declare and register a new test case.
*
* Possible signatures are:
*
* - TEST_CASE(C): C gets added to the tests array of the file module.
* - TEST_CASE(S, C): C gets added to the tests array of suite S.
* - TEST_CASE(S, G, C): C gets added to the tests array of group G in suite S.
*
* The test body declaration must follow the macro.
*/
#define TEST_CASE(...) TEST_CASE__MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
/* Declare the MunitSuite[] and the MunitTest[] arrays that compose the test
* suite identified by S. */
#define TEST_SUITE__DECLARE(S) \
static MunitSuite _##S##_suites[TEST__CAP]; \
static MunitTest _##S##_tests[TEST__CAP]; \
static MunitTestSetup _##S##_setup = NULL; \
static MunitTestTearDown _##S##_tear_down = NULL; \
static int _##S##_suites_n = 0; \
static int _##S##_tests_n = 0; \
__attribute__((constructor)) static void _##S##_init(void) \
{ \
memset(_##S##_suites, 0, sizeof(_##S##_suites)); \
memset(_##S##_tests, 0, sizeof(_##S##_tests)); \
(void)_##S##_suites_n; \
(void)_##S##_tests_n; \
(void)_##S##_setup; \
(void)_##S##_tear_down; \
}
/* Set the tests and suites attributes of the next available slot of the
* MunitSuite[] array of S1 to the MunitTest[] and MunitSuite[] arrays of S2,
* using the given PREXIX. */
#define TEST_SUITE__ADD_CHILD(S1, PREFIX, S2) \
__attribute__((constructor)) static void _##S1##_##S2##_init(void) \
{ \
int n = _##S1##_suites_n; \
_##S1##_suites[n].prefix = PREFIX; \
_##S1##_suites[n].tests = _##S2##_tests; \
_##S1##_suites[n].suites = _##S2##_suites; \
_##S1##_suites[n].iterations = 0; \
_##S1##_suites[n].options = 0; \
_##S1##_suites_n = n + 1; \
}
/* Set the tests attribute of the next available slot of the MunitSuite[] array
* of S to the MunitTest[] array of G, using /G as prefix. */
#define TEST_SUITE__ADD_GROUP(S, G) \
__attribute__((constructor)) static void _##S##_##G##_init(void) \
{ \
int n = _##S##_suites_n; \
_##S##_suites[n].prefix = "/" #G; \
_##S##_suites[n].tests = _##S##_##G##_tests; \
_##S##_suites[n].suites = NULL; \
_##S##_suites[n].iterations = 0; \
_##S##_suites[n].options = 0; \
_##S##_suites_n = n + 1; \
}
/* Choose the appropriate TEST_SETUP__N_ARGS() macro depending on the number of
* arguments passed to TEST_SETUP(). */
#define TEST_SETUP__MACRO_CHOOSER(...) \
TEST__GET_3RD_ARG(__VA_ARGS__, TEST_SETUP__2_ARGS, TEST_SETUP__1_ARGS)
#define TEST_SETUP__1_ARGS(S) \
static void *S##__setup(const MunitParameter[], void *); \
__attribute__((constructor)) static void _##S##_setup_init(void) \
{ \
_##S##_setup = S##__setup; \
} \
static void *S##__setup(const MunitParameter params[], void *user_data)
#define TEST_SETUP__2_ARGS(S, F) \
__attribute__((constructor)) static void _##S##_setup_init(void) \
{ \
_##S##_setup = F; \
}
/* Choose the appropriate TEST_TEAR_DOWN__N_ARGS() macro depending on the number
* of arguments passed to TEST_TEAR_DOWN(). */
#define TEST_TEAR_DOWN__MACRO_CHOOSER(...) \
TEST__GET_3RD_ARG(__VA_ARGS__, TEST_TEAR_DOWN__2_ARGS, \
TEST_TEAR_DOWN__1_ARGS)
#define TEST_TEAR_DOWN__1_ARGS(S) \
static void S##__tear_down(void *data); \
__attribute__((constructor)) static void _##S##__tear_down_init(void) \
{ \
_##S##_tear_down = S##__tear_down; \
} \
static void S##__tear_down(void *data)
#define TEST_TEAR_DOWN__2_ARGS(S, F) \
__attribute__((constructor)) static void _##S##_tear_down_init(void) \
{ \
_##S##_tear_down = F; \
}
/* Choose the appropriate TEST_CASE__N_ARGS() macro depending on the number of
* arguments passed to TEST_CASE(). */
#define TEST_CASE__MACRO_CHOOSER(...) \
TEST__GET_5TH_ARG(__VA_ARGS__, TEST_CASE__4_ARGS, TEST_CASE__3_ARGS, \
TEST_CASE__2_ARGS)
/* Add the test case to the module's MunitTest[] array. */
#define TEST_CASE__2_ARGS(C, PARAMS) \
static MunitResult test_##C(const MunitParameter[], void *); \
TEST_CASE__ADD_TO_MODULE(C, PARAMS); \
static MunitResult test_##C(const MunitParameter params[], void *data)
/* Add test case C to the MunitTest[] array of suite S. */
#define TEST_CASE__3_ARGS(S, C, PARAMS) \
static MunitResult test_##S##_##C(const MunitParameter[], void *); \
TEST_CASE__ADD_TO_SUITE(S, C, PARAMS); \
static MunitResult test_##S##_##C(const MunitParameter params[], \
void *data)
/* Add test case C to the MunitTest[] array of group G of suite S. */
#define TEST_CASE__4_ARGS(S, G, C, PARAMS) \
static MunitResult test_##S##_##G##_##C(const MunitParameter[], \
void *); \
TEST_CASE__ADD_TO_GROUP(S, G, C, PARAMS); \
static MunitResult test_##S##_##G##_##C(const MunitParameter params[], \
void *data)
/* Add a test case to the MunitTest[] array of the file module. */
#define TEST_CASE__ADD_TO_MODULE(C, PARAMS) \
__attribute__((constructor)) static void _module_tests_##C##_init( \
void) \
{ \
MunitTest *tests = _module_tests; \
int n = _module_tests_n; \
TEST_CASE__SET_IN_ARRAY(tests, n, "/" #C, test_##C, NULL, \
NULL, PARAMS); \
_module_tests_n = n + 1; \
}
/* Add a test case to the MunitTest[] array of suite S. */
#define TEST_CASE__ADD_TO_SUITE(S, C, PARAMS) \
__attribute__((constructor)) static void _##S##_tests_##C##_init(void) \
{ \
MunitTest *tests = _##S##_tests; \
int n = _##S##_tests_n; \
TEST_CASE__SET_IN_ARRAY(tests, n, "/" #C, test_##S##_##C, \
_##S##_setup, _##S##_tear_down, \
PARAMS); \
_##S##_tests_n = n + 1; \
}
/* Add a test case to MunitTest[] array of group G in suite S. */
#define TEST_CASE__ADD_TO_GROUP(S, G, C, PARAMS) \
__attribute__(( \
constructor)) static void _##S##_##G##_tests_##C##_init(void) \
{ \
MunitTest *tests = _##S##_##G##_tests; \
int n = _##S##_##G##_tests_n; \
TEST_CASE__SET_IN_ARRAY(tests, n, "/" #C, \
test_##S##_##G##_##C, _##S##_setup, \
_##S##_tear_down, PARAMS); \
_##S##_##G##_tests_n = n + 1; \
}
/* Set the values of the I'th test case slot in the given test array */
#define TEST_CASE__SET_IN_ARRAY(TESTS, I, NAME, FUNC, SETUP, TEAR_DOWN, \
PARAMS) \
TESTS[I].name = NAME; \
TESTS[I].test = FUNC; \
TESTS[I].setup = SETUP; \
TESTS[I].tear_down = TEAR_DOWN; \
TESTS[I].options = 0; \
TESTS[I].parameters = PARAMS
#define TEST__GET_3RD_ARG(arg1, arg2, arg3, ...) arg3
#define TEST__GET_5TH_ARG(arg1, arg2, arg3, arg4, arg5, ...) arg5
#endif /* TEST_RUNNER_H */
dqlite-1.16.0/test/lib/server.c 0000664 0000000 0000000 00000010337 14512203222 0016255 0 ustar 00root root 0000000 0000000 #include
#include "fs.h"
#include "server.h"
static int endpointConnect(void *data, const char *address, int *fd)
{
struct sockaddr_un addr;
int rv;
(void)address;
(void)data;
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path + 1, address + 1);
*fd = socket(AF_UNIX, SOCK_STREAM, 0);
munit_assert_int(*fd, !=, -1);
rv = connect(*fd, (struct sockaddr *)&addr,
sizeof(sa_family_t) + strlen(address + 1) + 1);
return rv;
}
void test_server_setup(struct test_server *s,
const unsigned id,
const MunitParameter params[])
{
(void)params;
s->id = id;
sprintf(s->address, "@%u", id);
s->dir = test_dir_setup();
s->role_management = false;
memset(s->others, 0, sizeof s->others);
}
void test_server_stop(struct test_server *s)
{
int rv;
test_server_client_close(s, &s->client);
if (s->role_management) {
dqlite_node_handover(s->dqlite);
rv = dqlite_node_stop(s->dqlite);
} else {
rv = dqlite_node_stop(s->dqlite);
}
munit_assert_int(rv, ==, 0);
dqlite_node_destroy(s->dqlite);
}
void test_server_tear_down(struct test_server *s)
{
test_server_stop(s);
test_dir_tear_down(s->dir);
}
void test_server_start(struct test_server *s, const MunitParameter params[])
{
int rv;
rv = dqlite_node_create(s->id, s->address, s->dir, &s->dqlite);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_bind_address(s->dqlite, s->address);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_connect_func(s->dqlite, endpointConnect, s);
munit_assert_int(rv, ==, 0);
rv = dqlite_node_set_network_latency_ms(s->dqlite, 10);
munit_assert_int(rv, ==, 0);
const char *snapshot_threshold_param =
munit_parameters_get(params, SNAPSHOT_THRESHOLD_PARAM);
if (snapshot_threshold_param != NULL) {
unsigned threshold = (unsigned)atoi(snapshot_threshold_param);
rv = dqlite_node_set_snapshot_params(s->dqlite, threshold,
threshold);
munit_assert_int(rv, ==, 0);
}
const char *disk_mode_param = munit_parameters_get(params, "disk_mode");
if (disk_mode_param != NULL) {
bool disk_mode = (bool)atoi(disk_mode_param);
if (disk_mode) {
rv = dqlite_node_enable_disk_mode(s->dqlite);
munit_assert_int(rv, ==, 0);
}
}
const char *target_voters_param =
munit_parameters_get(params, "target_voters");
if (target_voters_param != NULL) {
int n = atoi(target_voters_param);
rv = dqlite_node_set_target_voters(s->dqlite, n);
munit_assert_int(rv, ==, 0);
}
const char *target_standbys_param =
munit_parameters_get(params, "target_standbys");
if (target_standbys_param != NULL) {
int n = atoi(target_standbys_param);
rv = dqlite_node_set_target_standbys(s->dqlite, n);
munit_assert_int(rv, ==, 0);
}
const char *role_management_param =
munit_parameters_get(params, "role_management");
if (role_management_param != NULL) {
bool role_management = (bool)atoi(role_management_param);
s->role_management = role_management;
if (role_management) {
rv = dqlite_node_enable_role_management(s->dqlite);
munit_assert_int(rv, ==, 0);
}
}
rv = dqlite_node_start(s->dqlite);
munit_assert_int(rv, ==, 0);
test_server_client_connect(s, &s->client);
}
struct client_proto *test_server_client(struct test_server *s)
{
return &s->client;
}
void test_server_client_reconnect(struct test_server *s, struct client_proto *c)
{
test_server_client_close(s, c);
test_server_client_connect(s, c);
}
void test_server_client_connect(struct test_server *s, struct client_proto *c)
{
int rv;
int fd;
rv = endpointConnect(NULL, s->address, &fd);
munit_assert_int(rv, ==, 0);
memset(c, 0, sizeof *c);
buffer__init(&c->read);
buffer__init(&c->write);
c->fd = fd;
}
void test_server_client_close(struct test_server *s, struct client_proto *c)
{
(void)s;
clientClose(c);
}
static void setOther(struct test_server *s, struct test_server *other)
{
unsigned i = other->id - 1;
munit_assert_ptr_null(s->others[i]);
s->others[i] = other;
}
void test_server_network(struct test_server *servers, unsigned n_servers)
{
unsigned i;
unsigned j;
for (i = 0; i < n_servers; i++) {
for (j = 0; j < n_servers; j++) {
struct test_server *server = &servers[i];
struct test_server *other = &servers[j];
if (i == j) {
continue;
}
setOther(server, other);
}
}
}
dqlite-1.16.0/test/lib/server.h 0000664 0000000 0000000 00000003322 14512203222 0016256 0 ustar 00root root 0000000 0000000 /* Setup fully blown servers running in standalone threads. */
#ifndef TEST_SERVER_H
#define TEST_SERVER_H
#include
#include
#include "../../src/client/protocol.h"
#include "../../include/dqlite.h"
#include "endpoint.h"
#include "munit.h"
#define SNAPSHOT_THRESHOLD_PARAM "snapshot-threshold"
struct test_server
{
unsigned id; /* Server ID. */
char address[8]; /* Server address. */
char *dir; /* Data directory. */
dqlite_node *dqlite; /* Dqlite instance. */
bool role_management;
struct client_proto client; /* Connected client. */
struct test_server *others[5]; /* Other servers, by ID-1. */
};
/* Initialize the test server. */
void test_server_setup(struct test_server *s,
unsigned id,
const MunitParameter params[]);
/* Cleanup the test server. */
void test_server_tear_down(struct test_server *s);
/* Start the test server. */
void test_server_start(struct test_server *s, const MunitParameter params[]);
/* Stop the test server. */
void test_server_stop(struct test_server *s);
/* Connect all the given the servers to each other. */
void test_server_network(struct test_server *servers, unsigned n_servers);
/* Return a client connected to the server. */
struct client_proto *test_server_client(struct test_server *s);
/* Closes and reopens a client connection to the server. */
void test_server_client_reconnect(struct test_server *s,
struct client_proto *c);
/* Opens a client connection to the server. */
void test_server_client_connect(struct test_server *s, struct client_proto *c);
/* Closes a client connection to ther server. */
void test_server_client_close(struct test_server *s, struct client_proto *c);
#endif /* TEST_SERVER_H */
dqlite-1.16.0/test/lib/sqlite.c 0000664 0000000 0000000 00000000612 14512203222 0016243 0 ustar 00root root 0000000 0000000 #include
#include "sqlite.h"
void test_sqlite_setup(const MunitParameter params[])
{
int rc;
(void)params;
rc = sqlite3_initialize();
if (rc != SQLITE_OK) {
munit_errorf("sqlite_init(): %s", sqlite3_errstr(rc));
}
}
void test_sqlite_tear_down()
{
int rc;
rc = sqlite3_shutdown();
if (rc != SQLITE_OK) {
munit_errorf("sqlite_shutdown(): %s", sqlite3_errstr(rc));
}
}
dqlite-1.16.0/test/lib/sqlite.h 0000664 0000000 0000000 00000000603 14512203222 0016250 0 ustar 00root root 0000000 0000000 /* Global SQLite configuration. */
#ifndef TEST_SQLITE_H
#define TEST_SQLITE_H
#include "munit.h"
/* Setup SQLite global state. */
void test_sqlite_setup(const MunitParameter params[]);
/* Teardown SQLite global state. */
void test_sqlite_tear_down(void);
#define SETUP_SQLITE test_sqlite_setup(params);
#define TEAR_DOWN_SQLITE test_sqlite_tear_down();
#endif /* TEST_SQLITE_H */
dqlite-1.16.0/test/lib/stmt.h 0000664 0000000 0000000 00000001551 14512203222 0015741 0 ustar 00root root 0000000 0000000 /**
* Setup a test prepared statement.
*/
#ifndef TEST_STMT_H
#define TEST_STMT_H
#include
#define FIXTURE_STMT sqlite3_stmt *stmt
#define STMT_PREPARE(CONN, STMT, SQL) \
{ \
int rc; \
rc = sqlite3_prepare_v2(CONN, SQL, -1, &STMT, NULL); \
munit_assert_int(rc, ==, 0); \
}
#define STMT_FINALIZE(STMT) sqlite3_finalize(STMT)
#define STMT_EXEC(CONN, SQL) \
{ \
int rc; \
char *msg; \
rc = sqlite3_exec(CONN, SQL, NULL, NULL, &msg); \
munit_assert_int(rc, ==, SQLITE_OK); \
}
#endif /* TEST_STMT_H */
dqlite-1.16.0/test/lib/util.h 0000664 0000000 0000000 00000001750 14512203222 0015730 0 ustar 00root root 0000000 0000000 /**
* Utility macros and functions.
*/
#ifndef TEST_UTIL_H
#define TEST_UTIL_H
#include "munit.h"
#include
/* Wait a bounded time in seconds until a condition is true. */
#define AWAIT_TRUE(FN, ARG, SEC) \
do { \
struct timespec _start = {0}; \
struct timespec _end = {0}; \
clock_gettime(CLOCK_MONOTONIC, &_start); \
clock_gettime(CLOCK_MONOTONIC, &_end); \
while (!FN(ARG) && ((_end.tv_sec - _start.tv_sec) < SEC)) { \
clock_gettime(CLOCK_MONOTONIC, &_end); \
} \
if (!FN(ARG)) { \
return MUNIT_FAIL; \
} \
} while (0)
#endif /* TEST_UTIL_H */
dqlite-1.16.0/test/lib/uv.c 0000664 0000000 0000000 00000002331 14512203222 0015374 0 ustar 00root root 0000000 0000000 #include "uv.h"
#define TEST_UV_MAX_LOOP_RUN 10 /* Max n. of loop iterations upon teardown */
void test_uv_setup(const MunitParameter params[], struct uv_loop_s *l)
{
int rv;
(void)params;
rv = uv_loop_init(l);
munit_assert_int(rv, ==, 0);
}
int test_uv_run(struct uv_loop_s *l, unsigned n)
{
unsigned i;
int rv;
munit_assert_int(n, >, 0);
for (i = 0; i < n; i++) {
rv = uv_run(l, UV_RUN_ONCE);
if (rv < 0) {
munit_errorf("uv_run: %s (%d)", uv_strerror(rv), rv);
}
if (rv == 0) {
break;
}
}
return rv;
}
void test_uv_stop(struct uv_loop_s *l)
{
unsigned n_handles;
/* Spin a few times to trigger pending callbacks. */
n_handles = test_uv_run(l, TEST_UV_MAX_LOOP_RUN);
if (n_handles > 0) {
munit_errorf("loop has still %d pending active handles",
n_handles);
}
}
static void test_uv__walk_cb(uv_handle_t *handle, void *arg)
{
(void)arg;
munit_logf(MUNIT_LOG_INFO, "handle %d", handle->type);
}
void test_uv_tear_down(struct uv_loop_s *l)
{
int rv;
rv = uv_loop_close(l);
if (rv != 0) {
uv_walk(l, test_uv__walk_cb, NULL);
munit_errorf("uv_loop_close: %s (%d)", uv_strerror(rv), rv);
}
rv = uv_replace_allocator(malloc, realloc, calloc, free);
munit_assert_int(rv, ==, 0);
}
dqlite-1.16.0/test/lib/uv.h 0000664 0000000 0000000 00000005173 14512203222 0015410 0 ustar 00root root 0000000 0000000 /**
* Add support for using the libuv loop in tests.
*/
#ifndef TEST_UV_H
#define TEST_UV_H
#include
#include "munit.h"
/* Max n. of loop iterations ran by a single function call */
#define TEST_UV_MAX_LOOP_RUN 10
/**
* Initialize the given libuv loop.
*/
void test_uv_setup(const MunitParameter params[], struct uv_loop_s *l);
/**
* Run the loop until there are no pending active handles.
*
* If there are still pending active handles after 10 loop iterations, the test
* will fail.
*
* This is meant to be used in tear down functions.
*/
void test_uv_stop(struct uv_loop_s *l);
/**
* Tear down the loop making sure no active handles are left.
*/
void test_uv_tear_down(struct uv_loop_s *l);
/**
* Run the loop until there are no pending active handles or the given amount of
* iterations is reached.
*
* Return non-zero if there are pending handles.
*/
int test_uv_run(struct uv_loop_s *l, unsigned n);
/**
* Run the loop until the given function returns true.
*
* If the loop exhausts all active handles or if #TEST_UV_MAX_LOOP_RUN is
* reached without @f returning #true, the test fails.
*/
#define test_uv_run_until(DATA, FUNC) \
{ \
unsigned i; \
int rv; \
for (i = 0; i < TEST_UV_MAX_LOOP_RUN; i++) { \
if (FUNC(DATA)) { \
break; \
} \
rv = uv_run(&f->loop, UV_RUN_ONCE); \
if (rv < 0) { \
munit_errorf("uv_run: %s", uv_strerror(rv)); \
} \
if (rv == 0) { \
if (FUNC(DATA)) { \
break; \
} \
munit_errorf( \
"uv_run: stopped after %u iterations", \
i + 1); \
} \
} \
if (i == TEST_UV_MAX_LOOP_RUN) { \
munit_errorf( \
"uv_run: condition not met in %d iterations", \
TEST_UV_MAX_LOOP_RUN); \
} \
}
#endif /* TEST_UV_H */
dqlite-1.16.0/test/lib/vfs.h 0000664 0000000 0000000 00000001276 14512203222 0015554 0 ustar 00root root 0000000 0000000 /**
* Setup an in-memory VFS instance to use in tests.
*/
#ifndef TEST_VFS_H
#define TEST_VFS_H
#include "../../src/vfs.h"
#define FIXTURE_VFS struct sqlite3_vfs vfs;
#define SETUP_VFS \
{ \
int rv_; \
rv_ = VfsInit(&f->vfs, f->config.name); \
munit_assert_int(rv_, ==, 0); \
rv_ = sqlite3_vfs_register(&f->vfs, 0); \
munit_assert_int(rv_, ==, 0); \
}
#define TEAR_DOWN_VFS \
{ \
sqlite3_vfs_unregister(&f->vfs); \
VfsClose(&f->vfs); \
}
#endif /* TEST_VFS_H */
dqlite-1.16.0/test/test_error.c 0000664 0000000 0000000 00000015466 14512203222 0016401 0 ustar 00root root 0000000 0000000 #include
#include "../include/dqlite.h"
#include "../src/error.h"
#include "./lib/heap.h"
#include "./lib/runner.h"
#include "./lib/sqlite.h"
TEST_MODULE(error);
/******************************************************************************
*
* Setup and tear down
*
******************************************************************************/
static void *setup(const MunitParameter params[], void *user_data)
{
dqlite__error *error;
test_heap_setup(params, user_data);
test_sqlite_setup(params);
error = (dqlite__error *)munit_malloc(sizeof(*error));
dqlite__error_init(error);
return error;
}
static void tear_down(void *data)
{
dqlite__error *error = data;
dqlite__error_close(error);
test_sqlite_tear_down();
test_heap_tear_down(data);
free(error);
}
/******************************************************************************
*
* dqlite__error_printf
*
******************************************************************************/
TEST_SUITE(printf);
TEST_SETUP(printf, setup);
TEST_TEAR_DOWN(printf, tear_down);
TEST_CASE(printf, success, NULL)
{
dqlite__error *error = data;
(void)params;
munit_assert_true(dqlite__error_is_null(error));
dqlite__error_printf(error, "hello %s", "world");
munit_assert_string_equal(*error, "hello world");
return MUNIT_OK;
}
TEST_CASE(printf, override, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_printf(error, "hello %s", "world");
dqlite__error_printf(error, "I'm %s!", "here");
munit_assert_string_equal(*error, "I'm here!");
return MUNIT_OK;
}
TEST_CASE(printf, oom, NULL)
{
dqlite__error *error = data;
(void)params;
test_heap_fault_config(0, 1);
test_heap_fault_enable();
dqlite__error_printf(error, "hello %s", "world");
munit_assert_string_equal(*error,
"error message unavailable (out of memory)");
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite__error_wrapf
*
******************************************************************************/
TEST_SUITE(wrapf);
TEST_SETUP(wrapf, setup);
TEST_TEAR_DOWN(wrapf, tear_down);
TEST_CASE(wrapf, success, NULL)
{
dqlite__error *error = data;
dqlite__error cause;
(void)params;
dqlite__error_init(&cause);
dqlite__error_printf(&cause, "hello %s", "world");
dqlite__error_wrapf(error, &cause, "boom");
dqlite__error_close(&cause);
munit_assert_string_equal(*error, "boom: hello world");
return MUNIT_OK;
}
TEST_CASE(wrapf, null_cause, NULL)
{
dqlite__error *error = data;
dqlite__error cause;
(void)params;
dqlite__error_init(&cause);
dqlite__error_wrapf(error, &cause, "boom");
dqlite__error_close(&cause);
munit_assert_string_equal(*error, "boom: (null)");
return MUNIT_OK;
}
TEST_CASE(wrapf, itself, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_printf(error, "I'm %s!", "here");
dqlite__error_wrapf(error, error, "boom");
munit_assert_string_equal(*error, "boom: I'm here!");
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite__error_oom
*
******************************************************************************/
TEST_SUITE(oom);
TEST_SETUP(oom, setup);
TEST_TEAR_DOWN(oom, tear_down);
TEST_CASE(oom, success, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_oom(error, "boom");
munit_assert_string_equal(*error, "boom: out of memory");
return MUNIT_OK;
}
TEST_CASE(oom, vargs, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_oom(error, "boom %d", 123);
munit_assert_string_equal(*error, "boom 123: out of memory");
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite__error_sys
*
******************************************************************************/
TEST_SUITE(sys);
TEST_SETUP(sys, setup);
TEST_TEAR_DOWN(sys, tear_down);
TEST_CASE(sys, success, NULL)
{
dqlite__error *error = data;
(void)params;
open("/foo/bar/egg/baz", 0);
dqlite__error_sys(error, "boom");
munit_assert_string_equal(*error, "boom: No such file or directory");
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite__error_uv
*
******************************************************************************/
TEST_SUITE(uv);
TEST_SETUP(uv, setup);
TEST_TEAR_DOWN(uv, tear_down);
TEST_CASE(uv, success, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_uv(error, UV_EBUSY, "boom");
munit_assert_string_equal(*error,
"boom: resource busy or locked (EBUSY)");
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite__error_copy
*
******************************************************************************/
TEST_SUITE(copy);
TEST_SETUP(copy, setup);
TEST_TEAR_DOWN(copy, tear_down);
TEST_CASE(copy, success, NULL)
{
dqlite__error *error = data;
int err;
char *msg;
(void)params;
dqlite__error_printf(error, "hello %s", "world");
err = dqlite__error_copy(error, &msg);
munit_assert_int(err, ==, 0);
munit_assert_string_equal(msg, "hello world");
sqlite3_free(msg);
return MUNIT_OK;
}
TEST_CASE(copy, null, NULL)
{
dqlite__error *error = data;
int err;
char *msg;
(void)params;
err = dqlite__error_copy(error, &msg);
munit_assert_int(err, ==, DQLITE_ERROR);
munit_assert_ptr_equal(msg, NULL);
return MUNIT_OK;
}
TEST_CASE(copy, oom, NULL)
{
dqlite__error *error = data;
int err;
char *msg;
(void)params;
return MUNIT_SKIP;
test_heap_fault_config(2, 1);
test_heap_fault_enable();
dqlite__error_printf(error, "hello");
err = dqlite__error_copy(error, &msg);
munit_assert_int(err, ==, DQLITE_NOMEM);
munit_assert_ptr_equal(msg, NULL);
return MUNIT_OK;
}
/******************************************************************************
*
* dqlite__error_is_disconnect
*
******************************************************************************/
TEST_SUITE(is_disconnect);
TEST_SETUP(is_disconnect, setup);
TEST_TEAR_DOWN(is_disconnect, tear_down);
TEST_CASE(is_disconnect, eof, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_uv(error, UV_EOF, "boom");
munit_assert_true(dqlite__error_is_disconnect(error));
return MUNIT_OK;
}
TEST_CASE(is_disconnect, econnreset, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_uv(error, UV_ECONNRESET, "boom");
munit_assert_true(dqlite__error_is_disconnect(error));
return MUNIT_OK;
}
TEST_CASE(is_disconnect, other, NULL)
{
dqlite__error *error = data;
(void)params;
dqlite__error_printf(error, "boom");
munit_assert_true(!dqlite__error_is_disconnect(error));
return MUNIT_OK;
}
TEST_CASE(is_disconnect, null, NULL)
{
dqlite__error *error = data;
(void)params;
munit_assert_true(!dqlite__error_is_disconnect(error));
return MUNIT_OK;
}
dqlite-1.16.0/test/test_integration.c 0000664 0000000 0000000 00000021072 14512203222 0017561 0 ustar 00root root 0000000 0000000 #include
#include