Merge tag '6.2.1' into unstable

Former-commit-id: bfed57e3e0edaa724b9d060a6bb8edc5a6de65fa
This commit is contained in:
John Sully 2021-05-19 02:59:48 +00:00
commit fe8efa916b
298 changed files with 30298 additions and 10182 deletions

View File

@ -1,20 +1,24 @@
--- ---
name: Bug report name: Bug report
about: Create a report to help us improve about: Help us improve KeyDB by reporting a bug
title: '' title: '[BUG]'
labels: '' labels: ''
assignees: '' assignees: ''
--- ---
**Describe the bug** **Describe the bug**
A clear and concise description of what the bug is.
** Log Files ** A short description of the bug.
These should be KeyDB logs, not syslogs or logs from your container manager. If you are reporting a crash there will be a line in your log stating:
"=== KEYDB BUG REPORT START: Cut & paste starting from here ==="
Please copy everything after this line. **To reproduce**
**To Reproduce** Steps to reproduce the behavior and/or a minimal code sample.
Do you know how to reproduce this? If so please provide repro steps.
**Expected behavior**
A description of what you expected to happen.
**Additional information**
Any additional information that is relevant to the problem.

20
.github/ISSUE_TEMPLATE/crash_report.md vendored Normal file
View File

@ -0,0 +1,20 @@
---
name: Crash report
about: Submit a crash report
title: '[CRASH]'
labels: ''
assignees: ''
---
**Crash report**
Paste the complete crash log between the quotes below. Please include a few lines from the log preceding the crash report to provide some context.
```
```
**Aditional information**
1. OS distribution and version
2. Steps to reproduce (if any)

View File

@ -1,20 +1,24 @@
--- ---
name: Feature request name: Feature request
about: Suggest an idea for this project about: Suggest a feature for KeyDB
title: '' title: '[NEW]'
labels: '' labels: ''
assignees: '' assignees: ''
--- ---
**Is your feature request related to a problem? Please describe.** **The problem/use-case that the feature addresses**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like** A description of the problem that the feature will solve, or the use-case with which the feature will be used.
A clear and concise description of what you want to happen.
**Describe alternatives you've considered** **Description of the feature**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context** A description of what you want to happen.
Add any other context or screenshots about the feature request here.
**Alternatives you've considered**
Any alternative solutions or features you've considered, including references to existing open and closed feature requests in this repository.
**Additional information**
Any additional information that is relevant to the feature request.

8
.github/ISSUE_TEMPLATE/other_stuff.md vendored Normal file
View File

@ -0,0 +1,8 @@
---
name: Other
about: Can't find the right issue type? Use this one!
title: ''
labels: ''
assignees: ''
---

21
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,21 @@
---
name: Question
about: Ask the Redis developers
title: '[QUESTION]'
labels: ''
assignees: ''
---
Please keep in mind that this issue tracker should be used for reporting bugs or proposing improvements to the Redis server.
Generally, questions about using Redis should be directed to the [community](https://redis.io/community):
* [the mailing list](https://groups.google.com/forum/#!forum/redis-db)
* [the `redis` tag at StackOverflow](http://stackoverflow.com/questions/tagged/redis)
* [/r/redis subreddit](http://www.reddit.com/r/redis)
* [the irc channel #redis](http://webchat.freenode.net/?channels=redis) on freenode
It is also possible that your question was already asked here, so please do a quick issues search before submitting. Lastly, if your question is about one of Redis' [clients](https://redis.io/clients), you may to contact your client's developers for help.
That said, please feel free to replace all this with your question :)

View File

@ -12,7 +12,7 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make BUILD_TLS=yes -j2 make REDIS_CFLAGS='-Werror' BUILD_TLS=yes -j2
- name: gen-cert - name: gen-cert
run: ./utils/gen-test-certs.sh run: ./utils/gen-test-certs.sh
- name: test-tls - name: test-tls
@ -44,7 +44,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: make - name: make
run: make -j2 run: make REDIS_CFLAGS='-Werror' -j2
build-libc-malloc: build-libc-malloc:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -54,5 +54,5 @@ jobs:
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get -y install uuid-dev libcurl4-openssl-dev sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make MALLOC=libc -j2 make REDIS_CFLAGS='-Werror' MALLOC=libc -j2

View File

@ -1,180 +0,0 @@
name: Daily
on:
pull_request:
branches:
# any PR to a release branch.
- '[0-9].[0-9]'
schedule:
- cron: '0 0 * * *'
jobs:
test-ubuntu-jemalloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make
- name: test
run: |
sudo apt-get install tcl8.5
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-ubuntu-libc-malloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make MALLOC=libc
- name: test
run: |
sudo apt-get install tcl8.5
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make BUILD_TLS=yes -j2
- name: "test (tls)"
run: |
sudo apt-get install tcl8.5 tcl-tls
./utils/gen-test-certs.sh
./runtest --accurate --verbose --tls
- name: test
run: ./runtest --accurate --verbose
- name: module api test (tls)
run: ./runtest-moduleapi --verbose --tls
test-ubuntu-arm:
runs-on: [self-hosted, linux, arm]
steps:
- uses: actions/checkout@v2
- name: make
run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make -j4
- name: test
run: |
sudo apt-get -y install tcl8.5
./runtest --clients 2 --verbose
- name: module tests
run: |
./runtest-moduleapi
test-valgrind:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
sudo apt-get -y install uuid-dev libcurl4-openssl-dev
make valgrind
- name: test
run: |
sudo apt-get update
sudo apt-get install tcl8.5 valgrind -y
./runtest --valgrind --verbose --clients 1
- name: module api test
run: ./runtest-moduleapi --valgrind --verbose --clients 1
test-centos7-jemalloc:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: centos:7
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
yum -y install centos-release-scl
yum -y install devtoolset-7
scl enable devtoolset-7 "make"
- name: test
run: |
yum -y install tcl
./runtest --accurate --verbose
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster
test-centos7-tls:
runs-on: ubuntu-latest
if: github.repository == 'redis/redis'
container: centos:7
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: |
yum -y install centos-release-scl epel-release
yum -y install devtoolset-7 openssl-devel openssl
scl enable devtoolset-7 "make BUILD_TLS=yes"
- name: test
run: |
yum -y install tcl tcltls
./utils/gen-test-certs.sh
./runtest --accurate --verbose --tls
./runtest --accurate --verbose
- name: module api test
run: |
./runtest-moduleapi --verbose --tls
./runtest-moduleapi --verbose
- name: sentinel tests
run: |
./runtest-sentinel --tls
./runtest-sentinel
- name: cluster tests
run: |
./runtest-cluster --tls
./runtest-cluster
test-macos-latest:
runs-on: macos-latest
if: github.repository == 'redis/redis'
timeout-minutes: 14400
steps:
- uses: actions/checkout@v2
- name: make
run: make
- name: test
run: |
./runtest --accurate --verbose --no-latency
- name: module api test
run: ./runtest-moduleapi --verbose
- name: sentinel tests
run: ./runtest-sentinel
- name: cluster tests
run: ./runtest-cluster

1
.gitignore vendored
View File

@ -58,3 +58,4 @@ Makefile.dep
.ccls .ccls
.ccls-cache/* .ccls-cache/*
compile_commands.json compile_commands.json
redis.code-workspace

File diff suppressed because it is too large Load Diff

96
CONDUCT Normal file
View File

@ -0,0 +1,96 @@
Contributor Covenant Code of Conduct
Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
this email address: redis@redis.io.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
1. Correction
Community Impact: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
Consequence: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
2. Warning
Community Impact: A violation through a single incident or series
of actions.
Consequence: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
3. Temporary Ban
Community Impact: A serious violation of community standards, including
sustained inappropriate behavior.
Consequence: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
4. Permanent Ban
Community Impact: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
Consequence: A permanent ban from any sort of public interaction within
the community.
Attribution
This Code of Conduct is adapted from the Contributor Covenant,
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by Mozillas code of conduct
enforcement ladder.
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.

View File

@ -1,5 +1,6 @@
Copyright (c) 2006-2020, Salvatore Sanfilippo Copyright (c) 2006-2020, Salvatore Sanfilippo
Copyright (C) 2019-2020, John Sully Copyright (C) 2019-2021, John Sully
Copyright (C) 2020-2021, EQ Alpha Technology Ltd.
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

View File

@ -118,6 +118,13 @@ installed):
% ./runtest --tls % ./runtest --tls
If TLS is built, running the tests with TLS enabled (you will need `tcl-tls`
installed):
% ./utils/gen-test-certs.sh
% ./runtest --tls
Fixing build problems with dependencies or cached build options Fixing build problems with dependencies or cached build options
--------- ---------
@ -169,6 +176,18 @@ To compile against jemalloc on Mac OS X systems, use:
% make MALLOC=jemalloc % make MALLOC=jemalloc
Monotonic clock
---------------
By default, Redis will build using the POSIX clock_gettime function as the
monotonic clock source. On most modern systems, the internal processor clock
can be used to improve performance. Cautions can be found here:
http://oliveryang.net/2015/09/pitfalls-of-TSC-usage/
To build with support for the processor's internal instruction clock, use:
% make CFLAGS="-DUSE_PROCESSOR_CLOCK"
Verbose build Verbose build
------------- -------------

13
deps/Makefile vendored
View File

@ -37,6 +37,7 @@ distclean:
-(cd linenoise && $(MAKE) clean) > /dev/null || true -(cd linenoise && $(MAKE) clean) > /dev/null || true
-(cd lua && $(MAKE) clean) > /dev/null || true -(cd lua && $(MAKE) clean) > /dev/null || true
-(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true -(cd jemalloc && [ -f Makefile ] && $(MAKE) distclean) > /dev/null || true
-(cd hdr_histogram && $(MAKE) clean) > /dev/null || true
-(rm -f .make-*) -(rm -f .make-*)
.PHONY: distclean .PHONY: distclean
@ -62,18 +63,24 @@ memkind:
cd memkind && $(MAKE) cd memkind && $(MAKE)
hdr_histogram: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd hdr_histogram && $(MAKE)
.PHONY: hdr_histogram
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
# Make isinf() available # Make isinf() available
LUA_CFLAGS= -D__C99FEATURES__=1 LUA_CFLAGS= -D__C99FEATURES__=1
endif endif
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' $(CFLAGS) LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI -DENABLE_CJSON_GLOBAL -DREDIS_STATIC='' -DLUA_USE_MKSTEMP $(CFLAGS)
LUA_LDFLAGS+= $(LDFLAGS) LUA_LDFLAGS+= $(LDFLAGS)
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more # lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
# challenging to cross-compile lua (and redis). These defines make it easier # challenging to cross-compile lua (and redis). These defines make it easier
# to fit redis into cross-compilation environments, which typically set AR. # to fit redis into cross-compilation environments, which typically set AR.
AR=ar AR=ar
ARFLAGS=rcu ARFLAGS=rc
lua: .make-prerequisites lua: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
@ -86,7 +93,7 @@ JEMALLOC_LDFLAGS= $(LDFLAGS)
jemalloc: .make-prerequisites jemalloc: .make-prerequisites
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR) @printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
cd jemalloc && ./configure --with-version=5.2.1-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ --enable-cc-silence CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" cd jemalloc && ./configure --with-version=5.2.1-0-g0 --with-lg-quantum=3 --with-jemalloc-prefix=je_ CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)"
cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a cd jemalloc && $(MAKE) CFLAGS="$(JEMALLOC_CFLAGS)" LDFLAGS="$(JEMALLOC_LDFLAGS)" lib/libjemalloc.a
.PHONY: jemalloc .PHONY: jemalloc

2
deps/README.md vendored
View File

@ -17,7 +17,7 @@ active defragmentation logic. However this feature of Redis is not mandatory
and Redis is able to understand if the Jemalloc version it is compiled and Redis is able to understand if the Jemalloc version it is compiled
against supports such Redis-specific modifications. So in theory, if you against supports such Redis-specific modifications. So in theory, if you
are not interested in the active defragmentation, you can replace Jemalloc are not interested in the active defragmentation, you can replace Jemalloc
just following tose steps: just following these steps:
1. Remove the jemalloc directory. 1. Remove the jemalloc directory.
2. Substitute it with the new jemalloc source tree. 2. Substitute it with the new jemalloc source tree.

121
deps/hdr_histogram/COPYING.txt vendored Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

41
deps/hdr_histogram/LICENSE.txt vendored Normal file
View File

@ -0,0 +1,41 @@
The code in this repository code was Written by Gil Tene, Michael Barker,
and Matt Warren, and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/
For users of this code who wish to consume it under the "BSD" license
rather than under the public domain or CC0 contribution text mentioned
above, the code found under this directory is *also* provided under the
following license (commonly referred to as the BSD 2-Clause License). This
license does not detract from the above stated release of the code into
the public domain, and simply represents an additional license granted by
the Author.
-----------------------------------------------------------------------------
** Beginning of "BSD 2-Clause License" text. **
Copyright (c) 2012, 2013, 2014 Gil Tene
Copyright (c) 2014 Michael Barker
Copyright (c) 2014 Matt Warren
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.

20
deps/hdr_histogram/Makefile vendored Normal file
View File

@ -0,0 +1,20 @@
STD=
WARN= -Wall
OPT= -Os
R_CFLAGS= $(STD) $(WARN) $(OPT) $(DEBUG) $(CFLAGS)
R_LDFLAGS= $(LDFLAGS)
DEBUG= -g
R_CC=$(CC) $(R_CFLAGS)
R_LD=$(CC) $(R_LDFLAGS)
hdr_histogram.o: hdr_histogram.h hdr_histogram.c
.c.o:
$(R_CC) -c $<
clean:
rm -f *.o

10
deps/hdr_histogram/README.md vendored Normal file
View File

@ -0,0 +1,10 @@
HdrHistogram_c v0.11.0
----------------------------------------------
This port contains a subset of the 'C' version of High Dynamic Range (HDR) Histogram available at [github.com/HdrHistogram/HdrHistogram_c](https://github.com/HdrHistogram/HdrHistogram_c).
The code present on `hdr_histogram.c`, `hdr_histogram.h`, and `hdr_atomic.c` was Written by Gil Tene, Michael Barker,
and Matt Warren, and released to the public domain, as explained at
http://creativecommons.org/publicdomain/zero/1.0/.

146
deps/hdr_histogram/hdr_atomic.h vendored Normal file
View File

@ -0,0 +1,146 @@
/**
* hdr_atomic.h
* Written by Philip Orwig and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/
*/
#ifndef HDR_ATOMIC_H__
#define HDR_ATOMIC_H__
#if defined(_MSC_VER)
#include <stdint.h>
#include <intrin.h>
#include <stdbool.h>
static void __inline * hdr_atomic_load_pointer(void** pointer)
{
_ReadBarrier();
return *pointer;
}
static void hdr_atomic_store_pointer(void** pointer, void* value)
{
_WriteBarrier();
*pointer = value;
}
static int64_t __inline hdr_atomic_load_64(int64_t* field)
{
_ReadBarrier();
return *field;
}
static void __inline hdr_atomic_store_64(int64_t* field, int64_t value)
{
_WriteBarrier();
*field = value;
}
static int64_t __inline hdr_atomic_exchange_64(volatile int64_t* field, int64_t value)
{
#if defined(_WIN64)
return _InterlockedExchange64(field, value);
#else
int64_t comparand;
int64_t initial_value = *field;
do
{
comparand = initial_value;
initial_value = _InterlockedCompareExchange64(field, value, comparand);
}
while (comparand != initial_value);
return initial_value;
#endif
}
static int64_t __inline hdr_atomic_add_fetch_64(volatile int64_t* field, int64_t value)
{
#if defined(_WIN64)
return _InterlockedExchangeAdd64(field, value) + value;
#else
int64_t comparand;
int64_t initial_value = *field;
do
{
comparand = initial_value;
initial_value = _InterlockedCompareExchange64(field, comparand + value, comparand);
}
while (comparand != initial_value);
return initial_value + value;
#endif
}
static bool __inline hdr_atomic_compare_exchange_64(volatile int64_t* field, int64_t* expected, int64_t desired)
{
return *expected == _InterlockedCompareExchange64(field, desired, *expected);
}
#elif defined(__ATOMIC_SEQ_CST)
#define hdr_atomic_load_pointer(x) __atomic_load_n(x, __ATOMIC_SEQ_CST)
#define hdr_atomic_store_pointer(f,v) __atomic_store_n(f,v, __ATOMIC_SEQ_CST)
#define hdr_atomic_load_64(x) __atomic_load_n(x, __ATOMIC_SEQ_CST)
#define hdr_atomic_store_64(f,v) __atomic_store_n(f,v, __ATOMIC_SEQ_CST)
#define hdr_atomic_exchange_64(f,i) __atomic_exchange_n(f,i, __ATOMIC_SEQ_CST)
#define hdr_atomic_add_fetch_64(field, value) __atomic_add_fetch(field, value, __ATOMIC_SEQ_CST)
#define hdr_atomic_compare_exchange_64(field, expected, desired) __atomic_compare_exchange_n(field, expected, desired, false, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
#elif defined(__x86_64__)
#include <stdint.h>
#include <stdbool.h>
static inline void* hdr_atomic_load_pointer(void** pointer)
{
void* p = *pointer;
asm volatile ("" ::: "memory");
return p;
}
static inline void hdr_atomic_store_pointer(void** pointer, void* value)
{
asm volatile ("lock; xchgq %0, %1" : "+q" (value), "+m" (*pointer));
}
static inline int64_t hdr_atomic_load_64(int64_t* field)
{
int64_t i = *field;
asm volatile ("" ::: "memory");
return i;
}
static inline void hdr_atomic_store_64(int64_t* field, int64_t value)
{
asm volatile ("lock; xchgq %0, %1" : "+q" (value), "+m" (*field));
}
static inline int64_t hdr_atomic_exchange_64(volatile int64_t* field, int64_t value)
{
int64_t result = 0;
asm volatile ("lock; xchgq %1, %2" : "=r" (result), "+q" (value), "+m" (*field));
return result;
}
static inline int64_t hdr_atomic_add_fetch_64(volatile int64_t* field, int64_t value)
{
return __sync_add_and_fetch(field, value);
}
static inline bool hdr_atomic_compare_exchange_64(volatile int64_t* field, int64_t* expected, int64_t desired)
{
int64_t original;
asm volatile( "lock; cmpxchgq %2, %1" : "=a"(original), "+m"(*field) : "q"(desired), "0"(*expected));
return original == *expected;
}
#else
#error "Unable to determine atomic operations for your platform"
#endif
#endif /* HDR_ATOMIC_H__ */

1155
deps/hdr_histogram/hdr_histogram.c vendored Normal file

File diff suppressed because it is too large Load Diff

509
deps/hdr_histogram/hdr_histogram.h vendored Normal file
View File

@ -0,0 +1,509 @@
/**
* hdr_histogram.h
* Written by Michael Barker and released to the public domain,
* as explained at http://creativecommons.org/publicdomain/zero/1.0/
*
* The source for the hdr_histogram utilises a few C99 constructs, specifically
* the use of stdint/stdbool and inline variable declaration.
*/
#ifndef HDR_HISTOGRAM_H
#define HDR_HISTOGRAM_H 1
#include <stdint.h>
#include <stdbool.h>
#include <stdio.h>
struct hdr_histogram
{
int64_t lowest_trackable_value;
int64_t highest_trackable_value;
int32_t unit_magnitude;
int32_t significant_figures;
int32_t sub_bucket_half_count_magnitude;
int32_t sub_bucket_half_count;
int64_t sub_bucket_mask;
int32_t sub_bucket_count;
int32_t bucket_count;
int64_t min_value;
int64_t max_value;
int32_t normalizing_index_offset;
double conversion_ratio;
int32_t counts_len;
int64_t total_count;
int64_t* counts;
};
#ifdef __cplusplus
extern "C" {
#endif
/**
* Allocate the memory and initialise the hdr_histogram.
*
* Due to the size of the histogram being the result of some reasonably
* involved math on the input parameters this function it is tricky to stack allocate.
* The histogram should be released with hdr_close
*
* @param lowest_trackable_value The smallest possible value to be put into the
* histogram.
* @param highest_trackable_value The largest possible value to be put into the
* histogram.
* @param significant_figures The level of precision for this histogram, i.e. the number
* of figures in a decimal number that will be maintained. E.g. a value of 3 will mean
* the results from the histogram will be accurate up to the first three digits. Must
* be a value between 1 and 5 (inclusive).
* @param result Output parameter to capture allocated histogram.
* @return 0 on success, EINVAL if lowest_trackable_value is < 1 or the
* significant_figure value is outside of the allowed range, ENOMEM if malloc
* failed.
*/
int hdr_init(
int64_t lowest_trackable_value,
int64_t highest_trackable_value,
int significant_figures,
struct hdr_histogram** result);
/**
* Free the memory and close the hdr_histogram.
*
* @param h The histogram you want to close.
*/
void hdr_close(struct hdr_histogram* h);
/**
* Allocate the memory and initialise the hdr_histogram. This is the equivalent of calling
* hdr_init(1, highest_trackable_value, significant_figures, result);
*
* @deprecated use hdr_init.
*/
int hdr_alloc(int64_t highest_trackable_value, int significant_figures, struct hdr_histogram** result);
/**
* Reset a histogram to zero - empty out a histogram and re-initialise it
*
* If you want to re-use an existing histogram, but reset everything back to zero, this
* is the routine to use.
*
* @param h The histogram you want to reset to empty.
*
*/
void hdr_reset(struct hdr_histogram* h);
/**
* Get the memory size of the hdr_histogram.
*
* @param h "This" pointer
* @return The amount of memory used by the hdr_histogram in bytes
*/
size_t hdr_get_memory_size(struct hdr_histogram* h);
/**
* Records a value in the histogram, will round this value of to a precision at or better
* than the significant_figure specified at construction time.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_value(struct hdr_histogram* h, int64_t value);
/**
* Records a value in the histogram, will round this value of to a precision at or better
* than the significant_figure specified at construction time.
*
* Will record this value atomically, however the whole structure may appear inconsistent
* when read concurrently with this update. Do NOT mix calls to this method with calls
* to non-atomic updates.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_value_atomic(struct hdr_histogram* h, int64_t value);
/**
* Records count values in the histogram, will round this value of to a
* precision at or better than the significant_figure specified at construction
* time.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param count Number of 'value's to add to the histogram
* @return false if any value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_values(struct hdr_histogram* h, int64_t value, int64_t count);
/**
* Records count values in the histogram, will round this value of to a
* precision at or better than the significant_figure specified at construction
* time.
*
* Will record this value atomically, however the whole structure may appear inconsistent
* when read concurrently with this update. Do NOT mix calls to this method with calls
* to non-atomic updates.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param count Number of 'value's to add to the histogram
* @return false if any value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count);
/**
* Record a value in the histogram and backfill based on an expected interval.
*
* Records a value in the histogram, will round this value of to a precision at or better
* than the significant_figure specified at contruction time. This is specifically used
* for recording latency. If the value is larger than the expected_interval then the
* latency recording system has experienced co-ordinated omission. This method fills in the
* values that would have occured had the client providing the load not been blocked.
* @param h "This" pointer
* @param value Value to add to the histogram
* @param expected_interval The delay between recording values.
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_corrected_value(struct hdr_histogram* h, int64_t value, int64_t expexcted_interval);
/**
* Record a value in the histogram and backfill based on an expected interval.
*
* Records a value in the histogram, will round this value of to a precision at or better
* than the significant_figure specified at contruction time. This is specifically used
* for recording latency. If the value is larger than the expected_interval then the
* latency recording system has experienced co-ordinated omission. This method fills in the
* values that would have occured had the client providing the load not been blocked.
*
* Will record this value atomically, however the whole structure may appear inconsistent
* when read concurrently with this update. Do NOT mix calls to this method with calls
* to non-atomic updates.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param expected_interval The delay between recording values.
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_corrected_value_atomic(struct hdr_histogram* h, int64_t value, int64_t expexcted_interval);
/**
* Record a value in the histogram 'count' times. Applies the same correcting logic
* as 'hdr_record_corrected_value'.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param count Number of 'value's to add to the histogram
* @param expected_interval The delay between recording values.
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_corrected_values(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval);
/**
* Record a value in the histogram 'count' times. Applies the same correcting logic
* as 'hdr_record_corrected_value'.
*
* Will record this value atomically, however the whole structure may appear inconsistent
* when read concurrently with this update. Do NOT mix calls to this method with calls
* to non-atomic updates.
*
* @param h "This" pointer
* @param value Value to add to the histogram
* @param count Number of 'value's to add to the histogram
* @param expected_interval The delay between recording values.
* @return false if the value is larger than the highest_trackable_value and can't be recorded,
* true otherwise.
*/
bool hdr_record_corrected_values_atomic(struct hdr_histogram* h, int64_t value, int64_t count, int64_t expected_interval);
/**
* Adds all of the values from 'from' to 'this' histogram. Will return the
* number of values that are dropped when copying. Values will be dropped
* if they around outside of h.lowest_trackable_value and
* h.highest_trackable_value.
*
* @param h "This" pointer
* @param from Histogram to copy values from.
* @return The number of values dropped when copying.
*/
int64_t hdr_add(struct hdr_histogram* h, const struct hdr_histogram* from);
/**
* Adds all of the values from 'from' to 'this' histogram. Will return the
* number of values that are dropped when copying. Values will be dropped
* if they around outside of h.lowest_trackable_value and
* h.highest_trackable_value.
*
* @param h "This" pointer
* @param from Histogram to copy values from.
* @return The number of values dropped when copying.
*/
int64_t hdr_add_while_correcting_for_coordinated_omission(
struct hdr_histogram* h, struct hdr_histogram* from, int64_t expected_interval);
/**
* Get minimum value from the histogram. Will return 2^63-1 if the histogram
* is empty.
*
* @param h "This" pointer
*/
int64_t hdr_min(const struct hdr_histogram* h);
/**
* Get maximum value from the histogram. Will return 0 if the histogram
* is empty.
*
* @param h "This" pointer
*/
int64_t hdr_max(const struct hdr_histogram* h);
/**
* Get the value at a specific percentile.
*
* @param h "This" pointer.
* @param percentile The percentile to get the value for
*/
int64_t hdr_value_at_percentile(const struct hdr_histogram* h, double percentile);
/**
* Gets the standard deviation for the values in the histogram.
*
* @param h "This" pointer
* @return The standard deviation
*/
double hdr_stddev(const struct hdr_histogram* h);
/**
* Gets the mean for the values in the histogram.
*
* @param h "This" pointer
* @return The mean
*/
double hdr_mean(const struct hdr_histogram* h);
/**
* Determine if two values are equivalent with the histogram's resolution.
* Where "equivalent" means that value samples recorded for any two
* equivalent values are counted in a common total count.
*
* @param h "This" pointer
* @param a first value to compare
* @param b second value to compare
* @return 'true' if values are equivalent with the histogram's resolution.
*/
bool hdr_values_are_equivalent(const struct hdr_histogram* h, int64_t a, int64_t b);
/**
* Get the lowest value that is equivalent to the given value within the histogram's resolution.
* Where "equivalent" means that value samples recorded for any two
* equivalent values are counted in a common total count.
*
* @param h "This" pointer
* @param value The given value
* @return The lowest value that is equivalent to the given value within the histogram's resolution.
*/
int64_t hdr_lowest_equivalent_value(const struct hdr_histogram* h, int64_t value);
/**
* Get the count of recorded values at a specific value
* (to within the histogram resolution at the value level).
*
* @param h "This" pointer
* @param value The value for which to provide the recorded count
* @return The total count of values recorded in the histogram within the value range that is
* {@literal >=} lowestEquivalentValue(<i>value</i>) and {@literal <=} highestEquivalentValue(<i>value</i>)
*/
int64_t hdr_count_at_value(const struct hdr_histogram* h, int64_t value);
int64_t hdr_count_at_index(const struct hdr_histogram* h, int32_t index);
int64_t hdr_value_at_index(const struct hdr_histogram* h, int32_t index);
struct hdr_iter_percentiles
{
bool seen_last_value;
int32_t ticks_per_half_distance;
double percentile_to_iterate_to;
double percentile;
};
struct hdr_iter_recorded
{
int64_t count_added_in_this_iteration_step;
};
struct hdr_iter_linear
{
int64_t value_units_per_bucket;
int64_t count_added_in_this_iteration_step;
int64_t next_value_reporting_level;
int64_t next_value_reporting_level_lowest_equivalent;
};
struct hdr_iter_log
{
double log_base;
int64_t count_added_in_this_iteration_step;
int64_t next_value_reporting_level;
int64_t next_value_reporting_level_lowest_equivalent;
};
/**
* The basic iterator. This is a generic structure
* that supports all of the types of iteration. Use
* the appropriate initialiser to get the desired
* iteration.
*
* @
*/
struct hdr_iter
{
const struct hdr_histogram* h;
/** raw index into the counts array */
int32_t counts_index;
/** snapshot of the length at the time the iterator is created */
int64_t total_count;
/** value directly from array for the current counts_index */
int64_t count;
/** sum of all of the counts up to and including the count at this index */
int64_t cumulative_count;
/** The current value based on counts_index */
int64_t value;
int64_t highest_equivalent_value;
int64_t lowest_equivalent_value;
int64_t median_equivalent_value;
int64_t value_iterated_from;
int64_t value_iterated_to;
union
{
struct hdr_iter_percentiles percentiles;
struct hdr_iter_recorded recorded;
struct hdr_iter_linear linear;
struct hdr_iter_log log;
} specifics;
bool (* _next_fp)(struct hdr_iter* iter);
};
/**
* Initalises the basic iterator.
*
* @param itr 'This' pointer
* @param h The histogram to iterate over
*/
void hdr_iter_init(struct hdr_iter* iter, const struct hdr_histogram* h);
/**
* Initialise the iterator for use with percentiles.
*/
void hdr_iter_percentile_init(struct hdr_iter* iter, const struct hdr_histogram* h, int32_t ticks_per_half_distance);
/**
* Initialise the iterator for use with recorded values.
*/
void hdr_iter_recorded_init(struct hdr_iter* iter, const struct hdr_histogram* h);
/**
* Initialise the iterator for use with linear values.
*/
void hdr_iter_linear_init(
struct hdr_iter* iter,
const struct hdr_histogram* h,
int64_t value_units_per_bucket);
/**
* Update the iterator value units per bucket
*/
void hdr_iter_linear_set_value_units_per_bucket(struct hdr_iter* iter, int64_t value_units_per_bucket);
/**
* Initialise the iterator for use with logarithmic values
*/
void hdr_iter_log_init(
struct hdr_iter* iter,
const struct hdr_histogram* h,
int64_t value_units_first_bucket,
double log_base);
/**
* Iterate to the next value for the iterator. If there are no more values
* available return faluse.
*
* @param itr 'This' pointer
* @return 'false' if there are no values remaining for this iterator.
*/
bool hdr_iter_next(struct hdr_iter* iter);
typedef enum
{
CLASSIC,
CSV
} format_type;
/**
* Print out a percentile based histogram to the supplied stream. Note that
* this call will not flush the FILE, this is left up to the user.
*
* @param h 'This' pointer
* @param stream The FILE to write the output to
* @param ticks_per_half_distance The number of iteration steps per half-distance to 100%
* @param value_scale Scale the output values by this amount
* @param format_type Format to use, e.g. CSV.
* @return 0 on success, error code on failure. EIO if an error occurs writing
* the output.
*/
int hdr_percentiles_print(
struct hdr_histogram* h, FILE* stream, int32_t ticks_per_half_distance,
double value_scale, format_type format);
/**
* Internal allocation methods, used by hdr_dbl_histogram.
*/
struct hdr_histogram_bucket_config
{
int64_t lowest_trackable_value;
int64_t highest_trackable_value;
int64_t unit_magnitude;
int64_t significant_figures;
int32_t sub_bucket_half_count_magnitude;
int32_t sub_bucket_half_count;
int64_t sub_bucket_mask;
int32_t sub_bucket_count;
int32_t bucket_count;
int32_t counts_len;
};
int hdr_calculate_bucket_config(
int64_t lowest_trackable_value,
int64_t highest_trackable_value,
int significant_figures,
struct hdr_histogram_bucket_config* cfg);
void hdr_init_preallocated(struct hdr_histogram* h, struct hdr_histogram_bucket_config* cfg);
int64_t hdr_size_of_equivalent_value_range(const struct hdr_histogram* h, int64_t value);
int64_t hdr_next_non_equivalent_value(const struct hdr_histogram* h, int64_t value);
int64_t hdr_median_equivalent_value(const struct hdr_histogram* h, int64_t value);
/**
* Used to reset counters after importing data manuallying into the histogram, used by the logging code
* and other custom serialisation tools.
*/
void hdr_reset_internal_counters(struct hdr_histogram* h);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -6,3 +6,4 @@
/*.a /*.a
/*.pc /*.pc
*.dSYM *.dSYM
tags

View File

@ -1,5 +1,4 @@
language: c language: c
sudo: false
compiler: compiler:
- gcc - gcc
- clang - clang
@ -8,17 +7,34 @@ os:
- linux - linux
- osx - osx
dist: bionic
branches: branches:
only: only:
- staging - staging
- trying - trying
- master - master
- /^release\/.*$/
install:
- if [ "$BITS" == "64" ]; then
wget https://github.com/redis/redis/archive/6.0.6.tar.gz;
tar -xzvf 6.0.6.tar.gz;
pushd redis-6.0.6 && BUILD_TLS=yes make && export PATH=$PWD/src:$PATH && popd;
fi
before_script: before_script:
- if [ "$TRAVIS_OS_NAME" == "osx" ] ; then brew update; brew install redis; fi - if [ "$TRAVIS_OS_NAME" == "osx" ]; then
curl -O https://distfiles.macports.org/MacPorts/MacPorts-2.6.2-10.13-HighSierra.pkg;
sudo installer -pkg MacPorts-2.6.2-10.13-HighSierra.pkg -target /;
export PATH=$PATH:/opt/local/bin && sudo port -v selfupdate;
sudo port -N install openssl redis;
fi;
addons: addons:
apt: apt:
sources:
- sourceline: 'ppa:chris-lea/redis-server'
packages: packages:
- libc6-dbg - libc6-dbg
- libc6-dev - libc6-dev
@ -27,14 +43,20 @@ addons:
- libc6-dbg:i386 - libc6-dbg:i386
- gcc-multilib - gcc-multilib
- g++-multilib - g++-multilib
- libssl-dev
- libssl-dev:i386
- valgrind - valgrind
- redis
env: env:
- BITS="32" - BITS="32"
- BITS="64" - BITS="64"
script: script:
- EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DHIREDIS_SSL:BOOL=ON"; - EXTRA_CMAKE_OPTS="-DENABLE_EXAMPLES:BOOL=ON -DENABLE_SSL:BOOL=ON";
if [ "$BITS" == "64" ]; then
EXTRA_CMAKE_OPTS="$EXTRA_CMAKE_OPTS -DENABLE_SSL_TESTS:BOOL=ON";
fi;
if [ "$TRAVIS_OS_NAME" == "osx" ]; then if [ "$TRAVIS_OS_NAME" == "osx" ]; then
if [ "$BITS" == "32" ]; then if [ "$BITS" == "32" ]; then
CFLAGS="-m32 -Werror"; CFLAGS="-m32 -Werror";
@ -58,12 +80,24 @@ script:
fi; fi;
fi; fi;
export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS export CFLAGS CXXFLAGS LDFLAGS TEST_PREFIX EXTRA_CMAKE_OPTS
- make && make clean;
if [ "$TRAVIS_OS_NAME" == "osx" ]; then
if [ "$BITS" == "64" ]; then
OPENSSL_PREFIX="$(ls -d /usr/local/Cellar/openssl@1.1/*)" USE_SSL=1 make;
fi;
else
USE_SSL=1 make;
fi;
- mkdir build/ && cd build/ - mkdir build/ && cd build/
- cmake .. ${EXTRA_CMAKE_OPTS} - cmake .. ${EXTRA_CMAKE_OPTS}
- make VERBOSE=1 - make VERBOSE=1
- ctest -V - if [ "$BITS" == "64" ]; then
TEST_SSL=1 SKIPS_AS_FAILS=1 ctest -V;
else
SKIPS_AS_FAILS=1 ctest -V;
fi;
matrix: jobs:
include: include:
# Windows MinGW cross compile on Linux # Windows MinGW cross compile on Linux
- os: linux - os: linux
@ -89,9 +123,9 @@ matrix:
- eval "${MATRIX_EVAL}" - eval "${MATRIX_EVAL}"
install: install:
- choco install ninja - choco install ninja
- choco install -y memurai-developer
script: script:
- mkdir build && cd build - mkdir build && cd build
- cmd.exe /C '"C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64 && - cmd.exe //C 'C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvarsall.bat' amd64 '&&'
cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake .. -G Ninja -DCMAKE_BUILD_TYPE=Release -DENABLE_EXAMPLES=ON '&&' ninja -v
ninja -v' - ./hiredis-test.exe
- ctest -V

View File

@ -1,18 +1,169 @@
### 1.0.0 (unreleased) ## [1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) - (2020-08-03)
Announcing Hiredis v1.0.0, which adds support for RESP3, SSL connections, allocator injection, and better Windows support! :tada:
_A big thanks to everyone who helped with this release. The following list includes everyone who contributed at least five lines, sorted by lines contributed._ :sparkling_heart:
[Michael Grunder](https://github.com/michael-grunder), [Yossi Gottlieb](https://github.com/yossigo),
[Mark Nunberg](https://github.com/mnunberg), [Marcus Geelnard](https://github.com/mbitsnbites),
[Justin Brewer](https://github.com/justinbrewer), [Valentino Geron](https://github.com/valentinogeron),
[Minun Dragonation](https://github.com/dragonation), [Omri Steiner](https://github.com/OmriSteiner),
[Sangmoon Yi](https://github.com/jman-krafton), [Jinjiazh](https://github.com/jinjiazhang),
[Odin Hultgren Van Der Horst](https://github.com/Miniwoffer), [Muhammad Zahalqa](https://github.com/tryfinally),
[Nick Rivera](https://github.com/heronr), [Qi Yang](https://github.com/movebean),
[kevin1018](https://github.com/kevin1018)
[Full Changelog](https://github.com/redis/hiredis/compare/v0.14.1...v1.0.0)
**BREAKING CHANGES**: **BREAKING CHANGES**:
* Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now * `redisOptions` now has two timeout fields. One for connecting, and one for commands. If you're presently using `options->timeout` you will need to change it to use `options->connect_timeout`. (See [example](https://github.com/redis/hiredis/commit/38b5ae543f5c99eb4ccabbe277770fc6bc81226f#diff-86ba39d37aa829c8c82624cce4f049fbL36))
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string * Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now protocol errors. This is consistent
with the RESP specification. On 32-bit platforms, the upper bound is lowered to `SIZE_MAX`.
User code should compare this to `size_t` values as well. If it was used to * `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
compare to other values, casting might be necessary or can be removed, if
casting was applied before.
### 0.x.x (unreleased) **New features:**
- Support for RESP3
[\#697](https://github.com/redis/hiredis/pull/697),
[\#805](https://github.com/redis/hiredis/pull/805),
[\#819](https://github.com/redis/hiredis/pull/819),
[\#841](https://github.com/redis/hiredis/pull/841)
([Yossi Gottlieb](https://github.com/yossigo), [Michael Grunder](https://github.com/michael-grunder))
- Support for SSL connections
[\#645](https://github.com/redis/hiredis/pull/645),
[\#699](https://github.com/redis/hiredis/pull/699),
[\#702](https://github.com/redis/hiredis/pull/702),
[\#708](https://github.com/redis/hiredis/pull/708),
[\#711](https://github.com/redis/hiredis/pull/711),
[\#821](https://github.com/redis/hiredis/pull/821),
[more](https://github.com/redis/hiredis/pulls?q=is%3Apr+is%3Amerged+SSL)
([Mark Nunberg](https://github.com/mnunberg), [Yossi Gottlieb](https://github.com/yossigo))
- Run-time allocator injection
[\#800](https://github.com/redis/hiredis/pull/800)
([Michael Grunder](https://github.com/michael-grunder))
- Improved Windows support (including MinGW and Windows CI)
[\#652](https://github.com/redis/hiredis/pull/652),
[\#663](https://github.com/redis/hiredis/pull/663)
([Marcus Geelnard](https://www.bitsnbites.eu/author/m/))
- Adds support for distinct connect and command timeouts
[\#839](https://github.com/redis/hiredis/pull/839),
[\#829](https://github.com/redis/hiredis/pull/829)
([Valentino Geron](https://github.com/valentinogeron))
- Add generic pointer and destructor to `redisContext` that users can use for context.
[\#855](https://github.com/redis/hiredis/pull/855)
([Michael Grunder](https://github.com/michael-grunder))
**Closed issues (that involved code changes):**
- Makefile does not install TLS libraries [\#809](https://github.com/redis/hiredis/issues/809)
- redisConnectWithOptions should not set command timeout [\#722](https://github.com/redis/hiredis/issues/722), [\#829](https://github.com/redis/hiredis/pull/829) ([valentinogeron](https://github.com/valentinogeron))
- Fix integer overflow in `sdsrange` [\#827](https://github.com/redis/hiredis/issues/827)
- INFO & CLUSTER commands failed when using RESP3 [\#802](https://github.com/redis/hiredis/issues/802)
- Windows compatibility patches [\#687](https://github.com/redis/hiredis/issues/687), [\#838](https://github.com/redis/hiredis/issues/838), [\#842](https://github.com/redis/hiredis/issues/842)
- RESP3 PUSH messages incorrectly use pending callback [\#825](https://github.com/redis/hiredis/issues/825)
- Asynchronous PSUBSCRIBE command fails when using RESP3 [\#815](https://github.com/redis/hiredis/issues/815)
- New SSL API [\#804](https://github.com/redis/hiredis/issues/804), [\#813](https://github.com/redis/hiredis/issues/813)
- Hard-coded limit of nested reply depth [\#794](https://github.com/redis/hiredis/issues/794)
- Fix TCP_NODELAY in Windows/OSX [\#679](https://github.com/redis/hiredis/issues/679), [\#690](https://github.com/redis/hiredis/issues/690), [\#779](https://github.com/redis/hiredis/issues/779), [\#785](https://github.com/redis/hiredis/issues/785),
- Added timers to libev adapter. [\#778](https://github.com/redis/hiredis/issues/778), [\#795](https://github.com/redis/hiredis/pull/795)
- Initialization discards const qualifier [\#777](https://github.com/redis/hiredis/issues/777)
- \[BUG\]\[MinGW64\] Error setting socket timeout [\#775](https://github.com/redis/hiredis/issues/775)
- undefined reference to hi_malloc [\#769](https://github.com/redis/hiredis/issues/769)
- hiredis pkg-config file incorrectly ignores multiarch libdir spec'n [\#767](https://github.com/redis/hiredis/issues/767)
- Don't use -G to build shared object on Solaris [\#757](https://github.com/redis/hiredis/issues/757)
- error when make USE\_SSL=1 [\#748](https://github.com/redis/hiredis/issues/748)
- Allow to change SSL Mode [\#646](https://github.com/redis/hiredis/issues/646)
- hiredis/adapters/libevent.h memleak [\#618](https://github.com/redis/hiredis/issues/618)
- redisLibuvPoll crash when server closes the connetion [\#545](https://github.com/redis/hiredis/issues/545)
- about redisAsyncDisconnect question [\#518](https://github.com/redis/hiredis/issues/518)
- hiredis adapters libuv error for help [\#508](https://github.com/redis/hiredis/issues/508)
- API/ABI changes analysis [\#506](https://github.com/redis/hiredis/issues/506)
- Memory leak patch in Redis [\#502](https://github.com/redis/hiredis/issues/502)
- Remove the depth limitation [\#421](https://github.com/redis/hiredis/issues/421)
**Merged pull requests:**
- Move SSL management to a distinct private pointer [\#855](https://github.com/redis/hiredis/pull/855) ([michael-grunder](https://github.com/michael-grunder))
- Move include to sockcompat.h to maintain style [\#850](https://github.com/redis/hiredis/pull/850) ([michael-grunder](https://github.com/michael-grunder))
- Remove erroneous tag and add license to push example [\#849](https://github.com/redis/hiredis/pull/849) ([michael-grunder](https://github.com/michael-grunder))
- fix windows compiling with mingw [\#848](https://github.com/redis/hiredis/pull/848) ([rmalizia44](https://github.com/rmalizia44))
- Some Windows quality of life improvements. [\#846](https://github.com/redis/hiredis/pull/846) ([michael-grunder](https://github.com/michael-grunder))
- Use \_WIN32 define instead of WIN32 [\#845](https://github.com/redis/hiredis/pull/845) ([michael-grunder](https://github.com/michael-grunder))
- Non Linux CI fixes [\#844](https://github.com/redis/hiredis/pull/844) ([michael-grunder](https://github.com/michael-grunder))
- Resp3 oob push support [\#841](https://github.com/redis/hiredis/pull/841) ([michael-grunder](https://github.com/michael-grunder))
- fix \#785: defer TCP\_NODELAY in async tcp connections [\#836](https://github.com/redis/hiredis/pull/836) ([OmriSteiner](https://github.com/OmriSteiner))
- sdsrange overflow fix [\#830](https://github.com/redis/hiredis/pull/830) ([michael-grunder](https://github.com/michael-grunder))
- Use explicit pointer casting for c++ compatibility [\#826](https://github.com/redis/hiredis/pull/826) ([aureus1](https://github.com/aureus1))
- Document allocator injection and completeness fix in test.c [\#824](https://github.com/redis/hiredis/pull/824) ([michael-grunder](https://github.com/michael-grunder))
- Use unique names for allocator struct members [\#823](https://github.com/redis/hiredis/pull/823) ([michael-grunder](https://github.com/michael-grunder))
- New SSL API to replace redisSecureConnection\(\). [\#821](https://github.com/redis/hiredis/pull/821) ([yossigo](https://github.com/yossigo))
- Add logic to handle RESP3 push messages [\#819](https://github.com/redis/hiredis/pull/819) ([michael-grunder](https://github.com/michael-grunder))
- Use standrad isxdigit instead of custom helper function. [\#814](https://github.com/redis/hiredis/pull/814) ([tryfinally](https://github.com/tryfinally))
- Fix missing SSL build/install options. [\#812](https://github.com/redis/hiredis/pull/812) ([yossigo](https://github.com/yossigo))
- Add link to ABI tracker [\#808](https://github.com/redis/hiredis/pull/808) ([michael-grunder](https://github.com/michael-grunder))
- Resp3 verbatim string support [\#805](https://github.com/redis/hiredis/pull/805) ([michael-grunder](https://github.com/michael-grunder))
- Allow users to replace allocator and handle OOM everywhere. [\#800](https://github.com/redis/hiredis/pull/800) ([michael-grunder](https://github.com/michael-grunder))
- Remove nested depth limitation. [\#797](https://github.com/redis/hiredis/pull/797) ([michael-grunder](https://github.com/michael-grunder))
- Attempt to fix compilation on Solaris [\#796](https://github.com/redis/hiredis/pull/796) ([michael-grunder](https://github.com/michael-grunder))
- Support timeouts in libev adapater [\#795](https://github.com/redis/hiredis/pull/795) ([michael-grunder](https://github.com/michael-grunder))
- Fix pkgconfig when installing to a custom lib dir [\#793](https://github.com/redis/hiredis/pull/793) ([michael-grunder](https://github.com/michael-grunder))
- Fix USE\_SSL=1 make/cmake on OSX and CMake tests [\#789](https://github.com/redis/hiredis/pull/789) ([michael-grunder](https://github.com/michael-grunder))
- Use correct libuv call on Windows [\#784](https://github.com/redis/hiredis/pull/784) ([michael-grunder](https://github.com/michael-grunder))
- Added CMake package config and fixed hiredis\_ssl on Windows [\#783](https://github.com/redis/hiredis/pull/783) ([michael-grunder](https://github.com/michael-grunder))
- CMake: Set hiredis\_ssl shared object version. [\#780](https://github.com/redis/hiredis/pull/780) ([yossigo](https://github.com/yossigo))
- Win32 tests and timeout fix [\#776](https://github.com/redis/hiredis/pull/776) ([michael-grunder](https://github.com/michael-grunder))
- Provides an optional cleanup callback for async data. [\#768](https://github.com/redis/hiredis/pull/768) ([heronr](https://github.com/heronr))
- Housekeeping fixes [\#764](https://github.com/redis/hiredis/pull/764) ([michael-grunder](https://github.com/michael-grunder))
- install alloc.h [\#756](https://github.com/redis/hiredis/pull/756) ([ch1aki](https://github.com/ch1aki))
- fix spelling mistakes [\#746](https://github.com/redis/hiredis/pull/746) ([ShooterIT](https://github.com/ShooterIT))
- Free the reply in redisGetReply when passed NULL [\#741](https://github.com/redis/hiredis/pull/741) ([michael-grunder](https://github.com/michael-grunder))
- Fix dead code in sslLogCallback relating to should\_log variable. [\#737](https://github.com/redis/hiredis/pull/737) ([natoscott](https://github.com/natoscott))
- Fix typo in dict.c. [\#731](https://github.com/redis/hiredis/pull/731) ([Kevin-Xi](https://github.com/Kevin-Xi))
- Adding an option to DISABLE\_TESTS [\#727](https://github.com/redis/hiredis/pull/727) ([pbotros](https://github.com/pbotros))
- Update README with SSL support. [\#720](https://github.com/redis/hiredis/pull/720) ([yossigo](https://github.com/yossigo))
- Fixes leaks in unit tests [\#715](https://github.com/redis/hiredis/pull/715) ([michael-grunder](https://github.com/michael-grunder))
- SSL Tests [\#711](https://github.com/redis/hiredis/pull/711) ([yossigo](https://github.com/yossigo))
- SSL Reorganization [\#708](https://github.com/redis/hiredis/pull/708) ([yossigo](https://github.com/yossigo))
- Fix MSVC build. [\#706](https://github.com/redis/hiredis/pull/706) ([yossigo](https://github.com/yossigo))
- SSL: Properly report SSL\_connect\(\) errors. [\#702](https://github.com/redis/hiredis/pull/702) ([yossigo](https://github.com/yossigo))
- Silent SSL trace to stdout by default. [\#699](https://github.com/redis/hiredis/pull/699) ([yossigo](https://github.com/yossigo))
- Port RESP3 support from Redis. [\#697](https://github.com/redis/hiredis/pull/697) ([yossigo](https://github.com/yossigo))
- Removed whitespace before newline [\#691](https://github.com/redis/hiredis/pull/691) ([Miniwoffer](https://github.com/Miniwoffer))
- Add install adapters header files [\#688](https://github.com/redis/hiredis/pull/688) ([kevin1018](https://github.com/kevin1018))
- Remove unnecessary null check before free [\#684](https://github.com/redis/hiredis/pull/684) ([qlyoung](https://github.com/qlyoung))
- redisReaderGetReply leak memory [\#671](https://github.com/redis/hiredis/pull/671) ([movebean](https://github.com/movebean))
- fix timeout code in windows [\#670](https://github.com/redis/hiredis/pull/670) ([jman-krafton](https://github.com/jman-krafton))
- test: fix errstr matching for musl libc [\#665](https://github.com/redis/hiredis/pull/665) ([ghost](https://github.com/ghost))
- Windows: MinGW fixes and Windows Travis builders [\#663](https://github.com/redis/hiredis/pull/663) ([mbitsnbites](https://github.com/mbitsnbites))
- The setsockopt and getsockopt API diffs from BSD socket and WSA one [\#662](https://github.com/redis/hiredis/pull/662) ([dragonation](https://github.com/dragonation))
- Fix Compile Error On Windows \(Visual Studio\) [\#658](https://github.com/redis/hiredis/pull/658) ([jinjiazhang](https://github.com/jinjiazhang))
- Fix NXDOMAIN test case [\#653](https://github.com/redis/hiredis/pull/653) ([michael-grunder](https://github.com/michael-grunder))
- Add MinGW support [\#652](https://github.com/redis/hiredis/pull/652) ([mbitsnbites](https://github.com/mbitsnbites))
- SSL Support [\#645](https://github.com/redis/hiredis/pull/645) ([mnunberg](https://github.com/mnunberg))
- Fix Invalid argument after redisAsyncConnectUnix [\#644](https://github.com/redis/hiredis/pull/644) ([codehz](https://github.com/codehz))
- Makefile: use predefined AR [\#632](https://github.com/redis/hiredis/pull/632) ([Mic92](https://github.com/Mic92))
- FreeBSD build fix [\#628](https://github.com/redis/hiredis/pull/628) ([devnexen](https://github.com/devnexen))
- Fix errors not propagating properly with libuv.h. [\#624](https://github.com/redis/hiredis/pull/624) ([yossigo](https://github.com/yossigo))
- Update README.md [\#621](https://github.com/redis/hiredis/pull/621) ([Crunsher](https://github.com/Crunsher))
- Fix redisBufferRead documentation [\#620](https://github.com/redis/hiredis/pull/620) ([hacst](https://github.com/hacst))
- Add CPPFLAGS to REAL\_CFLAGS [\#614](https://github.com/redis/hiredis/pull/614) ([thomaslee](https://github.com/thomaslee))
- Update createArray to take size\_t [\#597](https://github.com/redis/hiredis/pull/597) ([justinbrewer](https://github.com/justinbrewer))
- fix common realloc mistake and add null check more [\#580](https://github.com/redis/hiredis/pull/580) ([charsyam](https://github.com/charsyam))
- Proper error reporting for connect failures [\#578](https://github.com/redis/hiredis/pull/578) ([mnunberg](https://github.com/mnunberg))
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
## [1.0.0-rc1](https://github.com/redis/hiredis/tree/v1.0.0-rc1) - (2020-07-29)
_Note: There were no changes to code between v1.0.0-rc1 and v1.0.0 so see v1.0.0 for changelog_
### 0.14.1 (2020-03-13)
* Adds safe allocation wrappers (CVE-2020-7105, #747, #752) (Michael Grunder)
### 0.14.0 (2018-09-25)
**BREAKING CHANGES**: **BREAKING CHANGES**:
* Change `redisReply.len` to `size_t`, as it denotes the the size of a string * Change `redisReply.len` to `size_t`, as it denotes the the size of a string
@ -20,10 +171,6 @@
User code should compare this to `size_t` values as well. User code should compare this to `size_t` values as well.
If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before. If it was used to compare to other values, casting might be necessary or can be removed, if casting was applied before.
* `redisReplyObjectFunctions.createArray` now takes `size_t` for its length parameter.
### 0.14.0 (2018-09-25)
* Make string2ll static to fix conflict with Redis (Tom Lee [c3188b]) * Make string2ll static to fix conflict with Redis (Tom Lee [c3188b])
* Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537]) * Use -dynamiclib instead of -shared for OSX (Ryan Schmidt [a65537])
* Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622]) * Use string2ll from Redis w/added tests (Michael Grunder [7bef04, 60f622])
@ -196,4 +343,3 @@ The parser, standalone since v0.12.0, can now be compiled on Windows
### 0.10.0 ### 0.10.0
* See commit log. * See commit log.

View File

@ -3,6 +3,8 @@ INCLUDE(GNUInstallDirs)
PROJECT(hiredis) PROJECT(hiredis)
OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF) OPTION(ENABLE_SSL "Build hiredis_ssl for SSL support" OFF)
OPTION(DISABLE_TESTS "If tests should be compiled or not" OFF)
OPTION(ENABLE_SSL_TESTS, "Should we test SSL connections" OFF)
MACRO(getVersionBit name) MACRO(getVersionBit name)
SET(VERSION_REGEX "^#define ${name} (.+)$") SET(VERSION_REGEX "^#define ${name} (.+)$")
@ -22,7 +24,8 @@ PROJECT(hiredis VERSION "${VERSION}")
SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples") SET(ENABLE_EXAMPLES OFF CACHE BOOL "Enable building hiredis examples")
ADD_LIBRARY(hiredis SHARED SET(hiredis_sources
alloc.c
async.c async.c
dict.c dict.c
hiredis.c hiredis.c
@ -31,20 +34,32 @@ ADD_LIBRARY(hiredis SHARED
sds.c sds.c
sockcompat.c) sockcompat.c)
SET(hiredis_sources ${hiredis_sources})
IF(WIN32)
ADD_COMPILE_DEFINITIONS(_CRT_SECURE_NO_WARNINGS WIN32_LEAN_AND_MEAN)
ENDIF()
ADD_LIBRARY(hiredis SHARED ${hiredis_sources})
SET_TARGET_PROPERTIES(hiredis SET_TARGET_PROPERTIES(hiredis
PROPERTIES PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE
VERSION "${HIREDIS_SONAME}") VERSION "${HIREDIS_SONAME}")
IF(WIN32 OR MINGW) IF(WIN32 OR MINGW)
TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32) TARGET_LINK_LIBRARIES(hiredis PRIVATE ws2_32)
ENDIF() ENDIF()
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC .)
TARGET_INCLUDE_DIRECTORIES(hiredis PUBLIC $<INSTALL_INTERFACE:.> $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY) CONFIGURE_FILE(hiredis.pc.in hiredis.pc @ONLY)
INSTALL(TARGETS hiredis INSTALL(TARGETS hiredis
DESTINATION "${CMAKE_INSTALL_LIBDIR}") EXPORT hiredis-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
INSTALL(FILES hiredis.h read.h sds.h async.h INSTALL(FILES hiredis.h read.h sds.h async.h alloc.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(DIRECTORY adapters INSTALL(DIRECTORY adapters
@ -53,6 +68,26 @@ INSTALL(DIRECTORY adapters
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
export(EXPORT hiredis-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis-targets.cmake"
NAMESPACE hiredis::)
SET(CMAKE_CONF_INSTALL_DIR share/hiredis)
SET(INCLUDE_INSTALL_DIR include)
include(CMakePackageConfigHelpers)
configure_package_config_file(hiredis-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
PATH_VARS INCLUDE_INSTALL_DIR)
INSTALL(EXPORT hiredis-targets
FILE hiredis-targets.cmake
NAMESPACE hiredis::
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis-config.cmake
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
IF(ENABLE_SSL) IF(ENABLE_SSL)
IF (NOT OPENSSL_ROOT_DIR) IF (NOT OPENSSL_ROOT_DIR)
IF (APPLE) IF (APPLE)
@ -60,26 +95,66 @@ IF(ENABLE_SSL)
ENDIF() ENDIF()
ENDIF() ENDIF()
FIND_PACKAGE(OpenSSL REQUIRED) FIND_PACKAGE(OpenSSL REQUIRED)
ADD_LIBRARY(hiredis_ssl SHARED SET(hiredis_ssl_sources
ssl.c) ssl.c)
ADD_LIBRARY(hiredis_ssl SHARED
${hiredis_ssl_sources})
IF (APPLE)
SET_PROPERTY(TARGET hiredis_ssl PROPERTY LINK_FLAGS "-Wl,-undefined -Wl,dynamic_lookup")
ENDIF()
SET_TARGET_PROPERTIES(hiredis_ssl
PROPERTIES
WINDOWS_EXPORT_ALL_SYMBOLS TRUE
VERSION "${HIREDIS_SONAME}")
TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}") TARGET_INCLUDE_DIRECTORIES(hiredis_ssl PRIVATE "${OPENSSL_INCLUDE_DIR}")
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES}) TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE ${OPENSSL_LIBRARIES})
IF (WIN32 OR MINGW)
TARGET_LINK_LIBRARIES(hiredis_ssl PRIVATE hiredis)
ENDIF()
CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY) CONFIGURE_FILE(hiredis_ssl.pc.in hiredis_ssl.pc @ONLY)
INSTALL(TARGETS hiredis_ssl INSTALL(TARGETS hiredis_ssl
DESTINATION "${CMAKE_INSTALL_LIBDIR}") EXPORT hiredis_ssl-targets
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR})
INSTALL(FILES hiredis_ssl.h INSTALL(FILES hiredis_ssl.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis) DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/hiredis)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl.pc
DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
export(EXPORT hiredis_ssl-targets
FILE "${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-targets.cmake"
NAMESPACE hiredis::)
SET(CMAKE_CONF_INSTALL_DIR share/hiredis_ssl)
configure_package_config_file(hiredis_ssl-config.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
INSTALL_DESTINATION ${CMAKE_CONF_INSTALL_DIR}
PATH_VARS INCLUDE_INSTALL_DIR)
INSTALL(EXPORT hiredis_ssl-targets
FILE hiredis_ssl-targets.cmake
NAMESPACE hiredis::
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/hiredis_ssl-config.cmake
DESTINATION ${CMAKE_CONF_INSTALL_DIR})
ENDIF() ENDIF()
IF(NOT (WIN32 OR MINGW)) IF(NOT DISABLE_TESTS)
ENABLE_TESTING() ENABLE_TESTING()
ADD_EXECUTABLE(hiredis-test test.c) ADD_EXECUTABLE(hiredis-test test.c)
IF(ENABLE_SSL_TESTS)
ADD_DEFINITIONS(-DHIREDIS_TEST_SSL=1)
TARGET_LINK_LIBRARIES(hiredis-test hiredis hiredis_ssl)
ELSE()
TARGET_LINK_LIBRARIES(hiredis-test hiredis) TARGET_LINK_LIBRARIES(hiredis-test hiredis)
ENDIF()
ADD_TEST(NAME hiredis-test ADD_TEST(NAME hiredis-test
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh) COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test.sh)
ENDIF() ENDIF()

71
deps/hiredis/Makefile vendored
View File

@ -3,16 +3,16 @@
# Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com> # Copyright (C) 2010-2011 Pieter Noordhuis <pcnoordhuis at gmail dot com>
# This file is released under the BSD license, see the COPYING file # This file is released under the BSD license, see the COPYING file
OBJ=net.o hiredis.o sds.o async.o read.o sockcompat.o OBJ=alloc.o net.o hiredis.o sds.o async.o read.o sockcompat.o
SSL_OBJ=ssl.o SSL_OBJ=ssl.o
EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib EXAMPLES=hiredis-example hiredis-example-libevent hiredis-example-libev hiredis-example-glib hiredis-example-push
ifeq ($(USE_SSL),1) ifeq ($(USE_SSL),1)
EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl EXAMPLES+=hiredis-example-ssl hiredis-example-libevent-ssl
endif endif
TESTS=hiredis-test TESTS=hiredis-test
LIBNAME=libhiredis LIBNAME=libhiredis
SSL_LIBNAME=libhiredis_ssl
PKGCONFNAME=hiredis.pc PKGCONFNAME=hiredis.pc
SSL_LIBNAME=libhiredis_ssl
SSL_PKGCONFNAME=hiredis_ssl.pc SSL_PKGCONFNAME=hiredis_ssl.pc
HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}') HIREDIS_MAJOR=$(shell grep HIREDIS_MAJOR hiredis.h | awk '{print $$3}')
@ -55,12 +55,17 @@ STLIBSUFFIX=a
DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME) DYLIB_MINOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR) DYLIB_MAJOR_NAME=$(LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX) DYLIBNAME=$(LIBNAME).$(DYLIBSUFFIX)
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME) DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(DYLIB_MINOR_NAME)
STLIBNAME=$(LIBNAME).$(STLIBSUFFIX) STLIBNAME=$(LIBNAME).$(STLIBSUFFIX)
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
STLIB_MAKE_CMD=$(AR) rcs STLIB_MAKE_CMD=$(AR) rcs
SSL_DYLIB_MINOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_SONAME)
SSL_DYLIB_MAJOR_NAME=$(SSL_LIBNAME).$(DYLIBSUFFIX).$(HIREDIS_MAJOR)
SSL_DYLIBNAME=$(SSL_LIBNAME).$(DYLIBSUFFIX)
SSL_STLIBNAME=$(SSL_LIBNAME).$(STLIBSUFFIX)
SSL_DYLIB_MAKE_CMD=$(CC) -shared -Wl,-soname,$(SSL_DYLIB_MINOR_NAME)
# Platform-specific overrides # Platform-specific overrides
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
@ -80,13 +85,22 @@ else
endif endif
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
IS_SUN_CC=$(shell sh -c '$(CC) -V 2>&1 |egrep -i -c "sun|studio"')
ifeq ($(IS_SUN_CC),1)
SUN_SHARED_FLAG=-G
else
SUN_SHARED_FLAG=-shared
endif
REAL_LDFLAGS+= -ldl -lnsl -lsocket REAL_LDFLAGS+= -ldl -lnsl -lsocket
DYLIB_MAKE_CMD=$(CC) -G -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(DYLIBNAME) -h $(DYLIB_MINOR_NAME) $(LDFLAGS)
SSL_DYLIB_MAKE_CMD=$(CC) $(SUN_SHARED_FLAG) -o $(SSL_DYLIBNAME) -h $(SSL_DYLIB_MINOR_NAME) $(LDFLAGS) $(SSL_LDFLAGS)
endif endif
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
DYLIBSUFFIX=dylib DYLIBSUFFIX=dylib
DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX) DYLIB_MINOR_NAME=$(LIBNAME).$(HIREDIS_SONAME).$(DYLIBSUFFIX)
DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS) DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(DYLIB_MINOR_NAME) -o $(DYLIBNAME) $(LDFLAGS)
SSL_DYLIB_MAKE_CMD=$(CC) -dynamiclib -Wl,-install_name,$(PREFIX)/$(LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME) -o $(SSL_DYLIBNAME) $(LDFLAGS) $(SSL_LDFLAGS)
DYLIB_PLUGIN=-Wl,-undefined -Wl,dynamic_lookup
endif endif
all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME) all: $(DYLIBNAME) $(STLIBNAME) hiredis-test $(PKGCONFNAME)
@ -95,15 +109,16 @@ all: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
endif endif
# Deps (use make dep to generate this) # Deps (use make dep to generate this)
async.o: async.c fmacros.h async.h hiredis.h read.h sds.h net.h dict.c dict.h alloc.o: alloc.c fmacros.h alloc.h
dict.o: dict.c fmacros.h dict.h async.o: async.c fmacros.h alloc.h async.h hiredis.h read.h sds.h net.h dict.c dict.h win32.h async_private.h
hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h net.h win32.h dict.o: dict.c fmacros.h alloc.h dict.h
net.o: net.c fmacros.h net.h hiredis.h read.h sds.h sockcompat.h win32.h hiredis.o: hiredis.c fmacros.h hiredis.h read.h sds.h alloc.h net.h async.h win32.h
read.o: read.c fmacros.h read.h sds.h net.o: net.c fmacros.h net.h hiredis.h read.h sds.h alloc.h sockcompat.h win32.h
sds.o: sds.c sds.h read.o: read.c fmacros.h alloc.h read.h sds.h win32.h
sds.o: sds.c sds.h sdsalloc.h alloc.h
sockcompat.o: sockcompat.c sockcompat.h sockcompat.o: sockcompat.c sockcompat.h
ssl.o: ssl.c hiredis.h ssl.o: ssl.c hiredis.h read.h sds.h alloc.h async.h win32.h async_private.h
test.o: test.c fmacros.h hiredis.h read.h sds.h test.o: test.c fmacros.h hiredis.h read.h sds.h alloc.h net.h sockcompat.h win32.h
$(DYLIBNAME): $(OBJ) $(DYLIBNAME): $(OBJ)
$(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS) $(DYLIB_MAKE_CMD) -o $(DYLIBNAME) $(OBJ) $(REAL_LDFLAGS)
@ -112,7 +127,7 @@ $(STLIBNAME): $(OBJ)
$(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ) $(STLIB_MAKE_CMD) $(STLIBNAME) $(OBJ)
$(SSL_DYLIBNAME): $(SSL_OBJ) $(SSL_DYLIBNAME): $(SSL_OBJ)
$(DYLIB_MAKE_CMD) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(SSL_LDFLAGS) $(SSL_DYLIB_MAKE_CMD) $(DYLIB_PLUGIN) -o $(SSL_DYLIBNAME) $(SSL_OBJ) $(REAL_LDFLAGS) $(LDFLAGS) $(SSL_LDFLAGS)
$(SSL_STLIBNAME): $(SSL_OBJ) $(SSL_STLIBNAME): $(SSL_OBJ)
$(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ) $(STLIB_MAKE_CMD) $(SSL_STLIBNAME) $(SSL_OBJ)
@ -146,6 +161,7 @@ hiredis-example-macosx: examples/example-macosx.c adapters/macosx.h $(STLIBNAME)
hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME) hiredis-example-ssl: examples/example-ssl.c $(STLIBNAME) $(SSL_STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(SSL_STLIBNAME) $(REAL_LDFLAGS) $(SSL_LDFLAGS)
ifndef AE_DIR ifndef AE_DIR
hiredis-example-ae: hiredis-example-ae:
@echo "Please specify AE_DIR (e.g. <redis repository>/src)" @echo "Please specify AE_DIR (e.g. <redis repository>/src)"
@ -180,13 +196,19 @@ endif
hiredis-example: examples/example.c $(STLIBNAME) hiredis-example: examples/example.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS) $(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
hiredis-example-push: examples/example-push.c $(STLIBNAME)
$(CC) -o examples/$@ $(REAL_CFLAGS) -I. $< $(STLIBNAME) $(REAL_LDFLAGS)
examples: $(EXAMPLES) examples: $(EXAMPLES)
TEST_LIBS = $(STLIBNAME) TEST_LIBS = $(STLIBNAME)
ifeq ($(USE_SSL),1) ifeq ($(USE_SSL),1)
TEST_LIBS += $(SSL_STLIBNAME) -lssl -lcrypto -lpthread TEST_LIBS += $(SSL_STLIBNAME)
TEST_LDFLAGS = $(SSL_LDFLAGS) -lssl -lcrypto -lpthread
endif endif
hiredis-test: test.o $(TEST_LIBS) hiredis-test: test.o $(TEST_LIBS)
$(CC) -o $@ $(REAL_CFLAGS) -I. $^ $(REAL_LDFLAGS) $(TEST_LDFLAGS)
hiredis-%: %.o $(STLIBNAME) hiredis-%: %.o $(STLIBNAME)
$(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS) $(CC) $(REAL_CFLAGS) -o $@ $< $(TEST_LIBS) $(REAL_LDFLAGS)
@ -221,7 +243,7 @@ $(PKGCONFNAME): hiredis.h
@echo Libs: -L\$${libdir} -lhiredis >> $@ @echo Libs: -L\$${libdir} -lhiredis >> $@
@echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@ @echo Cflags: -I\$${includedir} -D_FILE_OFFSET_BITS=64 >> $@
$(SSL_PKGCONFNAME): hiredis.h $(SSL_PKGCONFNAME): hiredis_ssl.h
@echo "Generating $@ for pkgconfig..." @echo "Generating $@ for pkgconfig..."
@echo prefix=$(PREFIX) > $@ @echo prefix=$(PREFIX) > $@
@echo exec_prefix=\$${prefix} >> $@ @echo exec_prefix=\$${prefix} >> $@
@ -237,7 +259,7 @@ $(SSL_PKGCONFNAME): hiredis.h
install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME) install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH) mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis.h async.h read.h sds.h $(INSTALL_INCLUDE_PATH) $(INSTALL) hiredis.h async.h read.h sds.h alloc.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters $(INSTALL) adapters/*.h $(INSTALL_INCLUDE_PATH)/adapters
$(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME) $(INSTALL) $(DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME) cd $(INSTALL_LIBRARY_PATH) && ln -sf $(DYLIB_MINOR_NAME) $(DYLIBNAME)
@ -245,6 +267,19 @@ install: $(DYLIBNAME) $(STLIBNAME) $(PKGCONFNAME)
mkdir -p $(INSTALL_PKGCONF_PATH) mkdir -p $(INSTALL_PKGCONF_PATH)
$(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH) $(INSTALL) $(PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
ifeq ($(USE_SSL),1)
install: install-ssl
install-ssl: $(SSL_DYLIBNAME) $(SSL_STLIBNAME) $(SSL_PKGCONFNAME)
mkdir -p $(INSTALL_INCLUDE_PATH) $(INSTALL_LIBRARY_PATH)
$(INSTALL) hiredis_ssl.h $(INSTALL_INCLUDE_PATH)
$(INSTALL) $(SSL_DYLIBNAME) $(INSTALL_LIBRARY_PATH)/$(SSL_DYLIB_MINOR_NAME)
cd $(INSTALL_LIBRARY_PATH) && ln -sf $(SSL_DYLIB_MINOR_NAME) $(SSL_DYLIBNAME)
$(INSTALL) $(SSL_STLIBNAME) $(INSTALL_LIBRARY_PATH)
mkdir -p $(INSTALL_PKGCONF_PATH)
$(INSTALL) $(SSL_PKGCONFNAME) $(INSTALL_PKGCONF_PATH)
endif
32bit: 32bit:
@echo "" @echo ""
@echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386" @echo "WARNING: if this fails under Linux you probably need to install libc6-dev-i386"

272
deps/hiredis/README.md vendored
View File

@ -1,6 +1,6 @@
[![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis) [![Build Status](https://travis-ci.org/redis/hiredis.png)](https://travis-ci.org/redis/hiredis)
**This Readme reflects the latest changed in the master branch. See [v0.13.3](https://github.com/redis/hiredis/tree/v0.13.3) for the Readme and documentation for the latest release.** **This Readme reflects the latest changed in the master branch. See [v1.0.0](https://github.com/redis/hiredis/tree/v1.0.0) for the Readme and documentation for the latest release ([API/ABI history](https://abi-laboratory.pro/?view=timeline&l=hiredis)).**
# HIREDIS # HIREDIS
@ -24,12 +24,31 @@ The library comes with multiple APIs. There is the
## Upgrading to `1.0.0` ## Upgrading to `1.0.0`
Version 1.0.0 marks a stable release of hiredis. Version 1.0.0 marks the first stable release of Hiredis.
It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory. It includes some minor breaking changes, mostly to make the exposed API more uniform and self-explanatory.
It also bundles the updated `sds` library, to sync up with upstream and Redis. It also bundles the updated `sds` library, to sync up with upstream and Redis.
For most applications a recompile against the new hiredis should be enough.
For code changes see the [Changelog](CHANGELOG.md). For code changes see the [Changelog](CHANGELOG.md).
_Note: As described below, a few member names have been changed but most applications should be able to upgrade with minor code changes and recompiling._
## IMPORTANT: Breaking changes from `0.14.1` -> `1.0.0`
* `redisContext` has two additional members (`free_privdata`, and `privctx`).
* `redisOptions.timeout` has been renamed to `redisOptions.connect_timeout`, and we've added `redisOptions.command_timeout`.
* `redisReplyObjectFunctions.createArray` now takes `size_t` instead of `int` for its length parameter.
## IMPORTANT: Breaking changes when upgrading from 0.13.x -> 0.14.x
Bulk and multi-bulk lengths less than -1 or greater than `LLONG_MAX` are now
protocol errors. This is consistent with the RESP specification. On 32-bit
platforms, the upper bound is lowered to `SIZE_MAX`.
Change `redisReply.len` to `size_t`, as it denotes the the size of a string
User code should compare this to `size_t` values as well. If it was used to
compare to other values, casting might be necessary or can be removed, if
casting was applied before.
## Upgrading from `<0.9.0` ## Upgrading from `<0.9.0`
Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing Version 0.9.0 is a major overhaul of hiredis in every aspect. However, upgrading existing
@ -110,6 +129,8 @@ The standard replies that `redisCommand` are of the type `redisReply`. The
`type` field in the `redisReply` should be used to test what kind of reply `type` field in the `redisReply` should be used to test what kind of reply
was received: was received:
### RESP2
* **`REDIS_REPLY_STATUS`**: * **`REDIS_REPLY_STATUS`**:
* The command replied with a status reply. The status string can be accessed using `reply->str`. * The command replied with a status reply. The status string can be accessed using `reply->str`.
The length of this string can be accessed using `reply->len`. The length of this string can be accessed using `reply->len`.
@ -134,16 +155,51 @@ was received:
and can be accessed via `reply->element[..index..]`. and can be accessed via `reply->element[..index..]`.
Redis may reply with nested arrays but this is fully supported. Redis may reply with nested arrays but this is fully supported.
### RESP3
Hiredis also supports every new `RESP3` data type which are as follows. For more information about the protocol see the `RESP3` [specification.](https://github.com/antirez/RESP3/blob/master/spec.md)
* **`REDIS_REPLY_DOUBLE`**:
* The command replied with a double-precision floating point number.
The value is stored as a string in the `str` member, and can be converted with `strtod` or similar.
* **`REDIS_REPLY_BOOL`**:
* A boolean true/false reply.
The value is stored in the `integer` member and will be either `0` or `1`.
* **`REDIS_REPLY_MAP`**:
* An array with the added invariant that there will always be an even number of elements.
The MAP is functionally equivelant to `REDIS_REPLY_ARRAY` except for the previously mentioned invariant.
* **`REDIS_REPLY_SET`**:
* An array response where each entry is unique.
Like the MAP type, the data is identical to an array response except there are no duplicate values.
* **`REDIS_REPLY_PUSH`**:
* An array that can be generated spontaneously by Redis.
This array response will always contain at least two subelements. The first contains the type of `PUSH` message (e.g. `message`, or `invalidate`), and the second being a sub-array with the `PUSH` payload itself.
* **`REDIS_REPLY_ATTR`**:
* An array structurally identical to a `MAP` but intended as meta-data about a reply.
_As of Redis 6.0.6 this reply type is not used in Redis_
* **`REDIS_REPLY_BIGNUM`**:
* A string representing an arbitrarily large signed or unsigned integer value.
The number will be encoded as a string in the `str` member of `redisReply`.
* **`REDIS_REPLY_VERB`**:
* A verbatim string, intended to be presented to the user without modification.
The string payload is stored in the `str` memeber, and type data is stored in the `vtype` member (e.g. `txt` for raw text or `md` for markdown).
Replies should be freed using the `freeReplyObject()` function. Replies should be freed using the `freeReplyObject()` function.
Note that this function will take care of freeing sub-reply objects Note that this function will take care of freeing sub-reply objects
contained in arrays and nested arrays, so there is no need for the user to contained in arrays and nested arrays, so there is no need for the user to
free the sub replies (it is actually harmful and will corrupt the memory). free the sub replies (it is actually harmful and will corrupt the memory).
**Important:** the current version of hiredis (0.10.0) frees replies when the **Important:** the current version of hiredis (1.0.0) frees replies when the
asynchronous API is used. This means you should not call `freeReplyObject` when asynchronous API is used. This means you should not call `freeReplyObject` when
you use this API. The reply is cleaned up by hiredis _after_ the callback you use this API. The reply is cleaned up by hiredis _after_ the callback
returns. This behavior will probably change in future releases, so make sure to returns. We may introduce a flag to make this configurable in future versions of the library.
keep an eye on the changelog when upgrading (see issue #39).
### Cleaning up ### Cleaning up
@ -205,16 +261,16 @@ a single call to `read(2)`):
redisReply *reply; redisReply *reply;
redisAppendCommand(context,"SET foo bar"); redisAppendCommand(context,"SET foo bar");
redisAppendCommand(context,"GET foo"); redisAppendCommand(context,"GET foo");
redisGetReply(context,&reply); // reply for SET redisGetReply(context,(void *)&reply); // reply for SET
freeReplyObject(reply); freeReplyObject(reply);
redisGetReply(context,&reply); // reply for GET redisGetReply(context,(void *)&reply); // reply for GET
freeReplyObject(reply); freeReplyObject(reply);
``` ```
This API can also be used to implement a blocking subscriber: This API can also be used to implement a blocking subscriber:
```c ```c
reply = redisCommand(context,"SUBSCRIBE foo"); reply = redisCommand(context,"SUBSCRIBE foo");
freeReplyObject(reply); freeReplyObject(reply);
while(redisGetReply(context,&reply) == REDIS_OK) { while(redisGetReply(context,(void *)&reply) == REDIS_OK) {
// consume message // consume message
freeReplyObject(reply); freeReplyObject(reply);
} }
@ -404,9 +460,199 @@ This should be done only in order to maximize performances when working with
large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again large payloads. The context should be set back to `REDIS_READER_MAX_BUF` again
as soon as possible in order to prevent allocation of useless memory. as soon as possible in order to prevent allocation of useless memory.
### Reader max array elements
By default the hiredis reply parser sets the maximum number of multi-bulk elements
to 2^32 - 1 or 4,294,967,295 entries. If you need to process multi-bulk replies
with more than this many elements you can set the value higher or to zero, meaning
unlimited with:
```c
context->reader->maxelements = 0;
```
## SSL/TLS Support
### Building
SSL/TLS support is not built by default and requires an explicit flag:
make USE_SSL=1
This requires OpenSSL development package (e.g. including header files to be
available.
When enabled, SSL/TLS support is built into extra `libhiredis_ssl.a` and
`libhiredis_ssl.so` static/dynamic libraries. This leaves the original libraries
unaffected so no additional dependencies are introduced.
### Using it
First, you'll need to make sure you include the SSL header file:
```c
#include "hiredis.h"
#include "hiredis_ssl.h"
```
You will also need to link against `libhiredis_ssl`, **in addition** to
`libhiredis` and add `-lssl -lcrypto` to satisfy its dependencies.
Hiredis implements SSL/TLS on top of its normal `redisContext` or
`redisAsyncContext`, so you will need to establish a connection first and then
initiate an SSL/TLS handshake.
#### Hiredis OpenSSL Wrappers
Before Hiredis can negotiate an SSL/TLS connection, it is necessary to
initialize OpenSSL and create a context. You can do that in two ways:
1. Work directly with the OpenSSL API to initialize the library's global context
and create `SSL_CTX *` and `SSL *` contexts. With an `SSL *` object you can
call `redisInitiateSSL()`.
2. Work with a set of Hiredis-provided wrappers around OpenSSL, create a
`redisSSLContext` object to hold configuration and use
`redisInitiateSSLWithContext()` to initiate the SSL/TLS handshake.
```c
/* An Hiredis SSL context. It holds SSL configuration and can be reused across
* many contexts.
*/
redisSSLContext *ssl;
/* An error variable to indicate what went wrong, if the context fails to
* initialize.
*/
redisSSLContextError ssl_error;
/* Initialize global OpenSSL state.
*
* You should call this only once when your app initializes, and only if
* you don't explicitly or implicitly initialize OpenSSL it elsewhere.
*/
redisInitOpenSSL();
/* Create SSL context */
ssl = redisCreateSSLContext(
"cacertbundle.crt", /* File name of trusted CA/ca bundle file, optional */
"/path/to/certs", /* Path of trusted certificates, optional */
"client_cert.pem", /* File name of client certificate file, optional */
"client_key.pem", /* File name of client private key, optional */
"redis.mydomain.com", /* Server name to request (SNI), optional */
&ssl_error
) != REDIS_OK) {
printf("SSL error: %s\n", redisSSLContextGetError(ssl_error);
/* Abort... */
}
/* Create Redis context and establish connection */
c = redisConnect("localhost", 6443);
if (c == NULL || c->err) {
/* Handle error and abort... */
}
/* Negotiate SSL/TLS */
if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
/* Handle error, in c->err / c->errstr */
}
```
## RESP3 PUSH replies
Redis 6.0 introduced PUSH replies with the reply-type `>`. These messages are generated spontaneously and can arrive at any time, so must be handled using callbacks.
### Default behavior
Hiredis installs handlers on `redisContext` and `redisAsyncContext` by default, which will intercept and free any PUSH replies detected. This means existing code will work as-is after upgrading to Redis 6 and switching to `RESP3`.
### Custom PUSH handler prototypes
The callback prototypes differ between `redisContext` and `redisAsyncContext`.
#### redisContext
```c
void my_push_handler(void *privdata, void *reply) {
/* Handle the reply */
/* Note: We need to free the reply in our custom handler for
blocking contexts. This lets us keep the reply if
we want. */
freeReplyObject(reply);
}
```
#### redisAsyncContext
```c
void my_async_push_handler(redisAsyncContext *ac, void *reply) {
/* Handle the reply */
/* Note: Because async hiredis always frees replies, you should
not call freeReplyObject in an async push callback. */
}
```
### Installing a custom handler
There are two ways to set your own PUSH handlers.
1. Set `push_cb` or `async_push_cb` in the `redisOptions` struct and connect with `redisConnectWithOptions` or `redisAsyncConnectWithOptions`.
```c
redisOptions = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
options->push_cb = my_push_handler;
redisContext *context = redisConnectWithOptions(&options);
```
2. Call `redisSetPushCallback` or `redisAsyncSetPushCallback` on a connected context.
```c
redisContext *context = redisConnect("127.0.0.1", 6379);
redisSetPushCallback(context, my_push_handler);
```
_Note `redisSetPushCallback` and `redisAsyncSetPushCallback` both return any currently configured handler, making it easy to override and then return to the old value._
### Specifying no handler
If you have a unique use-case where you don't want hiredis to automatically intercept and free PUSH replies, you will want to configure no handler at all. This can be done in two ways.
1. Set the `REDIS_OPT_NO_PUSH_AUTOFREE` flag in `redisOptions` and leave the callback function pointer `NULL`.
```c
redisOptions = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
options->options |= REDIS_OPT_NO_PUSH_AUTOFREE;
redisContext *context = redisConnectWithOptions(&options);
```
3. Call `redisSetPushCallback` with `NULL` once connected.
```c
redisContext *context = redisConnect("127.0.0.1", 6379);
redisSetPushCallback(context, NULL);
```
_Note: With no handler configured, calls to `redisCommand` may generate more than one reply, so this strategy is only applicable when there's some kind of blocking`redisGetReply()` loop (e.g. `MONITOR` or `SUBSCRIBE` workloads)._
## Allocator injection
Hiredis uses a pass-thru structure of function pointers defined in [alloc.h](https://github.com/redis/hiredis/blob/f5d25850/alloc.h#L41) that contain the currently configured allocation and deallocation functions. By default they just point to libc (`malloc`, `calloc`, `realloc`, etc).
### Overriding
One can override the allocators like so:
```c
hiredisAllocFuncs myfuncs = {
.mallocFn = my_malloc,
.callocFn = my_calloc,
.reallocFn = my_realloc,
.strdupFn = my_strdup,
.freeFn = my_free,
};
// Override allocators (function returns current allocators if needed)
hiredisAllocFuncs orig = hiredisSetAllocators(&myfuncs);
```
To reset the allocators to their default libc function simply call:
```c
hiredisResetAllocators();
```
## AUTHORS ## AUTHORS
Hiredis was written by Salvatore Sanfilippo (antirez at gmail) and Salvatore Sanfilippo (antirez at gmail),\
Pieter Noordhuis (pcnoordhuis at gmail) and is released under the BSD license. Pieter Noordhuis (pcnoordhuis at gmail)\
Hiredis is currently maintained by Matt Stancliff (matt at genges dot com) and Michael Grunder (michael dot grunder at gmail)
Jan-Erik Rediger (janerik at fnordig dot com)
_Hiredis is released under the BSD license._

View File

@ -96,7 +96,7 @@ static void redisAeCleanup(void *privdata) {
redisAeEvents *e = (redisAeEvents*)privdata; redisAeEvents *e = (redisAeEvents*)privdata;
redisAeDelRead(privdata); redisAeDelRead(privdata);
redisAeDelWrite(privdata); redisAeDelWrite(privdata);
free(e); hi_free(e);
} }
static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) { static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
@ -108,7 +108,10 @@ static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisAeEvents*)malloc(sizeof(*e)); e = (redisAeEvents*)hi_malloc(sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
e->loop = loop; e->loop = loop;
e->fd = c->fd; e->fd = c->fd;

View File

@ -134,6 +134,9 @@ redis_source_new (redisAsyncContext *ac)
g_return_val_if_fail(ac != NULL, NULL); g_return_val_if_fail(ac != NULL, NULL);
source = (RedisSource *)g_source_new(&source_funcs, sizeof *source); source = (RedisSource *)g_source_new(&source_funcs, sizeof *source);
if (source == NULL)
return NULL;
source->ac = ac; source->ac = ac;
source->poll_fd.fd = c->fd; source->poll_fd.fd = c->fd;
source->poll_fd.events = 0; source->poll_fd.events = 0;

View File

@ -43,7 +43,7 @@ static void redisIvykisCleanup(void *privdata) {
redisIvykisEvents *e = (redisIvykisEvents*)privdata; redisIvykisEvents *e = (redisIvykisEvents*)privdata;
iv_fd_unregister(&e->fd); iv_fd_unregister(&e->fd);
free(e); hi_free(e);
} }
static int redisIvykisAttach(redisAsyncContext *ac) { static int redisIvykisAttach(redisAsyncContext *ac) {
@ -55,7 +55,10 @@ static int redisIvykisAttach(redisAsyncContext *ac) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisIvykisEvents*)malloc(sizeof(*e)); e = (redisIvykisEvents*)hi_malloc(sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* Register functions to start/stop listening for events */

View File

@ -41,6 +41,7 @@ typedef struct redisLibevEvents {
struct ev_loop *loop; struct ev_loop *loop;
int reading, writing; int reading, writing;
ev_io rev, wev; ev_io rev, wev;
ev_timer timer;
} redisLibevEvents; } redisLibevEvents;
static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) { static void redisLibevReadEvent(EV_P_ ev_io *watcher, int revents) {
@ -103,11 +104,39 @@ static void redisLibevDelWrite(void *privdata) {
} }
} }
static void redisLibevStopTimer(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata;
struct ev_loop *loop = e->loop;
((void)loop);
ev_timer_stop(EV_A_ &e->timer);
}
static void redisLibevCleanup(void *privdata) { static void redisLibevCleanup(void *privdata) {
redisLibevEvents *e = (redisLibevEvents*)privdata; redisLibevEvents *e = (redisLibevEvents*)privdata;
redisLibevDelRead(privdata); redisLibevDelRead(privdata);
redisLibevDelWrite(privdata); redisLibevDelWrite(privdata);
free(e); redisLibevStopTimer(privdata);
hi_free(e);
}
static void redisLibevTimeout(EV_P_ ev_timer *timer, int revents) {
((void)revents);
redisLibevEvents *e = (redisLibevEvents*)timer->data;
redisAsyncHandleTimeout(e->context);
}
static void redisLibevSetTimeout(void *privdata, struct timeval tv) {
redisLibevEvents *e = (redisLibevEvents*)privdata;
struct ev_loop *loop = e->loop;
((void)loop);
if (!ev_is_active(&e->timer)) {
ev_init(&e->timer, redisLibevTimeout);
e->timer.data = e;
}
e->timer.repeat = tv.tv_sec + tv.tv_usec / 1000000.00;
ev_timer_again(EV_A_ &e->timer);
} }
static int redisLibevAttach(EV_P_ redisAsyncContext *ac) { static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
@ -119,14 +148,16 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibevEvents*)malloc(sizeof(*e)); e = (redisLibevEvents*)hi_calloc(1, sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
#if EV_MULTIPLICITY #if EV_MULTIPLICITY
e->loop = loop; e->loop = loop;
#else #else
e->loop = NULL; e->loop = NULL;
#endif #endif
e->reading = e->writing = 0;
e->rev.data = e; e->rev.data = e;
e->wev.data = e; e->wev.data = e;
@ -136,6 +167,7 @@ static int redisLibevAttach(EV_P_ redisAsyncContext *ac) {
ac->ev.addWrite = redisLibevAddWrite; ac->ev.addWrite = redisLibevAddWrite;
ac->ev.delWrite = redisLibevDelWrite; ac->ev.delWrite = redisLibevDelWrite;
ac->ev.cleanup = redisLibevCleanup; ac->ev.cleanup = redisLibevCleanup;
ac->ev.scheduleTimer = redisLibevSetTimeout;
ac->ev.data = e; ac->ev.data = e;
/* Initialize read/write events */ /* Initialize read/write events */

View File

@ -47,7 +47,7 @@ typedef struct redisLibeventEvents {
} redisLibeventEvents; } redisLibeventEvents;
static void redisLibeventDestroy(redisLibeventEvents *e) { static void redisLibeventDestroy(redisLibeventEvents *e) {
free(e); hi_free(e);
} }
static void redisLibeventHandler(int fd, short event, void *arg) { static void redisLibeventHandler(int fd, short event, void *arg) {
@ -152,7 +152,10 @@ static int redisLibeventAttach(redisAsyncContext *ac, struct event_base *base) {
return REDIS_ERR; return REDIS_ERR;
/* Create container for context and r/w events */ /* Create container for context and r/w events */
e = (redisLibeventEvents*)calloc(1, sizeof(*e)); e = (redisLibeventEvents*)hi_calloc(1, sizeof(*e));
if (e == NULL)
return REDIS_ERR;
e->context = ac; e->context = ac;
/* Register functions to start/stop listening for events */ /* Register functions to start/stop listening for events */

View File

@ -73,7 +73,7 @@ static void redisLibuvDelWrite(void *privdata) {
static void on_close(uv_handle_t* handle) { static void on_close(uv_handle_t* handle) {
redisLibuvEvents* p = (redisLibuvEvents*)handle->data; redisLibuvEvents* p = (redisLibuvEvents*)handle->data;
free(p); hi_free(p);
} }
@ -98,15 +98,13 @@ static int redisLibuvAttach(redisAsyncContext* ac, uv_loop_t* loop) {
ac->ev.delWrite = redisLibuvDelWrite; ac->ev.delWrite = redisLibuvDelWrite;
ac->ev.cleanup = redisLibuvCleanup; ac->ev.cleanup = redisLibuvCleanup;
redisLibuvEvents* p = (redisLibuvEvents*)malloc(sizeof(*p)); redisLibuvEvents* p = (redisLibuvEvents*)hi_malloc(sizeof(*p));
if (p == NULL)
if (!p) {
return REDIS_ERR; return REDIS_ERR;
}
memset(p, 0, sizeof(*p)); memset(p, 0, sizeof(*p));
if (uv_poll_init(loop, &p->handle, c->fd) != 0) { if (uv_poll_init_socket(loop, &p->handle, c->fd) != 0) {
return REDIS_ERR; return REDIS_ERR;
} }

View File

@ -27,7 +27,7 @@ static int freeRedisRunLoop(RedisRunLoop* redisRunLoop) {
CFSocketInvalidate(redisRunLoop->socketRef); CFSocketInvalidate(redisRunLoop->socketRef);
CFRelease(redisRunLoop->socketRef); CFRelease(redisRunLoop->socketRef);
} }
free(redisRunLoop); hi_free(redisRunLoop);
} }
return REDIS_ERR; return REDIS_ERR;
} }
@ -80,8 +80,9 @@ static int redisMacOSAttach(redisAsyncContext *redisAsyncCtx, CFRunLoopRef runLo
/* Nothing should be attached when something is already attached */ /* Nothing should be attached when something is already attached */
if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR; if( redisAsyncCtx->ev.data != NULL ) return REDIS_ERR;
RedisRunLoop* redisRunLoop = (RedisRunLoop*) calloc(1, sizeof(RedisRunLoop)); RedisRunLoop* redisRunLoop = (RedisRunLoop*) hi_calloc(1, sizeof(RedisRunLoop));
if( !redisRunLoop ) return REDIS_ERR; if (redisRunLoop == NULL)
return REDIS_ERR;
/* Setup redis stuff */ /* Setup redis stuff */
redisRunLoop->context = redisAsyncCtx; redisRunLoop->context = redisAsyncCtx;

86
deps/hiredis/alloc.c vendored Normal file
View File

@ -0,0 +1,86 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "fmacros.h"
#include "alloc.h"
#include <string.h>
#include <stdlib.h>
hiredisAllocFuncs hiredisAllocFns = {
.mallocFn = malloc,
.callocFn = calloc,
.reallocFn = realloc,
.strdupFn = strdup,
.freeFn = free,
};
/* Override hiredis' allocators with ones supplied by the user */
hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *override) {
hiredisAllocFuncs orig = hiredisAllocFns;
hiredisAllocFns = *override;
return orig;
}
/* Reset allocators to use libc defaults */
void hiredisResetAllocators(void) {
hiredisAllocFns = (hiredisAllocFuncs) {
.mallocFn = malloc,
.callocFn = calloc,
.reallocFn = realloc,
.strdupFn = strdup,
.freeFn = free,
};
}
#ifdef _WIN32
void *hi_malloc(size_t size) {
return hiredisAllocFns.mallocFn(size);
}
void *hi_calloc(size_t nmemb, size_t size) {
return hiredisAllocFns.callocFn(nmemb, size);
}
void *hi_realloc(void *ptr, size_t size) {
return hiredisAllocFns.reallocFn(ptr, size);
}
char *hi_strdup(const char *str) {
return hiredisAllocFns.strdupFn(str);
}
void hi_free(void *ptr) {
hiredisAllocFns.freeFn(ptr);
}
#endif

91
deps/hiredis/alloc.h vendored Normal file
View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef HIREDIS_ALLOC_H
#define HIREDIS_ALLOC_H
#include <stddef.h> /* for size_t */
#ifdef __cplusplus
extern "C" {
#endif
/* Structure pointing to our actually configured allocators */
typedef struct hiredisAllocFuncs {
void *(*mallocFn)(size_t);
void *(*callocFn)(size_t,size_t);
void *(*reallocFn)(void*,size_t);
char *(*strdupFn)(const char*);
void (*freeFn)(void*);
} hiredisAllocFuncs;
hiredisAllocFuncs hiredisSetAllocators(hiredisAllocFuncs *ha);
void hiredisResetAllocators(void);
#ifndef _WIN32
/* Hiredis' configured allocator function pointer struct */
extern hiredisAllocFuncs hiredisAllocFns;
static inline void *hi_malloc(size_t size) {
return hiredisAllocFns.mallocFn(size);
}
static inline void *hi_calloc(size_t nmemb, size_t size) {
return hiredisAllocFns.callocFn(nmemb, size);
}
static inline void *hi_realloc(void *ptr, size_t size) {
return hiredisAllocFns.reallocFn(ptr, size);
}
static inline char *hi_strdup(const char *str) {
return hiredisAllocFns.strdupFn(str);
}
static inline void hi_free(void *ptr) {
hiredisAllocFns.freeFn(ptr);
}
#else
void *hi_malloc(size_t size);
void *hi_calloc(size_t nmemb, size_t size);
void *hi_realloc(void *ptr, size_t size);
char *hi_strdup(const char *str);
void hi_free(void *ptr);
#endif
#ifdef __cplusplus
}
#endif
#endif /* HIREDIS_ALLOC_H */

199
deps/hiredis/async.c vendored
View File

@ -30,6 +30,7 @@
*/ */
#include "fmacros.h" #include "fmacros.h"
#include "alloc.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _MSC_VER #ifndef _MSC_VER
@ -46,18 +47,24 @@
#include "async_private.h" #include "async_private.h"
/* Forward declaration of function in hiredis.c */ /* Forward declarations of hiredis.c functions */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len); int __redisAppendCommand(redisContext *c, const char *cmd, size_t len);
void __redisSetError(redisContext *c, int type, const char *str);
/* Functions managing dictionary of callbacks for pub/sub. */ /* Functions managing dictionary of callbacks for pub/sub. */
static unsigned int callbackHash(const void *key) { static unsigned int callbackHash(const void *key) {
return dictGenHashFunction((const unsigned char *)key, return dictGenHashFunction((const unsigned char *)key,
sdslen((const sds)key)); hi_sdslen((const hisds)key));
} }
static void *callbackValDup(void *privdata, const void *src) { static void *callbackValDup(void *privdata, const void *src) {
((void) privdata); ((void) privdata);
redisCallback *dup = malloc(sizeof(*dup)); redisCallback *dup;
dup = hi_malloc(sizeof(*dup));
if (dup == NULL)
return NULL;
memcpy(dup,src,sizeof(*dup)); memcpy(dup,src,sizeof(*dup));
return dup; return dup;
} }
@ -66,20 +73,20 @@ static int callbackKeyCompare(void *privdata, const void *key1, const void *key2
int l1, l2; int l1, l2;
((void) privdata); ((void) privdata);
l1 = sdslen((const sds)key1); l1 = hi_sdslen((const hisds)key1);
l2 = sdslen((const sds)key2); l2 = hi_sdslen((const hisds)key2);
if (l1 != l2) return 0; if (l1 != l2) return 0;
return memcmp(key1,key2,l1) == 0; return memcmp(key1,key2,l1) == 0;
} }
static void callbackKeyDestructor(void *privdata, void *key) { static void callbackKeyDestructor(void *privdata, void *key) {
((void) privdata); ((void) privdata);
sdsfree((sds)key); hi_sdsfree((hisds)key);
} }
static void callbackValDestructor(void *privdata, void *val) { static void callbackValDestructor(void *privdata, void *val) {
((void) privdata); ((void) privdata);
free(val); hi_free(val);
} }
static dictType callbackDict = { static dictType callbackDict = {
@ -93,10 +100,19 @@ static dictType callbackDict = {
static redisAsyncContext *redisAsyncInitialize(redisContext *c) { static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
redisAsyncContext *ac; redisAsyncContext *ac;
dict *channels = NULL, *patterns = NULL;
ac = realloc(c,sizeof(redisAsyncContext)); channels = dictCreate(&callbackDict,NULL);
if (channels == NULL)
goto oom;
patterns = dictCreate(&callbackDict,NULL);
if (patterns == NULL)
goto oom;
ac = hi_realloc(c,sizeof(redisAsyncContext));
if (ac == NULL) if (ac == NULL)
return NULL; goto oom;
c = &(ac->c); c = &(ac->c);
@ -108,6 +124,7 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->err = 0; ac->err = 0;
ac->errstr = NULL; ac->errstr = NULL;
ac->data = NULL; ac->data = NULL;
ac->dataCleanup = NULL;
ac->ev.data = NULL; ac->ev.data = NULL;
ac->ev.addRead = NULL; ac->ev.addRead = NULL;
@ -124,9 +141,14 @@ static redisAsyncContext *redisAsyncInitialize(redisContext *c) {
ac->replies.tail = NULL; ac->replies.tail = NULL;
ac->sub.invalid.head = NULL; ac->sub.invalid.head = NULL;
ac->sub.invalid.tail = NULL; ac->sub.invalid.tail = NULL;
ac->sub.channels = dictCreate(&callbackDict,NULL); ac->sub.channels = channels;
ac->sub.patterns = dictCreate(&callbackDict,NULL); ac->sub.patterns = patterns;
return ac; return ac;
oom:
if (channels) dictRelease(channels);
if (patterns) dictRelease(patterns);
return NULL;
} }
/* We want the error field to be accessible directly instead of requiring /* We want the error field to be accessible directly instead of requiring
@ -145,16 +167,26 @@ redisAsyncContext *redisAsyncConnectWithOptions(const redisOptions *options) {
redisContext *c; redisContext *c;
redisAsyncContext *ac; redisAsyncContext *ac;
/* Clear any erroneously set sync callback and flag that we don't want to
* use freeReplyObject by default. */
myOptions.push_cb = NULL;
myOptions.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
myOptions.options |= REDIS_OPT_NONBLOCK; myOptions.options |= REDIS_OPT_NONBLOCK;
c = redisConnectWithOptions(&myOptions); c = redisConnectWithOptions(&myOptions);
if (c == NULL) { if (c == NULL) {
return NULL; return NULL;
} }
ac = redisAsyncInitialize(c); ac = redisAsyncInitialize(c);
if (ac == NULL) { if (ac == NULL) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
/* Set any configured async push handler */
redisAsyncSetPushCallback(ac, myOptions.async_push_cb);
__redisAsyncCopyError(ac); __redisAsyncCopyError(ac);
return ac; return ac;
} }
@ -214,7 +246,7 @@ static int __redisPushCallback(redisCallbackList *list, redisCallback *source) {
redisCallback *cb; redisCallback *cb;
/* Copy callback from stack to heap */ /* Copy callback from stack to heap */
cb = malloc(sizeof(*cb)); cb = hi_malloc(sizeof(*cb));
if (cb == NULL) if (cb == NULL)
return REDIS_ERR_OOM; return REDIS_ERR_OOM;
@ -242,7 +274,7 @@ static int __redisShiftCallback(redisCallbackList *list, redisCallback *target)
/* Copy callback from heap to stack */ /* Copy callback from heap to stack */
if (target != NULL) if (target != NULL)
memcpy(target,cb,sizeof(*cb)); memcpy(target,cb,sizeof(*cb));
free(cb); hi_free(cb);
return REDIS_OK; return REDIS_OK;
} }
return REDIS_ERR; return REDIS_ERR;
@ -257,6 +289,14 @@ static void __redisRunCallback(redisAsyncContext *ac, redisCallback *cb, redisRe
} }
} }
static void __redisRunPushCallback(redisAsyncContext *ac, redisReply *reply) {
if (ac->push_cb != NULL) {
ac->c.flags |= REDIS_IN_CALLBACK;
ac->push_cb(ac, reply);
ac->c.flags &= ~REDIS_IN_CALLBACK;
}
}
/* Helper function to free the context. */ /* Helper function to free the context. */
static void __redisAsyncFree(redisAsyncContext *ac) { static void __redisAsyncFree(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
@ -272,18 +312,28 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK) while (__redisShiftCallback(&ac->sub.invalid,&cb) == REDIS_OK)
__redisRunCallback(ac,&cb,NULL); __redisRunCallback(ac,&cb,NULL);
/* Run subscription callbacks callbacks with NULL reply */ /* Run subscription callbacks with NULL reply */
if (ac->sub.channels) {
it = dictGetIterator(ac->sub.channels); it = dictGetIterator(ac->sub.channels);
if (it != NULL) {
while ((de = dictNext(it)) != NULL) while ((de = dictNext(it)) != NULL)
__redisRunCallback(ac,dictGetEntryVal(de),NULL); __redisRunCallback(ac,dictGetEntryVal(de),NULL);
dictReleaseIterator(it); dictReleaseIterator(it);
dictRelease(ac->sub.channels); }
dictRelease(ac->sub.channels);
}
if (ac->sub.patterns) {
it = dictGetIterator(ac->sub.patterns); it = dictGetIterator(ac->sub.patterns);
if (it != NULL) {
while ((de = dictNext(it)) != NULL) while ((de = dictNext(it)) != NULL)
__redisRunCallback(ac,dictGetEntryVal(de),NULL); __redisRunCallback(ac,dictGetEntryVal(de),NULL);
dictReleaseIterator(it); dictReleaseIterator(it);
}
dictRelease(ac->sub.patterns); dictRelease(ac->sub.patterns);
}
/* Signal event lib to clean up */ /* Signal event lib to clean up */
_EL_CLEANUP(ac); _EL_CLEANUP(ac);
@ -298,6 +348,10 @@ static void __redisAsyncFree(redisAsyncContext *ac) {
} }
} }
if (ac->dataCleanup) {
ac->dataCleanup(ac->data);
}
/* Cleanup self */ /* Cleanup self */
redisFree(c); redisFree(c);
} }
@ -364,11 +418,11 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
dictEntry *de; dictEntry *de;
int pvariant; int pvariant;
char *stype; char *stype;
sds sname; hisds sname;
/* Custom reply functions are not supported for pub/sub. This will fail /* Custom reply functions are not supported for pub/sub. This will fail
* very hard when they are used... */ * very hard when they are used... */
if (reply->type == REDIS_REPLY_ARRAY) { if (reply->type == REDIS_REPLY_ARRAY || reply->type == REDIS_REPLY_PUSH) {
assert(reply->elements >= 2); assert(reply->elements >= 2);
assert(reply->element[0]->type == REDIS_REPLY_STRING); assert(reply->element[0]->type == REDIS_REPLY_STRING);
stype = reply->element[0]->str; stype = reply->element[0]->str;
@ -381,7 +435,10 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
/* Locate the right callback */ /* Locate the right callback */
assert(reply->element[1]->type == REDIS_REPLY_STRING); assert(reply->element[1]->type == REDIS_REPLY_STRING);
sname = sdsnewlen(reply->element[1]->str,reply->element[1]->len); sname = hi_sdsnewlen(reply->element[1]->str,reply->element[1]->len);
if (sname == NULL)
goto oom;
de = dictFind(callbacks,sname); de = dictFind(callbacks,sname);
if (de != NULL) { if (de != NULL) {
cb = dictGetEntryVal(de); cb = dictGetEntryVal(de);
@ -409,12 +466,39 @@ static int __redisGetSubscribeCallback(redisAsyncContext *ac, redisReply *reply,
c->flags &= ~REDIS_SUBSCRIBED; c->flags &= ~REDIS_SUBSCRIBED;
} }
} }
sdsfree(sname); hi_sdsfree(sname);
} else { } else {
/* Shift callback for invalid commands. */ /* Shift callback for invalid commands. */
__redisShiftCallback(&ac->sub.invalid,dstcb); __redisShiftCallback(&ac->sub.invalid,dstcb);
} }
return REDIS_OK; return REDIS_OK;
oom:
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
}
#define redisIsSpontaneousPushReply(r) \
(redisIsPushReply(r) && !redisIsSubscribeReply(r))
static int redisIsSubscribeReply(redisReply *reply) {
char *str;
size_t len, off;
/* We will always have at least one string with the subscribe/message type */
if (reply->elements < 1 || reply->element[0]->type != REDIS_REPLY_STRING ||
reply->element[0]->len < sizeof("message") - 1)
{
return 0;
}
/* Get the string/len moving past 'p' if needed */
off = tolower(reply->element[0]->str[0]) == 'p';
str = reply->element[0]->str + off;
len = reply->element[0]->len - off;
return !strncasecmp(str, "subscribe", len) ||
!strncasecmp(str, "message", len);
} }
void redisProcessCallbacks(redisAsyncContext *ac) { void redisProcessCallbacks(redisAsyncContext *ac) {
@ -427,7 +511,7 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
if (reply == NULL) { if (reply == NULL) {
/* When the connection is being disconnected and there are /* When the connection is being disconnected and there are
* no more replies, this is the cue to really disconnect. */ * no more replies, this is the cue to really disconnect. */
if (c->flags & REDIS_DISCONNECTING && sdslen(c->obuf) == 0 if (c->flags & REDIS_DISCONNECTING && hi_sdslen(c->obuf) == 0
&& ac->replies.head == NULL) { && ac->replies.head == NULL) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
return; return;
@ -443,8 +527,18 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
break; break;
} }
/* Even if the context is subscribed, pending regular callbacks will /* Send any non-subscribe related PUSH messages to our PUSH handler
* get a reply before pub/sub messages arrive. */ * while allowing subscribe related PUSH messages to pass through.
* This allows existing code to be backward compatible and work in
* either RESP2 or RESP3 mode. */
if (redisIsSpontaneousPushReply(reply)) {
__redisRunPushCallback(ac, reply);
c->reader->fn->freeObject(reply);
continue;
}
/* Even if the context is subscribed, pending regular
* callbacks will get a reply before pub/sub messages arrive. */
if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) { if (__redisShiftCallback(&ac->replies,&cb) != REDIS_OK) {
/* /*
* A spontaneous reply in a not-subscribed context can be the error * A spontaneous reply in a not-subscribed context can be the error
@ -497,20 +591,31 @@ void redisProcessCallbacks(redisAsyncContext *ac) {
__redisAsyncDisconnect(ac); __redisAsyncDisconnect(ac);
} }
static void __redisAsyncHandleConnectFailure(redisAsyncContext *ac) {
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR);
__redisAsyncDisconnect(ac);
}
/* Internal helper function to detect socket status the first time a read or /* Internal helper function to detect socket status the first time a read or
* write event fires. When connecting was not successful, the connect callback * write event fires. When connecting was not successful, the connect callback
* is called with a REDIS_ERR status and the context is free'd. */ * is called with a REDIS_ERR status and the context is free'd. */
static int __redisAsyncHandleConnect(redisAsyncContext *ac) { static int __redisAsyncHandleConnect(redisAsyncContext *ac) {
int completed = 0; int completed = 0;
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
if (redisCheckConnectDone(c, &completed) == REDIS_ERR) { if (redisCheckConnectDone(c, &completed) == REDIS_ERR) {
/* Error! */ /* Error! */
redisCheckSocketError(c); redisCheckSocketError(c);
if (ac->onConnect) ac->onConnect(ac, REDIS_ERR); __redisAsyncHandleConnectFailure(ac);
__redisAsyncDisconnect(ac);
return REDIS_ERR; return REDIS_ERR;
} else if (completed == 1) { } else if (completed == 1) {
/* connected! */ /* connected! */
if (c->connection_type == REDIS_CONN_TCP &&
redisSetTcpNoDelay(c) == REDIS_ERR) {
__redisAsyncHandleConnectFailure(ac);
return REDIS_ERR;
}
if (ac->onConnect) ac->onConnect(ac, REDIS_OK); if (ac->onConnect) ac->onConnect(ac, REDIS_OK);
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
return REDIS_OK; return REDIS_OK;
@ -582,8 +687,6 @@ void redisAsyncHandleWrite(redisAsyncContext *ac) {
c->funcs->async_write(ac); c->funcs->async_write(ac);
} }
void __redisSetError(redisContext *c, int type, const char *str);
void redisAsyncHandleTimeout(redisAsyncContext *ac) { void redisAsyncHandleTimeout(redisAsyncContext *ac) {
redisContext *c = &(ac->c); redisContext *c = &(ac->c);
redisCallback cb; redisCallback cb;
@ -641,7 +744,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
const char *cstr, *astr; const char *cstr, *astr;
size_t clen, alen; size_t clen, alen;
const char *p; const char *p;
sds sname; hisds sname;
int ret; int ret;
/* Don't accept new commands when the connection is about to be closed. */ /* Don't accept new commands when the connection is about to be closed. */
@ -665,7 +768,10 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
/* Add every channel/pattern to the list of subscription callbacks. */ /* Add every channel/pattern to the list of subscription callbacks. */
while ((p = nextArgument(p,&astr,&alen)) != NULL) { while ((p = nextArgument(p,&astr,&alen)) != NULL) {
sname = sdsnewlen(astr,alen); sname = hi_sdsnewlen(astr,alen);
if (sname == NULL)
goto oom;
if (pvariant) if (pvariant)
cbdict = ac->sub.patterns; cbdict = ac->sub.patterns;
else else
@ -680,7 +786,7 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
ret = dictReplace(cbdict,sname,&cb); ret = dictReplace(cbdict,sname,&cb);
if (ret == 0) sdsfree(sname); if (ret == 0) hi_sdsfree(sname);
} }
} else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) { } else if (strncasecmp(cstr,"unsubscribe\r\n",13) == 0) {
/* It is only useful to call (P)UNSUBSCRIBE when the context is /* It is only useful to call (P)UNSUBSCRIBE when the context is
@ -709,6 +815,9 @@ static int __redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
_EL_ADD_WRITE(ac); _EL_ADD_WRITE(ac);
return REDIS_OK; return REDIS_OK;
oom:
__redisSetError(&(ac->c), REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
} }
int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) { int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, const char *format, va_list ap) {
@ -722,7 +831,7 @@ int redisvAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdat
return REDIS_ERR; return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
free(cmd); hi_free(cmd);
return status; return status;
} }
@ -736,14 +845,14 @@ int redisAsyncCommand(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata
} }
int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) { int redisAsyncCommandArgv(redisAsyncContext *ac, redisCallbackFn *fn, void *privdata, int argc, const char **argv, const size_t *argvlen) {
sds cmd; hisds cmd;
int len; int len;
int status; int status;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
if (len < 0) if (len < 0)
return REDIS_ERR; return REDIS_ERR;
status = __redisAsyncCommand(ac,fn,privdata,cmd,len); status = __redisAsyncCommand(ac,fn,privdata,cmd,len);
sdsfree(cmd); hi_sdsfree(cmd);
return status; return status;
} }
@ -752,15 +861,27 @@ int redisAsyncFormattedCommand(redisAsyncContext *ac, redisCallbackFn *fn, void
return status; return status;
} }
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) { redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn) {
if (!ac->c.timeout) { redisAsyncPushFn *old = ac->push_cb;
ac->c.timeout = calloc(1, sizeof(tv)); ac->push_cb = fn;
return old;
} }
if (tv.tv_sec == ac->c.timeout->tv_sec && int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv) {
tv.tv_usec == ac->c.timeout->tv_usec) { if (!ac->c.command_timeout) {
return; ac->c.command_timeout = hi_calloc(1, sizeof(tv));
if (ac->c.command_timeout == NULL) {
__redisSetError(&ac->c, REDIS_ERR_OOM, "Out of memory");
__redisAsyncCopyError(ac);
return REDIS_ERR;
}
} }
*ac->c.timeout = tv; if (tv.tv_sec != ac->c.command_timeout->tv_sec ||
tv.tv_usec != ac->c.command_timeout->tv_usec)
{
*ac->c.command_timeout = tv;
}
return REDIS_OK;
} }

View File

@ -70,6 +70,7 @@ typedef struct redisAsyncContext {
/* Not used by hiredis */ /* Not used by hiredis */
void *data; void *data;
void (*dataCleanup)(void *privdata);
/* Event library data and hooks */ /* Event library data and hooks */
struct { struct {
@ -105,6 +106,9 @@ typedef struct redisAsyncContext {
struct dict *channels; struct dict *channels;
struct dict *patterns; struct dict *patterns;
} sub; } sub;
/* Any configured RESP3 PUSH handler */
redisAsyncPushFn *push_cb;
} redisAsyncContext; } redisAsyncContext;
/* Functions that proxy to hiredis */ /* Functions that proxy to hiredis */
@ -117,7 +121,8 @@ redisAsyncContext *redisAsyncConnectUnix(const char *path);
int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn); int redisAsyncSetConnectCallback(redisAsyncContext *ac, redisConnectCallback *fn);
int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn); int redisAsyncSetDisconnectCallback(redisAsyncContext *ac, redisDisconnectCallback *fn);
void redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv); redisAsyncPushFn *redisAsyncSetPushCallback(redisAsyncContext *ac, redisAsyncPushFn *fn);
int redisAsyncSetTimeout(redisAsyncContext *ac, struct timeval tv);
void redisAsyncDisconnect(redisAsyncContext *ac); void redisAsyncDisconnect(redisAsyncContext *ac);
void redisAsyncFree(redisAsyncContext *ac); void redisAsyncFree(redisAsyncContext *ac);

View File

@ -51,18 +51,21 @@
#define _EL_CLEANUP(ctx) do { \ #define _EL_CLEANUP(ctx) do { \
if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \ if ((ctx)->ev.cleanup) (ctx)->ev.cleanup((ctx)->ev.data); \
ctx->ev.cleanup = NULL; \ ctx->ev.cleanup = NULL; \
} while(0); } while(0)
static inline void refreshTimeout(redisAsyncContext *ctx) { static inline void refreshTimeout(redisAsyncContext *ctx) {
if (ctx->c.timeout && ctx->ev.scheduleTimer && #define REDIS_TIMER_ISSET(tvp) \
(ctx->c.timeout->tv_sec || ctx->c.timeout->tv_usec)) { (tvp && ((tvp)->tv_sec || (tvp)->tv_usec))
ctx->ev.scheduleTimer(ctx->ev.data, *ctx->c.timeout);
// } else { #define REDIS_EL_TIMER(ac, tvp) \
// printf("Not scheduling timer.. (tmo=%p)\n", ctx->c.timeout); if ((ac)->ev.scheduleTimer && REDIS_TIMER_ISSET(tvp)) { \
// if (ctx->c.timeout){ (ac)->ev.scheduleTimer((ac)->ev.data, *(tvp)); \
// printf("tv_sec: %u. tv_usec: %u\n", ctx->c.timeout->tv_sec, }
// ctx->c.timeout->tv_usec);
// } if (ctx->c.flags & REDIS_CONNECTED) {
REDIS_EL_TIMER(ctx, ctx->c.command_timeout);
} else {
REDIS_EL_TIMER(ctx, ctx->c.connect_timeout);
} }
} }

36
deps/hiredis/dict.c vendored
View File

@ -34,6 +34,7 @@
*/ */
#include "fmacros.h" #include "fmacros.h"
#include "alloc.h"
#include <stdlib.h> #include <stdlib.h>
#include <assert.h> #include <assert.h>
#include <limits.h> #include <limits.h>
@ -71,7 +72,10 @@ static void _dictReset(dict *ht) {
/* Create a new hash table */ /* Create a new hash table */
static dict *dictCreate(dictType *type, void *privDataPtr) { static dict *dictCreate(dictType *type, void *privDataPtr) {
dict *ht = malloc(sizeof(*ht)); dict *ht = hi_malloc(sizeof(*ht));
if (ht == NULL)
return NULL;
_dictInit(ht,type,privDataPtr); _dictInit(ht,type,privDataPtr);
return ht; return ht;
} }
@ -97,7 +101,9 @@ static int dictExpand(dict *ht, unsigned long size) {
_dictInit(&n, ht->type, ht->privdata); _dictInit(&n, ht->type, ht->privdata);
n.size = realsize; n.size = realsize;
n.sizemask = realsize-1; n.sizemask = realsize-1;
n.table = calloc(realsize,sizeof(dictEntry*)); n.table = hi_calloc(realsize,sizeof(dictEntry*));
if (n.table == NULL)
return DICT_ERR;
/* Copy all the elements from the old to the new table: /* Copy all the elements from the old to the new table:
* note that if the old hash table is empty ht->size is zero, * note that if the old hash table is empty ht->size is zero,
@ -124,7 +130,7 @@ static int dictExpand(dict *ht, unsigned long size) {
} }
} }
assert(ht->used == 0); assert(ht->used == 0);
free(ht->table); hi_free(ht->table);
/* Remap the new hashtable in the old */ /* Remap the new hashtable in the old */
*ht = n; *ht = n;
@ -142,7 +148,10 @@ static int dictAdd(dict *ht, void *key, void *val) {
return DICT_ERR; return DICT_ERR;
/* Allocates the memory and stores key */ /* Allocates the memory and stores key */
entry = malloc(sizeof(*entry)); entry = hi_malloc(sizeof(*entry));
if (entry == NULL)
return DICT_ERR;
entry->next = ht->table[index]; entry->next = ht->table[index];
ht->table[index] = entry; ht->table[index] = entry;
@ -166,6 +175,9 @@ static int dictReplace(dict *ht, void *key, void *val) {
return 1; return 1;
/* It already exists, get the entry */ /* It already exists, get the entry */
entry = dictFind(ht, key); entry = dictFind(ht, key);
if (entry == NULL)
return 0;
/* Free the old value and set the new one */ /* Free the old value and set the new one */
/* Set the new value and free the old one. Note that it is important /* Set the new value and free the old one. Note that it is important
* to do that in this order, as the value may just be exactly the same * to do that in this order, as the value may just be exactly the same
@ -199,7 +211,7 @@ static int dictDelete(dict *ht, const void *key) {
dictFreeEntryKey(ht,de); dictFreeEntryKey(ht,de);
dictFreeEntryVal(ht,de); dictFreeEntryVal(ht,de);
free(de); hi_free(de);
ht->used--; ht->used--;
return DICT_OK; return DICT_OK;
} }
@ -222,13 +234,13 @@ static int _dictClear(dict *ht) {
nextHe = he->next; nextHe = he->next;
dictFreeEntryKey(ht, he); dictFreeEntryKey(ht, he);
dictFreeEntryVal(ht, he); dictFreeEntryVal(ht, he);
free(he); hi_free(he);
ht->used--; ht->used--;
he = nextHe; he = nextHe;
} }
} }
/* Free the table and the allocated cache structure */ /* Free the table and the allocated cache structure */
free(ht->table); hi_free(ht->table);
/* Re-initialize the table */ /* Re-initialize the table */
_dictReset(ht); _dictReset(ht);
return DICT_OK; /* never fails */ return DICT_OK; /* never fails */
@ -237,7 +249,7 @@ static int _dictClear(dict *ht) {
/* Clear & Release the hash table */ /* Clear & Release the hash table */
static void dictRelease(dict *ht) { static void dictRelease(dict *ht) {
_dictClear(ht); _dictClear(ht);
free(ht); hi_free(ht);
} }
static dictEntry *dictFind(dict *ht, const void *key) { static dictEntry *dictFind(dict *ht, const void *key) {
@ -256,7 +268,9 @@ static dictEntry *dictFind(dict *ht, const void *key) {
} }
static dictIterator *dictGetIterator(dict *ht) { static dictIterator *dictGetIterator(dict *ht) {
dictIterator *iter = malloc(sizeof(*iter)); dictIterator *iter = hi_malloc(sizeof(*iter));
if (iter == NULL)
return NULL;
iter->ht = ht; iter->ht = ht;
iter->index = -1; iter->index = -1;
@ -286,7 +300,7 @@ static dictEntry *dictNext(dictIterator *iter) {
} }
static void dictReleaseIterator(dictIterator *iter) { static void dictReleaseIterator(dictIterator *iter) {
free(iter); hi_free(iter);
} }
/* ------------------------- private functions ------------------------------ */ /* ------------------------- private functions ------------------------------ */
@ -294,7 +308,7 @@ static void dictReleaseIterator(dictIterator *iter) {
/* Expand the hash table if needed */ /* Expand the hash table if needed */
static int _dictExpandIfNeeded(dict *ht) { static int _dictExpandIfNeeded(dict *ht) {
/* If the hash table is empty expand it to the initial size, /* If the hash table is empty expand it to the initial size,
* if the table is "full" dobule its size. */ * if the table is "full" double its size. */
if (ht->size == 0) if (ht->size == 0)
return dictExpand(ht, DICT_HT_INITIAL_SIZE); return dictExpand(ht, DICT_HT_INITIAL_SIZE);
if (ht->used == ht->size) if (ht->used == ht->size)

View File

@ -44,3 +44,6 @@ ENDIF()
ADD_EXECUTABLE(example example.c) ADD_EXECUTABLE(example example.c)
TARGET_LINK_LIBRARIES(example hiredis) TARGET_LINK_LIBRARIES(example hiredis)
ADD_EXECUTABLE(example-push example-push.c)
TARGET_LINK_LIBRARIES(example-push hiredis)

View File

@ -33,7 +33,9 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
iv_init(); iv_init();

View File

@ -33,7 +33,9 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);
if (c->err) { if (c->err) {

View File

@ -34,7 +34,10 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
struct event_base *base = event_base_new(); struct event_base *base = event_base_new();
if (argc < 5) { if (argc < 5) {
fprintf(stderr, fprintf(stderr,
@ -52,13 +55,25 @@ int main (int argc, char **argv) {
const char *certKey = argv[5]; const char *certKey = argv[5];
const char *caCert = argc > 5 ? argv[6] : NULL; const char *caCert = argc > 5 ? argv[6] : NULL;
redisSSLContext *ssl;
redisSSLContextError ssl_error;
redisInitOpenSSL();
ssl = redisCreateSSLContext(caCert, NULL,
cert, certKey, NULL, &ssl_error);
if (!ssl) {
printf("Error: %s\n", redisSSLContextGetError(ssl_error));
return 1;
}
redisAsyncContext *c = redisAsyncConnect(hostname, port); redisAsyncContext *c = redisAsyncConnect(hostname, port);
if (c->err) { if (c->err) {
/* Let *c leak for now... */ /* Let *c leak for now... */
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
return 1; return 1;
} }
if (redisSecureConnection(&c->c, caCert, cert, certKey, "sni") != REDIS_OK) { if (redisInitiateSSLWithContext(&c->c, ssl) != REDIS_OK) {
printf("SSL Error!\n"); printf("SSL Error!\n");
exit(1); exit(1);
} }
@ -69,5 +84,7 @@ int main (int argc, char **argv) {
redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue); redisAsyncCommand(c, NULL, NULL, "SET key %b", value, nvalue);
redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key"); redisAsyncCommand(c, getCallback, (char*)"end-1", "GET key");
event_base_dispatch(base); event_base_dispatch(base);
redisFreeSSLContext(ssl);
return 0; return 0;
} }

View File

@ -38,13 +38,16 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
struct event_base *base = event_base_new(); struct event_base *base = event_base_new();
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379); REDIS_OPTIONS_SET_TCP(&options, "127.0.0.1", 6379);
struct timeval tv = {0}; struct timeval tv = {0};
tv.tv_sec = 1; tv.tv_sec = 1;
options.timeout = &tv; options.connect_timeout = &tv;
redisAsyncContext *c = redisAsyncConnectWithOptions(&options); redisAsyncContext *c = redisAsyncConnectWithOptions(&options);

View File

@ -33,7 +33,10 @@ void disconnectCallback(const redisAsyncContext *c, int status) {
} }
int main (int argc, char **argv) { int main (int argc, char **argv) {
#ifndef _WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
#endif
uv_loop_t* loop = uv_default_loop(); uv_loop_t* loop = uv_default_loop();
redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379); redisAsyncContext *c = redisAsyncConnect("127.0.0.1", 6379);

160
deps/hiredis/examples/example-push.c vendored Normal file
View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <hiredis.h>
#include <win32.h>
#define KEY_COUNT 5
#define panicAbort(fmt, ...) \
do { \
fprintf(stderr, "%s:%d:%s(): " fmt, __FILE__, __LINE__, __func__, __VA_ARGS__); \
exit(-1); \
} while (0)
static void assertReplyAndFree(redisContext *context, redisReply *reply, int type) {
if (reply == NULL)
panicAbort("NULL reply from server (error: %s)", context->errstr);
if (reply->type != type) {
if (reply->type == REDIS_REPLY_ERROR)
fprintf(stderr, "Redis Error: %s\n", reply->str);
panicAbort("Expected reply type %d but got type %d", type, reply->type);
}
freeReplyObject(reply);
}
/* Switch to the RESP3 protocol and enable client tracking */
static void enableClientTracking(redisContext *c) {
redisReply *reply = redisCommand(c, "HELLO 3");
if (reply == NULL || c->err) {
panicAbort("NULL reply or server error (error: %s)", c->errstr);
}
if (reply->type != REDIS_REPLY_MAP) {
fprintf(stderr, "Error: Can't send HELLO 3 command. Are you sure you're ");
fprintf(stderr, "connected to redis-server >= 6.0.0?\nRedis error: %s\n",
reply->type == REDIS_REPLY_ERROR ? reply->str : "(unknown)");
exit(-1);
}
freeReplyObject(reply);
/* Enable client tracking */
reply = redisCommand(c, "CLIENT TRACKING ON");
assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
}
void pushReplyHandler(void *privdata, void *r) {
redisReply *reply = r;
int *invalidations = privdata;
/* Sanity check on the invalidation reply */
if (reply->type != REDIS_REPLY_PUSH || reply->elements != 2 ||
reply->element[1]->type != REDIS_REPLY_ARRAY ||
reply->element[1]->element[0]->type != REDIS_REPLY_STRING)
{
panicAbort("%s", "Can't parse PUSH message!");
}
/* Increment our invalidation count */
*invalidations += 1;
printf("pushReplyHandler(): INVALIDATE '%s' (invalidation count: %d)\n",
reply->element[1]->element[0]->str, *invalidations);
freeReplyObject(reply);
}
/* We aren't actually freeing anything here, but it is included to show that we can
* have hiredis call our data destructor when freeing the context */
void privdata_dtor(void *privdata) {
unsigned int *icount = privdata;
printf("privdata_dtor(): In context privdata dtor (invalidations: %u)\n", *icount);
}
int main(int argc, char **argv) {
unsigned int j, invalidations = 0;
redisContext *c;
redisReply *reply;
const char *hostname = (argc > 1) ? argv[1] : "127.0.0.1";
int port = (argc > 2) ? atoi(argv[2]) : 6379;
redisOptions o = {0};
REDIS_OPTIONS_SET_TCP(&o, hostname, port);
/* Set our context privdata to the address of our invalidation counter. Each
* time our PUSH handler is called, hiredis will pass the privdata for context.
*
* This could also be done after we create the context like so:
*
* c->privdata = &invalidations;
* c->free_privdata = privdata_dtor;
*/
REDIS_OPTIONS_SET_PRIVDATA(&o, &invalidations, privdata_dtor);
/* Set our custom PUSH message handler */
o.push_cb = pushReplyHandler;
c = redisConnectWithOptions(&o);
if (c == NULL || c->err)
panicAbort("Connection error: %s", c ? c->errstr : "OOM");
/* Enable RESP3 and turn on client tracking */
enableClientTracking(c);
/* Set some keys and then read them back. Once we do that, Redis will deliver
* invalidation push messages whenever the key is modified */
for (j = 0; j < KEY_COUNT; j++) {
reply = redisCommand(c, "SET key:%d initial:%d", j, j);
assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
reply = redisCommand(c, "GET key:%d", j);
assertReplyAndFree(c, reply, REDIS_REPLY_STRING);
}
/* Trigger invalidation messages by updating keys we just read */
for (j = 0; j < KEY_COUNT; j++) {
printf(" main(): SET key:%d update:%d\n", j, j);
reply = redisCommand(c, "SET key:%d update:%d", j, j);
assertReplyAndFree(c, reply, REDIS_REPLY_STATUS);
printf(" main(): SET REPLY OK\n");
}
printf("\nTotal detected invalidations: %d, expected: %d\n", invalidations, KEY_COUNT);
/* PING server */
redisFree(c);
}

View File

@ -4,9 +4,12 @@
#include <hiredis.h> #include <hiredis.h>
#include <hiredis_ssl.h> #include <hiredis_ssl.h>
#include <win32.h>
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j; unsigned int j;
redisSSLContext *ssl;
redisSSLContextError ssl_error;
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
if (argc < 4) { if (argc < 4) {
@ -19,10 +22,18 @@ int main(int argc, char **argv) {
const char *key = argv[4]; const char *key = argv[4];
const char *ca = argc > 4 ? argv[5] : NULL; const char *ca = argc > 4 ? argv[5] : NULL;
redisInitOpenSSL();
ssl = redisCreateSSLContext(ca, NULL, cert, key, NULL, &ssl_error);
if (!ssl) {
printf("SSL Context error: %s\n",
redisSSLContextGetError(ssl_error));
exit(1);
}
struct timeval tv = { 1, 500000 }; // 1.5 seconds struct timeval tv = { 1, 500000 }; // 1.5 seconds
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, hostname, port); REDIS_OPTIONS_SET_TCP(&options, hostname, port);
options.timeout = &tv; options.connect_timeout = &tv;
c = redisConnectWithOptions(&options); c = redisConnectWithOptions(&options);
if (c == NULL || c->err) { if (c == NULL || c->err) {
@ -35,7 +46,7 @@ int main(int argc, char **argv) {
exit(1); exit(1);
} }
if (redisSecureConnection(c, ca, cert, key, "sni") != REDIS_OK) { if (redisInitiateSSLWithContext(c, ssl) != REDIS_OK) {
printf("Couldn't initialize SSL!\n"); printf("Couldn't initialize SSL!\n");
printf("Error: %s\n", c->errstr); printf("Error: %s\n", c->errstr);
redisFree(c); redisFree(c);
@ -93,5 +104,7 @@ int main(int argc, char **argv) {
/* Disconnects and frees the context */ /* Disconnects and frees the context */
redisFree(c); redisFree(c);
redisFreeSSLContext(ssl);
return 0; return 0;
} }

View File

@ -1,8 +1,8 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <hiredis.h> #include <hiredis.h>
#include <win32.h>
int main(int argc, char **argv) { int main(int argc, char **argv) {
unsigned int j, isunix = 0; unsigned int j, isunix = 0;

13
deps/hiredis/hiredis-config.cmake.in vendored Normal file
View File

@ -0,0 +1,13 @@
@PACKAGE_INIT@
set_and_check(hiredis_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
IF (NOT TARGET hiredis::hiredis)
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis-targets.cmake)
ENDIF()
SET(hiredis_LIBRARIES hiredis::hiredis)
SET(hiredis_INCLUDE_DIRS ${hiredis_INCLUDEDIR})
check_required_components(hiredis)

308
deps/hiredis/hiredis.c vendored
View File

@ -44,8 +44,11 @@
#include "async.h" #include "async.h"
#include "win32.h" #include "win32.h"
extern int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout);
extern int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout);
static redisContextFuncs redisContextDefaultFuncs = { static redisContextFuncs redisContextDefaultFuncs = {
.free_privdata = NULL, .free_privctx = NULL,
.async_read = redisAsyncRead, .async_read = redisAsyncRead,
.async_write = redisAsyncWrite, .async_write = redisAsyncWrite,
.read = redisNetRead, .read = redisNetRead,
@ -74,7 +77,7 @@ static redisReplyObjectFunctions defaultFunctions = {
/* Create a reply object */ /* Create a reply object */
static redisReply *createReplyObject(int type) { static redisReply *createReplyObject(int type) {
redisReply *r = calloc(1,sizeof(*r)); redisReply *r = hi_calloc(1,sizeof(*r));
if (r == NULL) if (r == NULL)
return NULL; return NULL;
@ -97,20 +100,22 @@ void freeReplyObject(void *reply) {
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP: case REDIS_REPLY_MAP:
case REDIS_REPLY_SET: case REDIS_REPLY_SET:
case REDIS_REPLY_PUSH:
if (r->element != NULL) { if (r->element != NULL) {
for (j = 0; j < r->elements; j++) for (j = 0; j < r->elements; j++)
freeReplyObject(r->element[j]); freeReplyObject(r->element[j]);
free(r->element); hi_free(r->element);
} }
break; break;
case REDIS_REPLY_ERROR: case REDIS_REPLY_ERROR:
case REDIS_REPLY_STATUS: case REDIS_REPLY_STATUS:
case REDIS_REPLY_STRING: case REDIS_REPLY_STRING:
case REDIS_REPLY_DOUBLE: case REDIS_REPLY_DOUBLE:
free(r->str); case REDIS_REPLY_VERB:
hi_free(r->str);
break; break;
} }
free(r); hi_free(r);
} }
static void *createStringObject(const redisReadTask *task, char *str, size_t len) { static void *createStringObject(const redisReadTask *task, char *str, size_t len) {
@ -128,22 +133,18 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
/* Copy string value */ /* Copy string value */
if (task->type == REDIS_REPLY_VERB) { if (task->type == REDIS_REPLY_VERB) {
buf = malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */ buf = hi_malloc(len-4+1); /* Skip 4 bytes of verbatim type header. */
if (buf == NULL) { if (buf == NULL) goto oom;
freeReplyObject(r);
return NULL;
}
memcpy(r->vtype,str,3); memcpy(r->vtype,str,3);
r->vtype[3] = '\0'; r->vtype[3] = '\0';
memcpy(buf,str+4,len-4); memcpy(buf,str+4,len-4);
buf[len-4] = '\0'; buf[len-4] = '\0';
r->len = len - 4; r->len = len - 4;
} else { } else {
buf = malloc(len+1); buf = hi_malloc(len+1);
if (buf == NULL) { if (buf == NULL) goto oom;
freeReplyObject(r);
return NULL;
}
memcpy(buf,str,len); memcpy(buf,str,len);
buf[len] = '\0'; buf[len] = '\0';
r->len = len; r->len = len;
@ -154,10 +155,15 @@ static void *createStringObject(const redisReadTask *task, char *str, size_t len
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY || assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET); parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
oom:
freeReplyObject(r);
return NULL;
} }
static void *createArrayObject(const redisReadTask *task, size_t elements) { static void *createArrayObject(const redisReadTask *task, size_t elements) {
@ -168,7 +174,7 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
return NULL; return NULL;
if (elements > 0) { if (elements > 0) {
r->element = calloc(elements,sizeof(redisReply*)); r->element = hi_calloc(elements,sizeof(redisReply*));
if (r->element == NULL) { if (r->element == NULL) {
freeReplyObject(r); freeReplyObject(r);
return NULL; return NULL;
@ -181,7 +187,8 @@ static void *createArrayObject(const redisReadTask *task, size_t elements) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY || assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET); parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
@ -200,7 +207,8 @@ static void *createIntegerObject(const redisReadTask *task, long long value) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY || assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET); parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
@ -214,7 +222,7 @@ static void *createDoubleObject(const redisReadTask *task, double value, char *s
return NULL; return NULL;
r->dval = value; r->dval = value;
r->str = malloc(len+1); r->str = hi_malloc(len+1);
if (r->str == NULL) { if (r->str == NULL) {
freeReplyObject(r); freeReplyObject(r);
return NULL; return NULL;
@ -249,7 +257,8 @@ static void *createNilObject(const redisReadTask *task) {
parent = task->parent->obj; parent = task->parent->obj;
assert(parent->type == REDIS_REPLY_ARRAY || assert(parent->type == REDIS_REPLY_ARRAY ||
parent->type == REDIS_REPLY_MAP || parent->type == REDIS_REPLY_MAP ||
parent->type == REDIS_REPLY_SET); parent->type == REDIS_REPLY_SET ||
parent->type == REDIS_REPLY_PUSH);
parent->element[task->idx] = r; parent->element[task->idx] = r;
} }
return r; return r;
@ -297,7 +306,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
const char *c = format; const char *c = format;
char *cmd = NULL; /* final command */ char *cmd = NULL; /* final command */
int pos; /* position in final command */ int pos; /* position in final command */
sds curarg, newarg; /* current argument */ hisds curarg, newarg; /* current argument */
int touched = 0; /* was the current argument touched? */ int touched = 0; /* was the current argument touched? */
char **curargv = NULL, **newargv = NULL; char **curargv = NULL, **newargv = NULL;
int argc = 0; int argc = 0;
@ -310,7 +319,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
return -1; return -1;
/* Build the command string accordingly to protocol */ /* Build the command string accordingly to protocol */
curarg = sdsempty(); curarg = hi_sdsempty();
if (curarg == NULL) if (curarg == NULL)
return -1; return -1;
@ -318,19 +327,19 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
if (*c != '%' || c[1] == '\0') { if (*c != '%' || c[1] == '\0') {
if (*c == ' ') { if (*c == ' ') {
if (touched) { if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1)); newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto memory_err; if (newargv == NULL) goto memory_err;
curargv = newargv; curargv = newargv;
curargv[argc++] = curarg; curargv[argc++] = curarg;
totlen += bulklen(sdslen(curarg)); totlen += bulklen(hi_sdslen(curarg));
/* curarg is put in argv so it can be overwritten. */ /* curarg is put in argv so it can be overwritten. */
curarg = sdsempty(); curarg = hi_sdsempty();
if (curarg == NULL) goto memory_err; if (curarg == NULL) goto memory_err;
touched = 0; touched = 0;
} }
} else { } else {
newarg = sdscatlen(curarg,c,1); newarg = hi_sdscatlen(curarg,c,1);
if (newarg == NULL) goto memory_err; if (newarg == NULL) goto memory_err;
curarg = newarg; curarg = newarg;
touched = 1; touched = 1;
@ -347,16 +356,16 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
arg = va_arg(ap,char*); arg = va_arg(ap,char*);
size = strlen(arg); size = strlen(arg);
if (size > 0) if (size > 0)
newarg = sdscatlen(curarg,arg,size); newarg = hi_sdscatlen(curarg,arg,size);
break; break;
case 'b': case 'b':
arg = va_arg(ap,char*); arg = va_arg(ap,char*);
size = va_arg(ap,size_t); size = va_arg(ap,size_t);
if (size > 0) if (size > 0)
newarg = sdscatlen(curarg,arg,size); newarg = hi_sdscatlen(curarg,arg,size);
break; break;
case '%': case '%':
newarg = sdscat(curarg,"%"); newarg = hi_sdscat(curarg,"%");
break; break;
default: default:
/* Try to detect printf format */ /* Try to detect printf format */
@ -444,7 +453,7 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
if (_l < sizeof(_format)-2) { if (_l < sizeof(_format)-2) {
memcpy(_format,c,_l); memcpy(_format,c,_l);
_format[_l] = '\0'; _format[_l] = '\0';
newarg = sdscatvprintf(curarg,_format,_cpy); newarg = hi_sdscatvprintf(curarg,_format,_cpy);
/* Update current position (note: outer blocks /* Update current position (note: outer blocks
* increment c twice so compensate here) */ * increment c twice so compensate here) */
@ -467,13 +476,13 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
/* Add the last argument if needed */ /* Add the last argument if needed */
if (touched) { if (touched) {
newargv = realloc(curargv,sizeof(char*)*(argc+1)); newargv = hi_realloc(curargv,sizeof(char*)*(argc+1));
if (newargv == NULL) goto memory_err; if (newargv == NULL) goto memory_err;
curargv = newargv; curargv = newargv;
curargv[argc++] = curarg; curargv[argc++] = curarg;
totlen += bulklen(sdslen(curarg)); totlen += bulklen(hi_sdslen(curarg));
} else { } else {
sdsfree(curarg); hi_sdsfree(curarg);
} }
/* Clear curarg because it was put in curargv or was free'd. */ /* Clear curarg because it was put in curargv or was free'd. */
@ -483,22 +492,22 @@ int redisvFormatCommand(char **target, const char *format, va_list ap) {
totlen += 1+countDigits(argc)+2; totlen += 1+countDigits(argc)+2;
/* Build the command at protocol level */ /* Build the command at protocol level */
cmd = malloc(totlen+1); cmd = hi_malloc(totlen+1);
if (cmd == NULL) goto memory_err; if (cmd == NULL) goto memory_err;
pos = sprintf(cmd,"*%d\r\n",argc); pos = sprintf(cmd,"*%d\r\n",argc);
for (j = 0; j < argc; j++) { for (j = 0; j < argc; j++) {
pos += sprintf(cmd+pos,"$%zu\r\n",sdslen(curargv[j])); pos += sprintf(cmd+pos,"$%zu\r\n",hi_sdslen(curargv[j]));
memcpy(cmd+pos,curargv[j],sdslen(curargv[j])); memcpy(cmd+pos,curargv[j],hi_sdslen(curargv[j]));
pos += sdslen(curargv[j]); pos += hi_sdslen(curargv[j]);
sdsfree(curargv[j]); hi_sdsfree(curargv[j]);
cmd[pos++] = '\r'; cmd[pos++] = '\r';
cmd[pos++] = '\n'; cmd[pos++] = '\n';
} }
assert(pos == totlen); assert(pos == totlen);
cmd[pos] = '\0'; cmd[pos] = '\0';
free(curargv); hi_free(curargv);
*target = cmd; *target = cmd;
return totlen; return totlen;
@ -513,12 +522,12 @@ memory_err:
cleanup: cleanup:
if (curargv) { if (curargv) {
while(argc--) while(argc--)
sdsfree(curargv[argc]); hi_sdsfree(curargv[argc]);
free(curargv); hi_free(curargv);
} }
sdsfree(curarg); hi_sdsfree(curarg);
free(cmd); hi_free(cmd);
return error_type; return error_type;
} }
@ -550,16 +559,16 @@ int redisFormatCommand(char **target, const char *format, ...) {
return len; return len;
} }
/* Format a command according to the Redis protocol using an sds string and /* Format a command according to the Redis protocol using an hisds string and
* sdscatfmt for the processing of arguments. This function takes the * hi_sdscatfmt for the processing of arguments. This function takes the
* number of arguments, an array with arguments and an array with their * number of arguments, an array with arguments and an array with their
* lengths. If the latter is set to NULL, strlen will be used to compute the * lengths. If the latter is set to NULL, strlen will be used to compute the
* argument lengths. * argument lengths.
*/ */
int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv, int redisFormatSdsCommandArgv(hisds *target, int argc, const char **argv,
const size_t *argvlen) const size_t *argvlen)
{ {
sds cmd; hisds cmd, aux;
unsigned long long totlen; unsigned long long totlen;
int j; int j;
size_t len; size_t len;
@ -576,32 +585,36 @@ int redisFormatSdsCommandArgv(sds *target, int argc, const char **argv,
} }
/* Use an SDS string for command construction */ /* Use an SDS string for command construction */
cmd = sdsempty(); cmd = hi_sdsempty();
if (cmd == NULL) if (cmd == NULL)
return -1; return -1;
/* We already know how much storage we need */ /* We already know how much storage we need */
cmd = sdsMakeRoomFor(cmd, totlen); aux = hi_sdsMakeRoomFor(cmd, totlen);
if (cmd == NULL) if (aux == NULL) {
hi_sdsfree(cmd);
return -1; return -1;
/* Construct command */
cmd = sdscatfmt(cmd, "*%i\r\n", argc);
for (j=0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
cmd = sdscatfmt(cmd, "$%u\r\n", len);
cmd = sdscatlen(cmd, argv[j], len);
cmd = sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
} }
assert(sdslen(cmd)==totlen); cmd = aux;
/* Construct command */
cmd = hi_sdscatfmt(cmd, "*%i\r\n", argc);
for (j=0; j < argc; j++) {
len = argvlen ? argvlen[j] : strlen(argv[j]);
cmd = hi_sdscatfmt(cmd, "$%u\r\n", len);
cmd = hi_sdscatlen(cmd, argv[j], len);
cmd = hi_sdscatlen(cmd, "\r\n", sizeof("\r\n")-1);
}
assert(hi_sdslen(cmd)==totlen);
*target = cmd; *target = cmd;
return totlen; return totlen;
} }
void redisFreeSdsCommand(sds cmd) { void redisFreeSdsCommand(hisds cmd) {
sdsfree(cmd); hi_sdsfree(cmd);
} }
/* Format a command according to the Redis protocol. This function takes the /* Format a command according to the Redis protocol. This function takes the
@ -627,7 +640,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
} }
/* Build the command at protocol level */ /* Build the command at protocol level */
cmd = malloc(totlen+1); cmd = hi_malloc(totlen+1);
if (cmd == NULL) if (cmd == NULL)
return -1; return -1;
@ -648,7 +661,7 @@ int redisFormatCommandArgv(char **target, int argc, const char **argv, const siz
} }
void redisFreeCommand(char *cmd) { void redisFreeCommand(char *cmd) {
free(cmd); hi_free(cmd);
} }
void __redisSetError(redisContext *c, int type, const char *str) { void __redisSetError(redisContext *c, int type, const char *str) {
@ -671,15 +684,21 @@ redisReader *redisReaderCreate(void) {
return redisReaderCreateWithFunctions(&defaultFunctions); return redisReaderCreateWithFunctions(&defaultFunctions);
} }
static redisContext *redisContextInit(const redisOptions *options) { static void redisPushAutoFree(void *privdata, void *reply) {
(void)privdata;
freeReplyObject(reply);
}
static redisContext *redisContextInit(void) {
redisContext *c; redisContext *c;
c = calloc(1, sizeof(*c)); c = hi_calloc(1, sizeof(*c));
if (c == NULL) if (c == NULL)
return NULL; return NULL;
c->funcs = &redisContextDefaultFuncs; c->funcs = &redisContextDefaultFuncs;
c->obuf = sdsempty();
c->obuf = hi_sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
c->fd = REDIS_INVALID_FD; c->fd = REDIS_INVALID_FD;
@ -687,7 +706,7 @@ static redisContext *redisContextInit(const redisOptions *options) {
redisFree(c); redisFree(c);
return NULL; return NULL;
} }
(void)options; /* options are used in other functions */
return c; return c;
} }
@ -696,18 +715,23 @@ void redisFree(redisContext *c) {
return; return;
redisNetClose(c); redisNetClose(c);
sdsfree(c->obuf); hi_sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
free(c->tcp.host); hi_free(c->tcp.host);
free(c->tcp.source_addr); hi_free(c->tcp.source_addr);
free(c->unix_sock.path); hi_free(c->unix_sock.path);
free(c->timeout); hi_free(c->connect_timeout);
free(c->saddr); hi_free(c->command_timeout);
if (c->funcs->free_privdata) { hi_free(c->saddr);
c->funcs->free_privdata(c->privdata);
} if (c->privdata && c->free_privdata)
c->free_privdata(c->privdata);
if (c->funcs->free_privctx)
c->funcs->free_privctx(c->privctx);
memset(c, 0xff, sizeof(*c)); memset(c, 0xff, sizeof(*c));
free(c); hi_free(c);
} }
redisFD redisFreeKeepFd(redisContext *c) { redisFD redisFreeKeepFd(redisContext *c) {
@ -721,35 +745,46 @@ int redisReconnect(redisContext *c) {
c->err = 0; c->err = 0;
memset(c->errstr, '\0', strlen(c->errstr)); memset(c->errstr, '\0', strlen(c->errstr));
if (c->privdata && c->funcs->free_privdata) { if (c->privctx && c->funcs->free_privctx) {
c->funcs->free_privdata(c->privdata); c->funcs->free_privctx(c->privctx);
c->privdata = NULL; c->privctx = NULL;
} }
redisNetClose(c); redisNetClose(c);
sdsfree(c->obuf); hi_sdsfree(c->obuf);
redisReaderFree(c->reader); redisReaderFree(c->reader);
c->obuf = sdsempty(); c->obuf = hi_sdsempty();
c->reader = redisReaderCreate(); c->reader = redisReaderCreate();
if (c->obuf == NULL || c->reader == NULL) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
}
int ret = REDIS_ERR;
if (c->connection_type == REDIS_CONN_TCP) { if (c->connection_type == REDIS_CONN_TCP) {
return redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port, ret = redisContextConnectBindTcp(c, c->tcp.host, c->tcp.port,
c->timeout, c->tcp.source_addr); c->connect_timeout, c->tcp.source_addr);
} else if (c->connection_type == REDIS_CONN_UNIX) { } else if (c->connection_type == REDIS_CONN_UNIX) {
return redisContextConnectUnix(c, c->unix_sock.path, c->timeout); ret = redisContextConnectUnix(c, c->unix_sock.path, c->connect_timeout);
} else { } else {
/* Something bad happened here and shouldn't have. There isn't /* Something bad happened here and shouldn't have. There isn't
enough information in the context to reconnect. */ enough information in the context to reconnect. */
__redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect"); __redisSetError(c,REDIS_ERR_OTHER,"Not enough information to reconnect");
ret = REDIS_ERR;
} }
return REDIS_ERR; if (c->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
redisContextSetTimeout(c, *c->command_timeout);
}
return ret;
} }
redisContext *redisConnectWithOptions(const redisOptions *options) { redisContext *redisConnectWithOptions(const redisOptions *options) {
redisContext *c = redisContextInit(options); redisContext *c = redisContextInit();
if (c == NULL) { if (c == NULL) {
return NULL; return NULL;
} }
@ -763,13 +798,29 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
c->flags |= REDIS_NO_AUTO_FREE; c->flags |= REDIS_NO_AUTO_FREE;
} }
/* Set any user supplied RESP3 PUSH handler or use freeReplyObject
* as a default unless specifically flagged that we don't want one. */
if (options->push_cb != NULL)
redisSetPushCallback(c, options->push_cb);
else if (!(options->options & REDIS_OPT_NO_PUSH_AUTOFREE))
redisSetPushCallback(c, redisPushAutoFree);
c->privdata = options->privdata;
c->free_privdata = options->free_privdata;
if (redisContextUpdateConnectTimeout(c, options->connect_timeout) != REDIS_OK ||
redisContextUpdateCommandTimeout(c, options->command_timeout) != REDIS_OK) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return c;
}
if (options->type == REDIS_CONN_TCP) { if (options->type == REDIS_CONN_TCP) {
redisContextConnectBindTcp(c, options->endpoint.tcp.ip, redisContextConnectBindTcp(c, options->endpoint.tcp.ip,
options->endpoint.tcp.port, options->timeout, options->endpoint.tcp.port, options->connect_timeout,
options->endpoint.tcp.source_addr); options->endpoint.tcp.source_addr);
} else if (options->type == REDIS_CONN_UNIX) { } else if (options->type == REDIS_CONN_UNIX) {
redisContextConnectUnix(c, options->endpoint.unix_socket, redisContextConnectUnix(c, options->endpoint.unix_socket,
options->timeout); options->connect_timeout);
} else if (options->type == REDIS_CONN_USERFD) { } else if (options->type == REDIS_CONN_USERFD) {
c->fd = options->endpoint.fd; c->fd = options->endpoint.fd;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
@ -777,9 +828,11 @@ redisContext *redisConnectWithOptions(const redisOptions *options) {
// Unknown type - FIXME - FREE // Unknown type - FIXME - FREE
return NULL; return NULL;
} }
if (options->timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
redisContextSetTimeout(c, *options->timeout); if (options->command_timeout != NULL && (c->flags & REDIS_BLOCK) && c->fd != REDIS_INVALID_FD) {
redisContextSetTimeout(c, *options->command_timeout);
} }
return c; return c;
} }
@ -795,7 +848,7 @@ redisContext *redisConnect(const char *ip, int port) {
redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) { redisContext *redisConnectWithTimeout(const char *ip, int port, const struct timeval tv) {
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, ip, port); REDIS_OPTIONS_SET_TCP(&options, ip, port);
options.timeout = &tv; options.connect_timeout = &tv;
return redisConnectWithOptions(&options); return redisConnectWithOptions(&options);
} }
@ -833,7 +886,7 @@ redisContext *redisConnectUnix(const char *path) {
redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) { redisContext *redisConnectUnixWithTimeout(const char *path, const struct timeval tv) {
redisOptions options = {0}; redisOptions options = {0};
REDIS_OPTIONS_SET_UNIX(&options, path); REDIS_OPTIONS_SET_UNIX(&options, path);
options.timeout = &tv; options.connect_timeout = &tv;
return redisConnectWithOptions(&options); return redisConnectWithOptions(&options);
} }
@ -865,6 +918,13 @@ int redisEnableKeepAlive(redisContext *c) {
return REDIS_OK; return REDIS_OK;
} }
/* Set a user provided RESP3 PUSH handler and return any old one set. */
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn) {
redisPushFn *old = c->push_cb;
c->push_cb = fn;
return old;
}
/* Use this function to handle a read event on the descriptor. It will try /* Use this function to handle a read event on the descriptor. It will try
* and read some bytes from the socket and feed them to the reply parser. * and read some bytes from the socket and feed them to the reply parser.
* *
@ -906,21 +966,27 @@ int redisBufferWrite(redisContext *c, int *done) {
if (c->err) if (c->err)
return REDIS_ERR; return REDIS_ERR;
if (sdslen(c->obuf) > 0) { if (hi_sdslen(c->obuf) > 0) {
int nwritten = c->funcs->write(c); ssize_t nwritten = c->funcs->write(c);
if (nwritten < 0) { if (nwritten < 0) {
return REDIS_ERR; return REDIS_ERR;
} else if (nwritten > 0) { } else if (nwritten > 0) {
if (nwritten == (signed)sdslen(c->obuf)) { if (nwritten == (ssize_t)hi_sdslen(c->obuf)) {
sdsfree(c->obuf); hi_sdsfree(c->obuf);
c->obuf = sdsempty(); c->obuf = hi_sdsempty();
if (c->obuf == NULL)
goto oom;
} else { } else {
sdsrange(c->obuf,nwritten,-1); if (hi_sdsrange(c->obuf,nwritten,-1) < 0) goto oom;
} }
} }
} }
if (done != NULL) *done = (sdslen(c->obuf) == 0); if (done != NULL) *done = (hi_sdslen(c->obuf) == 0);
return REDIS_OK; return REDIS_OK;
oom:
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
} }
/* Internal helper function to try and get a reply from the reader, /* Internal helper function to try and get a reply from the reader,
@ -930,9 +996,21 @@ int redisGetReplyFromReader(redisContext *c, void **reply) {
__redisSetError(c,c->reader->err,c->reader->errstr); __redisSetError(c,c->reader->err,c->reader->errstr);
return REDIS_ERR; return REDIS_ERR;
} }
return REDIS_OK; return REDIS_OK;
} }
/* Internal helper that returns 1 if the reply was a RESP3 PUSH
* message and we handled it with a user-provided callback. */
static int redisHandledPushReply(redisContext *c, void *reply) {
if (reply && c->push_cb && redisIsPushReply(reply)) {
c->push_cb(c->privdata, reply);
return 1;
}
return 0;
}
int redisGetReply(redisContext *c, void **reply) { int redisGetReply(redisContext *c, void **reply) {
int wdone = 0; int wdone = 0;
void *aux = NULL; void *aux = NULL;
@ -953,13 +1031,23 @@ int redisGetReply(redisContext *c, void **reply) {
do { do {
if (redisBufferRead(c) == REDIS_ERR) if (redisBufferRead(c) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
/* We loop here in case the user has specified a RESP3
* PUSH handler (e.g. for client tracking). */
do {
if (redisGetReplyFromReader(c,&aux) == REDIS_ERR) if (redisGetReplyFromReader(c,&aux) == REDIS_ERR)
return REDIS_ERR; return REDIS_ERR;
} while (redisHandledPushReply(c, aux));
} while (aux == NULL); } while (aux == NULL);
} }
/* Set reply object */ /* Set reply or free it if we were passed NULL */
if (reply != NULL) *reply = aux; if (reply != NULL) {
*reply = aux;
} else {
freeReplyObject(aux);
}
return REDIS_OK; return REDIS_OK;
} }
@ -971,9 +1059,9 @@ int redisGetReply(redisContext *c, void **reply) {
* the reply (or replies in pub/sub). * the reply (or replies in pub/sub).
*/ */
int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) { int __redisAppendCommand(redisContext *c, const char *cmd, size_t len) {
sds newbuf; hisds newbuf;
newbuf = sdscatlen(c->obuf,cmd,len); newbuf = hi_sdscatlen(c->obuf,cmd,len);
if (newbuf == NULL) { if (newbuf == NULL) {
__redisSetError(c,REDIS_ERR_OOM,"Out of memory"); __redisSetError(c,REDIS_ERR_OOM,"Out of memory");
return REDIS_ERR; return REDIS_ERR;
@ -1006,11 +1094,11 @@ int redisvAppendCommand(redisContext *c, const char *format, va_list ap) {
} }
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
free(cmd); hi_free(cmd);
return REDIS_ERR; return REDIS_ERR;
} }
free(cmd); hi_free(cmd);
return REDIS_OK; return REDIS_OK;
} }
@ -1025,7 +1113,7 @@ int redisAppendCommand(redisContext *c, const char *format, ...) {
} }
int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) { int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const size_t *argvlen) {
sds cmd; hisds cmd;
int len; int len;
len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen); len = redisFormatSdsCommandArgv(&cmd,argc,argv,argvlen);
@ -1035,11 +1123,11 @@ int redisAppendCommandArgv(redisContext *c, int argc, const char **argv, const s
} }
if (__redisAppendCommand(c,cmd,len) != REDIS_OK) { if (__redisAppendCommand(c,cmd,len) != REDIS_OK) {
sdsfree(cmd); hi_sdsfree(cmd);
return REDIS_ERR; return REDIS_ERR;
} }
sdsfree(cmd); hi_sdsfree(cmd);
return REDIS_OK; return REDIS_OK;
} }

View File

@ -39,14 +39,16 @@
#include <sys/time.h> /* for struct timeval */ #include <sys/time.h> /* for struct timeval */
#else #else
struct timeval; /* forward declaration */ struct timeval; /* forward declaration */
typedef long long ssize_t;
#endif #endif
#include <stdint.h> /* uintXX_t, etc */ #include <stdint.h> /* uintXX_t, etc */
#include "sds.h" /* for sds */ #include "sds.h" /* for hisds */
#include "alloc.h" /* for allocation wrappers */
#define HIREDIS_MAJOR 0 #define HIREDIS_MAJOR 1
#define HIREDIS_MINOR 14 #define HIREDIS_MINOR 0
#define HIREDIS_PATCH 0 #define HIREDIS_PATCH 0
#define HIREDIS_SONAME 0.14 #define HIREDIS_SONAME 1.0.0
/* Connection type can be blocking or non-blocking and is set in the /* Connection type can be blocking or non-blocking and is set in the
* least significant bit of the flags field in redisContext. */ * least significant bit of the flags field in redisContext. */
@ -90,6 +92,15 @@ struct timeval; /* forward declaration */
* SO_REUSEADDR is being used. */ * SO_REUSEADDR is being used. */
#define REDIS_CONNECT_RETRIES 10 #define REDIS_CONNECT_RETRIES 10
/* Forward declarations for structs defined elsewhere */
struct redisAsyncContext;
struct redisContext;
/* RESP3 push helpers and callback prototypes */
#define redisIsPushReply(r) (((redisReply*)(r))->type == REDIS_REPLY_PUSH)
typedef void (redisPushFn)(void *, void *);
typedef void (redisAsyncPushFn)(struct redisAsyncContext *, void *);
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -101,7 +112,7 @@ typedef struct redisReply {
double dval; /* The double when type is REDIS_REPLY_DOUBLE */ double dval; /* The double when type is REDIS_REPLY_DOUBLE */
size_t len; /* Length of string */ size_t len; /* Length of string */
char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING char *str; /* Used for REDIS_REPLY_ERROR, REDIS_REPLY_STRING
and REDIS_REPLY_DOUBLE (in additionl to dval). */ REDIS_REPLY_VERB, and REDIS_REPLY_DOUBLE (in additional to dval). */
char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null char vtype[4]; /* Used for REDIS_REPLY_VERB, contains the null
terminated 3 character content type, such as "txt". */ terminated 3 character content type, such as "txt". */
size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */ size_t elements; /* number of elements, for REDIS_REPLY_ARRAY */
@ -117,9 +128,9 @@ void freeReplyObject(void *reply);
int redisvFormatCommand(char **target, const char *format, va_list ap); int redisvFormatCommand(char **target, const char *format, va_list ap);
int redisFormatCommand(char **target, const char *format, ...); int redisFormatCommand(char **target, const char *format, ...);
int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen); int redisFormatCommandArgv(char **target, int argc, const char **argv, const size_t *argvlen);
int redisFormatSdsCommandArgv(sds *target, int argc, const char ** argv, const size_t *argvlen); int redisFormatSdsCommandArgv(hisds *target, int argc, const char ** argv, const size_t *argvlen);
void redisFreeCommand(char *cmd); void redisFreeCommand(char *cmd);
void redisFreeSdsCommand(sds cmd); void redisFreeSdsCommand(hisds cmd);
enum redisConnectionType { enum redisConnectionType {
REDIS_CONN_TCP, REDIS_CONN_TCP,
@ -138,6 +149,9 @@ struct redisSsl;
*/ */
#define REDIS_OPT_NOAUTOFREE 0x04 #define REDIS_OPT_NOAUTOFREE 0x04
/* Don't automatically intercept and free RESP3 PUSH replies. */
#define REDIS_OPT_NO_PUSH_AUTOFREE 0x08
/* In Unix systems a file descriptor is a regular signed int, with -1 /* In Unix systems a file descriptor is a regular signed int, with -1
* representing an invalid descriptor. In Windows it is a SOCKET * representing an invalid descriptor. In Windows it is a SOCKET
* (32- or 64-bit unsigned integer depending on the architecture), where * (32- or 64-bit unsigned integer depending on the architecture), where
@ -162,8 +176,11 @@ typedef struct {
int type; int type;
/* bit field of REDIS_OPT_xxx */ /* bit field of REDIS_OPT_xxx */
int options; int options;
/* timeout value. if NULL, no timeout is used */ /* timeout value for connect operation. If NULL, no timeout is used */
const struct timeval *timeout; const struct timeval *connect_timeout;
/* timeout value for commands. If NULL, no timeout is used. This can be
* updated at runtime with redisSetTimeout/redisAsyncSetTimeout. */
const struct timeval *command_timeout;
union { union {
/** use this field for tcp/ip connections */ /** use this field for tcp/ip connections */
struct { struct {
@ -178,6 +195,14 @@ typedef struct {
* file descriptor */ * file descriptor */
redisFD fd; redisFD fd;
} endpoint; } endpoint;
/* Optional user defined data/destructor */
void *privdata;
void (*free_privdata)(void *);
/* A user defined PUSH message callback */
redisPushFn *push_cb;
redisAsyncPushFn *async_push_cb;
} redisOptions; } redisOptions;
/** /**
@ -192,15 +217,16 @@ typedef struct {
(opts)->type = REDIS_CONN_UNIX; \ (opts)->type = REDIS_CONN_UNIX; \
(opts)->endpoint.unix_socket = path; (opts)->endpoint.unix_socket = path;
struct redisAsyncContext; #define REDIS_OPTIONS_SET_PRIVDATA(opts, data, dtor) \
struct redisContext; (opts)->privdata = data; \
(opts)->free_privdata = dtor; \
typedef struct redisContextFuncs { typedef struct redisContextFuncs {
void (*free_privdata)(void *); void (*free_privctx)(void *);
void (*async_read)(struct redisAsyncContext *); void (*async_read)(struct redisAsyncContext *);
void (*async_write)(struct redisAsyncContext *); void (*async_write)(struct redisAsyncContext *);
int (*read)(struct redisContext *, char *, size_t); ssize_t (*read)(struct redisContext *, char *, size_t);
int (*write)(struct redisContext *); ssize_t (*write)(struct redisContext *);
} redisContextFuncs; } redisContextFuncs;
/* Context for a connection to Redis */ /* Context for a connection to Redis */
@ -215,7 +241,8 @@ typedef struct redisContext {
redisReader *reader; /* Protocol reader */ redisReader *reader; /* Protocol reader */
enum redisConnectionType connection_type; enum redisConnectionType connection_type;
struct timeval *timeout; struct timeval *connect_timeout;
struct timeval *command_timeout;
struct { struct {
char *host; char *host;
@ -231,8 +258,17 @@ typedef struct redisContext {
struct sockadr *saddr; struct sockadr *saddr;
size_t addrlen; size_t addrlen;
/* Additional private data for hiredis addons such as SSL */ /* Optional data and corresponding destructor users can use to provide
* context to a given redisContext. Not used by hiredis. */
void *privdata; void *privdata;
void (*free_privdata)(void *);
/* Internal context pointer presently used by hiredis to manage
* SSL connections. */
void *privctx;
/* An optional RESP3 PUSH handler */
redisPushFn *push_cb;
} redisContext; } redisContext;
redisContext *redisConnectWithOptions(const redisOptions *options); redisContext *redisConnectWithOptions(const redisOptions *options);
@ -259,6 +295,7 @@ redisContext *redisConnectFd(redisFD fd);
*/ */
int redisReconnect(redisContext *c); int redisReconnect(redisContext *c);
redisPushFn *redisSetPushCallback(redisContext *c, redisPushFn *fn);
int redisSetTimeout(redisContext *c, const struct timeval tv); int redisSetTimeout(redisContext *c, const struct timeval tv);
int redisEnableKeepAlive(redisContext *c); int redisEnableKeepAlive(redisContext *c);
void redisFree(redisContext *c); void redisFree(redisContext *c);

View File

@ -1,6 +1,7 @@
prefix=@CMAKE_INSTALL_PREFIX@ prefix=@CMAKE_INSTALL_PREFIX@
install_libdir=@CMAKE_INSTALL_LIBDIR@
exec_prefix=${prefix} exec_prefix=${prefix}
libdir=${exec_prefix}/lib libdir=${exec_prefix}/${install_libdir}
includedir=${prefix}/include includedir=${prefix}/include
pkgincludedir=${includedir}/hiredis pkgincludedir=${includedir}/hiredis

View File

@ -0,0 +1,13 @@
@PACKAGE_INIT@
set_and_check(hiredis_ssl_INCLUDEDIR "@PACKAGE_INCLUDE_INSTALL_DIR@")
IF (NOT TARGET hiredis::hiredis_ssl)
INCLUDE(${CMAKE_CURRENT_LIST_DIR}/hiredis_ssl-targets.cmake)
ENDIF()
SET(hiredis_ssl_LIBRARIES hiredis::hiredis_ssl)
SET(hiredis_ssl_INCLUDE_DIRS ${hiredis_ssl_INCLUDEDIR})
check_required_components(hiredis_ssl)

View File

@ -32,22 +32,96 @@
#ifndef __HIREDIS_SSL_H #ifndef __HIREDIS_SSL_H
#define __HIREDIS_SSL_H #define __HIREDIS_SSL_H
#ifdef __cplusplus
extern "C" {
#endif
/* This is the underlying struct for SSL in ssl.h, which is not included to /* This is the underlying struct for SSL in ssl.h, which is not included to
* keep build dependencies short here. * keep build dependencies short here.
*/ */
struct ssl_st; struct ssl_st;
/** /* A wrapper around OpenSSL SSL_CTX to allow easy SSL use without directly
* Secure the connection using SSL. This should be done before any command is * calling OpenSSL.
* executed on the connection.
*/ */
int redisSecureConnection(redisContext *c, const char *capath, const char *certpath, typedef struct redisSSLContext redisSSLContext;
const char *keypath, const char *servername);
/** /**
* Initiate SSL/TLS negotiation on a provided context. * Initialization errors that redisCreateSSLContext() may return.
*/
typedef enum {
REDIS_SSL_CTX_NONE = 0, /* No Error */
REDIS_SSL_CTX_CREATE_FAILED, /* Failed to create OpenSSL SSL_CTX */
REDIS_SSL_CTX_CERT_KEY_REQUIRED, /* Client cert and key must both be specified or skipped */
REDIS_SSL_CTX_CA_CERT_LOAD_FAILED, /* Failed to load CA Certificate or CA Path */
REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED, /* Failed to load client certificate */
REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED /* Failed to load private key */
} redisSSLContextError;
/**
* Return the error message corresponding with the specified error code.
*/
const char *redisSSLContextGetError(redisSSLContextError error);
/**
* Helper function to initialize the OpenSSL library.
*
* OpenSSL requires one-time initialization before it can be used. Callers should
* call this function only once, and only if OpenSSL is not directly initialized
* elsewhere.
*/
int redisInitOpenSSL(void);
/**
* Helper function to initialize an OpenSSL context that can be used
* to initiate SSL connections.
*
* cacert_filename is an optional name of a CA certificate/bundle file to load
* and use for validation.
*
* capath is an optional directory path where trusted CA certificate files are
* stored in an OpenSSL-compatible structure.
*
* cert_filename and private_key_filename are optional names of a client side
* certificate and private key files to use for authentication. They need to
* be both specified or omitted.
*
* server_name is an optional and will be used as a server name indication
* (SNI) TLS extension.
*
* If error is non-null, it will be populated in case the context creation fails
* (returning a NULL).
*/
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error);
/**
* Free a previously created OpenSSL context.
*/
void redisFreeSSLContext(redisSSLContext *redis_ssl_ctx);
/**
* Initiate SSL on an existing redisContext.
*
* This is similar to redisInitiateSSL() but does not require the caller
* to directly interact with OpenSSL, and instead uses a redisSSLContext
* previously created using redisCreateSSLContext().
*/
int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx);
/**
* Initiate SSL/TLS negotiation on a provided OpenSSL SSL object.
*/ */
int redisInitiateSSL(redisContext *c, struct ssl_st *ssl); int redisInitiateSSL(redisContext *c, struct ssl_st *ssl);
#ifdef __cplusplus
}
#endif
#endif /* __HIREDIS_SSL_H */ #endif /* __HIREDIS_SSL_H */

119
deps/hiredis/net.c vendored
View File

@ -57,8 +57,8 @@ void redisNetClose(redisContext *c) {
} }
} }
int redisNetRead(redisContext *c, char *buf, size_t bufcap) { ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap) {
int nread = recv(c->fd, buf, bufcap, 0); ssize_t nread = recv(c->fd, buf, bufcap, 0);
if (nread == -1) { if (nread == -1) {
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */ /* Try again later */
@ -79,8 +79,8 @@ int redisNetRead(redisContext *c, char *buf, size_t bufcap) {
} }
} }
int redisNetWrite(redisContext *c) { ssize_t redisNetWrite(redisContext *c) {
int nwritten = send(c->fd, c->obuf, sdslen(c->obuf), 0); ssize_t nwritten = send(c->fd, c->obuf, hi_sdslen(c->obuf), 0);
if (nwritten < 0) { if (nwritten < 0) {
if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) { if ((errno == EWOULDBLOCK && !(c->flags & REDIS_BLOCK)) || (errno == EINTR)) {
/* Try again later */ /* Try again later */
@ -203,7 +203,7 @@ int redisKeepAlive(redisContext *c, int interval) {
return REDIS_OK; return REDIS_OK;
} }
static int redisSetTcpNoDelay(redisContext *c) { int redisSetTcpNoDelay(redisContext *c) {
int yes = 1; int yes = 1;
if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) { if (setsockopt(c->fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(TCP_NODELAY)");
@ -217,7 +217,7 @@ static int redisSetTcpNoDelay(redisContext *c) {
static int redisContextTimeoutMsec(redisContext *c, long *result) static int redisContextTimeoutMsec(redisContext *c, long *result)
{ {
const struct timeval *timeout = c->timeout; const struct timeval *timeout = c->connect_timeout;
long msec = -1; long msec = -1;
/* Only use timeout when not NULL. */ /* Only use timeout when not NULL. */
@ -316,11 +316,7 @@ int redisCheckSocketError(redisContext *c) {
int redisContextSetTimeout(redisContext *c, const struct timeval tv) { int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
const void *to_ptr = &tv; const void *to_ptr = &tv;
size_t to_sz = sizeof(tv); size_t to_sz = sizeof(tv);
#ifdef _WIN32
DWORD timeout_msec = tv.tv_sec * 1000 + tv.tv_usec / 1000;
to_ptr = &timeout_msec;
to_sz = sizeof(timeout_msec);
#endif
if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) { if (setsockopt(c->fd,SOL_SOCKET,SO_RCVTIMEO,to_ptr,to_sz) == -1) {
__redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)"); __redisSetErrorFromErrno(c,REDIS_ERR_IO,"setsockopt(SO_RCVTIMEO)");
return REDIS_ERR; return REDIS_ERR;
@ -332,6 +328,38 @@ int redisContextSetTimeout(redisContext *c, const struct timeval tv) {
return REDIS_OK; return REDIS_OK;
} }
int redisContextUpdateConnectTimeout(redisContext *c, const struct timeval *timeout) {
/* Same timeval struct, short circuit */
if (c->connect_timeout == timeout)
return REDIS_OK;
/* Allocate context timeval if we need to */
if (c->connect_timeout == NULL) {
c->connect_timeout = hi_malloc(sizeof(*c->connect_timeout));
if (c->connect_timeout == NULL)
return REDIS_ERR;
}
memcpy(c->connect_timeout, timeout, sizeof(*c->connect_timeout));
return REDIS_OK;
}
int redisContextUpdateCommandTimeout(redisContext *c, const struct timeval *timeout) {
/* Same timeval struct, short circuit */
if (c->command_timeout == timeout)
return REDIS_OK;
/* Allocate context timeval if we need to */
if (c->command_timeout == NULL) {
c->command_timeout = hi_malloc(sizeof(*c->command_timeout));
if (c->command_timeout == NULL)
return REDIS_ERR;
}
memcpy(c->command_timeout, timeout, sizeof(*c->command_timeout));
return REDIS_OK;
}
static int _redisContextConnectTcp(redisContext *c, const char *addr, int port, static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
const struct timeval *timeout, const struct timeval *timeout,
const char *source_addr) { const char *source_addr) {
@ -356,21 +384,19 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
* This is a bit ugly, but atleast it works and doesn't leak memory. * This is a bit ugly, but atleast it works and doesn't leak memory.
**/ **/
if (c->tcp.host != addr) { if (c->tcp.host != addr) {
free(c->tcp.host); hi_free(c->tcp.host);
c->tcp.host = strdup(addr); c->tcp.host = hi_strdup(addr);
if (c->tcp.host == NULL)
goto oom;
} }
if (timeout) { if (timeout) {
if (c->timeout != timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
if (c->timeout == NULL) goto oom;
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else { } else {
free(c->timeout); hi_free(c->connect_timeout);
c->timeout = NULL; c->connect_timeout = NULL;
} }
if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) { if (redisContextTimeoutMsec(c, &timeout_msec) != REDIS_OK) {
@ -379,11 +405,11 @@ static int _redisContextConnectTcp(redisContext *c, const char *addr, int port,
} }
if (source_addr == NULL) { if (source_addr == NULL) {
free(c->tcp.source_addr); hi_free(c->tcp.source_addr);
c->tcp.source_addr = NULL; c->tcp.source_addr = NULL;
} else if (c->tcp.source_addr != source_addr) { } else if (c->tcp.source_addr != source_addr) {
free(c->tcp.source_addr); hi_free(c->tcp.source_addr);
c->tcp.source_addr = strdup(source_addr); c->tcp.source_addr = hi_strdup(source_addr);
} }
snprintf(_port, 6, "%d", port); snprintf(_port, 6, "%d", port);
@ -446,8 +472,11 @@ addrretry:
} }
/* For repeat connection */ /* For repeat connection */
free(c->saddr); hi_free(c->saddr);
c->saddr = malloc(p->ai_addrlen); c->saddr = hi_malloc(p->ai_addrlen);
if (c->saddr == NULL)
goto oom;
memcpy(c->saddr, p->ai_addr, p->ai_addrlen); memcpy(c->saddr, p->ai_addr, p->ai_addrlen);
c->addrlen = p->ai_addrlen; c->addrlen = p->ai_addrlen;
@ -474,12 +503,12 @@ addrretry:
wait_for_ready: wait_for_ready:
if (redisContextWaitReady(c,timeout_msec) != REDIS_OK) if (redisContextWaitReady(c,timeout_msec) != REDIS_OK)
goto error; goto error;
if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error;
} }
} }
if (blocking && redisSetBlocking(c,1) != REDIS_OK) if (blocking && redisSetBlocking(c,1) != REDIS_OK)
goto error; goto error;
if (redisSetTcpNoDelay(c) != REDIS_OK)
goto error;
c->flags |= REDIS_CONNECTED; c->flags |= REDIS_CONNECTED;
rv = REDIS_OK; rv = REDIS_OK;
@ -492,6 +521,8 @@ addrretry:
goto error; goto error;
} }
oom:
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
error: error:
rv = REDIS_ERR; rv = REDIS_ERR;
end: end:
@ -525,25 +556,32 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
return REDIS_ERR; return REDIS_ERR;
c->connection_type = REDIS_CONN_UNIX; c->connection_type = REDIS_CONN_UNIX;
if (c->unix_sock.path != path) if (c->unix_sock.path != path) {
c->unix_sock.path = strdup(path); hi_free(c->unix_sock.path);
c->unix_sock.path = hi_strdup(path);
if (c->unix_sock.path == NULL)
goto oom;
}
if (timeout) { if (timeout) {
if (c->timeout != timeout) { if (redisContextUpdateConnectTimeout(c, timeout) == REDIS_ERR)
if (c->timeout == NULL) goto oom;
c->timeout = malloc(sizeof(struct timeval));
memcpy(c->timeout, timeout, sizeof(struct timeval));
}
} else { } else {
free(c->timeout); hi_free(c->connect_timeout);
c->timeout = NULL; c->connect_timeout = NULL;
} }
if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK) if (redisContextTimeoutMsec(c,&timeout_msec) != REDIS_OK)
return REDIS_ERR; return REDIS_ERR;
sa = (struct sockaddr_un*)(c->saddr = malloc(sizeof(struct sockaddr_un))); /* Don't leak sockaddr if we're reconnecting */
if (c->saddr) hi_free(c->saddr);
sa = (struct sockaddr_un*)(c->saddr = hi_malloc(sizeof(struct sockaddr_un)));
if (sa == NULL)
goto oom;
c->addrlen = sizeof(struct sockaddr_un); c->addrlen = sizeof(struct sockaddr_un);
sa->sun_family = AF_UNIX; sa->sun_family = AF_UNIX;
strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1); strncpy(sa->sun_path, path, sizeof(sa->sun_path) - 1);
@ -568,4 +606,7 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
errno = EPROTONOSUPPORT; errno = EPROTONOSUPPORT;
return REDIS_ERR; return REDIS_ERR;
#endif /* _WIN32 */ #endif /* _WIN32 */
oom:
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
} }

6
deps/hiredis/net.h vendored
View File

@ -38,8 +38,8 @@
#include "hiredis.h" #include "hiredis.h"
void redisNetClose(redisContext *c); void redisNetClose(redisContext *c);
int redisNetRead(redisContext *c, char *buf, size_t bufcap); ssize_t redisNetRead(redisContext *c, char *buf, size_t bufcap);
int redisNetWrite(redisContext *c); ssize_t redisNetWrite(redisContext *c);
int redisCheckSocketError(redisContext *c); int redisCheckSocketError(redisContext *c);
int redisContextSetTimeout(redisContext *c, const struct timeval tv); int redisContextSetTimeout(redisContext *c, const struct timeval tv);
@ -51,4 +51,6 @@ int redisContextConnectUnix(redisContext *c, const char *path, const struct time
int redisKeepAlive(redisContext *c, int interval); int redisKeepAlive(redisContext *c, int interval);
int redisCheckConnectDone(redisContext *c, int *completed); int redisCheckConnectDone(redisContext *c, int *completed);
int redisSetTcpNoDelay(redisContext *c);
#endif #endif

156
deps/hiredis/read.c vendored
View File

@ -42,10 +42,14 @@
#include <limits.h> #include <limits.h>
#include <math.h> #include <math.h>
#include "alloc.h"
#include "read.h" #include "read.h"
#include "sds.h" #include "sds.h"
#include "win32.h" #include "win32.h"
/* Initial size of our nested reply stack and how much we grow it when needd */
#define REDIS_READER_STACK_SIZE 9
static void __redisReaderSetError(redisReader *r, int type, const char *str) { static void __redisReaderSetError(redisReader *r, int type, const char *str) {
size_t len; size_t len;
@ -55,7 +59,7 @@ static void __redisReaderSetError(redisReader *r, int type, const char *str) {
} }
/* Clear input buffer on errors. */ /* Clear input buffer on errors. */
sdsfree(r->buf); hi_sdsfree(r->buf);
r->buf = NULL; r->buf = NULL;
r->pos = r->len = 0; r->pos = r->len = 0;
@ -243,11 +247,12 @@ static void moveToNextTask(redisReader *r) {
return; return;
} }
cur = &(r->rstack[r->ridx]); cur = r->task[r->ridx];
prv = &(r->rstack[r->ridx-1]); prv = r->task[r->ridx-1];
assert(prv->type == REDIS_REPLY_ARRAY || assert(prv->type == REDIS_REPLY_ARRAY ||
prv->type == REDIS_REPLY_MAP || prv->type == REDIS_REPLY_MAP ||
prv->type == REDIS_REPLY_SET); prv->type == REDIS_REPLY_SET ||
prv->type == REDIS_REPLY_PUSH);
if (cur->idx == prv->elements-1) { if (cur->idx == prv->elements-1) {
r->ridx--; r->ridx--;
} else { } else {
@ -262,7 +267,7 @@ static void moveToNextTask(redisReader *r) {
} }
static int processLineItem(redisReader *r) { static int processLineItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
void *obj; void *obj;
char *p; char *p;
int len; int len;
@ -297,7 +302,7 @@ static int processLineItem(redisReader *r) {
if (strcasecmp(buf,",inf") == 0) { if (strcasecmp(buf,",inf") == 0) {
d = INFINITY; /* Positive infinite. */ d = INFINITY; /* Positive infinite. */
} else if (strcasecmp(buf,",-inf") == 0) { } else if (strcasecmp(buf,",-inf") == 0) {
d = -INFINITY; /* Nevative infinite. */ d = -INFINITY; /* Negative infinite. */
} else { } else {
d = strtod((char*)buf,&eptr); d = strtod((char*)buf,&eptr);
if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) { if (buf[0] == '\0' || eptr[0] != '\0' || isnan(d)) {
@ -344,7 +349,7 @@ static int processLineItem(redisReader *r) {
} }
static int processBulkItem(redisReader *r) { static int processBulkItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
void *obj = NULL; void *obj = NULL;
char *p, *s; char *p, *s;
long long len; long long len;
@ -415,18 +420,42 @@ static int processBulkItem(redisReader *r) {
return REDIS_ERR; return REDIS_ERR;
} }
static int redisReaderGrow(redisReader *r) {
redisReadTask **aux;
int newlen;
/* Grow our stack size */
newlen = r->tasks + REDIS_READER_STACK_SIZE;
aux = hi_realloc(r->task, sizeof(*r->task) * newlen);
if (aux == NULL)
goto oom;
r->task = aux;
/* Allocate new tasks */
for (; r->tasks < newlen; r->tasks++) {
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
if (r->task[r->tasks] == NULL)
goto oom;
}
return REDIS_OK;
oom:
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
/* Process the array, map and set types. */ /* Process the array, map and set types. */
static int processAggregateItem(redisReader *r) { static int processAggregateItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
void *obj; void *obj;
char *p; char *p;
long long elements; long long elements;
int root = 0, len; int root = 0, len;
/* Set error for nested multi bulks with depth > 7 */ /* Set error for nested multi bulks with depth > 7 */
if (r->ridx == 8) { if (r->ridx == r->tasks - 1) {
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, if (redisReaderGrow(r) == REDIS_ERR)
"No support for nested multi bulk replies with depth > 7");
return REDIS_ERR; return REDIS_ERR;
} }
@ -439,7 +468,9 @@ static int processAggregateItem(redisReader *r) {
root = (r->ridx == 0); root = (r->ridx == 0);
if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX)) { if (elements < -1 || (LLONG_MAX > SIZE_MAX && elements > SIZE_MAX) ||
(r->maxelements > 0 && elements > r->maxelements))
{
__redisReaderSetError(r,REDIS_ERR_PROTOCOL, __redisReaderSetError(r,REDIS_ERR_PROTOCOL,
"Multi-bulk length out of range"); "Multi-bulk length out of range");
return REDIS_ERR; return REDIS_ERR;
@ -475,12 +506,12 @@ static int processAggregateItem(redisReader *r) {
cur->elements = elements; cur->elements = elements;
cur->obj = obj; cur->obj = obj;
r->ridx++; r->ridx++;
r->rstack[r->ridx].type = -1; r->task[r->ridx]->type = -1;
r->rstack[r->ridx].elements = -1; r->task[r->ridx]->elements = -1;
r->rstack[r->ridx].idx = 0; r->task[r->ridx]->idx = 0;
r->rstack[r->ridx].obj = NULL; r->task[r->ridx]->obj = NULL;
r->rstack[r->ridx].parent = cur; r->task[r->ridx]->parent = cur;
r->rstack[r->ridx].privdata = r->privdata; r->task[r->ridx]->privdata = r->privdata;
} else { } else {
moveToNextTask(r); moveToNextTask(r);
} }
@ -495,7 +526,7 @@ static int processAggregateItem(redisReader *r) {
} }
static int processItem(redisReader *r) { static int processItem(redisReader *r) {
redisReadTask *cur = &(r->rstack[r->ridx]); redisReadTask *cur = r->task[r->ridx];
char *p; char *p;
/* check if we need to read type */ /* check if we need to read type */
@ -535,6 +566,9 @@ static int processItem(redisReader *r) {
case '=': case '=':
cur->type = REDIS_REPLY_VERB; cur->type = REDIS_REPLY_VERB;
break; break;
case '>':
cur->type = REDIS_REPLY_PUSH;
break;
default: default:
__redisReaderSetErrorProtocolByte(r,*p); __redisReaderSetErrorProtocolByte(r,*p);
return REDIS_ERR; return REDIS_ERR;
@ -560,6 +594,7 @@ static int processItem(redisReader *r) {
case REDIS_REPLY_ARRAY: case REDIS_REPLY_ARRAY:
case REDIS_REPLY_MAP: case REDIS_REPLY_MAP:
case REDIS_REPLY_SET: case REDIS_REPLY_SET:
case REDIS_REPLY_PUSH:
return processAggregateItem(r); return processAggregateItem(r);
default: default:
assert(NULL); assert(NULL);
@ -570,33 +605,57 @@ static int processItem(redisReader *r) {
redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) { redisReader *redisReaderCreateWithFunctions(redisReplyObjectFunctions *fn) {
redisReader *r; redisReader *r;
r = calloc(1,sizeof(redisReader)); r = hi_calloc(1,sizeof(redisReader));
if (r == NULL) if (r == NULL)
return NULL; return NULL;
r->fn = fn; r->buf = hi_sdsempty();
r->buf = sdsempty(); if (r->buf == NULL)
r->maxbuf = REDIS_READER_MAX_BUF; goto oom;
if (r->buf == NULL) {
free(r); r->task = hi_calloc(REDIS_READER_STACK_SIZE, sizeof(*r->task));
return NULL; if (r->task == NULL)
goto oom;
for (; r->tasks < REDIS_READER_STACK_SIZE; r->tasks++) {
r->task[r->tasks] = hi_calloc(1, sizeof(**r->task));
if (r->task[r->tasks] == NULL)
goto oom;
} }
r->fn = fn;
r->maxbuf = REDIS_READER_MAX_BUF;
r->maxelements = REDIS_READER_MAX_ARRAY_ELEMENTS;
r->ridx = -1; r->ridx = -1;
return r; return r;
oom:
redisReaderFree(r);
return NULL;
} }
void redisReaderFree(redisReader *r) { void redisReaderFree(redisReader *r) {
if (r == NULL) if (r == NULL)
return; return;
if (r->reply != NULL && r->fn && r->fn->freeObject) if (r->reply != NULL && r->fn && r->fn->freeObject)
r->fn->freeObject(r->reply); r->fn->freeObject(r->reply);
sdsfree(r->buf);
free(r); if (r->task) {
/* We know r->task[i] is allocated if i < r->tasks */
for (int i = 0; i < r->tasks; i++) {
hi_free(r->task[i]);
}
hi_free(r->task);
}
hi_sdsfree(r->buf);
hi_free(r);
} }
int redisReaderFeed(redisReader *r, const char *buf, size_t len) { int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
sds newbuf; hisds newbuf;
/* Return early when this reader is in an erroneous state. */ /* Return early when this reader is in an erroneous state. */
if (r->err) if (r->err)
@ -605,26 +664,25 @@ int redisReaderFeed(redisReader *r, const char *buf, size_t len) {
/* Copy the provided buffer. */ /* Copy the provided buffer. */
if (buf != NULL && len >= 1) { if (buf != NULL && len >= 1) {
/* Destroy internal buffer when it is empty and is quite large. */ /* Destroy internal buffer when it is empty and is quite large. */
if (r->len == 0 && r->maxbuf != 0 && sdsavail(r->buf) > r->maxbuf) { if (r->len == 0 && r->maxbuf != 0 && hi_sdsavail(r->buf) > r->maxbuf) {
sdsfree(r->buf); hi_sdsfree(r->buf);
r->buf = sdsempty(); r->buf = hi_sdsempty();
if (r->buf == 0) goto oom;
r->pos = 0; r->pos = 0;
/* r->buf should not be NULL since we just free'd a larger one. */
assert(r->buf != NULL);
} }
newbuf = sdscatlen(r->buf,buf,len); newbuf = hi_sdscatlen(r->buf,buf,len);
if (newbuf == NULL) { if (newbuf == NULL) goto oom;
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
}
r->buf = newbuf; r->buf = newbuf;
r->len = sdslen(r->buf); r->len = hi_sdslen(r->buf);
} }
return REDIS_OK; return REDIS_OK;
oom:
__redisReaderSetErrorOOM(r);
return REDIS_ERR;
} }
int redisReaderGetReply(redisReader *r, void **reply) { int redisReaderGetReply(redisReader *r, void **reply) {
@ -642,12 +700,12 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Set first item to process when the stack is empty. */ /* Set first item to process when the stack is empty. */
if (r->ridx == -1) { if (r->ridx == -1) {
r->rstack[0].type = -1; r->task[0]->type = -1;
r->rstack[0].elements = -1; r->task[0]->elements = -1;
r->rstack[0].idx = -1; r->task[0]->idx = -1;
r->rstack[0].obj = NULL; r->task[0]->obj = NULL;
r->rstack[0].parent = NULL; r->task[0]->parent = NULL;
r->rstack[0].privdata = r->privdata; r->task[0]->privdata = r->privdata;
r->ridx = 0; r->ridx = 0;
} }
@ -663,9 +721,9 @@ int redisReaderGetReply(redisReader *r, void **reply) {
/* Discard part of the buffer when we've consumed at least 1k, to avoid /* Discard part of the buffer when we've consumed at least 1k, to avoid
* doing unnecessary calls to memmove() in sds.c. */ * doing unnecessary calls to memmove() in sds.c. */
if (r->pos >= 1024) { if (r->pos >= 1024) {
sdsrange(r->buf,r->pos,-1); if (hi_sdsrange(r->buf,r->pos,-1) < 0) return REDIS_ERR;
r->pos = 0; r->pos = 0;
r->len = sdslen(r->buf); r->len = hi_sdslen(r->buf);
} }
/* Emit a reply when there is one. */ /* Emit a reply when there is one. */

13
deps/hiredis/read.h vendored
View File

@ -63,7 +63,11 @@
#define REDIS_REPLY_BIGNUM 13 #define REDIS_REPLY_BIGNUM 13
#define REDIS_REPLY_VERB 14 #define REDIS_REPLY_VERB 14
#define REDIS_READER_MAX_BUF (1024*16) /* Default max unused reader buffer. */ /* Default max unused reader buffer. */
#define REDIS_READER_MAX_BUF (1024*16)
/* Default multi-bulk element limit */
#define REDIS_READER_MAX_ARRAY_ELEMENTS ((1LL<<32) - 1)
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
@ -71,7 +75,7 @@ extern "C" {
typedef struct redisReadTask { typedef struct redisReadTask {
int type; int type;
int elements; /* number of elements in multibulk container */ long long elements; /* number of elements in multibulk container */
int idx; /* index in parent (array) object */ int idx; /* index in parent (array) object */
void *obj; /* holds user-generated value for a read task */ void *obj; /* holds user-generated value for a read task */
struct redisReadTask *parent; /* parent task */ struct redisReadTask *parent; /* parent task */
@ -96,8 +100,11 @@ typedef struct redisReader {
size_t pos; /* Buffer cursor */ size_t pos; /* Buffer cursor */
size_t len; /* Buffer length */ size_t len; /* Buffer length */
size_t maxbuf; /* Max length of unused buffer */ size_t maxbuf; /* Max length of unused buffer */
long long maxelements; /* Max multi-bulk elements */
redisReadTask **task;
int tasks;
redisReadTask rstack[9];
int ridx; /* Index of current read task */ int ridx; /* Index of current read task */
void *reply; /* Temporary reply pointer */ void *reply; /* Temporary reply pointer */

828
deps/hiredis/sds.c vendored

File diff suppressed because it is too large Load Diff

264
deps/hiredis/sds.h vendored
View File

@ -30,29 +30,31 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#ifndef __SDS_H #ifndef HIREDIS_SDS_H
#define __SDS_H #define HIREDIS_SDS_H
#define SDS_MAX_PREALLOC (1024*1024) #define HI_SDS_MAX_PREALLOC (1024*1024)
#ifdef _MSC_VER #ifdef _MSC_VER
#define __attribute__(x) #define __attribute__(x)
typedef long long ssize_t;
#define SSIZE_MAX (LLONG_MAX >> 1)
#endif #endif
#include <sys/types.h> #include <sys/types.h>
#include <stdarg.h> #include <stdarg.h>
#include <stdint.h> #include <stdint.h>
typedef char *sds; typedef char *hisds;
/* Note: sdshdr5 is never used, we just access the flags byte directly. /* Note: sdshdr5 is never used, we just access the flags byte directly.
* However is here to document the layout of type 5 SDS strings. */ * However is here to document the layout of type 5 SDS strings. */
struct __attribute__ ((__packed__)) sdshdr5 { struct __attribute__ ((__packed__)) hisdshdr5 {
unsigned char flags; /* 3 lsb of type, and 5 msb of string length */ unsigned char flags; /* 3 lsb of type, and 5 msb of string length */
#ifndef __cplusplus #ifndef __cplusplus
char buf[]; char buf[];
#endif #endif
}; };
struct __attribute__ ((__packed__)) sdshdr8 { struct __attribute__ ((__packed__)) hisdshdr8 {
uint8_t len; /* used */ uint8_t len; /* used */
uint8_t alloc; /* excluding the header and null terminator */ uint8_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */ unsigned char flags; /* 3 lsb of type, 5 unused bits */
@ -60,7 +62,7 @@ struct __attribute__ ((__packed__)) sdshdr8 {
char buf[]; char buf[];
#endif #endif
}; };
struct __attribute__ ((__packed__)) sdshdr16 { struct __attribute__ ((__packed__)) hisdshdr16 {
uint16_t len; /* used */ uint16_t len; /* used */
uint16_t alloc; /* excluding the header and null terminator */ uint16_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */ unsigned char flags; /* 3 lsb of type, 5 unused bits */
@ -68,7 +70,7 @@ struct __attribute__ ((__packed__)) sdshdr16 {
char buf[]; char buf[];
#endif #endif
}; };
struct __attribute__ ((__packed__)) sdshdr32 { struct __attribute__ ((__packed__)) hisdshdr32 {
uint32_t len; /* used */ uint32_t len; /* used */
uint32_t alloc; /* excluding the header and null terminator */ uint32_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */ unsigned char flags; /* 3 lsb of type, 5 unused bits */
@ -76,7 +78,7 @@ struct __attribute__ ((__packed__)) sdshdr32 {
char buf[]; char buf[];
#endif #endif
}; };
struct __attribute__ ((__packed__)) sdshdr64 { struct __attribute__ ((__packed__)) hisdshdr64 {
uint64_t len; /* used */ uint64_t len; /* used */
uint64_t alloc; /* excluding the header and null terminator */ uint64_t alloc; /* excluding the header and null terminator */
unsigned char flags; /* 3 lsb of type, 5 unused bits */ unsigned char flags; /* 3 lsb of type, 5 unused bits */
@ -85,203 +87,203 @@ struct __attribute__ ((__packed__)) sdshdr64 {
#endif #endif
}; };
#define SDS_TYPE_5 0 #define HI_SDS_TYPE_5 0
#define SDS_TYPE_8 1 #define HI_SDS_TYPE_8 1
#define SDS_TYPE_16 2 #define HI_SDS_TYPE_16 2
#define SDS_TYPE_32 3 #define HI_SDS_TYPE_32 3
#define SDS_TYPE_64 4 #define HI_SDS_TYPE_64 4
#define SDS_TYPE_MASK 7 #define HI_SDS_TYPE_MASK 7
#define SDS_TYPE_BITS 3 #define HI_SDS_TYPE_BITS 3
#define SDS_HDR_VAR(T,s) struct sdshdr##T *sh = (struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T))); #define HI_SDS_HDR_VAR(T,s) struct hisdshdr##T *sh = (struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T)));
#define SDS_HDR(T,s) ((struct sdshdr##T *)((s)-(sizeof(struct sdshdr##T)))) #define HI_SDS_HDR(T,s) ((struct hisdshdr##T *)((s)-(sizeof(struct hisdshdr##T))))
#define SDS_TYPE_5_LEN(f) ((f)>>SDS_TYPE_BITS) #define HI_SDS_TYPE_5_LEN(f) ((f)>>HI_SDS_TYPE_BITS)
static inline size_t sdslen(const sds s) { static inline size_t hi_sdslen(const hisds s) {
unsigned char flags = s[-1]; unsigned char flags = s[-1];
switch(__builtin_expect((flags&SDS_TYPE_MASK), SDS_TYPE_5)) { switch(__builtin_expect((flags&HI_SDS_TYPE_MASK), HI_SDS_TYPE_5)) {
case SDS_TYPE_5: case HI_SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags); return HI_SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8: case HI_SDS_TYPE_8:
return SDS_HDR(8,s)->len; return HI_SDS_HDR(8,s)->len;
case SDS_TYPE_16: case HI_SDS_TYPE_16:
return SDS_HDR(16,s)->len; return HI_SDS_HDR(16,s)->len;
case SDS_TYPE_32: case HI_SDS_TYPE_32:
return SDS_HDR(32,s)->len; return HI_SDS_HDR(32,s)->len;
case SDS_TYPE_64: case HI_SDS_TYPE_64:
return SDS_HDR(64,s)->len; return HI_SDS_HDR(64,s)->len;
} }
return 0; return 0;
} }
static inline size_t sdsavail(const sds s) { static inline size_t hi_sdsavail(const hisds s) {
unsigned char flags = s[-1]; unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) { switch(flags&HI_SDS_TYPE_MASK) {
case SDS_TYPE_5: { case HI_SDS_TYPE_5: {
return 0; return 0;
} }
case SDS_TYPE_8: { case HI_SDS_TYPE_8: {
SDS_HDR_VAR(8,s); HI_SDS_HDR_VAR(8,s);
return sh->alloc - sh->len; return sh->alloc - sh->len;
} }
case SDS_TYPE_16: { case HI_SDS_TYPE_16: {
SDS_HDR_VAR(16,s); HI_SDS_HDR_VAR(16,s);
return sh->alloc - sh->len; return sh->alloc - sh->len;
} }
case SDS_TYPE_32: { case HI_SDS_TYPE_32: {
SDS_HDR_VAR(32,s); HI_SDS_HDR_VAR(32,s);
return sh->alloc - sh->len; return sh->alloc - sh->len;
} }
case SDS_TYPE_64: { case HI_SDS_TYPE_64: {
SDS_HDR_VAR(64,s); HI_SDS_HDR_VAR(64,s);
return sh->alloc - sh->len; return sh->alloc - sh->len;
} }
} }
return 0; return 0;
} }
static inline void sdssetlen(sds s, size_t newlen) { static inline void hi_sdssetlen(hisds s, size_t newlen) {
unsigned char flags = s[-1]; unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) { switch(flags&HI_SDS_TYPE_MASK) {
case SDS_TYPE_5: case HI_SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; unsigned char *fp = ((unsigned char*)s)-1;
*fp = (unsigned char)(SDS_TYPE_5 | (newlen << SDS_TYPE_BITS)); *fp = (unsigned char)(HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS));
} }
break; break;
case SDS_TYPE_8: case HI_SDS_TYPE_8:
SDS_HDR(8,s)->len = (uint8_t)newlen; HI_SDS_HDR(8,s)->len = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case HI_SDS_TYPE_16:
SDS_HDR(16,s)->len = (uint16_t)newlen; HI_SDS_HDR(16,s)->len = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case HI_SDS_TYPE_32:
SDS_HDR(32,s)->len = (uint32_t)newlen; HI_SDS_HDR(32,s)->len = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case HI_SDS_TYPE_64:
SDS_HDR(64,s)->len = (uint64_t)newlen; HI_SDS_HDR(64,s)->len = (uint64_t)newlen;
break; break;
} }
} }
static inline void sdsinclen(sds s, size_t inc) { static inline void hi_sdsinclen(hisds s, size_t inc) {
unsigned char flags = s[-1]; unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) { switch(flags&HI_SDS_TYPE_MASK) {
case SDS_TYPE_5: case HI_SDS_TYPE_5:
{ {
unsigned char *fp = ((unsigned char*)s)-1; unsigned char *fp = ((unsigned char*)s)-1;
unsigned char newlen = SDS_TYPE_5_LEN(flags)+(unsigned char)inc; unsigned char newlen = HI_SDS_TYPE_5_LEN(flags)+(unsigned char)inc;
*fp = SDS_TYPE_5 | (newlen << SDS_TYPE_BITS); *fp = HI_SDS_TYPE_5 | (newlen << HI_SDS_TYPE_BITS);
} }
break; break;
case SDS_TYPE_8: case HI_SDS_TYPE_8:
SDS_HDR(8,s)->len += (uint8_t)inc; HI_SDS_HDR(8,s)->len += (uint8_t)inc;
break; break;
case SDS_TYPE_16: case HI_SDS_TYPE_16:
SDS_HDR(16,s)->len += (uint16_t)inc; HI_SDS_HDR(16,s)->len += (uint16_t)inc;
break; break;
case SDS_TYPE_32: case HI_SDS_TYPE_32:
SDS_HDR(32,s)->len += (uint32_t)inc; HI_SDS_HDR(32,s)->len += (uint32_t)inc;
break; break;
case SDS_TYPE_64: case HI_SDS_TYPE_64:
SDS_HDR(64,s)->len += (uint64_t)inc; HI_SDS_HDR(64,s)->len += (uint64_t)inc;
break; break;
} }
} }
/* sdsalloc() = sdsavail() + sdslen() */ /* hi_sdsalloc() = hi_sdsavail() + hi_sdslen() */
static inline size_t sdsalloc(const sds s) { static inline size_t hi_sdsalloc(const hisds s) {
unsigned char flags = s[-1]; unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) { switch(flags & HI_SDS_TYPE_MASK) {
case SDS_TYPE_5: case HI_SDS_TYPE_5:
return SDS_TYPE_5_LEN(flags); return HI_SDS_TYPE_5_LEN(flags);
case SDS_TYPE_8: case HI_SDS_TYPE_8:
return SDS_HDR(8,s)->alloc; return HI_SDS_HDR(8,s)->alloc;
case SDS_TYPE_16: case HI_SDS_TYPE_16:
return SDS_HDR(16,s)->alloc; return HI_SDS_HDR(16,s)->alloc;
case SDS_TYPE_32: case HI_SDS_TYPE_32:
return SDS_HDR(32,s)->alloc; return HI_SDS_HDR(32,s)->alloc;
case SDS_TYPE_64: case HI_SDS_TYPE_64:
return SDS_HDR(64,s)->alloc; return HI_SDS_HDR(64,s)->alloc;
} }
return 0; return 0;
} }
static inline void sdssetalloc(sds s, size_t newlen) { static inline void hi_sdssetalloc(hisds s, size_t newlen) {
unsigned char flags = s[-1]; unsigned char flags = s[-1];
switch(flags&SDS_TYPE_MASK) { switch(flags&HI_SDS_TYPE_MASK) {
case SDS_TYPE_5: case HI_SDS_TYPE_5:
/* Nothing to do, this type has no total allocation info. */ /* Nothing to do, this type has no total allocation info. */
break; break;
case SDS_TYPE_8: case HI_SDS_TYPE_8:
SDS_HDR(8,s)->alloc = (uint8_t)newlen; HI_SDS_HDR(8,s)->alloc = (uint8_t)newlen;
break; break;
case SDS_TYPE_16: case HI_SDS_TYPE_16:
SDS_HDR(16,s)->alloc = (uint16_t)newlen; HI_SDS_HDR(16,s)->alloc = (uint16_t)newlen;
break; break;
case SDS_TYPE_32: case HI_SDS_TYPE_32:
SDS_HDR(32,s)->alloc = (uint32_t)newlen; HI_SDS_HDR(32,s)->alloc = (uint32_t)newlen;
break; break;
case SDS_TYPE_64: case HI_SDS_TYPE_64:
SDS_HDR(64,s)->alloc = (uint64_t)newlen; HI_SDS_HDR(64,s)->alloc = (uint64_t)newlen;
break; break;
} }
} }
sds sdsnewlen(const void *init, size_t initlen); hisds hi_sdsnewlen(const void *init, size_t initlen);
sds sdsnew(const char *init); hisds hi_sdsnew(const char *init);
sds sdsempty(void); hisds hi_sdsempty(void);
sds sdsdup(const sds s); hisds hi_sdsdup(const hisds s);
void sdsfree(sds s); void hi_sdsfree(hisds s);
sds sdsgrowzero(sds s, size_t len); hisds hi_sdsgrowzero(hisds s, size_t len);
sds sdscatlen(sds s, const void *t, size_t len); hisds hi_sdscatlen(hisds s, const void *t, size_t len);
sds sdscat(sds s, const char *t); hisds hi_sdscat(hisds s, const char *t);
sds sdscatsds(sds s, const sds t); hisds hi_sdscatsds(hisds s, const hisds t);
sds sdscpylen(sds s, const char *t, size_t len); hisds hi_sdscpylen(hisds s, const char *t, size_t len);
sds sdscpy(sds s, const char *t); hisds hi_sdscpy(hisds s, const char *t);
sds sdscatvprintf(sds s, const char *fmt, va_list ap); hisds hi_sdscatvprintf(hisds s, const char *fmt, va_list ap);
#ifdef __GNUC__ #ifdef __GNUC__
sds sdscatprintf(sds s, const char *fmt, ...) hisds hi_sdscatprintf(hisds s, const char *fmt, ...)
__attribute__((format(printf, 2, 3))); __attribute__((format(printf, 2, 3)));
#else #else
sds sdscatprintf(sds s, const char *fmt, ...); hisds hi_sdscatprintf(hisds s, const char *fmt, ...);
#endif #endif
sds sdscatfmt(sds s, char const *fmt, ...); hisds hi_sdscatfmt(hisds s, char const *fmt, ...);
sds sdstrim(sds s, const char *cset); hisds hi_sdstrim(hisds s, const char *cset);
void sdsrange(sds s, int start, int end); int hi_sdsrange(hisds s, ssize_t start, ssize_t end);
void sdsupdatelen(sds s); void hi_sdsupdatelen(hisds s);
void sdsclear(sds s); void hi_sdsclear(hisds s);
int sdscmp(const sds s1, const sds s2); int hi_sdscmp(const hisds s1, const hisds s2);
sds *sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count); hisds *hi_sdssplitlen(const char *s, int len, const char *sep, int seplen, int *count);
void sdsfreesplitres(sds *tokens, int count); void hi_sdsfreesplitres(hisds *tokens, int count);
void sdstolower(sds s); void hi_sdstolower(hisds s);
void sdstoupper(sds s); void hi_sdstoupper(hisds s);
sds sdsfromlonglong(long long value); hisds hi_sdsfromlonglong(long long value);
sds sdscatrepr(sds s, const char *p, size_t len); hisds hi_sdscatrepr(hisds s, const char *p, size_t len);
sds *sdssplitargs(const char *line, int *argc); hisds *hi_sdssplitargs(const char *line, int *argc);
sds sdsmapchars(sds s, const char *from, const char *to, size_t setlen); hisds hi_sdsmapchars(hisds s, const char *from, const char *to, size_t setlen);
sds sdsjoin(char **argv, int argc, char *sep); hisds hi_sdsjoin(char **argv, int argc, char *sep);
sds sdsjoinsds(sds *argv, int argc, const char *sep, size_t seplen); hisds hi_sdsjoinsds(hisds *argv, int argc, const char *sep, size_t seplen);
/* Low level functions exposed to the user API */ /* Low level functions exposed to the user API */
sds sdsMakeRoomFor(sds s, size_t addlen); hisds hi_sdsMakeRoomFor(hisds s, size_t addlen);
void sdsIncrLen(sds s, int incr); void hi_sdsIncrLen(hisds s, int incr);
sds sdsRemoveFreeSpace(sds s); hisds hi_sdsRemoveFreeSpace(hisds s);
size_t sdsAllocSize(sds s); size_t hi_sdsAllocSize(hisds s);
void *sdsAllocPtr(sds s); void *hi_sdsAllocPtr(hisds s);
/* Export the allocator used by SDS to the program using SDS. /* Export the allocator used by SDS to the program using SDS.
* Sometimes the program SDS is linked to, may use a different set of * Sometimes the program SDS is linked to, may use a different set of
* allocators, but may want to allocate or free things that SDS will * allocators, but may want to allocate or free things that SDS will
* respectively free or allocate. */ * respectively free or allocate. */
void *sds_malloc(size_t size); void *hi_sds_malloc(size_t size);
void *sds_realloc(void *ptr, size_t size); void *hi_sds_realloc(void *ptr, size_t size);
void sds_free(void *ptr); void hi_sds_free(void *ptr);
#ifdef REDIS_TEST #ifdef REDIS_TEST
int sdsTest(int argc, char *argv[]); int hi_sdsTest(int argc, char *argv[]);
#endif #endif
#endif #endif /* HIREDIS_SDS_H */

View File

@ -37,6 +37,8 @@
* the include of your alternate allocator if needed (not needed in order * the include of your alternate allocator if needed (not needed in order
* to use the default libc allocator). */ * to use the default libc allocator). */
#define s_malloc malloc #include "alloc.h"
#define s_realloc realloc
#define s_free free #define hi_s_malloc hi_malloc
#define hi_s_realloc hi_realloc
#define hi_s_free hi_free

94
deps/hiredis/sdscompat.h vendored Normal file
View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2020, Michael Grunder <michael dot grunder at gmail dot com>
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
/*
* SDS compatibility header.
*
* This simple file maps sds types and calls to their unique hiredis symbol names.
* It's useful when we build Hiredis as a dependency of Redis and want to call
* Hiredis' sds symbols rather than the ones built into Redis, as the libraries
* have slightly diverged and could cause hard to track down ABI incompatibility
* bugs.
*
*/
#ifndef HIREDIS_SDS_COMPAT
#define HIREDIS_SDS_COMPAT
#define sds hisds
#define sdslen hi_sdslen
#define sdsavail hi_sdsavail
#define sdssetlen hi_sdssetlen
#define sdsinclen hi_sdsinclen
#define sdsalloc hi_sdsalloc
#define sdssetalloc hi_sdssetalloc
#define sdsAllocPtr hi_sdsAllocPtr
#define sdsAllocSize hi_sdsAllocSize
#define sdscat hi_sdscat
#define sdscatfmt hi_sdscatfmt
#define sdscatlen hi_sdscatlen
#define sdscatprintf hi_sdscatprintf
#define sdscatrepr hi_sdscatrepr
#define sdscatsds hi_sdscatsds
#define sdscatvprintf hi_sdscatvprintf
#define sdsclear hi_sdsclear
#define sdscmp hi_sdscmp
#define sdscpy hi_sdscpy
#define sdscpylen hi_sdscpylen
#define sdsdup hi_sdsdup
#define sdsempty hi_sdsempty
#define sds_free hi_sds_free
#define sdsfree hi_sdsfree
#define sdsfreesplitres hi_sdsfreesplitres
#define sdsfromlonglong hi_sdsfromlonglong
#define sdsgrowzero hi_sdsgrowzero
#define sdsIncrLen hi_sdsIncrLen
#define sdsjoin hi_sdsjoin
#define sdsjoinsds hi_sdsjoinsds
#define sdsll2str hi_sdsll2str
#define sdsMakeRoomFor hi_sdsMakeRoomFor
#define sds_malloc hi_sds_malloc
#define sdsmapchars hi_sdsmapchars
#define sdsnew hi_sdsnew
#define sdsnewlen hi_sdsnewlen
#define sdsrange hi_sdsrange
#define sds_realloc hi_sds_realloc
#define sdsRemoveFreeSpace hi_sdsRemoveFreeSpace
#define sdssplitargs hi_sdssplitargs
#define sdssplitlen hi_sdssplitlen
#define sdstolower hi_sdstolower
#define sdstoupper hi_sdstoupper
#define sdstrim hi_sdstrim
#define sdsull2str hi_sdsull2str
#define sdsupdatelen hi_sdsupdatelen
#endif /* HIREDIS_SDS_COMPAT */

View File

@ -212,7 +212,7 @@ int win32_getsockopt(SOCKET sockfd, int level, int optname, void *optval, sockle
int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) { int win32_setsockopt(SOCKET sockfd, int level, int optname, const void *optval, socklen_t optlen) {
int ret = 0; int ret = 0;
if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) { if ((level == SOL_SOCKET) && ((optname == SO_RCVTIMEO) || (optname == SO_SNDTIMEO))) {
struct timeval *tv = optval; const struct timeval *tv = optval;
DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000; DWORD timeout = tv->tv_sec * 1000 + tv->tv_usec / 1000;
ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD)); ret = setsockopt(sockfd, level, optname, (const char*)&timeout, sizeof(DWORD));
} else { } else {

View File

@ -49,9 +49,10 @@
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#include <stddef.h> #include <stddef.h>
#include <errno.h>
#ifdef _MSC_VER #ifdef _MSC_VER
typedef signed long ssize_t; typedef long long ssize_t;
#endif #endif
/* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */ /* Emulate the parts of the BSD socket API that we need (override the winsock signatures). */

318
deps/hiredis/ssl.c vendored
View File

@ -34,25 +34,33 @@
#include "async.h" #include "async.h"
#include <assert.h> #include <assert.h>
#include <pthread.h>
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <pthread.h>
#endif
#include <openssl/ssl.h> #include <openssl/ssl.h>
#include <openssl/err.h> #include <openssl/err.h>
#include "win32.h"
#include "async_private.h" #include "async_private.h"
#include "hiredis_ssl.h"
void __redisSetError(redisContext *c, int type, const char *str); void __redisSetError(redisContext *c, int type, const char *str);
/* The SSL context is attached to SSL/TLS connections as a privdata. */ struct redisSSLContext {
typedef struct redisSSLContext { /* Associated OpenSSL SSL_CTX as created by redisCreateSSLContext() */
/**
* OpenSSL SSL_CTX; It is optional and will not be set when using
* user-supplied SSL.
*/
SSL_CTX *ssl_ctx; SSL_CTX *ssl_ctx;
/* Requested SNI, or NULL */
char *server_name;
};
/* The SSL connection context is attached to SSL/TLS connections as a privdata. */
typedef struct redisSSL {
/** /**
* OpenSSL SSL object. * OpenSSL SSL object.
*/ */
@ -72,43 +80,11 @@ typedef struct redisSSLContext {
* should resume whenever a read takes place, if possible * should resume whenever a read takes place, if possible
*/ */
int pendingWrite; int pendingWrite;
} redisSSLContext; } redisSSL;
/* Forward declaration */ /* Forward declaration */
redisContextFuncs redisContextSSLFuncs; redisContextFuncs redisContextSSLFuncs;
#ifdef HIREDIS_SSL_TRACE
/**
* Callback used for debugging
*/
static void sslLogCallback(const SSL *ssl, int where, int ret) {
const char *retstr = "";
int should_log = 1;
/* Ignore low-level SSL stuff */
if (where & SSL_CB_ALERT) {
should_log = 1;
}
if (where == SSL_CB_HANDSHAKE_START || where == SSL_CB_HANDSHAKE_DONE) {
should_log = 1;
}
if ((where & SSL_CB_EXIT) && ret == 0) {
should_log = 1;
}
if (!should_log) {
return;
}
retstr = SSL_alert_type_string(ret);
printf("ST(0x%x). %s. R(0x%x)%s\n", where, SSL_state_string_long(ssl), ret, retstr);
if (where == SSL_CB_HANDSHAKE_DONE) {
printf("Using SSL version %s. Cipher=%s\n", SSL_get_version(ssl), SSL_get_cipher_name(ssl));
}
}
#endif
/** /**
* OpenSSL global initialization and locking handling callbacks. * OpenSSL global initialization and locking handling callbacks.
* Note that this is only required for OpenSSL < 1.1.0. * Note that this is only required for OpenSSL < 1.1.0.
@ -119,6 +95,18 @@ static void sslLogCallback(const SSL *ssl, int where, int ret) {
#endif #endif
#ifdef HIREDIS_USE_CRYPTO_LOCKS #ifdef HIREDIS_USE_CRYPTO_LOCKS
#ifdef _WIN32
typedef CRITICAL_SECTION sslLockType;
static void sslLockInit(sslLockType* l) {
InitializeCriticalSection(l);
}
static void sslLockAcquire(sslLockType* l) {
EnterCriticalSection(l);
}
static void sslLockRelease(sslLockType* l) {
LeaveCriticalSection(l);
}
#else
typedef pthread_mutex_t sslLockType; typedef pthread_mutex_t sslLockType;
static void sslLockInit(sslLockType *l) { static void sslLockInit(sslLockType *l) {
pthread_mutex_init(l, NULL); pthread_mutex_init(l, NULL);
@ -129,7 +117,9 @@ static void sslLockAcquire(sslLockType *l) {
static void sslLockRelease(sslLockType *l) { static void sslLockRelease(sslLockType *l) {
pthread_mutex_unlock(l); pthread_mutex_unlock(l);
} }
static pthread_mutex_t *ossl_locks; #endif
static sslLockType* ossl_locks;
static void opensslDoLock(int mode, int lkid, const char *f, int line) { static void opensslDoLock(int mode, int lkid, const char *f, int line) {
sslLockType *l = ossl_locks + lkid; sslLockType *l = ossl_locks + lkid;
@ -144,36 +134,151 @@ static void opensslDoLock(int mode, int lkid, const char *f, int line) {
(void)line; (void)line;
} }
static void initOpensslLocks(void) { static int initOpensslLocks(void) {
unsigned ii, nlocks; unsigned ii, nlocks;
if (CRYPTO_get_locking_callback() != NULL) { if (CRYPTO_get_locking_callback() != NULL) {
/* Someone already set the callback before us. Don't destroy it! */ /* Someone already set the callback before us. Don't destroy it! */
return; return REDIS_OK;
} }
nlocks = CRYPTO_num_locks(); nlocks = CRYPTO_num_locks();
ossl_locks = malloc(sizeof(*ossl_locks) * nlocks); ossl_locks = hi_malloc(sizeof(*ossl_locks) * nlocks);
if (ossl_locks == NULL)
return REDIS_ERR;
for (ii = 0; ii < nlocks; ii++) { for (ii = 0; ii < nlocks; ii++) {
sslLockInit(ossl_locks + ii); sslLockInit(ossl_locks + ii);
} }
CRYPTO_set_locking_callback(opensslDoLock); CRYPTO_set_locking_callback(opensslDoLock);
return REDIS_OK;
} }
#endif /* HIREDIS_USE_CRYPTO_LOCKS */ #endif /* HIREDIS_USE_CRYPTO_LOCKS */
int redisInitOpenSSL(void)
{
SSL_library_init();
#ifdef HIREDIS_USE_CRYPTO_LOCKS
initOpensslLocks();
#endif
return REDIS_OK;
}
/**
* redisSSLContext helper context destruction.
*/
const char *redisSSLContextGetError(redisSSLContextError error)
{
switch (error) {
case REDIS_SSL_CTX_NONE:
return "No Error";
case REDIS_SSL_CTX_CREATE_FAILED:
return "Failed to create OpenSSL SSL_CTX";
case REDIS_SSL_CTX_CERT_KEY_REQUIRED:
return "Client cert and key must both be specified or skipped";
case REDIS_SSL_CTX_CA_CERT_LOAD_FAILED:
return "Failed to load CA Certificate or CA Path";
case REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED:
return "Failed to load client certificate";
case REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED:
return "Failed to load private key";
default:
return "Unknown error code";
}
}
void redisFreeSSLContext(redisSSLContext *ctx)
{
if (!ctx)
return;
if (ctx->server_name) {
hi_free(ctx->server_name);
ctx->server_name = NULL;
}
if (ctx->ssl_ctx) {
SSL_CTX_free(ctx->ssl_ctx);
ctx->ssl_ctx = NULL;
}
hi_free(ctx);
}
/**
* redisSSLContext helper context initialization.
*/
redisSSLContext *redisCreateSSLContext(const char *cacert_filename, const char *capath,
const char *cert_filename, const char *private_key_filename,
const char *server_name, redisSSLContextError *error)
{
redisSSLContext *ctx = hi_calloc(1, sizeof(redisSSLContext));
if (ctx == NULL)
goto error;
ctx->ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (!ctx->ssl_ctx) {
if (error) *error = REDIS_SSL_CTX_CREATE_FAILED;
goto error;
}
SSL_CTX_set_options(ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ctx->ssl_ctx, SSL_VERIFY_PEER, NULL);
if ((cert_filename != NULL && private_key_filename == NULL) ||
(private_key_filename != NULL && cert_filename == NULL)) {
if (error) *error = REDIS_SSL_CTX_CERT_KEY_REQUIRED;
goto error;
}
if (capath || cacert_filename) {
if (!SSL_CTX_load_verify_locations(ctx->ssl_ctx, cacert_filename, capath)) {
if (error) *error = REDIS_SSL_CTX_CA_CERT_LOAD_FAILED;
goto error;
}
}
if (cert_filename) {
if (!SSL_CTX_use_certificate_chain_file(ctx->ssl_ctx, cert_filename)) {
if (error) *error = REDIS_SSL_CTX_CLIENT_CERT_LOAD_FAILED;
goto error;
}
if (!SSL_CTX_use_PrivateKey_file(ctx->ssl_ctx, private_key_filename, SSL_FILETYPE_PEM)) {
if (error) *error = REDIS_SSL_CTX_PRIVATE_KEY_LOAD_FAILED;
goto error;
}
}
if (server_name)
ctx->server_name = hi_strdup(server_name);
return ctx;
error:
redisFreeSSLContext(ctx);
return NULL;
}
/** /**
* SSL Connection initialization. * SSL Connection initialization.
*/ */
static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
if (c->privdata) { static int redisSSLConnect(redisContext *c, SSL *ssl) {
if (c->privctx) {
__redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated"); __redisSetError(c, REDIS_ERR_OTHER, "redisContext was already associated");
return REDIS_ERR; return REDIS_ERR;
} }
c->privdata = calloc(1, sizeof(redisSSLContext));
redisSSL *rssl = hi_calloc(1, sizeof(redisSSL));
if (rssl == NULL) {
__redisSetError(c, REDIS_ERR_OOM, "Out of memory");
return REDIS_ERR;
}
c->funcs = &redisContextSSLFuncs; c->funcs = &redisContextSSLFuncs;
redisSSLContext *rssl = c->privdata;
rssl->ssl_ctx = ssl_ctx;
rssl->ssl = ssl; rssl->ssl = ssl;
SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); SSL_set_mode(rssl->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER);
@ -183,12 +288,14 @@ static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
ERR_clear_error(); ERR_clear_error();
int rv = SSL_connect(rssl->ssl); int rv = SSL_connect(rssl->ssl);
if (rv == 1) { if (rv == 1) {
c->privctx = rssl;
return REDIS_OK; return REDIS_OK;
} }
rv = SSL_get_error(rssl->ssl, rv); rv = SSL_get_error(rssl->ssl, rv);
if (((c->flags & REDIS_BLOCK) == 0) && if (((c->flags & REDIS_BLOCK) == 0) &&
(rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) { (rv == SSL_ERROR_WANT_READ || rv == SSL_ERROR_WANT_WRITE)) {
c->privctx = rssl;
return REDIS_OK; return REDIS_OK;
} }
@ -203,83 +310,58 @@ static int redisSSLConnect(redisContext *c, SSL_CTX *ssl_ctx, SSL *ssl) {
} }
__redisSetError(c, REDIS_ERR_IO, err); __redisSetError(c, REDIS_ERR_IO, err);
} }
hi_free(rssl);
return REDIS_ERR; return REDIS_ERR;
} }
/**
* A wrapper around redisSSLConnect() for users who manage their own context and
* create their own SSL object.
*/
int redisInitiateSSL(redisContext *c, SSL *ssl) { int redisInitiateSSL(redisContext *c, SSL *ssl) {
return redisSSLConnect(c, NULL, ssl); return redisSSLConnect(c, ssl);
} }
int redisSecureConnection(redisContext *c, const char *capath, /**
const char *certpath, const char *keypath, const char *servername) { * A wrapper around redisSSLConnect() for users who use redisSSLContext and don't
* manage their own SSL objects.
*/
SSL_CTX *ssl_ctx = NULL; int redisInitiateSSLWithContext(redisContext *c, redisSSLContext *redis_ssl_ctx)
SSL *ssl = NULL; {
if (!c || !redis_ssl_ctx)
return REDIS_ERR;
/* Initialize global OpenSSL stuff */ /* We want to verify that redisSSLConnect() won't fail on this, as it will
static int isInit = 0; * not own the SSL object in that case and we'll end up leaking.
if (!isInit) { */
isInit = 1; if (c->privctx)
SSL_library_init(); return REDIS_ERR;
#ifdef HIREDIS_USE_CRYPTO_LOCKS
initOpensslLocks();
#endif
}
ssl_ctx = SSL_CTX_new(SSLv23_client_method()); SSL *ssl = SSL_new(redis_ssl_ctx->ssl_ctx);
if (!ssl_ctx) {
__redisSetError(c, REDIS_ERR_OTHER, "Failed to create SSL_CTX");
goto error;
}
#ifdef HIREDIS_SSL_TRACE
SSL_CTX_set_info_callback(ssl_ctx, sslLogCallback);
#endif
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER, NULL);
if ((certpath != NULL && keypath == NULL) || (keypath != NULL && certpath == NULL)) {
__redisSetError(c, REDIS_ERR_OTHER, "certpath and keypath must be specified together");
goto error;
}
if (capath) {
if (!SSL_CTX_load_verify_locations(ssl_ctx, capath, NULL)) {
__redisSetError(c, REDIS_ERR_OTHER, "Invalid CA certificate");
goto error;
}
}
if (certpath) {
if (!SSL_CTX_use_certificate_chain_file(ssl_ctx, certpath)) {
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client certificate");
goto error;
}
if (!SSL_CTX_use_PrivateKey_file(ssl_ctx, keypath, SSL_FILETYPE_PEM)) {
__redisSetError(c, REDIS_ERR_OTHER, "Invalid client key");
goto error;
}
}
ssl = SSL_new(ssl_ctx);
if (!ssl) { if (!ssl) {
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance"); __redisSetError(c, REDIS_ERR_OTHER, "Couldn't create new SSL instance");
goto error; goto error;
} }
if (servername) {
if (!SSL_set_tlsext_host_name(ssl, servername)) { if (redis_ssl_ctx->server_name) {
__redisSetError(c, REDIS_ERR_OTHER, "Couldn't set server name indication"); if (!SSL_set_tlsext_host_name(ssl, redis_ssl_ctx->server_name)) {
__redisSetError(c, REDIS_ERR_OTHER, "Failed to set server_name/SNI");
goto error; goto error;
} }
} }
return redisSSLConnect(c, ssl_ctx, ssl); return redisSSLConnect(c, ssl);
error: error:
if (ssl) SSL_free(ssl); if (ssl)
if (ssl_ctx) SSL_CTX_free(ssl_ctx); SSL_free(ssl);
return REDIS_ERR; return REDIS_ERR;
} }
static int maybeCheckWant(redisSSLContext *rssl, int rv) { static int maybeCheckWant(redisSSL *rssl, int rv) {
/** /**
* If the error is WANT_READ or WANT_WRITE, the appropriate flags are set * If the error is WANT_READ or WANT_WRITE, the appropriate flags are set
* and true is returned. False is returned otherwise * and true is returned. False is returned otherwise
@ -299,23 +381,19 @@ static int maybeCheckWant(redisSSLContext *rssl, int rv) {
* Implementation of redisContextFuncs for SSL connections. * Implementation of redisContextFuncs for SSL connections.
*/ */
static void redisSSLFreeContext(void *privdata){ static void redisSSLFree(void *privctx){
redisSSLContext *rsc = privdata; redisSSL *rsc = privctx;
if (!rsc) return; if (!rsc) return;
if (rsc->ssl) { if (rsc->ssl) {
SSL_free(rsc->ssl); SSL_free(rsc->ssl);
rsc->ssl = NULL; rsc->ssl = NULL;
} }
if (rsc->ssl_ctx) { hi_free(rsc);
SSL_CTX_free(rsc->ssl_ctx);
rsc->ssl_ctx = NULL;
}
free(rsc);
} }
static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) { static ssize_t redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
redisSSLContext *rssl = c->privdata; redisSSL *rssl = c->privctx;
int nread = SSL_read(rssl->ssl, buf, bufcap); int nread = SSL_read(rssl->ssl, buf, bufcap);
if (nread > 0) { if (nread > 0) {
@ -356,10 +434,10 @@ static int redisSSLRead(redisContext *c, char *buf, size_t bufcap) {
} }
} }
static int redisSSLWrite(redisContext *c) { static ssize_t redisSSLWrite(redisContext *c) {
redisSSLContext *rssl = c->privdata; redisSSL *rssl = c->privctx;
size_t len = rssl->lastLen ? rssl->lastLen : sdslen(c->obuf); size_t len = rssl->lastLen ? rssl->lastLen : hi_sdslen(c->obuf);
int rv = SSL_write(rssl->ssl, c->obuf, len); int rv = SSL_write(rssl->ssl, c->obuf, len);
if (rv > 0) { if (rv > 0) {
@ -380,7 +458,7 @@ static int redisSSLWrite(redisContext *c) {
static void redisSSLAsyncRead(redisAsyncContext *ac) { static void redisSSLAsyncRead(redisAsyncContext *ac) {
int rv; int rv;
redisSSLContext *rssl = ac->c.privdata; redisSSL *rssl = ac->c.privctx;
redisContext *c = &ac->c; redisContext *c = &ac->c;
rssl->wantRead = 0; rssl->wantRead = 0;
@ -410,7 +488,7 @@ static void redisSSLAsyncRead(redisAsyncContext *ac) {
static void redisSSLAsyncWrite(redisAsyncContext *ac) { static void redisSSLAsyncWrite(redisAsyncContext *ac) {
int rv, done = 0; int rv, done = 0;
redisSSLContext *rssl = ac->c.privdata; redisSSL *rssl = ac->c.privctx;
redisContext *c = &ac->c; redisContext *c = &ac->c;
rssl->pendingWrite = 0; rssl->pendingWrite = 0;
@ -439,7 +517,7 @@ static void redisSSLAsyncWrite(redisAsyncContext *ac) {
} }
redisContextFuncs redisContextSSLFuncs = { redisContextFuncs redisContextSSLFuncs = {
.free_privdata = redisSSLFreeContext, .free_privctx = redisSSLFree,
.async_read = redisSSLAsyncRead, .async_read = redisSSLAsyncRead,
.async_write = redisSSLAsyncWrite, .async_write = redisSSLAsyncWrite,
.read = redisSSLRead, .read = redisSSLRead,

553
deps/hiredis/test.c vendored
View File

@ -1,22 +1,24 @@
#include "fmacros.h" #include "fmacros.h"
#include "sockcompat.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#ifndef _WIN32
#include <strings.h> #include <strings.h>
#include <sys/socket.h>
#include <sys/time.h> #include <sys/time.h>
#include <netdb.h> #endif
#include <assert.h> #include <assert.h>
#include <unistd.h>
#include <signal.h> #include <signal.h>
#include <errno.h> #include <errno.h>
#include <limits.h> #include <limits.h>
#include "hiredis.h" #include "hiredis.h"
#include "async.h"
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
#include "hiredis_ssl.h" #include "hiredis_ssl.h"
#endif #endif
#include "net.h" #include "net.h"
#include "win32.h"
enum connection_type { enum connection_type {
CONN_TCP, CONN_TCP,
@ -47,15 +49,35 @@ struct config {
} ssl; } ssl;
}; };
struct privdata {
int dtor_counter;
};
struct pushCounters {
int nil;
int str;
};
#ifdef HIREDIS_TEST_SSL
redisSSLContext *_ssl_ctx = NULL;
#endif
/* The following lines make up our testing "framework" :) */ /* The following lines make up our testing "framework" :) */
static int tests = 0, fails = 0; static int tests = 0, fails = 0, skips = 0;
#define test(_s) { printf("#%02d ", ++tests); printf(_s); } #define test(_s) { printf("#%02d ", ++tests); printf(_s); }
#define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;} #define test_cond(_c) if(_c) printf("\033[0;32mPASSED\033[0;0m\n"); else {printf("\033[0;31mFAILED\033[0;0m\n"); fails++;}
#define test_skipped() { printf("\033[01;33mSKIPPED\033[0;0m\n"); skips++; }
static long long usec(void) { static long long usec(void) {
#ifndef _MSC_VER
struct timeval tv; struct timeval tv;
gettimeofday(&tv,NULL); gettimeofday(&tv,NULL);
return (((long long)tv.tv_sec)*1000000)+tv.tv_usec; return (((long long)tv.tv_sec)*1000000)+tv.tv_usec;
#else
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
return (((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime) / 10;
#endif
} }
/* The assert() calls below have side effects, so we need assert() /* The assert() calls below have side effects, so we need assert()
@ -65,6 +87,43 @@ static long long usec(void) {
#define assert(e) (void)(e) #define assert(e) (void)(e)
#endif #endif
/* Helper to extract Redis version information. Aborts on any failure. */
#define REDIS_VERSION_FIELD "redis_version:"
void get_redis_version(redisContext *c, int *majorptr, int *minorptr) {
redisReply *reply;
char *eptr, *s, *e;
int major, minor;
reply = redisCommand(c, "INFO");
if (reply == NULL || c->err || reply->type != REDIS_REPLY_STRING)
goto abort;
if ((s = strstr(reply->str, REDIS_VERSION_FIELD)) == NULL)
goto abort;
s += strlen(REDIS_VERSION_FIELD);
/* We need a field terminator and at least 'x.y.z' (5) bytes of data */
if ((e = strstr(s, "\r\n")) == NULL || (e - s) < 5)
goto abort;
/* Extract version info */
major = strtol(s, &eptr, 10);
if (*eptr != '.') goto abort;
minor = strtol(eptr+1, NULL, 10);
/* Push info the caller wants */
if (majorptr) *majorptr = major;
if (minorptr) *minorptr = minor;
freeReplyObject(reply);
return;
abort:
freeReplyObject(reply);
fprintf(stderr, "Error: Cannot determine Redis version, aborting\n");
exit(1);
}
static redisContext *select_database(redisContext *c) { static redisContext *select_database(redisContext *c) {
redisReply *reply; redisReply *reply;
@ -87,6 +146,26 @@ static redisContext *select_database(redisContext *c) {
return c; return c;
} }
/* Switch protocol */
static void send_hello(redisContext *c, int version) {
redisReply *reply;
int expected;
reply = redisCommand(c, "HELLO %d", version);
expected = version == 3 ? REDIS_REPLY_MAP : REDIS_REPLY_ARRAY;
assert(reply != NULL && reply->type == expected);
freeReplyObject(reply);
}
/* Togggle client tracking */
static void send_client_tracking(redisContext *c, const char *str) {
redisReply *reply;
reply = redisCommand(c, "CLIENT TRACKING %s", str);
assert(reply != NULL && reply->type == REDIS_REPLY_STATUS);
freeReplyObject(reply);
}
static int disconnect(redisContext *c, int keep_fd) { static int disconnect(redisContext *c, int keep_fd) {
redisReply *reply; redisReply *reply;
@ -105,9 +184,9 @@ static int disconnect(redisContext *c, int keep_fd) {
return -1; return -1;
} }
static void do_ssl_handshake(redisContext *c, struct config config) { static void do_ssl_handshake(redisContext *c) {
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
redisSecureConnection(c, config.ssl.ca_cert, config.ssl.cert, config.ssl.key, NULL); redisInitiateSSLWithContext(c, _ssl_ctx);
if (c->err) { if (c->err) {
printf("SSL error: %s\n", c->errstr); printf("SSL error: %s\n", c->errstr);
redisFree(c); redisFree(c);
@ -115,7 +194,6 @@ static void do_ssl_handshake(redisContext *c, struct config config) {
} }
#else #else
(void) c; (void) c;
(void) config;
#endif #endif
} }
@ -150,7 +228,7 @@ static redisContext *do_connect(struct config config) {
} }
if (config.type == CONN_SSL) { if (config.type == CONN_SSL) {
do_ssl_handshake(c, config); do_ssl_handshake(c);
} }
return select_database(c); return select_database(c);
@ -160,7 +238,7 @@ static void do_reconnect(redisContext *c, struct config config) {
redisReconnect(c); redisReconnect(c);
if (config.type == CONN_SSL) { if (config.type == CONN_SSL) {
do_ssl_handshake(c, config); do_ssl_handshake(c);
} }
} }
@ -172,43 +250,43 @@ static void test_format_commands(void) {
len = redisFormatCommand(&cmd,"SET foo bar"); len = redisFormatCommand(&cmd,"SET foo bar");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2)); len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd); hi_free(cmd);
test("Format command with %%s string interpolation: "); test("Format command with %%s string interpolation: ");
len = redisFormatCommand(&cmd,"SET %s %s","foo","bar"); len = redisFormatCommand(&cmd,"SET %s %s","foo","bar");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2)); len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd); hi_free(cmd);
test("Format command with %%s and an empty string: "); test("Format command with %%s and an empty string: ");
len = redisFormatCommand(&cmd,"SET %s %s","foo",""); len = redisFormatCommand(&cmd,"SET %s %s","foo","");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(0+2)); len == 4+4+(3+2)+4+(3+2)+4+(0+2));
free(cmd); hi_free(cmd);
test("Format command with an empty string in between proper interpolations: "); test("Format command with an empty string in between proper interpolations: ");
len = redisFormatCommand(&cmd,"SET %s %s","","foo"); len = redisFormatCommand(&cmd,"SET %s %s","","foo");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$0\r\n\r\n$3\r\nfoo\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(0+2)+4+(3+2)); len == 4+4+(3+2)+4+(0+2)+4+(3+2));
free(cmd); hi_free(cmd);
test("Format command with %%b string interpolation: "); test("Format command with %%b string interpolation: ");
len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3); len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"b\0r",(size_t)3);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nb\0r\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2)); len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd); hi_free(cmd);
test("Format command with %%b and an empty string: "); test("Format command with %%b and an empty string: ");
len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0); len = redisFormatCommand(&cmd,"SET %b %b","foo",(size_t)3,"",(size_t)0);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$0\r\n\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(0+2)); len == 4+4+(3+2)+4+(3+2)+4+(0+2));
free(cmd); hi_free(cmd);
test("Format command with literal %%: "); test("Format command with literal %%: ");
len = redisFormatCommand(&cmd,"SET %% %%"); len = redisFormatCommand(&cmd,"SET %% %%");
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$1\r\n%\r\n$1\r\n%\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(1+2)+4+(1+2)); len == 4+4+(3+2)+4+(1+2)+4+(1+2));
free(cmd); hi_free(cmd);
/* Vararg width depends on the type. These tests make sure that the /* Vararg width depends on the type. These tests make sure that the
* width is correctly determined using the format and subsequent varargs * width is correctly determined using the format and subsequent varargs
@ -219,7 +297,7 @@ static void test_format_commands(void) {
len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \ len = redisFormatCommand(&cmd,"key:%08" fmt " str:%s", value, "hello"); \
test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:00000123\r\n$9\r\nstr:hello\r\n",len) == 0 && \
len == 4+5+(12+2)+4+(9+2)); \ len == 4+5+(12+2)+4+(9+2)); \
free(cmd); \ hi_free(cmd); \
} while(0) } while(0)
#define FLOAT_WIDTH_TEST(type) do { \ #define FLOAT_WIDTH_TEST(type) do { \
@ -228,7 +306,7 @@ static void test_format_commands(void) {
len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \ len = redisFormatCommand(&cmd,"key:%08.3f str:%s", value, "hello"); \
test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \ test_cond(strncmp(cmd,"*2\r\n$12\r\nkey:0123.000\r\n$9\r\nstr:hello\r\n",len) == 0 && \
len == 4+5+(12+2)+4+(9+2)); \ len == 4+5+(12+2)+4+(9+2)); \
free(cmd); \ hi_free(cmd); \
} while(0) } while(0)
INTEGER_WIDTH_TEST("d", int); INTEGER_WIDTH_TEST("d", int);
@ -259,29 +337,29 @@ static void test_format_commands(void) {
len = redisFormatCommandArgv(&cmd,argc,argv,NULL); len = redisFormatCommandArgv(&cmd,argc,argv,NULL);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2)); len == 4+4+(3+2)+4+(3+2)+4+(3+2));
free(cmd); hi_free(cmd);
test("Format command by passing argc/argv with lengths: "); test("Format command by passing argc/argv with lengths: ");
len = redisFormatCommandArgv(&cmd,argc,argv,lens); len = redisFormatCommandArgv(&cmd,argc,argv,lens);
test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2)); len == 4+4+(3+2)+4+(7+2)+4+(3+2));
free(cmd); hi_free(cmd);
sds sds_cmd; hisds sds_cmd;
sds_cmd = sdsempty(); sds_cmd = NULL;
test("Format command into sds by passing argc/argv without lengths: "); test("Format command into hisds by passing argc/argv without lengths: ");
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL); len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,NULL);
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(3+2)+4+(3+2)); len == 4+4+(3+2)+4+(3+2)+4+(3+2));
sdsfree(sds_cmd); hi_sdsfree(sds_cmd);
sds_cmd = sdsempty(); sds_cmd = NULL;
test("Format command into sds by passing argc/argv with lengths: "); test("Format command into hisds by passing argc/argv with lengths: ");
len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens); len = redisFormatSdsCommandArgv(&sds_cmd,argc,argv,lens);
test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 && test_cond(strncmp(sds_cmd,"*3\r\n$3\r\nSET\r\n$7\r\nfoo\0xxx\r\n$3\r\nbar\r\n",len) == 0 &&
len == 4+4+(3+2)+4+(7+2)+4+(3+2)); len == 4+4+(3+2)+4+(7+2)+4+(3+2));
sdsfree(sds_cmd); hi_sdsfree(sds_cmd);
} }
static void test_append_formatted_commands(struct config config) { static void test_append_formatted_commands(struct config config) {
@ -300,7 +378,7 @@ static void test_append_formatted_commands(struct config config) {
assert(redisGetReply(c, (void*)&reply) == REDIS_OK); assert(redisGetReply(c, (void*)&reply) == REDIS_OK);
free(cmd); hi_free(cmd);
freeReplyObject(reply); freeReplyObject(reply);
disconnect(c, 0); disconnect(c, 0);
@ -308,7 +386,7 @@ static void test_append_formatted_commands(struct config config) {
static void test_reply_reader(void) { static void test_reply_reader(void) {
redisReader *reader; redisReader *reader;
void *reply; void *reply, *root;
int ret; int ret;
int i; int i;
@ -332,16 +410,26 @@ static void test_reply_reader(void) {
strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0); strcasecmp(reader->errstr,"Protocol error, got \"@\" as reply type byte") == 0);
redisReaderFree(reader); redisReaderFree(reader);
test("Set error on nested multi bulks with depth > 7: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
test("Can handle arbitrarily nested multi-bulks: ");
for (i = 0; i < 9; i++) { for (i = 0; i < 128; i++) {
redisReaderFeed(reader,(char*)"*1\r\n", 4); redisReaderFeed(reader,(char*)"*1\r\n", 4);
} }
redisReaderFeed(reader,(char*)"$6\r\nLOLWUT\r\n",12);
ret = redisReaderGetReply(reader,&reply);
root = reply; /* Keep track of the root reply */
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_ARRAY &&
((redisReply*)reply)->elements == 1);
ret = redisReaderGetReply(reader,NULL); test("Can parse arbitrarily nested multi-bulks correctly: ");
test_cond(ret == REDIS_ERR && while(i--) {
strncasecmp(reader->errstr,"No support for",14) == 0); assert(reply != NULL && ((redisReply*)reply)->type == REDIS_REPLY_ARRAY);
reply = ((redisReply*)reply)->element[0];
}
test_cond(((redisReply*)reply)->type == REDIS_REPLY_STRING &&
!memcmp(((redisReply*)reply)->str, "LOLWUT", 6));
freeReplyObject(root);
redisReaderFree(reader); redisReaderFree(reader);
test("Correctly parses LLONG_MAX: "); test("Correctly parses LLONG_MAX: ");
@ -400,6 +488,16 @@ static void test_reply_reader(void) {
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
test("Can configure maximum multi-bulk elements: ");
reader = redisReaderCreate();
reader->maxelements = 1024;
redisReaderFeed(reader, "*1025\r\n", 7);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_ERR &&
strcasecmp(reader->errstr, "Multi-bulk length out of range") == 0);
freeReplyObject(reply);
redisReaderFree(reader);
#if LLONG_MAX > SIZE_MAX #if LLONG_MAX > SIZE_MAX
test("Set error when array > SIZE_MAX: "); test("Set error when array > SIZE_MAX: ");
reader = redisReaderCreate(); reader = redisReaderCreate();
@ -459,6 +557,32 @@ static void test_reply_reader(void) {
((redisReply*)reply)->elements == 0); ((redisReply*)reply)->elements == 0);
freeReplyObject(reply); freeReplyObject(reply);
redisReaderFree(reader); redisReaderFree(reader);
/* RESP3 verbatim strings (GitHub issue #802) */
test("Can parse RESP3 verbatim strings: ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)"=10\r\ntxt:LOLWUT\r\n",17);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_VERB &&
!memcmp(((redisReply*)reply)->str,"LOLWUT", 6));
freeReplyObject(reply);
redisReaderFree(reader);
/* RESP3 push messages (Github issue #815) */
test("Can parse RESP3 push messages: ");
reader = redisReaderCreate();
redisReaderFeed(reader,(char*)">2\r\n$6\r\nLOLWUT\r\n:42\r\n",21);
ret = redisReaderGetReply(reader,&reply);
test_cond(ret == REDIS_OK &&
((redisReply*)reply)->type == REDIS_REPLY_PUSH &&
((redisReply*)reply)->elements == 2 &&
((redisReply*)reply)->element[0]->type == REDIS_REPLY_STRING &&
!memcmp(((redisReply*)reply)->element[0]->str,"LOLWUT",6) &&
((redisReply*)reply)->element[1]->type == REDIS_REPLY_INTEGER &&
((redisReply*)reply)->element[1]->integer == 42);
freeReplyObject(reply);
redisReaderFree(reader);
} }
static void test_free_null(void) { static void test_free_null(void) {
@ -474,6 +598,47 @@ static void test_free_null(void) {
test_cond(reply == NULL); test_cond(reply == NULL);
} }
static void *hi_malloc_fail(size_t size) {
(void)size;
return NULL;
}
static void *hi_calloc_fail(size_t nmemb, size_t size) {
(void)nmemb;
(void)size;
return NULL;
}
static void *hi_realloc_fail(void *ptr, size_t size) {
(void)ptr;
(void)size;
return NULL;
}
static void test_allocator_injection(void) {
hiredisAllocFuncs ha = {
.mallocFn = hi_malloc_fail,
.callocFn = hi_calloc_fail,
.reallocFn = hi_realloc_fail,
.strdupFn = strdup,
.freeFn = free,
};
// Override hiredis allocators
hiredisSetAllocators(&ha);
test("redisContext uses injected allocators: ");
redisContext *c = redisConnect("localhost", 6379);
test_cond(c == NULL);
test("redisReader uses injected allocators: ");
redisReader *reader = redisReaderCreate();
test_cond(reader == NULL);
// Return allocators to default
hiredisResetAllocators();
}
#define HIREDIS_BAD_DOMAIN "idontexist-noreally.com" #define HIREDIS_BAD_DOMAIN "idontexist-noreally.com"
static void test_blocking_connection_errors(void) { static void test_blocking_connection_errors(void) {
redisContext *c; redisContext *c;
@ -491,19 +656,19 @@ static void test_blocking_connection_errors(void) {
(strcmp(c->errstr, "Name or service not known") == 0 || (strcmp(c->errstr, "Name or service not known") == 0 ||
strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 || strcmp(c->errstr, "Can't resolve: " HIREDIS_BAD_DOMAIN) == 0 ||
strcmp(c->errstr, "Name does not resolve") == 0 || strcmp(c->errstr, "Name does not resolve") == 0 ||
strcmp(c->errstr, strcmp(c->errstr, "nodename nor servname provided, or not known") == 0 ||
"nodename nor servname provided, or not known") == 0 ||
strcmp(c->errstr, "No address associated with hostname") == 0 || strcmp(c->errstr, "No address associated with hostname") == 0 ||
strcmp(c->errstr, "Temporary failure in name resolution") == 0 || strcmp(c->errstr, "Temporary failure in name resolution") == 0 ||
strcmp(c->errstr, strcmp(c->errstr, "hostname nor servname provided, or not known") == 0 ||
"hostname nor servname provided, or not known") == 0 || strcmp(c->errstr, "no address associated with name") == 0 ||
strcmp(c->errstr, "no address associated with name") == 0)); strcmp(c->errstr, "No such host is known. ") == 0));
redisFree(c); redisFree(c);
} else { } else {
printf("Skipping NXDOMAIN test. Found evil ISP!\n"); printf("Skipping NXDOMAIN test. Found evil ISP!\n");
freeaddrinfo(ai_tmp); freeaddrinfo(ai_tmp);
} }
#ifndef _WIN32
test("Returns error when the port is not open: "); test("Returns error when the port is not open: ");
c = redisConnect((char*)"localhost", 1); c = redisConnect((char*)"localhost", 1);
test_cond(c->err == REDIS_ERR_IO && test_cond(c->err == REDIS_ERR_IO &&
@ -514,11 +679,166 @@ static void test_blocking_connection_errors(void) {
c = redisConnectUnix((char*)"/tmp/idontexist.sock"); c = redisConnectUnix((char*)"/tmp/idontexist.sock");
test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */ test_cond(c->err == REDIS_ERR_IO); /* Don't care about the message... */
redisFree(c); redisFree(c);
#endif
}
/* Test push handler */
void push_handler(void *privdata, void *r) {
struct pushCounters *pcounts = privdata;
redisReply *reply = r, *payload;
assert(reply && reply->type == REDIS_REPLY_PUSH && reply->elements == 2);
payload = reply->element[1];
if (payload->type == REDIS_REPLY_ARRAY) {
payload = payload->element[0];
}
if (payload->type == REDIS_REPLY_STRING) {
pcounts->str++;
} else if (payload->type == REDIS_REPLY_NIL) {
pcounts->nil++;
}
freeReplyObject(reply);
}
/* Dummy function just to test setting a callback with redisOptions */
void push_handler_async(redisAsyncContext *ac, void *reply) {
(void)ac;
(void)reply;
}
static void test_resp3_push_handler(redisContext *c) {
struct pushCounters pc = {0};
redisPushFn *old = NULL;
redisReply *reply;
void *privdata;
/* Switch to RESP3 and turn on client tracking */
send_hello(c, 3);
send_client_tracking(c, "ON");
privdata = c->privdata;
c->privdata = &pc;
reply = redisCommand(c, "GET key:0");
assert(reply != NULL);
freeReplyObject(reply);
test("RESP3 PUSH messages are handled out of band by default: ");
reply = redisCommand(c, "SET key:0 val:0");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
freeReplyObject(reply);
assert((reply = redisCommand(c, "GET key:0")) != NULL);
freeReplyObject(reply);
old = redisSetPushCallback(c, push_handler);
test("We can set a custom RESP3 PUSH handler: ");
reply = redisCommand(c, "SET key:0 val:0");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.str == 1);
freeReplyObject(reply);
test("We properly handle a NIL invalidation payload: ");
reply = redisCommand(c, "FLUSHDB");
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS && pc.nil == 1);
freeReplyObject(reply);
/* Unset the push callback and generate an invalidate message making
* sure it is not handled out of band. */
test("With no handler, PUSH replies come in-band: ");
redisSetPushCallback(c, NULL);
assert((reply = redisCommand(c, "GET key:0")) != NULL);
freeReplyObject(reply);
assert((reply = redisCommand(c, "SET key:0 invalid")) != NULL);
test_cond(reply->type == REDIS_REPLY_PUSH);
freeReplyObject(reply);
test("With no PUSH handler, no replies are lost: ");
assert(redisGetReply(c, (void**)&reply) == REDIS_OK);
test_cond(reply != NULL && reply->type == REDIS_REPLY_STATUS);
freeReplyObject(reply);
/* Return to the originally set PUSH handler */
assert(old != NULL);
redisSetPushCallback(c, old);
/* Switch back to RESP2 and disable tracking */
c->privdata = privdata;
send_client_tracking(c, "OFF");
send_hello(c, 2);
}
redisOptions get_redis_tcp_options(struct config config) {
redisOptions options = {0};
REDIS_OPTIONS_SET_TCP(&options, config.tcp.host, config.tcp.port);
return options;
}
static void test_resp3_push_options(struct config config) {
redisAsyncContext *ac;
redisContext *c;
redisOptions options;
test("We set a default RESP3 handler for redisContext: ");
options = get_redis_tcp_options(config);
assert((c = redisConnectWithOptions(&options)) != NULL);
test_cond(c->push_cb != NULL);
redisFree(c);
test("We don't set a default RESP3 push handler for redisAsyncContext: ");
options = get_redis_tcp_options(config);
assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
test_cond(ac->c.push_cb == NULL);
redisAsyncFree(ac);
test("Our REDIS_OPT_NO_PUSH_AUTOFREE flag works: ");
options = get_redis_tcp_options(config);
options.options |= REDIS_OPT_NO_PUSH_AUTOFREE;
assert((c = redisConnectWithOptions(&options)) != NULL);
test_cond(c->push_cb == NULL);
redisFree(c);
test("We can use redisOptions to set a custom PUSH handler for redisContext: ");
options = get_redis_tcp_options(config);
options.push_cb = push_handler;
assert((c = redisConnectWithOptions(&options)) != NULL);
test_cond(c->push_cb == push_handler);
redisFree(c);
test("We can use redisOptions to set a custom PUSH handler for redisAsyncContext: ");
options = get_redis_tcp_options(config);
options.async_push_cb = push_handler_async;
assert((ac = redisAsyncConnectWithOptions(&options)) != NULL);
test_cond(ac->push_cb == push_handler_async);
redisAsyncFree(ac);
}
void free_privdata(void *privdata) {
struct privdata *data = privdata;
data->dtor_counter++;
}
static void test_privdata_hooks(struct config config) {
struct privdata data = {0};
redisOptions options;
redisContext *c;
test("We can use redisOptions to set privdata: ");
options = get_redis_tcp_options(config);
REDIS_OPTIONS_SET_PRIVDATA(&options, &data, free_privdata);
assert((c = redisConnectWithOptions(&options)) != NULL);
test_cond(c->privdata == &data);
test("Our privdata destructor fires when we free the context: ");
redisFree(c);
test_cond(data.dtor_counter == 1);
} }
static void test_blocking_connection(struct config config) { static void test_blocking_connection(struct config config) {
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
int major;
c = do_connect(config); c = do_connect(config);
@ -591,14 +911,42 @@ static void test_blocking_connection(struct config config) {
strcasecmp(reply->element[1]->str,"pong") == 0); strcasecmp(reply->element[1]->str,"pong") == 0);
freeReplyObject(reply); freeReplyObject(reply);
/* Make sure passing NULL to redisGetReply is safe */
test("Can pass NULL to redisGetReply: ");
assert(redisAppendCommand(c, "PING") == REDIS_OK);
test_cond(redisGetReply(c, NULL) == REDIS_OK);
get_redis_version(c, &major, NULL);
if (major >= 6) test_resp3_push_handler(c);
test_resp3_push_options(config);
test_privdata_hooks(config);
disconnect(c, 0); disconnect(c, 0);
} }
/* Send DEBUG SLEEP 0 to detect if we have this command */
static int detect_debug_sleep(redisContext *c) {
int detected;
redisReply *reply = redisCommand(c, "DEBUG SLEEP 0\r\n");
if (reply == NULL || c->err) {
const char *cause = c->err ? c->errstr : "(none)";
fprintf(stderr, "Error testing for DEBUG SLEEP (Redis error: %s), exiting\n", cause);
exit(-1);
}
detected = reply->type == REDIS_REPLY_STATUS;
freeReplyObject(reply);
return detected;
}
static void test_blocking_connection_timeouts(struct config config) { static void test_blocking_connection_timeouts(struct config config) {
redisContext *c; redisContext *c;
redisReply *reply; redisReply *reply;
ssize_t s; ssize_t s;
const char *cmd = "DEBUG SLEEP 3\r\n"; const char *sleep_cmd = "DEBUG SLEEP 3\r\n";
struct timeval tv; struct timeval tv;
c = do_connect(config); c = do_connect(config);
@ -615,14 +963,24 @@ static void test_blocking_connection_timeouts(struct config config) {
c = do_connect(config); c = do_connect(config);
test("Does not return a reply when the command times out: "); test("Does not return a reply when the command times out: ");
redisAppendFormattedCommand(c, cmd, strlen(cmd)); if (detect_debug_sleep(c)) {
redisAppendFormattedCommand(c, sleep_cmd, strlen(sleep_cmd));
s = c->funcs->write(c); s = c->funcs->write(c);
tv.tv_sec = 0; tv.tv_sec = 0;
tv.tv_usec = 10000; tv.tv_usec = 10000;
redisSetTimeout(c, tv); redisSetTimeout(c, tv);
reply = redisCommand(c, "GET foo"); reply = redisCommand(c, "GET foo");
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO && strcmp(c->errstr, "Resource temporarily unavailable") == 0); #ifndef _WIN32
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_IO &&
strcmp(c->errstr, "Resource temporarily unavailable") == 0);
#else
test_cond(s > 0 && reply == NULL && c->err == REDIS_ERR_TIMEOUT &&
strcmp(c->errstr, "recv timeout") == 0);
#endif
freeReplyObject(reply); freeReplyObject(reply);
} else {
test_skipped();
}
test("Reconnect properly reconnects after a timeout: "); test("Reconnect properly reconnects after a timeout: ");
do_reconnect(c, config); do_reconnect(c, config);
@ -649,18 +1007,7 @@ static void test_blocking_io_errors(struct config config) {
/* Connect to target given by config. */ /* Connect to target given by config. */
c = do_connect(config); c = do_connect(config);
{ get_redis_version(c, &major, &minor);
/* Find out Redis version to determine the path for the next test */
const char *field = "redis_version:";
char *p, *eptr;
reply = redisCommand(c,"INFO");
p = strstr(reply->str,field);
major = strtol(p+strlen(field),&eptr,10);
p = eptr+1; /* char next to the first "." */
minor = strtol(p,&eptr,10);
freeReplyObject(reply);
}
test("Returns I/O error when the connection is lost: "); test("Returns I/O error when the connection is lost: ");
reply = redisCommand(c,"QUIT"); reply = redisCommand(c,"QUIT");
@ -674,6 +1021,7 @@ static void test_blocking_io_errors(struct config config) {
test_cond(reply == NULL); test_cond(reply == NULL);
} }
#ifndef _WIN32
/* On 2.0, QUIT will cause the connection to be closed immediately and /* On 2.0, QUIT will cause the connection to be closed immediately and
* the read(2) for the reply on QUIT will set the error to EOF. * the read(2) for the reply on QUIT will set the error to EOF.
* On >2.0, QUIT will return with OK and another read(2) needed to be * On >2.0, QUIT will return with OK and another read(2) needed to be
@ -681,14 +1029,19 @@ static void test_blocking_io_errors(struct config config) {
* conditions, the error will be set to EOF. */ * conditions, the error will be set to EOF. */
assert(c->err == REDIS_ERR_EOF && assert(c->err == REDIS_ERR_EOF &&
strcmp(c->errstr,"Server closed the connection") == 0); strcmp(c->errstr,"Server closed the connection") == 0);
#endif
redisFree(c); redisFree(c);
c = do_connect(config); c = do_connect(config);
test("Returns I/O error on socket timeout: "); test("Returns I/O error on socket timeout: ");
struct timeval tv = { 0, 1000 }; struct timeval tv = { 0, 1000 };
assert(redisSetTimeout(c,tv) == REDIS_OK); assert(redisSetTimeout(c,tv) == REDIS_OK);
test_cond(redisGetReply(c,&_reply) == REDIS_ERR && int respcode = redisGetReply(c,&_reply);
c->err == REDIS_ERR_IO && errno == EAGAIN); #ifndef _WIN32
test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_IO && errno == EAGAIN);
#else
test_cond(respcode == REDIS_ERR && c->err == REDIS_ERR_TIMEOUT);
#endif
redisFree(c); redisFree(c);
} }
@ -716,6 +1069,18 @@ static void test_invalid_timeout_errors(struct config config) {
redisFree(c); redisFree(c);
} }
/* Wrap malloc to abort on failure so OOM checks don't make the test logic
* harder to follow. */
void *hi_malloc_safe(size_t size) {
void *ptr = hi_malloc(size);
if (ptr == NULL) {
fprintf(stderr, "Error: Out of memory\n");
exit(-1);
}
return ptr;
}
static void test_throughput(struct config config) { static void test_throughput(struct config config) {
redisContext *c = do_connect(config); redisContext *c = do_connect(config);
redisReply **replies; redisReply **replies;
@ -727,7 +1092,7 @@ static void test_throughput(struct config config) {
freeReplyObject(redisCommand(c,"LPUSH mylist foo")); freeReplyObject(redisCommand(c,"LPUSH mylist foo"));
num = 1000; num = 1000;
replies = malloc(sizeof(redisReply*)*num); replies = hi_malloc_safe(sizeof(redisReply*)*num);
t1 = usec(); t1 = usec();
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
replies[i] = redisCommand(c,"PING"); replies[i] = redisCommand(c,"PING");
@ -735,10 +1100,10 @@ static void test_throughput(struct config config) {
} }
t2 = usec(); t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]); for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies); hi_free(replies);
printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0); printf("\t(%dx PING: %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num); replies = hi_malloc_safe(sizeof(redisReply*)*num);
t1 = usec(); t1 = usec();
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
replies[i] = redisCommand(c,"LRANGE mylist 0 499"); replies[i] = redisCommand(c,"LRANGE mylist 0 499");
@ -747,10 +1112,10 @@ static void test_throughput(struct config config) {
} }
t2 = usec(); t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]); for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies); hi_free(replies);
printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0); printf("\t(%dx LRANGE with 500 elements: %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num); replies = hi_malloc_safe(sizeof(redisReply*)*num);
t1 = usec(); t1 = usec();
for (i = 0; i < num; i++) { for (i = 0; i < num; i++) {
replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000); replies[i] = redisCommand(c, "INCRBY incrkey %d", 1000000);
@ -758,11 +1123,11 @@ static void test_throughput(struct config config) {
} }
t2 = usec(); t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]); for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies); hi_free(replies);
printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0); printf("\t(%dx INCRBY: %.3fs)\n", num, (t2-t1)/1000000.0);
num = 10000; num = 10000;
replies = malloc(sizeof(redisReply*)*num); replies = hi_malloc_safe(sizeof(redisReply*)*num);
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
redisAppendCommand(c,"PING"); redisAppendCommand(c,"PING");
t1 = usec(); t1 = usec();
@ -772,10 +1137,10 @@ static void test_throughput(struct config config) {
} }
t2 = usec(); t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]); for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies); hi_free(replies);
printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); printf("\t(%dx PING (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num); replies = hi_malloc_safe(sizeof(redisReply*)*num);
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
redisAppendCommand(c,"LRANGE mylist 0 499"); redisAppendCommand(c,"LRANGE mylist 0 499");
t1 = usec(); t1 = usec();
@ -786,10 +1151,10 @@ static void test_throughput(struct config config) {
} }
t2 = usec(); t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]); for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies); hi_free(replies);
printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); printf("\t(%dx LRANGE with 500 elements (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
replies = malloc(sizeof(redisReply*)*num); replies = hi_malloc_safe(sizeof(redisReply*)*num);
for (i = 0; i < num; i++) for (i = 0; i < num; i++)
redisAppendCommand(c,"INCRBY incrkey %d", 1000000); redisAppendCommand(c,"INCRBY incrkey %d", 1000000);
t1 = usec(); t1 = usec();
@ -799,7 +1164,7 @@ static void test_throughput(struct config config) {
} }
t2 = usec(); t2 = usec();
for (i = 0; i < num; i++) freeReplyObject(replies[i]); for (i = 0; i < num; i++) freeReplyObject(replies[i]);
free(replies); hi_free(replies);
printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0); printf("\t(%dx INCRBY (pipelined): %.3fs)\n", num, (t2-t1)/1000000.0);
disconnect(c, 0); disconnect(c, 0);
@ -916,9 +1281,8 @@ int main(int argc, char **argv) {
}; };
int throughput = 1; int throughput = 1;
int test_inherit_fd = 1; int test_inherit_fd = 1;
int skips_as_fails = 0;
/* Ignore broken pipe signal (for I/O error tests). */ int test_unix_socket;
signal(SIGPIPE, SIG_IGN);
/* Parse command line options. */ /* Parse command line options. */
argv++; argc--; argv++; argc--;
@ -936,6 +1300,8 @@ int main(int argc, char **argv) {
throughput = 0; throughput = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) { } else if (argc >= 1 && !strcmp(argv[0],"--skip-inherit-fd")) {
test_inherit_fd = 0; test_inherit_fd = 0;
} else if (argc >= 1 && !strcmp(argv[0],"--skips-as-fails")) {
skips_as_fails = 1;
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
} else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) { } else if (argc >= 2 && !strcmp(argv[0],"--ssl-port")) {
argv++; argc--; argv++; argc--;
@ -960,6 +1326,19 @@ int main(int argc, char **argv) {
argv++; argc--; argv++; argc--;
} }
#ifndef _WIN32
/* Ignore broken pipe signal (for I/O error tests). */
signal(SIGPIPE, SIG_IGN);
test_unix_socket = access(cfg.unix_sock.path, F_OK) == 0;
#else
/* Unix sockets don't exist in Windows */
test_unix_socket = 0;
#endif
test_allocator_injection();
test_format_commands(); test_format_commands();
test_reply_reader(); test_reply_reader();
test_blocking_connection_errors(); test_blocking_connection_errors();
@ -974,15 +1353,25 @@ int main(int argc, char **argv) {
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
printf("\nTesting against Unix socket connection (%s):\n", cfg.unix_sock.path); printf("\nTesting against Unix socket connection (%s): ", cfg.unix_sock.path);
if (test_unix_socket) {
printf("\n");
cfg.type = CONN_UNIX; cfg.type = CONN_UNIX;
test_blocking_connection(cfg); test_blocking_connection(cfg);
test_blocking_connection_timeouts(cfg); test_blocking_connection_timeouts(cfg);
test_blocking_io_errors(cfg); test_blocking_io_errors(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
} else {
test_skipped();
}
#ifdef HIREDIS_TEST_SSL #ifdef HIREDIS_TEST_SSL
if (cfg.ssl.port && cfg.ssl.host) { if (cfg.ssl.port && cfg.ssl.host) {
redisInitOpenSSL();
_ssl_ctx = redisCreateSSLContext(cfg.ssl.ca_cert, NULL, cfg.ssl.cert, cfg.ssl.key, NULL, NULL);
assert(_ssl_ctx != NULL);
printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port); printf("\nTesting against SSL connection (%s:%d):\n", cfg.ssl.host, cfg.ssl.port);
cfg.type = CONN_SSL; cfg.type = CONN_SSL;
@ -992,21 +1381,31 @@ int main(int argc, char **argv) {
test_invalid_timeout_errors(cfg); test_invalid_timeout_errors(cfg);
test_append_formatted_commands(cfg); test_append_formatted_commands(cfg);
if (throughput) test_throughput(cfg); if (throughput) test_throughput(cfg);
redisFreeSSLContext(_ssl_ctx);
_ssl_ctx = NULL;
} }
#endif #endif
if (test_inherit_fd) { if (test_inherit_fd) {
printf("\nTesting against inherited fd (%s):\n", cfg.unix_sock.path); printf("\nTesting against inherited fd (%s): ", cfg.unix_sock.path);
if (test_unix_socket) {
printf("\n");
cfg.type = CONN_FD; cfg.type = CONN_FD;
test_blocking_connection(cfg); test_blocking_connection(cfg);
} else {
test_skipped();
}
} }
if (fails || (skips_as_fails && skips)) {
if (fails) {
printf("*** %d TESTS FAILED ***\n", fails); printf("*** %d TESTS FAILED ***\n", fails);
if (skips) {
printf("*** %d TESTS SKIPPED ***\n", skips);
}
return 1; return 1;
} }
printf("ALL TESTS PASSED\n"); printf("ALL TESTS PASSED (%d skipped)\n", skips);
return 0; return 0;
} }

10
deps/hiredis/test.sh vendored
View File

@ -4,7 +4,9 @@ REDIS_SERVER=${REDIS_SERVER:-redis-server}
REDIS_PORT=${REDIS_PORT:-56379} REDIS_PORT=${REDIS_PORT:-56379}
REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443} REDIS_SSL_PORT=${REDIS_SSL_PORT:-56443}
TEST_SSL=${TEST_SSL:-0} TEST_SSL=${TEST_SSL:-0}
SKIPS_AS_FAILS=${SKIPS_AS_FAILS-:0}
SSL_TEST_ARGS= SSL_TEST_ARGS=
SKIPS_ARG=
tmpdir=$(mktemp -d) tmpdir=$(mktemp -d)
PID_FILE=${tmpdir}/hiredis-test-redis.pid PID_FILE=${tmpdir}/hiredis-test-redis.pid
@ -67,4 +69,10 @@ fi
cat ${tmpdir}/redis.conf cat ${tmpdir}/redis.conf
${REDIS_SERVER} ${tmpdir}/redis.conf ${REDIS_SERVER} ${tmpdir}/redis.conf
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} # Wait until we detect the unix socket
while [ ! -S "${SOCK_FILE}" ]; do sleep 1; done
# Treat skips as failures if directed
[ "$SKIPS_AS_FAILS" = 1 ] && SKIPS_ARG="--skips-as-fails"
${TEST_PREFIX:-} ./hiredis-test -h 127.0.0.1 -p ${REDIS_PORT} -s ${SOCK_FILE} ${SSL_TEST_ARGS} ${SKIPS_ARG}

View File

@ -517,7 +517,7 @@ CTARGET='-o $@'
LDTARGET='-o $@' LDTARGET='-o $@'
TEST_LD_MODE= TEST_LD_MODE=
EXTRA_LDFLAGS= EXTRA_LDFLAGS=
ARFLAGS='crus' ARFLAGS='crs'
AROUT=' $@' AROUT=' $@'
CC_MM=1 CC_MM=1

2
deps/lua/src/ldo.c vendored
View File

@ -493,7 +493,7 @@ static void f_parser (lua_State *L, void *ud) {
Proto *tf; Proto *tf;
Closure *cl; Closure *cl;
struct SParser *p = cast(struct SParser *, ud); struct SParser *p = cast(struct SParser *, ud);
int c = luaZ_lookahead(p->z); luaZ_lookahead(p->z);
luaC_checkGC(L); luaC_checkGC(L);
tf = (luaY_parser)(L, p->z, tf = (luaY_parser)(L, p->z,
&p->buff, p->name); &p->buff, p->name);

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,8 @@
TCL_VERSIONS="8.5 8.6" TCL_VERSIONS="8.5 8.6"
TCLSH="" TCLSH=""
export ASAN_OPTIONS=allocator_may_return_null=1 $ASAN_OPTIONS
for VERSION in $TCL_VERSIONS; do for VERSION in $TCL_VERSIONS; do
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
done done

View File

@ -8,7 +8,7 @@ done
if [ -z $TCLSH ] if [ -z $TCLSH ]
then then
echo "You need tcl 8.5 or newer in order to run the Redis Sentinel test" echo "You need tcl 8.5 or newer in order to run the Redis Cluster test"
exit 1 exit 1
fi fi
$TCLSH tests/cluster/run.tcl $* $TCLSH tests/cluster/run.tcl $*

View File

@ -1,6 +1,7 @@
#!/bin/sh #!/bin/sh
TCL_VERSIONS="8.5 8.6" TCL_VERSIONS="8.5 8.6"
TCLSH="" TCLSH=""
[ -z "$MAKE" ] && MAKE=make
for VERSION in $TCL_VERSIONS; do for VERSION in $TCL_VERSIONS; do
TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL TCL=`which tclsh$VERSION 2>/dev/null` && TCLSH=$TCL
@ -8,11 +9,11 @@ done
if [ -z $TCLSH ] if [ -z $TCLSH ]
then then
echo "You need tcl 8.5 or newer in order to run the Redis test" echo "You need tcl 8.5 or newer in order to run the Redis ModuleApi test"
exit 1 exit 1
fi fi
make -C tests/modules && \ $MAKE -C tests/modules && \
$TCLSH tests/test_helper.tcl \ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/commandfilter \ --single unit/moduleapi/commandfilter \
--single unit/moduleapi/fork \ --single unit/moduleapi/fork \
@ -22,6 +23,7 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/hooks \ --single unit/moduleapi/hooks \
--single unit/moduleapi/misc \ --single unit/moduleapi/misc \
--single unit/moduleapi/blockonkeys \ --single unit/moduleapi/blockonkeys \
--single unit/moduleapi/blockonbackground \
--single unit/moduleapi/scan \ --single unit/moduleapi/scan \
--single unit/moduleapi/datatype \ --single unit/moduleapi/datatype \
--single unit/moduleapi/auth \ --single unit/moduleapi/auth \
@ -30,5 +32,10 @@ $TCLSH tests/test_helper.tcl \
--single unit/moduleapi/moduleloadsave \ --single unit/moduleapi/moduleloadsave \
--single unit/moduleapi/getkeys \ --single unit/moduleapi/getkeys \
--single unit/moduleapi/timers \ --single unit/moduleapi/timers \
--single unit/moduleapi/test_lazyfree \
--single unit/moduleapi/defrag \
--single unit/moduleapi/hash \
--single unit/moduleapi/zset \
--single unit/moduleapi/stream \
--config server-threads 3 \ --config server-threads 3 \
"${@}" "${@}"

View File

@ -124,6 +124,42 @@ sentinel monitor mymaster 127.0.0.1 6379 2
# Default is 30 seconds. # Default is 30 seconds.
sentinel down-after-milliseconds mymaster 30000 sentinel down-after-milliseconds mymaster 30000
# IMPORTANT NOTE: starting with KeyDB 6.2 ACL capability is supported for
# Sentinel mode, please refer to the KeyDB website https://redis.io/topics/acl
# for more details.
# Sentinel's ACL users are defined in the following format:
#
# user <username> ... acl rules ...
#
# For example:
#
# user worker +@admin +@connection ~* on >ffa9203c493aa99
#
# For more information about ACL configuration please refer to the Redis
# website at https://redis.io/topics/acl and redis server configuration
# template redis.conf.
# ACL LOG
#
# The ACL Log tracks failed commands and authentication events associated
# with ACLs. The ACL Log is useful to troubleshoot failed commands blocked
# by ACLs. The ACL Log is stored in memory. You can reclaim memory with
# ACL LOG RESET. Define the maximum entry length of the ACL Log below.
acllog-max-len 128
# Using an external ACL file
#
# Instead of configuring users here in this file, it is possible to use
# a stand-alone file just listing users. The two methods cannot be mixed:
# if you configure users here and at the same time you activate the external
# ACL file, the server will refuse to start.
#
# The format of the external ACL user file is exactly the same as the
# format that is used inside redis.conf to describe users.
#
# aclfile /etc/redis/sentinel-users.acl
# requirepass <password> # requirepass <password>
# #
# You can configure Sentinel itself to require a password, however when doing # You can configure Sentinel itself to require a password, however when doing
@ -131,6 +167,29 @@ sentinel down-after-milliseconds mymaster 30000
# other Sentinels. So you need to configure all your Sentinels in a given # other Sentinels. So you need to configure all your Sentinels in a given
# group with the same "requirepass" password. Check the following documentation # group with the same "requirepass" password. Check the following documentation
# for more info: https://redis.io/topics/sentinel # for more info: https://redis.io/topics/sentinel
#
# IMPORTANT NOTE: starting with Redis 6.2 "requirepass" is a compatibility
# layer on top of the ACL system. The option effect will be just setting
# the password for the default user. Clients will still authenticate using
# AUTH <password> as usually, or more explicitly with AUTH default <password>
# if they follow the new protocol: both will work.
#
# New config files are advised to use separate authentication control for
# incoming connections (via ACL), and for outgoing connections (via
# sentinel-user and sentinel-pass)
#
# The requirepass is not compatable with aclfile option and the ACL LOAD
# command, these will cause requirepass to be ignored.
# sentinel sentinel-user <username>
#
# You can configure Sentinel to authenticate with other Sentinels with specific
# user name.
# sentinel sentinel-pass <password>
#
# The password for Sentinel to authenticate with other Sentinels. If sentinel-user
# is not configured, Sentinel will use 'default' user with sentinel-pass to authenticate.
# sentinel parallel-syncs <master-name> <numreplicas> # sentinel parallel-syncs <master-name> <numreplicas>
# #
@ -262,3 +321,21 @@ sentinel deny-scripts-reconfig yes
# is possible to just rename a command to itself: # is possible to just rename a command to itself:
# #
# SENTINEL rename-command mymaster CONFIG CONFIG # SENTINEL rename-command mymaster CONFIG CONFIG
# HOSTNAMES SUPPORT
#
# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR
# to specify an IP address. Also, it requires the Redis replica-announce-ip
# keyword to specify only IP addresses.
#
# You may enable hostnames support by enabling resolve-hostnames. Note
# that you must make sure your DNS is configured properly and that DNS
# resolution does not introduce very long delays.
#
SENTINEL resolve-hostnames no
# When resolve-hostnames is enabled, Sentinel still uses IP addresses
# when exposing instances to users, configuration files, etc. If you want
# to retain the hostnames when announced, enable announce-hostnames below.
#
SENTINEL announce-hostnames no

View File

@ -16,13 +16,15 @@ release_hdr := $(shell sh -c './mkreleasehdr.sh')
uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not') uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not') uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
OPTIMIZATION?=-O2 OPTIMIZATION?=-O2
DEPENDENCY_TARGETS=hiredis linenoise lua DEPENDENCY_TARGETS=hiredis linenoise lua hdr_histogram
NODEPS:=clean distclean NODEPS:=clean distclean
# Default settings # Default settings
STD=-std=c11 -pedantic -DREDIS_STATIC='' STD=-pedantic -DREDIS_STATIC=''
CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS CXX_STD=-std=c++14 -pedantic -fno-rtti -D__STDC_FORMAT_MACROS
ifneq (,$(findstring clang,$(CC))) ifneq (,$(findstring clang,$(CC)))
STD+=-Wno-c11-extensions
else
ifneq (,$(findstring FreeBSD,$(uname_S))) ifneq (,$(findstring FreeBSD,$(uname_S)))
STD+=-Wno-c11-extensions STD+=-Wno-c11-extensions
endif endif
@ -30,6 +32,16 @@ endif
WARN=-Wall -W -Wno-missing-field-initializers WARN=-Wall -W -Wno-missing-field-initializers
OPT=$(OPTIMIZATION) OPT=$(OPTIMIZATION)
# Detect if the compiler supports C11 _Atomic
C11_ATOMIC := $(shell sh -c 'echo "\#include <stdatomic.h>" > foo.c; \
$(CC) -std=c11 -c foo.c -o foo.o > /dev/null 2>&1; \
if [ -f foo.o ]; then echo "yes"; rm foo.o; fi; rm foo.c')
ifeq ($(C11_ATOMIC),yes)
STD+=-std=c11
else
STD+=-std=c99
endif
PREFIX?=/usr/local PREFIX?=/usr/local
INSTALL_BIN=$(PREFIX)/bin INSTALL_BIN=$(PREFIX)/bin
INSTALL=install INSTALL=install
@ -117,9 +129,11 @@ endif
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
# SunOS # SunOS
ifneq ($(@@),32bit) ifeq ($(findstring -m32,$(FINAL_CFLAGS)),)
CFLAGS+=-m64 CFLAGS+=-m64
CXXFLAGS+= -m64 CXXFLAGS+= -m64
endif
ifeq ($(findstring -m32,$(FINAL_LDFLAGS)),)
LDFLAGS+=-m64 LDFLAGS+=-m64
endif endif
DEBUG=-g DEBUG=-g
@ -133,8 +147,18 @@ else
ifeq ($(uname_S),Darwin) ifeq ($(uname_S),Darwin)
# Darwin # Darwin
FINAL_LIBS+= -ldl FINAL_LIBS+= -ldl
# Homebrew's OpenSSL is not linked to /usr/local to avoid
# conflicts with the system's LibreSSL installation so it
# must be referenced explicitly during build.
ifeq ($(uname_M),arm64)
# Homebrew arm64 uses /opt/homebrew as HOMEBREW_PREFIX
OPENSSL_CFLAGS=-I/opt/homebrew/opt/openssl/include
OPENSSL_LDFLAGS=-L/opt/homebrew/opt/openssl/lib
else
# Homebrew x86/ppc uses /usr/local as HOMEBREW_PREFIX
OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include OPENSSL_CFLAGS=-I/usr/local/opt/openssl/include
OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib OPENSSL_LDFLAGS=-L/usr/local/opt/openssl/lib
endif
else else
ifeq ($(uname_S),AIX) ifeq ($(uname_S),AIX)
# AIX # AIX
@ -202,11 +226,13 @@ endif
endif endif
endif endif
# Include paths to dependencies # Include paths to dependencies
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src FINAL_CXXFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src -I../deps/hdr_histogram
# Determine systemd support and/or build preference (defaulting to auto-detection) # Determine systemd support and/or build preference (defaulting to auto-detection)
BUILD_WITH_SYSTEMD=no BUILD_WITH_SYSTEMD=no
LIBSYSTEMD_LIBS=-lsystemd
# If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to # If 'USE_SYSTEMD' in the environment is neither "no" nor "yes", try to
# auto-detect libsystemd's presence and link accordingly. # auto-detect libsystemd's presence and link accordingly.
ifneq ($(USE_SYSTEMD),no) ifneq ($(USE_SYSTEMD),no)
@ -215,17 +241,18 @@ ifneq ($(USE_SYSTEMD),no)
# (unless a later check tells us otherwise) # (unless a later check tells us otherwise)
ifeq ($(LIBSYSTEMD_PKGCONFIG),0) ifeq ($(LIBSYSTEMD_PKGCONFIG),0)
BUILD_WITH_SYSTEMD=yes BUILD_WITH_SYSTEMD=yes
LIBSYSTEMD_LIBS=$(shell $(PKG_CONFIG) --libs libsystemd)
endif endif
endif endif
# If 'USE_SYSTEMD' is set to "yes" use pkg-config if available or fall back to
# default -lsystemd.
ifeq ($(USE_SYSTEMD),yes) ifeq ($(USE_SYSTEMD),yes)
ifneq ($(LIBSYSTEMD_PKGCONFIG),0)
$(error USE_SYSTEMD is set to "$(USE_SYSTEMD)", but $(PKG_CONFIG) cannot find libsystemd)
endif
# Force building with libsystemd
BUILD_WITH_SYSTEMD=yes BUILD_WITH_SYSTEMD=yes
endif endif
ifeq ($(BUILD_WITH_SYSTEMD),yes) ifeq ($(BUILD_WITH_SYSTEMD),yes)
FINAL_LIBS+=$(shell $(PKG_CONFIG) --libs libsystemd) FINAL_LIBS+=$(LIBSYSTEMD_LIBS)
FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD FINAL_CFLAGS+= -DHAVE_LIBSYSTEMD
endif endif
@ -256,10 +283,9 @@ ifeq ($(MALLOC),memkind)
endif endif
ifeq ($(BUILD_TLS),yes) ifeq ($(BUILD_TLS),yes)
FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS) FINAL_CFLAGS+=-DUSE_OPENSSL $(OPENSSL_CFLAGS)
FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS) FINAL_CXXFLAGS+=-DUSE_OPENSSL $(OPENSSL_CXXFLAGS)
FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS) FINAL_LDFLAGS+=$(OPENSSL_LDFLAGS)
FINAL_LIBS += ../deps/hiredis/libhiredis_ssl.a -lssl -lcrypto
LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?) LIBSSL_PKGCONFIG := $(shell $(PKG_CONFIG) --exists libssl && echo $$?)
ifeq ($(LIBSSL_PKGCONFIG),0) ifeq ($(LIBSSL_PKGCONFIG),0)
LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl) LIBSSL_LIBS=$(shell $(PKG_CONFIG) --libs libssl)
@ -296,11 +322,11 @@ endif
REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX) REDIS_SERVER_NAME=keydb-server$(PROG_SUFFIX)
REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX) REDIS_SENTINEL_NAME=keydb-sentinel$(PROG_SUFFIX)
REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o $(ASM_OBJ) REDIS_SERVER_OBJ=adlist.o quicklist.o ae.o anet.o dict.o server.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o t_nhash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o cluster.o crc16.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crcspeed.o crc64.o bitops.o sentinel.o notify.o setproctitle.o blocked.o hyperloglog.o latency.o sparkline.o redis-check-rdb.o redis-check-aof.o geo.o lazyfree.o module.o evict.o expire.o geohash.o geohash_helper.o childinfo.o defrag.o siphash.o rax.o t_stream.o listpack.o localtime.o acl.o storage.o rdb-s3.o fastlock.o new.o tracking.o cron.o connection.o tls.o sha256.o motd.o timeout.o setcpuaffinity.o monotonic.o mt19937-64.o $(ASM_OBJ)
REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX) REDIS_CLI_NAME=keydb-cli$(PROG_SUFFIX)
REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o $(ASM_OBJ) REDIS_CLI_OBJ=anet.o adlist.o dict.o redis-cli.o redis-cli-cpphelper.o zmalloc.o release.o anet.o ae.o crcspeed.o crc64.o siphash.o crc16.o storage-lite.o fastlock.o new.o motd.o monotonic.o cli_common.o mt19937-64.o $(ASM_OBJ)
REDIS_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX) REDIS_BENCHMARK_NAME=keydb-benchmark$(PROG_SUFFIX)
REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o $(ASM_OBJ) REDIS_BENCHMARK_OBJ=ae.o anet.o redis-benchmark.o adlist.o dict.o zmalloc.o release.o crcspeed.o crc64.o siphash.o redis-benchmark.o storage-lite.o fastlock.o new.o monotonic.o cli_common.o mt19937-64.o $(ASM_OBJ)
REDIS_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX) REDIS_CHECK_RDB_NAME=keydb-check-rdb$(PROG_SUFFIX)
REDIS_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX) REDIS_CHECK_AOF_NAME=keydb-check-aof$(PROG_SUFFIX)
@ -374,9 +400,9 @@ $(REDIS_CLI_NAME): $(REDIS_CLI_OBJ)
# keydb-benchmark # keydb-benchmark
$(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ) $(REDIS_BENCHMARK_NAME): $(REDIS_BENCHMARK_OBJ)
$(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a $(FINAL_LIBS) $(REDIS_LD) -o $@ $^ ../deps/hiredis/libhiredis.a ../deps/hdr_histogram/hdr_histogram.o $(FINAL_LIBS)
dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c dict-benchmark: dict.cpp zmalloc.cpp sds.c siphash.c mt19937-64.c
$(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS) $(REDIS_CC) $(FINAL_CFLAGS) $^ -D DICT_BENCHMARK_MAIN -o $@ $(FINAL_LIBS)
DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d) DEP = $(REDIS_SERVER_OBJ:%.o=%.d) $(REDIS_CLI_OBJ:%.o=%.d) $(REDIS_BENCHMARK_OBJ:%.o=%.d)
@ -407,10 +433,10 @@ distclean: clean
.PHONY: distclean .PHONY: distclean
test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) test: $(REDIS_SERVER_NAME) $(REDIS_CHECK_AOF_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME)
@(cd ..; ./runtest) @(cd ..; ./runtest)
test-sentinel: $(REDIS_SENTINEL_NAME) test-sentinel: $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME)
@(cd ..; ./runtest-sentinel) @(cd ..; ./runtest-sentinel)
check: test check: test
@ -422,10 +448,6 @@ lcov:
@genhtml --legend -o lcov-html KeyDB.info @genhtml --legend -o lcov-html KeyDB.info
@genhtml --legend -o lcov-html KeyDB.info | grep lines | awk '{print $$2;}' | sed 's/%//g' @genhtml --legend -o lcov-html KeyDB.info | grep lines | awk '{print $$2;}' | sed 's/%//g'
test-sds: sds.c sds.h
$(REDIS_CC) sds.c zmalloc.cpp -DSDS_TEST_MAIN $(FINAL_LIBS) -o /tmp/sds_test
/tmp/sds_test
.PHONY: lcov .PHONY: lcov
bench: $(REDIS_BENCHMARK_NAME) bench: $(REDIS_BENCHMARK_NAME)
@ -447,7 +469,7 @@ valgrind:
$(MAKE) OPTIMIZATION="-O0" USEASM="false" MALLOC="libc" CFLAGS="-DSANITIZE" CXXFLAGS="-DSANITIZE" $(MAKE) OPTIMIZATION="-O0" USEASM="false" MALLOC="libc" CFLAGS="-DSANITIZE" CXXFLAGS="-DSANITIZE"
helgrind: helgrind:
$(MAKE) OPTIMIZATION="-O0" MALLOC="libc" CFLAGS="-D__ATOMIC_VAR_FORCE_SYNC_MACROS" $(MAKE) OPTIMIZATION="-O0" MALLOC="libc" CFLAGS="-D__ATOMIC_VAR_FORCE_SYNC_MACROS" REDIS_CFLAGS="-I/usr/local/include" REDIS_LDFLAGS="-L/usr/local/lib"
src/help.h: src/help.h:
@../utils/generate-command-help.rb > help.h @../utils/generate-command-help.rb > help.h
@ -457,8 +479,8 @@ install: all
$(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_SERVER_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_BENCHMARK_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN) $(REDIS_INSTALL) $(REDIS_CLI_NAME) $(INSTALL_BIN)
$(REDIS_INSTALL) $(REDIS_CHECK_RDB_NAME) $(INSTALL_BIN) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_RDB_NAME)
$(REDIS_INSTALL) $(REDIS_CHECK_AOF_NAME) $(INSTALL_BIN) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_CHECK_AOF_NAME)
@ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME) @ln -sf $(REDIS_SERVER_NAME) $(INSTALL_BIN)/$(REDIS_SENTINEL_NAME)
uninstall: uninstall:

View File

@ -55,6 +55,10 @@ list *UsersToLoad; /* This is a list of users found in the configuration file
list *ACLLog; /* Our security log, the user is able to inspect that list *ACLLog; /* Our security log, the user is able to inspect that
using the ACL LOG command .*/ using the ACL LOG command .*/
static rax *commandId = NULL; /* Command name to id mapping */
static unsigned long nextid = 0; /* Next command id that has not been assigned */
struct ACLCategoryItem { struct ACLCategoryItem {
const char *name; const char *name;
uint64_t flag; uint64_t flag;
@ -88,18 +92,22 @@ struct ACLUserFlag {
const char *name; const char *name;
uint64_t flag; uint64_t flag;
} ACLUserFlags[] = { } ACLUserFlags[] = {
/* Note: the order here dictates the emitted order at ACLDescribeUser */
{"on", USER_FLAG_ENABLED}, {"on", USER_FLAG_ENABLED},
{"off", USER_FLAG_DISABLED}, {"off", USER_FLAG_DISABLED},
{"allkeys", USER_FLAG_ALLKEYS}, {"allkeys", USER_FLAG_ALLKEYS},
{"allchannels", USER_FLAG_ALLCHANNELS},
{"allcommands", USER_FLAG_ALLCOMMANDS}, {"allcommands", USER_FLAG_ALLCOMMANDS},
{"nopass", USER_FLAG_NOPASS}, {"nopass", USER_FLAG_NOPASS},
{"skip-sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD_SKIP},
{"sanitize-payload", USER_FLAG_SANITIZE_PAYLOAD},
{NULL,0} /* Terminator. */ {NULL,0} /* Terminator. */
}; };
void ACLResetSubcommandsForCommand(user *u, unsigned long id); void ACLResetSubcommandsForCommand(user *u, unsigned long id);
void ACLResetSubcommands(user *u); void ACLResetSubcommands(user *u);
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub); void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub);
void ACLFreeLogEntry(struct ACLLogEntry *le); void ACLFreeLogEntry(const void *le);
/* The length of the string representation of a hashed password. */ /* The length of the string representation of a hashed password. */
#define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2 #define HASH_PASSWORD_LEN SHA256_BLOCK_SIZE*2
@ -240,16 +248,20 @@ user *ACLCreateUser(const char *name, size_t namelen) {
if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL; if (raxFind(Users,(unsigned char*)name,namelen) != raxNotFound) return NULL;
user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL); user *u = (user*)zmalloc(sizeof(*u), MALLOC_LOCAL);
u->name = sdsnewlen(name,namelen); u->name = sdsnewlen(name,namelen);
u->flags = USER_FLAG_DISABLED; u->flags = USER_FLAG_DISABLED | g_pserver->acl_pubusub_default;
u->allowed_subcommands = NULL; u->allowed_subcommands = NULL;
u->passwords = listCreate(); u->passwords = listCreate();
u->patterns = listCreate(); u->patterns = listCreate();
u->channels = listCreate();
listSetMatchMethod(u->passwords,ACLListMatchSds); listSetMatchMethod(u->passwords,ACLListMatchSds);
listSetFreeMethod(u->passwords,ACLListFreeSds); listSetFreeMethod(u->passwords,ACLListFreeSds);
listSetDupMethod(u->passwords,ACLListDupSds); listSetDupMethod(u->passwords,ACLListDupSds);
listSetMatchMethod(u->patterns,ACLListMatchSds); listSetMatchMethod(u->patterns,ACLListMatchSds);
listSetFreeMethod(u->patterns,ACLListFreeSds); listSetFreeMethod(u->patterns,ACLListFreeSds);
listSetDupMethod(u->patterns,ACLListDupSds); listSetDupMethod(u->patterns,ACLListDupSds);
listSetMatchMethod(u->channels,ACLListMatchSds);
listSetFreeMethod(u->channels,ACLListFreeSds);
listSetDupMethod(u->channels,ACLListDupSds);
memset(u->allowed_commands,0,sizeof(u->allowed_commands)); memset(u->allowed_commands,0,sizeof(u->allowed_commands));
raxInsert(Users,(unsigned char*)name,namelen,u,NULL); raxInsert(Users,(unsigned char*)name,namelen,u,NULL);
return u; return u;
@ -278,6 +290,7 @@ void ACLFreeUser(user *u) {
sdsfree(u->name); sdsfree(u->name);
listRelease(u->passwords); listRelease(u->passwords);
listRelease(u->patterns); listRelease(u->patterns);
listRelease(u->channels);
ACLResetSubcommands(u); ACLResetSubcommands(u);
zfree(u); zfree(u);
} }
@ -291,14 +304,14 @@ void ACLFreeUserAndKillClients(user *u) {
listRewind(g_pserver->clients,&li); listRewind(g_pserver->clients,&li);
while ((ln = listNext(&li)) != NULL) { while ((ln = listNext(&li)) != NULL) {
client *c = (client*)listNodeValue(ln); client *c = (client*)listNodeValue(ln);
if (c->puser == u) { if (c->user == u) {
/* We'll free the conenction asynchronously, so /* We'll free the connection asynchronously, so
* in theory to set a different user is not needed. * in theory to set a different user is not needed.
* However if there are bugs in Redis, soon or later * However if there are bugs in Redis, soon or later
* this may result in some security hole: it's much * this may result in some security hole: it's much
* more defensive to set the default user and put * more defensive to set the default user and put
* it in non authenticated mode. */ * it in non authenticated mode. */
c->puser = DefaultUser; c->user = DefaultUser;
c->authenticated = 0; c->authenticated = 0;
/* We will write replies to this client later, so we can't /* We will write replies to this client later, so we can't
* close it directly even if async. */ * close it directly even if async. */
@ -318,8 +331,10 @@ void ACLFreeUserAndKillClients(user *u) {
void ACLCopyUser(user *dst, user *src) { void ACLCopyUser(user *dst, user *src) {
listRelease(dst->passwords); listRelease(dst->passwords);
listRelease(dst->patterns); listRelease(dst->patterns);
listRelease(dst->channels);
dst->passwords = listDup(src->passwords); dst->passwords = listDup(src->passwords);
dst->patterns = listDup(src->patterns); dst->patterns = listDup(src->patterns);
dst->channels = listDup(src->channels);
memcpy(dst->allowed_commands,src->allowed_commands, memcpy(dst->allowed_commands,src->allowed_commands,
sizeof(dst->allowed_commands)); sizeof(dst->allowed_commands));
dst->flags = src->flags; dst->flags = src->flags;
@ -601,9 +616,10 @@ sds ACLDescribeUser(user *u) {
/* Flags. */ /* Flags. */
for (int j = 0; ACLUserFlags[j].flag; j++) { for (int j = 0; ACLUserFlags[j].flag; j++) {
/* Skip the allcommands and allkeys flags because they'll be emitted /* Skip the allcommands, allkeys and allchannels flags because they'll
* later as ~* and +@all. */ * be emitted later as +@all, ~* and &*. */
if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS || if (ACLUserFlags[j].flag == USER_FLAG_ALLKEYS ||
ACLUserFlags[j].flag == USER_FLAG_ALLCHANNELS ||
ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue; ACLUserFlags[j].flag == USER_FLAG_ALLCOMMANDS) continue;
if (u->flags & ACLUserFlags[j].flag) { if (u->flags & ACLUserFlags[j].flag) {
res = sdscat(res,ACLUserFlags[j].name); res = sdscat(res,ACLUserFlags[j].name);
@ -635,6 +651,19 @@ sds ACLDescribeUser(user *u) {
} }
} }
/* Pub/sub channel patterns. */
if (u->flags & USER_FLAG_ALLCHANNELS) {
res = sdscatlen(res,"&* ",3);
} else {
listRewind(u->channels,&li);
while((ln = listNext(&li))) {
sds thispat = (sds)listNodeValue(ln);
res = sdscatlen(res,"&",1);
res = sdscatsds(res,thispat);
res = sdscatlen(res," ",1);
}
}
/* Command rules. */ /* Command rules. */
sds rules = ACLDescribeUserCommandRules(u); sds rules = ACLDescribeUserCommandRules(u);
res = sdscatsds(res,rules); res = sdscatsds(res,rules);
@ -680,7 +709,6 @@ void ACLResetSubcommands(user *u) {
u->allowed_subcommands = NULL; u->allowed_subcommands = NULL;
} }
/* Add a subcommand to the list of subcommands for the user 'u' and /* Add a subcommand to the list of subcommands for the user 'u' and
* the command id specified. */ * the command id specified. */
void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) { void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
@ -741,6 +769,12 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* It is possible to specify multiple patterns. * It is possible to specify multiple patterns.
* allkeys Alias for ~* * allkeys Alias for ~*
* resetkeys Flush the list of allowed keys patterns. * resetkeys Flush the list of allowed keys patterns.
* &<pattern> Add a pattern of channels that can be mentioned as part of
* Pub/Sub commands. For instance &* allows all the channels. The
* pattern is a glob-style pattern like the one of PSUBSCRIBE.
* It is possible to specify multiple patterns.
* allchannels Alias for &*
* resetchannels Flush the list of allowed keys patterns.
* ><password> Add this password to the list of valid password for the user. * ><password> Add this password to the list of valid password for the user.
* For example >mypass will add "mypass" to the list. * For example >mypass will add "mypass" to the list.
* This directive clears the "nopass" flag (see later). * This directive clears the "nopass" flag (see later).
@ -779,14 +813,14 @@ void ACLAddAllowedSubcommand(user *u, unsigned long id, const char *sub) {
* *
* When an error is returned, errno is set to the following values: * When an error is returned, errno is set to the following values:
* *
* EINVAL: The specified opcode is not understood or the key pattern is * EINVAL: The specified opcode is not understood or the key/channel pattern is
* invalid (contains non allowed characters). * invalid (contains non allowed characters).
* ENOENT: The command name or command category provided with + or - is not * ENOENT: The command name or command category provided with + or - is not
* known. * known.
* EBUSY: The subcommand you want to add is about a command that is currently
* fully added.
* EEXIST: You are adding a key pattern after "*" was already added. This is * EEXIST: You are adding a key pattern after "*" was already added. This is
* almost surely an error on the user side. * almost surely an error on the user side.
* EISDIR: You are adding a channel pattern after "*" was already added. This is
* almost surely an error on the user side.
* ENODEV: The password you are trying to remove from the user does not exist. * ENODEV: The password you are trying to remove from the user does not exist.
* EBADMSG: The hash you are trying to add is not a valid hash. * EBADMSG: The hash you are trying to add is not a valid hash.
*/ */
@ -799,6 +833,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
} else if (!strcasecmp(op,"off")) { } else if (!strcasecmp(op,"off")) {
u->flags |= USER_FLAG_DISABLED; u->flags |= USER_FLAG_DISABLED;
u->flags &= ~USER_FLAG_ENABLED; u->flags &= ~USER_FLAG_ENABLED;
} else if (!strcasecmp(op,"skip-sanitize-payload")) {
u->flags |= USER_FLAG_SANITIZE_PAYLOAD_SKIP;
u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD;
} else if (!strcasecmp(op,"sanitize-payload")) {
u->flags &= ~USER_FLAG_SANITIZE_PAYLOAD_SKIP;
u->flags |= USER_FLAG_SANITIZE_PAYLOAD;
} else if (!strcasecmp(op,"allkeys") || } else if (!strcasecmp(op,"allkeys") ||
!strcasecmp(op,"~*")) !strcasecmp(op,"~*"))
{ {
@ -807,6 +847,14 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
} else if (!strcasecmp(op,"resetkeys")) { } else if (!strcasecmp(op,"resetkeys")) {
u->flags &= ~USER_FLAG_ALLKEYS; u->flags &= ~USER_FLAG_ALLKEYS;
listEmpty(u->patterns); listEmpty(u->patterns);
} else if (!strcasecmp(op,"allchannels") ||
!strcasecmp(op,"&*"))
{
u->flags |= USER_FLAG_ALLCHANNELS;
listEmpty(u->channels);
} else if (!strcasecmp(op,"resetchannels")) {
u->flags &= ~USER_FLAG_ALLCHANNELS;
listEmpty(u->channels);
} else if (!strcasecmp(op,"allcommands") || } else if (!strcasecmp(op,"allcommands") ||
!strcasecmp(op,"+@all")) !strcasecmp(op,"+@all"))
{ {
@ -874,12 +922,29 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
} }
sds newpat = sdsnewlen(op+1,oplen-1); sds newpat = sdsnewlen(op+1,oplen-1);
listNode *ln = listSearchKey(u->patterns,newpat); listNode *ln = listSearchKey(u->patterns,newpat);
/* Avoid re-adding the same pattern multiple times. */ /* Avoid re-adding the same key pattern multiple times. */
if (ln == NULL) if (ln == NULL)
listAddNodeTail(u->patterns,newpat); listAddNodeTail(u->patterns,newpat);
else else
sdsfree(newpat); sdsfree(newpat);
u->flags &= ~USER_FLAG_ALLKEYS; u->flags &= ~USER_FLAG_ALLKEYS;
} else if (op[0] == '&') {
if (u->flags & USER_FLAG_ALLCHANNELS) {
errno = EISDIR;
return C_ERR;
}
if (ACLStringHasSpaces(op+1,oplen-1)) {
errno = EINVAL;
return C_ERR;
}
sds newpat = sdsnewlen(op+1,oplen-1);
listNode *ln = listSearchKey(u->channels,newpat);
/* Avoid re-adding the same channel pattern multiple times. */
if (ln == NULL)
listAddNodeTail(u->channels,newpat);
else
sdsfree(newpat);
u->flags &= ~USER_FLAG_ALLCHANNELS;
} else if (op[0] == '+' && op[1] != '@') { } else if (op[0] == '+' && op[1] != '@') {
if (strchr(op,'|') == NULL) { if (strchr(op,'|') == NULL) {
if (ACLLookupCommand(op+1) == NULL) { if (ACLLookupCommand(op+1) == NULL) {
@ -912,22 +977,12 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
return C_ERR; return C_ERR;
} }
/* The command should not be set right now in the command
* bitmap, because adding a subcommand of a fully added
* command is probably an error on the user side. */
unsigned long id = ACLGetCommandID(copy); unsigned long id = ACLGetCommandID(copy);
if (ACLGetUserCommandBit(u,id) == 1) { /* Add the subcommand to the list of valid ones, if the command is not set. */
zfree(copy); if (ACLGetUserCommandBit(u,id) == 0) {
errno = EBUSY; ACLAddAllowedSubcommand(u,id,sub);
return C_ERR;
} }
/* Add the subcommand to the list of valid ones. */
ACLAddAllowedSubcommand(u,id,sub);
/* We have to clear the command bit so that we force the
* subcommand check. */
ACLSetUserCommandBit(u,id,0);
zfree(copy); zfree(copy);
} }
} else if (op[0] == '-' && op[1] != '@') { } else if (op[0] == '-' && op[1] != '@') {
@ -947,7 +1002,9 @@ int ACLSetUser(user *u, const char *op, ssize_t oplen) {
} else if (!strcasecmp(op,"reset")) { } else if (!strcasecmp(op,"reset")) {
serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK); serverAssert(ACLSetUser(u,"resetpass",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK); serverAssert(ACLSetUser(u,"resetkeys",-1) == C_OK);
serverAssert(ACLSetUser(u,"resetchannels",-1) == C_OK);
serverAssert(ACLSetUser(u,"off",-1) == C_OK); serverAssert(ACLSetUser(u,"off",-1) == C_OK);
serverAssert(ACLSetUser(u,"sanitize-payload",-1) == C_OK);
serverAssert(ACLSetUser(u,"-@all",-1) == C_OK); serverAssert(ACLSetUser(u,"-@all",-1) == C_OK);
} else { } else {
errno = EINVAL; errno = EINVAL;
@ -964,15 +1021,16 @@ const char *ACLSetUserStringError(void) {
errmsg = "Unknown command or category name in ACL"; errmsg = "Unknown command or category name in ACL";
else if (errno == EINVAL) else if (errno == EINVAL)
errmsg = "Syntax error"; errmsg = "Syntax error";
else if (errno == EBUSY)
errmsg = "Adding a subcommand of a command already fully "
"added is not allowed. Remove the command to start. "
"Example: -DEBUG +DEBUG|DIGEST";
else if (errno == EEXIST) else if (errno == EEXIST)
errmsg = "Adding a pattern after the * pattern (or the " errmsg = "Adding a pattern after the * pattern (or the "
"'allkeys' flag) is not valid and does not have any " "'allkeys' flag) is not valid and does not have any "
"effect. Try 'resetkeys' to start with an empty " "effect. Try 'resetkeys' to start with an empty "
"list of patterns"; "list of patterns";
else if (errno == EISDIR)
errmsg = "Adding a pattern after the * pattern (or the "
"'allchannels' flag) is not valid and does not have any "
"effect. Try 'resetchannels' to start with an empty "
"list of channels";
else if (errno == ENODEV) else if (errno == ENODEV)
errmsg = "The password you are trying to remove from the user does " errmsg = "The password you are trying to remove from the user does "
"not exist"; "not exist";
@ -988,6 +1046,7 @@ void ACLInitDefaultUser(void) {
DefaultUser = ACLCreateUser("default",7); DefaultUser = ACLCreateUser("default",7);
ACLSetUser(DefaultUser,"+@all",-1); ACLSetUser(DefaultUser,"+@all",-1);
ACLSetUser(DefaultUser,"~*",-1); ACLSetUser(DefaultUser,"~*",-1);
ACLSetUser(DefaultUser,"&*",-1);
ACLSetUser(DefaultUser,"on",-1); ACLSetUser(DefaultUser,"on",-1);
ACLSetUser(DefaultUser,"nopass",-1); ACLSetUser(DefaultUser,"nopass",-1);
} }
@ -998,7 +1057,6 @@ void ACLInit(void) {
UsersToLoad = listCreate(); UsersToLoad = listCreate();
ACLLog = listCreate(); ACLLog = listCreate();
ACLInitDefaultUser(); ACLInitDefaultUser();
g_pserver->requirepass = NULL; /* Only used for backward compatibility. */
} }
/* Check the username and password pair and return C_OK if they are valid, /* Check the username and password pair and return C_OK if they are valid,
@ -1052,7 +1110,7 @@ int ACLCheckUserCredentials(robj *username, robj *password) {
int ACLAuthenticateUser(client *c, robj *username, robj *password) { int ACLAuthenticateUser(client *c, robj *username, robj *password) {
if (ACLCheckUserCredentials(username,password) == C_OK) { if (ACLCheckUserCredentials(username,password) == C_OK) {
c->authenticated = 1; c->authenticated = 1;
c->puser = ACLGetUserByName((sds)ptrFromObj(username),sdslen((sds)ptrFromObj(username))); c->user = ACLGetUserByName((sds)ptrFromObj(username),sdslen((sds)ptrFromObj(username)));
moduleNotifyUserChanged(c); moduleNotifyUserChanged(c);
return C_OK; return C_OK;
} else { } else {
@ -1068,18 +1126,16 @@ int ACLAuthenticateUser(client *c, robj *username, robj *password) {
* command name, so that a command retains the same ID in case of modules that * command name, so that a command retains the same ID in case of modules that
* are unloaded and later reloaded. */ * are unloaded and later reloaded. */
unsigned long ACLGetCommandID(const char *cmdname) { unsigned long ACLGetCommandID(const char *cmdname) {
static rax *map = NULL;
static unsigned long nextid = 0;
sds lowername = sdsnew(cmdname); sds lowername = sdsnew(cmdname);
sdstolower(lowername); sdstolower(lowername);
if (map == NULL) map = raxNew(); if (commandId == NULL) commandId = raxNew();
void *id = raxFind(map,(unsigned char*)lowername,sdslen(lowername)); void *id = raxFind(commandId,(unsigned char*)lowername,sdslen(lowername));
if (id != raxNotFound) { if (id != raxNotFound) {
sdsfree(lowername); sdsfree(lowername);
return (unsigned long)id; return (unsigned long)id;
} }
raxInsert(map,(unsigned char*)lowername,strlen(lowername), raxInsert(commandId,(unsigned char*)lowername,strlen(lowername),
(void*)nextid,NULL); (void*)nextid,NULL);
sdsfree(lowername); sdsfree(lowername);
unsigned long thisid = nextid; unsigned long thisid = nextid;
@ -1097,6 +1153,13 @@ unsigned long ACLGetCommandID(const char *cmdname) {
return thisid; return thisid;
} }
/* Clear command id table and reset nextid to 0. */
void ACLClearCommandID(void) {
if (commandId) raxFree(commandId);
commandId = NULL;
nextid = 0;
}
/* Return an username by its name, or NULL if the user does not exist. */ /* Return an username by its name, or NULL if the user does not exist. */
user *ACLGetUserByName(const char *name, size_t namelen) { user *ACLGetUserByName(const char *name, size_t namelen) {
void *myuser = raxFind(Users,(unsigned char*)name,namelen); void *myuser = raxFind(Users,(unsigned char*)name,namelen);
@ -1106,7 +1169,7 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
/* Check if the command is ready to be executed in the client 'c', already /* Check if the command is ready to be executed in the client 'c', already
* referenced by c->cmd, and can be executed by this client according to the * referenced by c->cmd, and can be executed by this client according to the
* ACLs associated to the client user c->puser. * ACLs associated to the client user c->user.
* *
* If the user can execute the command ACL_OK is returned, otherwise * If the user can execute the command ACL_OK is returned, otherwise
* ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the * ACL_DENIED_CMD or ACL_DENIED_KEY is returned: the first in case the
@ -1114,7 +1177,7 @@ user *ACLGetUserByName(const char *name, size_t namelen) {
* command, the second if the command is denied because the user is trying * command, the second if the command is denied because the user is trying
* to access keys that are not among the specified patterns. */ * to access keys that are not among the specified patterns. */
int ACLCheckCommandPerm(client *c, int *keyidxptr) { int ACLCheckCommandPerm(client *c, int *keyidxptr) {
user *u = c->puser; user *u = c->user;
uint64_t id = c->cmd->id; uint64_t id = c->cmd->id;
/* If there is no associated user, the connection can run anything. */ /* If there is no associated user, the connection can run anything. */
@ -1149,7 +1212,7 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
/* Check if the user can execute commands explicitly touching the keys /* Check if the user can execute commands explicitly touching the keys
* mentioned in the command arguments. */ * mentioned in the command arguments. */
if (!(c->puser->flags & USER_FLAG_ALLKEYS) && if (!(c->user->flags & USER_FLAG_ALLKEYS) &&
(c->cmd->getkeys_proc || c->cmd->firstkey)) (c->cmd->getkeys_proc || c->cmd->firstkey))
{ {
getKeysResult result = GETKEYS_RESULT_INIT; getKeysResult result = GETKEYS_RESULT_INIT;
@ -1187,6 +1250,119 @@ int ACLCheckCommandPerm(client *c, int *keyidxptr) {
return ACL_OK; return ACL_OK;
} }
/* Check if the provided channel is whitelisted by the given allowed channels
* list. Glob-style pattern matching is employed, unless the literal flag is
* set. Returns ACL_OK if access is granted or ACL_DENIED_CHANNEL otherwise. */
int ACLCheckPubsubChannelPerm(sds channel, list *allowed, int literal) {
listIter li;
listNode *ln;
size_t clen = sdslen(channel);
int match = 0;
listRewind(allowed,&li);
while((ln = listNext(&li))) {
sds pattern = (sds)listNodeValue(ln);
size_t plen = sdslen(pattern);
if ((literal && !sdscmp(pattern,channel)) ||
(!literal && stringmatchlen(pattern,plen,channel,clen,0)))
{
match = 1;
break;
}
}
if (!match) {
return ACL_DENIED_CHANNEL;
}
return ACL_OK;
}
/* Check if the user's existing pub/sub clients violate the ACL pub/sub
* permissions specified via the upcoming argument, and kill them if so. */
void ACLKillPubsubClientsIfNeeded(user *u, list *upcoming) {
listIter li, lpi;
listNode *ln, *lpn;
robj *o;
int kill = 0;
/* Nothing to kill when the upcoming are a literal super set of the original
* permissions. */
listRewind(u->channels,&li);
while (!kill && ((ln = listNext(&li)) != NULL)) {
sds pattern = (sds)listNodeValue(ln);
kill = (ACLCheckPubsubChannelPerm(pattern,upcoming,1) ==
ACL_DENIED_CHANNEL);
}
if (!kill) return;
/* Scan all connected clients to find the user's pub/subs. */
listRewind(g_pserver->clients,&li);
while ((ln = listNext(&li)) != NULL) {
client *c = (client*)listNodeValue(ln);
kill = 0;
if (c->user == u && getClientType(c) == CLIENT_TYPE_PUBSUB) {
/* Check for pattern violations. */
listRewind(c->pubsub_patterns,&lpi);
while (!kill && ((lpn = listNext(&lpi)) != NULL)) {
o = (robj*)lpn->value;
kill = (ACLCheckPubsubChannelPerm(szFromObj(o),upcoming,1) ==
ACL_DENIED_CHANNEL);
}
/* Check for channel violations. */
if (!kill) {
dictIterator *di = dictGetIterator(c->pubsub_channels);
dictEntry *de;
while (!kill && ((de = dictNext(di)) != NULL)) {
o = (robj*)dictGetKey(de);
kill = (ACLCheckPubsubChannelPerm(szFromObj(o),upcoming,0) ==
ACL_DENIED_CHANNEL);
}
dictReleaseIterator(di);
}
/* Kill it. */
if (kill) {
freeClientAsync(c);
}
}
}
}
/* Check if the pub/sub channels of the command, that's ready to be executed in
* the client 'c', can be executed by this client according to the ACLs channels
* associated to the client user c->user.
*
* idx and count are the index and count of channel arguments from the
* command. The literal argument controls whether the user's ACL channels are
* evaluated as literal values or matched as glob-like patterns.
*
* If the user can execute the command ACL_OK is returned, otherwise
* ACL_DENIED_CHANNEL. */
int ACLCheckPubsubPerm(client *c, int idx, int count, int literal, int *idxptr) {
user *u = c->user;
/* If there is no associated user, the connection can run anything. */
if (u == NULL) return ACL_OK;
/* Check if the user can access the channels mentioned in the command's
* arguments. */
if (!(c->user->flags & USER_FLAG_ALLCHANNELS)) {
for (int j = idx; j < idx+count; j++) {
if (ACLCheckPubsubChannelPerm(szFromObj(c->argv[j]),u->channels,literal)
!= ACL_OK) {
if (idxptr) *idxptr = j;
return ACL_DENIED_CHANNEL;
}
}
}
/* If we survived all the above checks, the user can execute the
* command. */
return ACL_OK;
}
/* ============================================================================= /* =============================================================================
* ACL loading / saving functions * ACL loading / saving functions
* ==========================================================================*/ * ==========================================================================*/
@ -1588,8 +1764,8 @@ int ACLLogMatchEntry(ACLLogEntry *a, ACLLogEntry *b) {
} }
/* Release an ACL log entry. */ /* Release an ACL log entry. */
void ACLFreeLogEntry(ACLLogEntry *leptr) { void ACLFreeLogEntry(const void *leptr) {
ACLLogEntry *le = leptr; ACLLogEntry *le = (ACLLogEntry*)leptr;
sdsfree(le->object); sdsfree(le->object);
sdsfree(le->username); sdsfree(le->username);
sdsfree(le->cinfo); sdsfree(le->cinfo);
@ -1602,24 +1778,25 @@ void ACLFreeLogEntry(ACLLogEntry *leptr) {
* the log entry instead of creating many entries for very similar ACL * the log entry instead of creating many entries for very similar ACL
* rules issues. * rules issues.
* *
* The keypos argument is only used when the reason is ACL_DENIED_KEY, since * The argpos argument is used when the reason is ACL_DENIED_KEY or
* it allows the function to log the key name that caused the problem. * ACL_DENIED_CHANNEL, since it allows the function to log the key or channel
* Similarly the username is only passed when we failed to authenticate the * name that caused the problem. Similarly the username is only passed when we
* user with AUTH or HELLO, for the ACL_DENIED_AUTH reason. Otherwise * failed to authenticate the user with AUTH or HELLO, for the ACL_DENIED_AUTH
* it will just be NULL. * reason. Otherwise it will just be NULL.
*/ */
void addACLLogEntry(client *c, int reason, int keypos, sds username) { void addACLLogEntry(client *c, int reason, int argpos, sds username) {
/* Create a new entry. */ /* Create a new entry. */
struct ACLLogEntry *le = (ACLLogEntry*)zmalloc(sizeof(*le)); struct ACLLogEntry *le = (ACLLogEntry*)zmalloc(sizeof(*le));
le->count = 1; le->count = 1;
le->reason = reason; le->reason = reason;
le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->puser->name); le->username = sdsdup(reason == ACL_DENIED_AUTH ? username : c->user->name);
le->ctime = mstime(); le->ctime = mstime();
switch(reason) { switch(reason) {
case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break; case ACL_DENIED_CMD: le->object = sdsnew(c->cmd->name); break;
case ACL_DENIED_KEY: le->object = sdsnew(szFromObj(c->argv[keypos])); break; case ACL_DENIED_KEY: le->object = sdsdup(szFromObj(c->argv[argpos])); break;
case ACL_DENIED_AUTH: le->object = sdsnew(szFromObj(c->argv[0])); break; case ACL_DENIED_CHANNEL: le->object = sdsdup(szFromObj(c->argv[argpos])); break;
case ACL_DENIED_AUTH: le->object = sdsdup(szFromObj(c->argv[0])); break;
default: le->object = sdsempty(); default: le->object = sdsempty();
} }
@ -1666,7 +1843,7 @@ void addACLLogEntry(client *c, int reason, int keypos, sds username) {
le->cinfo = NULL; le->cinfo = NULL;
ACLFreeLogEntry(le); ACLFreeLogEntry(le);
} else { } else {
/* Add it to our list of entires. We'll have to trim the list /* Add it to our list of entries. We'll have to trim the list
* to its maximum size. */ * to its maximum size. */
listAddNodeHead(ACLLog, le); listAddNodeHead(ACLLog, le);
while(listLength(ACLLog) > g_pserver->acllog_max_len) { while(listLength(ACLLog) > g_pserver->acllog_max_len) {
@ -1727,6 +1904,11 @@ void aclCommand(client *c) {
} }
} }
/* Existing pub/sub clients authenticated with the user may need to be
* disconnected if (some of) their channel permissions were revoked. */
if (u && !(tempu->flags & USER_FLAG_ALLCHANNELS))
ACLKillPubsubClientsIfNeeded(u,tempu->channels);
/* Overwrite the user with the temporary user we modified above. */ /* Overwrite the user with the temporary user we modified above. */
if (!u) u = ACLCreateUser(username,sdslen(username)); if (!u) u = ACLCreateUser(username,sdslen(username));
serverAssert(u != NULL); serverAssert(u != NULL);
@ -1762,7 +1944,7 @@ void aclCommand(client *c) {
return; return;
} }
addReplyMapLen(c,4); addReplyMapLen(c,5);
/* Flags */ /* Flags */
addReplyBulkCString(c,"flags"); addReplyBulkCString(c,"flags");
@ -1807,6 +1989,22 @@ void aclCommand(client *c) {
addReplyBulkCBuffer(c,thispat,sdslen(thispat)); addReplyBulkCBuffer(c,thispat,sdslen(thispat));
} }
} }
/* Pub/sub patterns */
addReplyBulkCString(c,"channels");
if (u->flags & USER_FLAG_ALLCHANNELS) {
addReplyArrayLen(c,1);
addReplyBulkCBuffer(c,"*",1);
} else {
addReplyArrayLen(c,listLength(u->channels));
listIter li;
listNode *ln;
listRewind(u->channels,&li);
while((ln = listNext(&li))) {
sds thispat = (sds)listNodeValue(ln);
addReplyBulkCBuffer(c,thispat,sdslen(thispat));
}
}
} else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) && } else if ((!strcasecmp(sub,"list") || !strcasecmp(sub,"users")) &&
c->argc == 2) c->argc == 2)
{ {
@ -1832,8 +2030,8 @@ void aclCommand(client *c) {
} }
raxStop(&ri); raxStop(&ri);
} else if (!strcasecmp(sub,"whoami") && c->argc == 2) { } else if (!strcasecmp(sub,"whoami") && c->argc == 2) {
if (c->puser != NULL) { if (c->user != NULL) {
addReplyBulkCBuffer(c,c->puser->name,sdslen(c->puser->name)); addReplyBulkCBuffer(c,c->user->name,sdslen(c->user->name));
} else { } else {
addReplyNull(c); addReplyNull(c);
} }
@ -1911,7 +2109,7 @@ void aclCommand(client *c) {
* the "RESET" command in order to flush the old entries. */ * the "RESET" command in order to flush the old entries. */
if (c->argc == 3) { if (c->argc == 3) {
if (!strcasecmp(szFromObj(c->argv[2]),"reset")) { if (!strcasecmp(szFromObj(c->argv[2]),"reset")) {
listSetFreeMethod(ACLLog,(void(*)(const void*))ACLFreeLogEntry); listSetFreeMethod(ACLLog,ACLFreeLogEntry);
listEmpty(ACLLog); listEmpty(ACLLog);
listSetFreeMethod(ACLLog,NULL); listSetFreeMethod(ACLLog,NULL);
addReply(c,shared.ok); addReply(c,shared.ok);
@ -1940,10 +2138,11 @@ void aclCommand(client *c) {
addReplyLongLong(c,le->count); addReplyLongLong(c,le->count);
addReplyBulkCString(c,"reason"); addReplyBulkCString(c,"reason");
const char *reasonstr = "INVALID_REASON"; const char *reasonstr;
switch(le->reason) { switch(le->reason) {
case ACL_DENIED_CMD: reasonstr="command"; break; case ACL_DENIED_CMD: reasonstr="command"; break;
case ACL_DENIED_KEY: reasonstr="key"; break; case ACL_DENIED_KEY: reasonstr="key"; break;
case ACL_DENIED_CHANNEL: reasonstr="channel"; break;
case ACL_DENIED_AUTH: reasonstr="auth"; break; case ACL_DENIED_AUTH: reasonstr="auth"; break;
default: reasonstr="unknown"; default: reasonstr="unknown";
} }
@ -1971,18 +2170,30 @@ void aclCommand(client *c) {
} }
} else if (c->argc == 2 && !strcasecmp(sub,"help")) { } else if (c->argc == 2 && !strcasecmp(sub,"help")) {
const char *help[] = { const char *help[] = {
"LOAD -- Reload users from the ACL file.", "CAT [<category>]",
"SAVE -- Save the current config to the ACL file.", " List all commands that belong to <category>, or all command categories",
"LIST -- Show user details in config file format.", " when no category is specified.",
"USERS -- List all the registered usernames.", "DELUSER <username> [<username> ...]",
"SETUSER <username> [attribs ...] -- Create or modify a user.", " Delete a list of users.",
"GETUSER <username> -- Get the user details.", "GETUSER <username>",
"DELUSER <username> [...] -- Delete a list of users.", " Get the user's details.",
"CAT -- List available categories.", "GENPASS [<bits>]",
"CAT <category> -- List commands inside category.", " Generate a secure 256-bit user password. The optional `bits` argument can",
"GENPASS [<bits>] -- Generate a secure user password.", " be used to specify a different size.",
"WHOAMI -- Return the current connection username.", "LIST",
"LOG [<count> | RESET] -- Show the ACL log entries.", " Show users details in config file format.",
"LOAD",
" Reload users from the ACL file.",
"LOG [<count> | RESET]",
" Show the ACL log entries.",
"SAVE",
" Save the current config to the ACL file.",
"SETUSER <username> <attribute> [<attribute> ...]",
" Create or modify a user with the specified attributes.",
"USERS",
" List all the registered usernames.",
"WHOAMI",
" Return the current connection username.",
NULL NULL
}; };
addReplyHelp(c,help); addReplyHelp(c,help);
@ -2011,7 +2222,7 @@ void addReplyCommandCategories(client *c, struct redisCommand *cmd) {
void authCommand(client *c) { void authCommand(client *c) {
/* Only two or three argument forms are allowed. */ /* Only two or three argument forms are allowed. */
if (c->argc > 3) { if (c->argc > 3) {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
@ -2028,7 +2239,7 @@ void authCommand(client *c) {
return; return;
} }
username = createStringObject("default",7); username = shared.default_username;
password = c->argv[1]; password = c->argv[1];
} else { } else {
username = c->argv[1]; username = c->argv[1];
@ -2038,11 +2249,19 @@ void authCommand(client *c) {
if (ACLAuthenticateUser(c,username,password) == C_OK) { if (ACLAuthenticateUser(c,username,password) == C_OK) {
addReply(c,shared.ok); addReply(c,shared.ok);
} else { } else {
addReplyError(c,"-WRONGPASS invalid username-password pair"); addReplyError(c,"-WRONGPASS invalid username-password pair or user is disabled.");
}
} }
/* Free the "default" string object we created for the two /* Set the password for the "default" ACL user. This implements supports for
* arguments form. */ * requirepass config, so passing in NULL will set the user to be nopass. */
if (c->argc == 2) decrRefCount(username); void ACLUpdateDefaultUserPassword(sds password) {
ACLSetUser(DefaultUser,"resetpass",-1);
if (password) {
sds aclop = sdscatlen(sdsnew(">"), password, sdslen(password));
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
} else {
ACLSetUser(DefaultUser,"nopass",-1);
}
} }

View File

@ -360,7 +360,8 @@ void listRotateHeadToTail(list *list) {
/* Add all the elements of the list 'o' at the end of the /* Add all the elements of the list 'o' at the end of the
* list 'l'. The list 'other' remains empty but otherwise valid. */ * list 'l'. The list 'other' remains empty but otherwise valid. */
void listJoin(list *l, list *o) { void listJoin(list *l, list *o) {
if (o->head) if (o->len == 0) return;
o->head->prev = l->tail; o->head->prev = l->tail;
if (l->tail) if (l->tail)
@ -368,7 +369,7 @@ void listJoin(list *l, list *o) {
else else
l->head = o->head; l->head = o->head;
if (o->tail) l->tail = o->tail; l->tail = o->tail;
l->len += o->len; l->len += o->len;
/* Setup other as an empty list. */ /* Setup other as an empty list. */

View File

@ -30,6 +30,10 @@
* POSSIBILITY OF SUCH DAMAGE. * POSSIBILITY OF SUCH DAMAGE.
*/ */
#include "ae.h"
#include "anet.h"
#include "fastlock.h"
#include <condition_variable> #include <condition_variable>
#include <atomic> #include <atomic>
#include <mutex> #include <mutex>
@ -271,12 +275,13 @@ aeEventLoop *aeCreateEventLoop(int setsize) {
aeEventLoop *eventLoop; aeEventLoop *eventLoop;
int i; int i;
monotonicInit(); /* just in case the calling app didn't initialize */
if ((eventLoop = (aeEventLoop*)zmalloc(sizeof(*eventLoop), MALLOC_LOCAL)) == NULL) goto err; if ((eventLoop = (aeEventLoop*)zmalloc(sizeof(*eventLoop), MALLOC_LOCAL)) == NULL) goto err;
eventLoop->events = (aeFileEvent*)zmalloc(sizeof(aeFileEvent)*setsize, MALLOC_LOCAL); eventLoop->events = (aeFileEvent*)zmalloc(sizeof(aeFileEvent)*setsize, MALLOC_LOCAL);
eventLoop->fired = (aeFiredEvent*)zmalloc(sizeof(aeFiredEvent)*setsize, MALLOC_LOCAL); eventLoop->fired = (aeFiredEvent*)zmalloc(sizeof(aeFiredEvent)*setsize, MALLOC_LOCAL);
if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err; if (eventLoop->events == NULL || eventLoop->fired == NULL) goto err;
eventLoop->setsize = setsize; eventLoop->setsize = setsize;
eventLoop->lastTime = time(NULL);
eventLoop->timeEventHead = NULL; eventLoop->timeEventHead = NULL;
eventLoop->timeEventNextId = 0; eventLoop->timeEventNextId = 0;
eventLoop->stop = 0; eventLoop->stop = 0;
@ -448,29 +453,6 @@ extern "C" int aeGetFileEvents(aeEventLoop *eventLoop, int fd) {
return fe->mask; return fe->mask;
} }
static void aeGetTime(long *seconds, long *milliseconds)
{
struct timeval tv;
gettimeofday(&tv, NULL);
*seconds = tv.tv_sec;
*milliseconds = tv.tv_usec/1000;
}
static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
long cur_sec, cur_ms, when_sec, when_ms;
aeGetTime(&cur_sec, &cur_ms);
when_sec = cur_sec + milliseconds/1000;
when_ms = cur_ms + milliseconds%1000;
if (when_ms >= 1000) {
when_sec ++;
when_ms -= 1000;
}
*sec = when_sec;
*ms = when_ms;
}
extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds, extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData, aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc) aeEventFinalizerProc *finalizerProc)
@ -482,7 +464,7 @@ extern "C" long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long millise
te = (aeTimeEvent*)zmalloc(sizeof(*te), MALLOC_LOCAL); te = (aeTimeEvent*)zmalloc(sizeof(*te), MALLOC_LOCAL);
if (te == NULL) return AE_ERR; if (te == NULL) return AE_ERR;
te->id = id; te->id = id;
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms); te->when = getMonotonicUs() + milliseconds * 1000;
te->timeProc = proc; te->timeProc = proc;
te->finalizerProc = finalizerProc; te->finalizerProc = finalizerProc;
te->clientData = clientData; te->clientData = clientData;
@ -509,10 +491,8 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
return AE_ERR; /* NO event with the specified ID found */ return AE_ERR; /* NO event with the specified ID found */
} }
/* Search the first timer to fire. /* How many milliseconds until the first timer should fire.
* This operation is useful to know how many time the select can be * If there are no timers, -1 is returned.
* put in sleep without to delay any event.
* If there are no timers NULL is returned.
* *
* Note that's O(N) since time events are unsorted. * Note that's O(N) since time events are unsorted.
* Possible optimizations (not needed by Redis so far, but...): * Possible optimizations (not needed by Redis so far, but...):
@ -520,20 +500,20 @@ extern "C" int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
* Much better but still insertion or deletion of timers is O(N). * Much better but still insertion or deletion of timers is O(N).
* 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)). * 2) Use a skiplist to have this operation as O(1) and insertion as O(log(N)).
*/ */
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop) static long msUntilEarliestTimer(aeEventLoop *eventLoop) {
{
serverAssert(g_eventLoopThisThread == NULL || g_eventLoopThisThread == eventLoop);
aeTimeEvent *te = eventLoop->timeEventHead; aeTimeEvent *te = eventLoop->timeEventHead;
aeTimeEvent *nearest = NULL; if (te == NULL) return -1;
aeTimeEvent *earliest = NULL;
while (te) { while (te) {
if (!nearest || te->when_sec < nearest->when_sec || if (!earliest || te->when < earliest->when)
(te->when_sec == nearest->when_sec && earliest = te;
te->when_ms < nearest->when_ms))
nearest = te;
te = te->next; te = te->next;
} }
return nearest;
monotime now = getMonotonicUs();
return (now >= earliest->when)
? 0 : (long)((earliest->when - now) / 1000);
} }
/* Process time events */ /* Process time events */
@ -542,29 +522,11 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
int processed = 0; int processed = 0;
aeTimeEvent *te; aeTimeEvent *te;
long long maxId; long long maxId;
time_t now = time(NULL);
/* If the system clock is moved to the future, and then set back to the
* right value, time events may be delayed in a random way. Often this
* means that scheduled operations will not be performed soon enough.
*
* Here we try to detect system clock skews, and force all the time
* events to be processed ASAP when this happens: the idea is that
* processing events earlier is less dangerous than delaying them
* indefinitely, and practice suggests it is. */
if (now < eventLoop->lastTime) {
te = eventLoop->timeEventHead;
while(te) {
te->when_sec = 0;
te = te->next;
}
}
eventLoop->lastTime = now;
te = eventLoop->timeEventHead; te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1; maxId = eventLoop->timeEventNextId-1;
monotime now = getMonotonicUs();
while(te) { while(te) {
long now_sec, now_ms;
long long id; long long id;
/* Remove events scheduled for deletion. */ /* Remove events scheduled for deletion. */
@ -586,6 +548,7 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
if (te->finalizerProc) { if (te->finalizerProc) {
if (!ulock.owns_lock()) ulock.lock(); if (!ulock.owns_lock()) ulock.lock();
te->finalizerProc(eventLoop, te->clientData); te->finalizerProc(eventLoop, te->clientData);
now = getMonotonicUs();
} }
zfree(te); zfree(te);
te = next; te = next;
@ -601,10 +564,8 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
te = te->next; te = te->next;
continue; continue;
} }
aeGetTime(&now_sec, &now_ms);
if (now_sec > te->when_sec || if (te->when <= now) {
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
if (!ulock.owns_lock()) ulock.lock(); if (!ulock.owns_lock()) ulock.lock();
int retval; int retval;
@ -613,8 +574,9 @@ static int processTimeEvents(aeEventLoop *eventLoop) {
retval = te->timeProc(eventLoop, id, te->clientData); retval = te->timeProc(eventLoop, id, te->clientData);
te->refcount--; te->refcount--;
processed++; processed++;
now = getMonotonicUs();
if (retval != AE_NOMORE) { if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms); te->when = now + retval * 1000;
} else { } else {
te->id = AE_DELETED_EVENT_ID; te->id = AE_DELETED_EVENT_ID;
} }
@ -704,37 +666,23 @@ int aeProcessEvents(aeEventLoop *eventLoop, int flags)
/* Nothing to do? return ASAP */ /* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0; if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
/* Note that we want call select() even if there are no /* Note that we want to call select() even if there are no
* file events to process as long as we want to process time * file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready * events, in order to sleep until the next time event is ready
* to fire. */ * to fire. */
if (eventLoop->maxfd != -1 || if (eventLoop->maxfd != -1 ||
((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) { ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int j; int j;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp; struct timeval tv, *tvp;
long msUntilTimer = -1;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT)) if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop); msUntilTimer = msUntilEarliestTimer(eventLoop);
if (shortest) {
long now_sec, now_ms;
aeGetTime(&now_sec, &now_ms); if (msUntilTimer >= 0) {
tv.tv_sec = msUntilTimer / 1000;
tv.tv_usec = (msUntilTimer % 1000) * 1000;
tvp = &tv; tvp = &tv;
/* How many milliseconds we need to wait for the next
* time event to fire? */
long long ms =
(shortest->when_sec - now_sec)*1000 +
shortest->when_ms - now_ms;
if (ms > 0) {
tvp->tv_sec = ms/1000;
tvp->tv_usec = (ms % 1000)*1000;
} else {
tvp->tv_sec = 0;
tvp->tv_usec = 0;
}
} else { } else {
/* If we have to check for events but need to return /* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to set the timeout * ASAP because of AE_DONT_WAIT we need to set the timeout
@ -816,14 +764,8 @@ void aeMain(aeEventLoop *eventLoop) {
eventLoop->stop = 0; eventLoop->stop = 0;
g_eventLoopThisThread = eventLoop; g_eventLoopThisThread = eventLoop;
while (!eventLoop->stop) { while (!eventLoop->stop) {
if (eventLoop->beforesleep != NULL) {
std::unique_lock<decltype(g_lock)> ulock(g_lock, std::defer_lock);
if (!(eventLoop->beforesleepFlags & AE_SLEEP_THREADSAFE))
ulock.lock();
eventLoop->beforesleep(eventLoop);
}
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_AFTER_SLEEP); aeProcessEvents(eventLoop, AE_ALL_EVENTS|AE_CALL_BEFORE_SLEEP|AE_CALL_AFTER_SLEEP);
serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing serverAssert(!aeThreadOwnsLock()); // we should have relinquished it after processing
} }
} }

View File

@ -36,7 +36,7 @@
#ifdef __cplusplus #ifdef __cplusplus
#include <functional> #include <functional>
#endif #endif
#include <time.h> #include "monotonic.h"
#include "fastlock.h" #include "fastlock.h"
#ifdef __cplusplus #ifdef __cplusplus
@ -91,8 +91,7 @@ typedef struct aeFileEvent {
/* Time event structure */ /* Time event structure */
typedef struct aeTimeEvent { typedef struct aeTimeEvent {
long long id; /* time event identifier. */ long long id; /* time event identifier. */
long when_sec; /* seconds */ monotime when;
long when_ms; /* milliseconds */
aeTimeProc *timeProc; aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc; aeEventFinalizerProc *finalizerProc;
void *clientData; void *clientData;
@ -113,7 +112,6 @@ typedef struct aeEventLoop {
int maxfd; /* highest file descriptor currently registered */ int maxfd; /* highest file descriptor currently registered */
int setsize; /* max number of file descriptors tracked */ int setsize; /* max number of file descriptors tracked */
long long timeEventNextId; long long timeEventNextId;
time_t lastTime; /* Used to detect system clock skew */
aeFileEvent *events; /* Registered events */ aeFileEvent *events; /* Registered events */
aeFiredEvent *fired; /* Fired events */ aeFiredEvent *fired; /* Fired events */
aeTimeEvent *timeEventHead; aeTimeEvent *timeEventHead;

View File

@ -51,6 +51,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state); zfree(state);
return -1; return -1;
} }
anetCloexec(state->epfd);
eventLoop->apidata = state; eventLoop->apidata = state;
return 0; return 0;
} }

View File

@ -67,7 +67,7 @@ static int evport_debug = 0;
typedef struct aeApiState { typedef struct aeApiState {
int portfd; /* event port */ int portfd; /* event port */
int npending; /* # of pending fds */ uint_t npending; /* # of pending fds */
int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */ int pending_fds[MAX_EVENT_BATCHSZ]; /* pending fds */
int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */ int pending_masks[MAX_EVENT_BATCHSZ]; /* pending fds' masks */
} aeApiState; } aeApiState;
@ -82,6 +82,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state); zfree(state);
return -1; return -1;
} }
anetCloexec(state->portfd);
state->npending = 0; state->npending = 0;
@ -95,6 +96,8 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
} }
static int aeApiResize(aeEventLoop *eventLoop, int setsize) { static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
(void) eventLoop;
(void) setsize;
/* Nothing to resize here. */ /* Nothing to resize here. */
return 0; return 0;
} }
@ -107,7 +110,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
} }
static int aeApiLookupPending(aeApiState *state, int fd) { static int aeApiLookupPending(aeApiState *state, int fd) {
int i; uint_t i;
for (i = 0; i < state->npending; i++) { for (i = 0; i < state->npending; i++) {
if (state->pending_fds[i] == fd) if (state->pending_fds[i] == fd)
@ -243,7 +246,7 @@ static void aeApiDelEvent(aeEventLoop *eventLoop, int fd, int mask) {
static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) { static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
aeApiState *state = eventLoop->apidata; aeApiState *state = eventLoop->apidata;
struct timespec timeout, *tsp; struct timespec timeout, *tsp;
int mask, i; uint_t mask, i;
uint_t nevents; uint_t nevents;
port_event_t event[MAX_EVENT_BATCHSZ]; port_event_t event[MAX_EVENT_BATCHSZ];

View File

@ -53,6 +53,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
zfree(state); zfree(state);
return -1; return -1;
} }
anetCloexec(state->kqfd);
eventLoop->apidata = state; eventLoop->apidata = state;
return 0; return 0;
} }

View File

@ -69,6 +69,11 @@ int anetSetBlock(char *err, int fd, int non_block) {
return ANET_ERR; return ANET_ERR;
} }
/* Check if this flag has been set or unset, if so,
* then there is no need to call fcntl to set/unset it again. */
if (!!(flags & O_NONBLOCK) == !!non_block)
return ANET_OK;
if (non_block) if (non_block)
flags |= O_NONBLOCK; flags |= O_NONBLOCK;
else else
@ -89,6 +94,29 @@ int anetBlock(char *err, int fd) {
return anetSetBlock(err,fd,0); return anetSetBlock(err,fd,0);
} }
/* Enable the FD_CLOEXEC on the given fd to avoid fd leaks.
* This function should be invoked for fd's on specific places
* where fork + execve system calls are called. */
int anetCloexec(int fd) {
int r;
int flags;
do {
r = fcntl(fd, F_GETFD);
} while (r == -1 && errno == EINTR);
if (r == -1 || (r & FD_CLOEXEC))
return r;
flags = r | FD_CLOEXEC;
do {
r = fcntl(fd, F_SETFD, flags);
} while (r == -1 && errno == EINTR);
return r;
}
/* Set TCP keep alive option to detect dead peers. The interval option /* Set TCP keep alive option to detect dead peers. The interval option
* is only used for Linux as we are using Linux-specific APIs to set * is only used for Linux as we are using Linux-specific APIs to set
* the probe send time, interval, and count. */ * the probe send time, interval, and count. */
@ -207,14 +235,13 @@ int anetRecvTimeout(char *err, int fd, long long ms) {
return ANET_OK; return ANET_OK;
} }
/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to /* Resolve the hostname "host" and set the string representation of the
* do the actual work. It resolves the hostname "host" and set the string * IP address into the buffer pointed by "ipbuf".
* representation of the IP address into the buffer pointed by "ipbuf".
* *
* If flags is set to ANET_IP_ONLY the function only resolves hostnames * If flags is set to ANET_IP_ONLY the function only resolves hostnames
* that are actually already IPv4 or IPv6 addresses. This turns the function * that are actually already IPv4 or IPv6 addresses. This turns the function
* into a validating / normalizing function. */ * into a validating / normalizing function. */
int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
int flags) int flags)
{ {
struct addrinfo hints, *info; struct addrinfo hints, *info;
@ -241,14 +268,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len,
return ANET_OK; return ANET_OK;
} }
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE);
}
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) {
return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY);
}
static int anetSetReuseAddr(char *err, int fd) { static int anetSetReuseAddr(char *err, int fd) {
int yes = 1; int yes = 1;
/* Make sure connection-intensive things like the redis benchmark /* Make sure connection-intensive things like the redis benchmark
@ -484,13 +503,12 @@ static int anetV6Only(char *err, int s) {
int yes = 1; int yes = 1;
if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) { if (setsockopt(s,IPPROTO_IPV6,IPV6_V6ONLY,&yes,sizeof(yes)) == -1) {
anetSetError(err, "setsockopt: %s", strerror(errno)); anetSetError(err, "setsockopt: %s", strerror(errno));
close(s);
return ANET_ERR; return ANET_ERR;
} }
return ANET_OK; return ANET_OK;
} }
static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen) static int _anetTcpServer(char *err, int port, const char *bindaddr, int af, int backlog, int fReusePort, int fFirstListen)
{ {
int s = -1, rv; int s = -1, rv;
char _port[6]; /* strlen("65535") */ char _port[6]; /* strlen("65535") */
@ -501,6 +519,10 @@ static int _anetTcpServer(char *err, int port, char *bindaddr, int af, int backl
hints.ai_family = af; hints.ai_family = af;
hints.ai_socktype = SOCK_STREAM; hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */ hints.ai_flags = AI_PASSIVE; /* No effect if bindaddr != NULL */
if (bindaddr && !strcmp("*", bindaddr))
bindaddr = NULL;
if (af == AF_INET6 && bindaddr && !strcmp("::*", bindaddr))
bindaddr = NULL;
if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) { if ((rv = getaddrinfo(bindaddr,_port,&hints,&servinfo)) != 0) {
anetSetError(err, "%s", gai_strerror(rv)); anetSetError(err, "%s", gai_strerror(rv));
@ -533,12 +555,12 @@ end:
return s; return s;
} }
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen) int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen)
{ {
return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen); return _anetTcpServer(err, port, bindaddr, AF_INET, backlog, fReusePort, fFirstListen);
} }
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen) int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen)
{ {
return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen); return _anetTcpServer(err, port, bindaddr, AF_INET6, backlog, fReusePort, fFirstListen);
} }
@ -607,11 +629,15 @@ int anetUnixAccept(char *err, int s) {
return fd; return fd;
} }
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) { int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type) {
struct sockaddr_storage sa; struct sockaddr_storage sa;
socklen_t salen = sizeof(sa); socklen_t salen = sizeof(sa);
if (fd_to_str_type == FD_TO_PEER_NAME) {
if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error; if (getpeername(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
} else {
if (getsockname(fd, (struct sockaddr *)&sa, &salen) == -1) goto error;
}
if (ip_len == 0) goto error; if (ip_len == 0) goto error;
if (sa.ss_family == AF_INET) { if (sa.ss_family == AF_INET) {
@ -623,7 +649,7 @@ int anetPeerToString(int fd, char *ip, size_t ip_len, int *port) {
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len); if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port); if (port) *port = ntohs(s->sin6_port);
} else if (sa.ss_family == AF_UNIX) { } else if (sa.ss_family == AF_UNIX) {
if (ip) strncpy(ip,"/unixsocket",ip_len); if (ip) snprintf(ip, ip_len, "/unixsocket");
if (port) *port = 0; if (port) *port = 0;
} else { } else {
goto error; goto error;
@ -651,41 +677,11 @@ int anetFormatAddr(char *buf, size_t buf_len, char *ip, int port) {
"[%s]:%d" : "%s:%d", ip, port); "[%s]:%d" : "%s:%d", ip, port);
} }
/* Like anetFormatAddr() but extract ip and port from the socket's peer. */ /* Like anetFormatAddr() but extract ip and port from the socket's peer/sockname. */
int anetFormatPeer(int fd, char *buf, size_t buf_len) { int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type) {
char ip[INET6_ADDRSTRLEN]; char ip[INET6_ADDRSTRLEN];
int port; int port;
anetPeerToString(fd,ip,sizeof(ip),&port); anetFdToString(fd,ip,sizeof(ip),&port,fd_to_str_type);
return anetFormatAddr(buf, buf_len, ip, port); return anetFormatAddr(buf, buf_len, ip, port);
} }
int anetSockName(int fd, char *ip, size_t ip_len, int *port) {
struct sockaddr_storage sa;
socklen_t salen = sizeof(sa);
if (getsockname(fd,(struct sockaddr*)&sa,&salen) == -1) {
if (port) *port = 0;
ip[0] = '?';
ip[1] = '\0';
return -1;
}
if (sa.ss_family == AF_INET) {
struct sockaddr_in *s = (struct sockaddr_in *)&sa;
if (ip) inet_ntop(AF_INET,(void*)&(s->sin_addr),ip,ip_len);
if (port) *port = ntohs(s->sin_port);
} else {
struct sockaddr_in6 *s = (struct sockaddr_in6 *)&sa;
if (ip) inet_ntop(AF_INET6,(void*)&(s->sin6_addr),ip,ip_len);
if (port) *port = ntohs(s->sin6_port);
}
return 0;
}
int anetFormatSock(int fd, char *fmt, size_t fmt_len) {
char ip[INET6_ADDRSTRLEN];
int port;
anetSockName(fd,ip,sizeof(ip),&port);
return anetFormatAddr(fmt, fmt_len, ip, port);
}

View File

@ -53,6 +53,10 @@ extern "C" {
#undef ip_len #undef ip_len
#endif #endif
/* FD to address string conversion types */
#define FD_TO_PEER_NAME 0
#define FD_TO_SOCK_NAME 1
int anetTcpConnect(char *err, const char *addr, int port); int anetTcpConnect(char *err, const char *addr, int port);
int anetTcpNonBlockConnect(char *err, const char *addr, int port); int anetTcpNonBlockConnect(char *err, const char *addr, int port);
int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr); int anetTcpNonBlockBindConnect(char *err, const char *addr, int port, const char *source_addr);
@ -60,27 +64,25 @@ int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port,
int anetUnixConnect(char *err, const char *path); int anetUnixConnect(char *err, const char *path);
int anetUnixNonBlockConnect(char *err, const char *path); int anetUnixNonBlockConnect(char *err, const char *path);
int anetRead(int fd, char *buf, int count); int anetRead(int fd, char *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags);
int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len); int anetTcpServer(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetTcpServer(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen); int anetTcp6Server(char *err, int port, const char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetTcp6Server(char *err, int port, char *bindaddr, int backlog, int fReusePort, int fFirstListen);
int anetUnixServer(char *err, char *path, mode_t perm, int backlog); int anetUnixServer(char *err, char *path, mode_t perm, int backlog);
int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port); int anetTcpAccept(char *err, int serversock, char *ip, size_t ip_len, int *port);
int anetUnixAccept(char *err, int serversock); int anetUnixAccept(char *err, int serversock);
int anetWrite(int fd, char *buf, int count); int anetWrite(int fd, char *buf, int count);
int anetNonBlock(char *err, int fd); int anetNonBlock(char *err, int fd);
int anetBlock(char *err, int fd); int anetBlock(char *err, int fd);
int anetCloexec(int fd);
int anetEnableTcpNoDelay(char *err, int fd); int anetEnableTcpNoDelay(char *err, int fd);
int anetDisableTcpNoDelay(char *err, int fd); int anetDisableTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd); int anetTcpKeepAlive(char *err, int fd);
int anetSendTimeout(char *err, int fd, long long ms); int anetSendTimeout(char *err, int fd, long long ms);
int anetRecvTimeout(char *err, int fd, long long ms); int anetRecvTimeout(char *err, int fd, long long ms);
int anetPeerToString(int fd, char *ip, size_t ip_len, int *port); int anetFdToString(int fd, char *ip, size_t ip_len, int *port, int fd_to_str_type);
int anetKeepAlive(char *err, int fd, int interval); int anetKeepAlive(char *err, int fd, int interval);
int anetSockName(int fd, char *ip, size_t ip_len, int *port);
int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port); int anetFormatAddr(char *fmt, size_t fmt_len, char *ip, int port);
int anetFormatPeer(int fd, char *fmt, size_t fmt_len); int anetFormatFdAddr(int fd, char *buf, size_t buf_len, int fd_to_str_type);
int anetFormatSock(int fd, char *fmt, size_t fmt_len);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -221,29 +221,27 @@ int aofFsyncInProgress(void) {
/* Starts a background task that performs fsync() against the specified /* Starts a background task that performs fsync() against the specified
* file descriptor (the one of the AOF file) in another thread. */ * file descriptor (the one of the AOF file) in another thread. */
void aof_background_fsync(int fd) { void aof_background_fsync(int fd) {
bioCreateBackgroundJob(BIO_AOF_FSYNC,(void*)(long)fd,NULL,NULL); bioCreateFsyncJob(fd);
} }
/* Kills an AOFRW child process if exists */ /* Kills an AOFRW child process if exists */
void killAppendOnlyChild(void) { void killAppendOnlyChild(void) {
int statloc; int statloc;
/* No AOFRW child? return. */ /* No AOFRW child? return. */
if (g_pserver->aof_child_pid == -1) return; if (g_pserver->child_type != CHILD_TYPE_AOF) return;
/* Kill AOFRW child, wait for child exit. */ /* Kill AOFRW child, wait for child exit. */
serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld", serverLog(LL_NOTICE,"Killing running AOF rewrite child: %ld",
(long) g_pserver->aof_child_pid); (long) g_pserver->child_pid);
if (kill(g_pserver->aof_child_pid,SIGUSR1) != -1) { if (kill(g_pserver->child_pid,SIGUSR1) != -1) {
while(wait3(&statloc,0,NULL) != g_pserver->aof_child_pid); while(wait3(&statloc,0,NULL) != g_pserver->child_pid);
} }
/* Reset the buffer accumulating changes while the child saves. */ /* Reset the buffer accumulating changes while the child saves. */
aofRewriteBufferReset(); aofRewriteBufferReset();
aofRemoveTempFile(g_pserver->aof_child_pid); aofRemoveTempFile(g_pserver->child_pid);
g_pserver->aof_child_pid = -1; resetChildState();
g_pserver->aof_rewrite_time_start = -1; g_pserver->aof_rewrite_time_start = -1;
/* Close pipes used for IPC between the two processes. */ /* Close pipes used for IPC between the two processes. */
aofClosePipes(); aofClosePipes();
closeChildInfoPipe();
updateDictResizePolicy();
} }
/* Called when the user switches from "appendonly yes" to "appendonly no" /* Called when the user switches from "appendonly yes" to "appendonly no"
@ -252,6 +250,8 @@ void stopAppendOnly(void) {
serverAssert(g_pserver->aof_state != AOF_OFF); serverAssert(g_pserver->aof_state != AOF_OFF);
flushAppendOnlyFile(1); flushAppendOnlyFile(1);
redis_fsync(g_pserver->aof_fd); redis_fsync(g_pserver->aof_fd);
g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
g_pserver->aof_last_fsync = g_pserver->unixtime;
close(g_pserver->aof_fd); close(g_pserver->aof_fd);
g_pserver->aof_fd = -1; g_pserver->aof_fd = -1;
@ -259,6 +259,8 @@ void stopAppendOnly(void) {
g_pserver->aof_state = AOF_OFF; g_pserver->aof_state = AOF_OFF;
g_pserver->aof_rewrite_scheduled = 0; g_pserver->aof_rewrite_scheduled = 0;
killAppendOnlyChild(); killAppendOnlyChild();
sdsfree(g_pserver->aof_buf);
g_pserver->aof_buf = sdsempty();
} }
/* Called when the user switches from "appendonly no" to "appendonly yes" /* Called when the user switches from "appendonly no" to "appendonly yes"
@ -280,14 +282,14 @@ int startAppendOnly(void) {
strerror(errno)); strerror(errno));
return C_ERR; return C_ERR;
} }
if (hasActiveChildProcess() && g_pserver->aof_child_pid == -1) { if (hasActiveChildProcess() && g_pserver->child_type != CHILD_TYPE_AOF) {
g_pserver->aof_rewrite_scheduled = 1; g_pserver->aof_rewrite_scheduled = 1;
serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible."); serverLog(LL_WARNING,"AOF was enabled but there is already another background operation. An AOF background was scheduled to start when possible.");
} else { } else {
/* If there is a pending AOF rewrite, we need to switch it off and /* If there is a pending AOF rewrite, we need to switch it off and
* start a new one: the old one cannot be reused because it is not * start a new one: the old one cannot be reused because it is not
* accumulating the AOF buffer. */ * accumulating the AOF buffer. */
if (g_pserver->aof_child_pid != -1) { if (g_pserver->child_type == CHILD_TYPE_AOF) {
serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now."); serverLog(LL_WARNING,"AOF was enabled but there is already an AOF rewriting in background. Stopping background AOF and starting a rewrite now.");
killAppendOnlyChild(); killAppendOnlyChild();
} }
@ -302,6 +304,12 @@ int startAppendOnly(void) {
g_pserver->aof_state = AOF_WAIT_REWRITE; g_pserver->aof_state = AOF_WAIT_REWRITE;
g_pserver->aof_last_fsync = g_pserver->unixtime; g_pserver->aof_last_fsync = g_pserver->unixtime;
g_pserver->aof_fd = newfd; g_pserver->aof_fd = newfd;
/* If AOF was in error state, we just ignore it and log the event. */
if (g_pserver->aof_last_write_status == C_ERR) {
serverLog(LL_WARNING,"AOF reopen, just ignore the last error.");
g_pserver->aof_last_write_status = C_OK;
}
return C_OK; return C_OK;
} }
@ -471,10 +479,11 @@ void flushAppendOnlyFile(int force) {
/* Handle the AOF write error. */ /* Handle the AOF write error. */
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) { if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
/* We can't recover when the fsync policy is ALWAYS since the /* We can't recover when the fsync policy is ALWAYS since the reply
* reply for the client is already in the output buffers, and we * for the client is already in the output buffers (both writes and
* have the contract with the user that on acknowledged write data * reads), and the changes to the db can't be rolled back. Since we
* is synced on disk. */ * have a contract with the user that on acknowledged or observed
* writes are is synced on disk, we must exit. */
serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting..."); serverLog(LL_WARNING,"Can't recover from AOF write error when the AOF fsync policy is 'always'. Exiting...");
exit(1); exit(1);
} else { } else {
@ -522,7 +531,14 @@ try_fsync:
/* redis_fsync is defined as fdatasync() for Linux in order to avoid /* redis_fsync is defined as fdatasync() for Linux in order to avoid
* flushing metadata. */ * flushing metadata. */
latencyStartMonitor(latency); latencyStartMonitor(latency);
redis_fsync(g_pserver->aof_fd); /* Let's try to get this data on the disk */ /* Let's try to get this data on the disk. To guarantee data safe when
* the AOF fsync policy is 'always', we should exit if failed to fsync
* AOF (see comment next to the exit(1) after write error above). */
if (redis_fsync(g_pserver->aof_fd) == -1) {
serverLog(LL_WARNING,"Can't persist AOF for fsync error when the "
"AOF fsync policy is 'always': %s. Exiting...", strerror(errno));
exit(1);
}
latencyEndMonitor(latency); latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-fsync-always",latency); latencyAddSampleIfNeeded("aof-fsync-always",latency);
g_pserver->aof_fsync_offset = g_pserver->aof_current_size; g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
@ -594,11 +610,10 @@ sds catAppendOnlyExpireAtCommand(sds buf, struct redisCommand *cmd, robj *key, r
} }
decrRefCount(seconds); decrRefCount(seconds);
argv[0] = createStringObject("PEXPIREAT",9); argv[0] = shared.pexpireat;
argv[1] = key; argv[1] = key;
argv[2] = createStringObjectFromLongLong(when); argv[2] = createStringObjectFromLongLong(when);
buf = catAppendOnlyGenericCommand(buf, 3, argv); buf = catAppendOnlyGenericCommand(buf, 3, argv);
decrRefCount(argv[0]);
decrRefCount(argv[2]); decrRefCount(argv[2]);
return buf; return buf;
} }
@ -654,42 +669,39 @@ sds catAppendOnlyExpireMemberAtCommand(sds buf, struct redisCommand *cmd, robj *
sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc) sds catCommandForAofAndActiveReplication(sds buf, struct redisCommand *cmd, robj **argv, int argc)
{ {
robj *tmpargv[3];
if (cmd->proc == expireCommand || cmd->proc == pexpireCommand || if (cmd->proc == expireCommand || cmd->proc == pexpireCommand ||
cmd->proc == expireatCommand) { cmd->proc == expireatCommand) {
/* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */ /* Translate EXPIRE/PEXPIRE/EXPIREAT into PEXPIREAT */
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]); buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setexCommand || cmd->proc == psetexCommand) {
/* Translate SETEX/PSETEX to SET and PEXPIREAT */
tmpargv[0] = createStringObject("SET",3);
tmpargv[1] = argv[1];
tmpargv[2] = argv[3];
buf = catAppendOnlyGenericCommand(buf,3,tmpargv);
decrRefCount(tmpargv[0]);
buf = catAppendOnlyExpireAtCommand(buf,cmd,argv[1],argv[2]);
} else if (cmd->proc == setCommand && argc > 3) { } else if (cmd->proc == setCommand && argc > 3) {
int i; robj *pxarg = NULL;
robj *exarg = NULL, *pxarg = NULL; /* When SET is used with EX/PX argument setGenericCommand propagates them with PX millisecond argument.
for (i = 3; i < argc; i ++) { * So since the command arguments are re-written there, we can rely here on the index of PX being 3. */
if (!strcasecmp(szFromObj(argv[i]), "ex")) exarg = argv[i+1]; if (!strcasecmp(szFromObj(argv[3]), "px")) {
if (!strcasecmp(szFromObj(argv[i]), "px")) pxarg = argv[i+1]; pxarg = argv[4];
} }
serverAssert(!(exarg && pxarg)); /* For AOF we convert SET key value relative time in milliseconds to SET key value absolute time in
if (exarg || pxarg) { * millisecond. Whenever the condition is true it implies that original SET has been transformed
/* Translate SET [EX seconds][PX milliseconds] to SET and PEXPIREAT */ * to SET PX with millisecond time argument so we do not need to worry about unit here.*/
buf = catAppendOnlyGenericCommand(buf,3,argv); if (pxarg) {
if (exarg) robj *millisecond = getDecodedObject(pxarg);
buf = catAppendOnlyExpireAtCommand(buf,cserver.expireCommand,argv[1], long long when = strtoll(szFromObj(millisecond),NULL,10);
exarg); when += mstime();
if (pxarg)
buf = catAppendOnlyExpireAtCommand(buf,cserver.pexpireCommand,argv[1], decrRefCount(millisecond);
pxarg);
robj *newargs[5];
newargs[0] = argv[0];
newargs[1] = argv[1];
newargs[2] = argv[2];
newargs[3] = shared.pxat;
newargs[4] = createStringObjectFromLongLong(when);
buf = catAppendOnlyGenericCommand(buf,5,newargs);
decrRefCount(newargs[4]);
} else { } else {
buf = catAppendOnlyGenericCommand(buf,argc,argv); buf = catAppendOnlyGenericCommand(buf,argc,argv);
} }
} else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand || } else if (cmd->proc == expireMemberCommand || cmd->proc == expireMemberAtCommand || cmd->proc == pexpireMemberAtCommand) {
cmd->proc == pexpireMemberAtCommand) {
/* Translate subkey expire commands to PEXPIREMEMBERAT */ /* Translate subkey expire commands to PEXPIREMEMBERAT */
buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc); buf = catAppendOnlyExpireMemberAtCommand(buf, cmd, argv, argc);
} else { } else {
@ -728,7 +740,7 @@ void feedAppendOnlyFile(struct redisCommand *cmd, int dictid, robj **argv, int a
* accumulate the differences between the child DB and the current one * accumulate the differences between the child DB and the current one
* in a buffer, so that when the child process will do its work we * in a buffer, so that when the child process will do its work we
* can append the differences to the new append only file. */ * can append the differences to the new append only file. */
if (g_pserver->aof_child_pid != -1) if (g_pserver->child_type == CHILD_TYPE_AOF)
aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf)); aofRewriteBufferAppend((unsigned char*)buf,sdslen(buf));
sdsfree(buf); sdsfree(buf);
@ -752,11 +764,24 @@ struct client *createAOFClient(void) {
c->querybuf_peak = 0; c->querybuf_peak = 0;
c->argc = 0; c->argc = 0;
c->argv = NULL; c->argv = NULL;
c->original_argc = 0;
c->original_argv = NULL;
c->argv_len_sum = 0; c->argv_len_sum = 0;
c->bufpos = 0; c->bufpos = 0;
c->flags = 0;
c->fPendingAsyncWrite = FALSE; c->fPendingAsyncWrite = FALSE;
c->fPendingAsyncWriteHandler = FALSE; c->fPendingAsyncWriteHandler = FALSE;
/*
* The AOF client should never be blocked (unlike master
* replication connection).
* This is because blocking the AOF client might cause
* deadlock (because potentially no one will unblock it).
* Also, if the AOF client will be blocked just for
* background processing there is a chance that the
* command execution order will be violated.
*/
c->flags = CLIENT_DENY_BLOCKING;
c->btype = BLOCKED_NONE; c->btype = BLOCKED_NONE;
/* We set the fake client as a replica waiting for the synchronization /* We set the fake client as a replica waiting for the synchronization
* so that Redis will not try to send replies to this client. */ * so that Redis will not try to send replies to this client. */
@ -766,8 +791,9 @@ struct client *createAOFClient(void) {
c->obuf_soft_limit_reached_time = 0; c->obuf_soft_limit_reached_time = 0;
c->watched_keys = listCreate(); c->watched_keys = listCreate();
c->peerid = NULL; c->peerid = NULL;
c->sockname = NULL;
c->resp = 2; c->resp = 2;
c->puser = NULL; c->user = NULL;
listSetFreeMethod(c->reply,freeClientReplyValue); listSetFreeMethod(c->reply,freeClientReplyValue);
listSetDupMethod(c->reply,dupClientReplyValue); listSetDupMethod(c->reply,dupClientReplyValue);
fastlock_init(&c->lock, "fake client"); fastlock_init(&c->lock, "fake client");
@ -790,6 +816,7 @@ void freeFakeClient(struct client *c) {
listRelease(c->reply); listRelease(c->reply);
listRelease(c->watched_keys); listRelease(c->watched_keys);
freeClientMultiState(c); freeClientMultiState(c);
freeClientOriginalArgv(c);
fastlock_unlock(&c->lock); fastlock_unlock(&c->lock);
fastlock_free(&c->lock); fastlock_free(&c->lock);
zfree(c); zfree(c);
@ -951,7 +978,7 @@ int loadAppendOnlyFile(char *filename) {
fakeClient->cmd = NULL; fakeClient->cmd = NULL;
if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp); if (g_pserver->aof_load_truncated) valid_up_to = ftello(fp);
if (g_pserver->key_load_delay) if (g_pserver->key_load_delay)
usleep(g_pserver->key_load_delay); debugDelay(g_pserver->key_load_delay);
} }
/* This point can only be reached when EOF is reached without errors. /* This point can only be reached when EOF is reached without errors.
@ -1052,15 +1079,25 @@ int rewriteListObject(rio *r, robj *key, robj *o) {
if (count == 0) { if (count == 0) {
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
if (rioWriteBulkString(r,"RPUSH",5) == 0) return 0; !rioWriteBulkString(r,"RPUSH",5) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key))
{
quicklistReleaseIterator(li);
return 0;
}
} }
if (entry.value) { if (entry.value) {
if (rioWriteBulkString(r,(char*)entry.value,entry.sz) == 0) return 0; if (!rioWriteBulkString(r,(char*)entry.value,entry.sz)) {
quicklistReleaseIterator(li);
return 0;
}
} else { } else {
if (rioWriteBulkLongLong(r,entry.longval) == 0) return 0; if (!rioWriteBulkLongLong(r,entry.longval)) {
quicklistReleaseIterator(li);
return 0;
}
} }
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
@ -1086,11 +1123,14 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
if (rioWriteBulkString(r,"SADD",4) == 0) return 0; !rioWriteBulkString(r,"SADD",4) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key))
{
return 0;
} }
if (rioWriteBulkLongLong(r,llval) == 0) return 0; }
if (!rioWriteBulkLongLong(r,llval)) return 0;
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
} }
@ -1104,11 +1144,18 @@ int rewriteSetObject(rio *r, robj *key, robj *o) {
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items) == 0) return 0; if (!rioWriteBulkCount(r,'*',2+cmd_items) ||
if (rioWriteBulkString(r,"SADD",4) == 0) return 0; !rioWriteBulkString(r,"SADD",4) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key))
{
dictReleaseIterator(di);
return 0;
}
}
if (!rioWriteBulkString(r,ele,sdslen(ele))) {
dictReleaseIterator(di);
return 0;
} }
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
} }
@ -1145,15 +1192,18 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; !rioWriteBulkString(r,"ZADD",4) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key))
{
return 0;
} }
if (rioWriteBulkDouble(r,score) == 0) return 0; }
if (!rioWriteBulkDouble(r,score)) return 0;
if (vstr != NULL) { if (vstr != NULL) {
if (rioWriteBulkString(r,(char*)vstr,vlen) == 0) return 0; if (!rioWriteBulkString(r,(char*)vstr,vlen)) return 0;
} else { } else {
if (rioWriteBulkLongLong(r,vll) == 0) return 0; if (!rioWriteBulkLongLong(r,vll)) return 0;
} }
zzlNext(zl,&eptr,&sptr); zzlNext(zl,&eptr,&sptr);
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
@ -1172,12 +1222,20 @@ int rewriteSortedSetObject(rio *r, robj *key, robj *o) {
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
if (rioWriteBulkString(r,"ZADD",4) == 0) return 0; !rioWriteBulkString(r,"ZADD",4) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key))
{
dictReleaseIterator(di);
return 0;
}
}
if (!rioWriteBulkDouble(r,*score) ||
!rioWriteBulkString(r,ele,sdslen(ele)))
{
dictReleaseIterator(di);
return 0;
} }
if (rioWriteBulkDouble(r,*score) == 0) return 0;
if (rioWriteBulkString(r,ele,sdslen(ele)) == 0) return 0;
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
} }
@ -1226,13 +1284,21 @@ int rewriteHashObject(rio *r, robj *key, robj *o) {
int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ? int cmd_items = (items > AOF_REWRITE_ITEMS_PER_CMD) ?
AOF_REWRITE_ITEMS_PER_CMD : items; AOF_REWRITE_ITEMS_PER_CMD : items;
if (rioWriteBulkCount(r,'*',2+cmd_items*2) == 0) return 0; if (!rioWriteBulkCount(r,'*',2+cmd_items*2) ||
if (rioWriteBulkString(r,"HMSET",5) == 0) return 0; !rioWriteBulkString(r,"HMSET",5) ||
if (rioWriteBulkObject(r,key) == 0) return 0; !rioWriteBulkObject(r,key))
{
hashTypeReleaseIterator(hi);
return 0;
}
} }
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) == 0) return 0; if (!rioWriteHashIteratorCursor(r, hi, OBJ_HASH_KEY) ||
if (rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE) == 0) return 0; !rioWriteHashIteratorCursor(r, hi, OBJ_HASH_VALUE))
{
hashTypeReleaseIterator(hi);
return 0;
}
if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0; if (++count == AOF_REWRITE_ITEMS_PER_CMD) count = 0;
items--; items--;
} }
@ -1278,6 +1344,20 @@ int rioWriteStreamPendingEntry(rio *r, robj *key, const char *groupname, size_t
return 1; return 1;
} }
/* Helper for rewriteStreamObject(): emit the XGROUP CREATECONSUMER is
* needed in order to create consumers that do not have any pending entries.
* All this in the context of the specified key and group. */
int rioWriteStreamEmptyConsumer(rio *r, robj *key, const char *groupname, size_t groupname_len, streamConsumer *consumer) {
/* XGROUP CREATECONSUMER <key> <group> <consumer> */
if (rioWriteBulkCount(r,'*',5) == 0) return 0;
if (rioWriteBulkString(r,"XGROUP",6) == 0) return 0;
if (rioWriteBulkString(r,"CREATECONSUMER",14) == 0) return 0;
if (rioWriteBulkObject(r,key) == 0) return 0;
if (rioWriteBulkString(r,groupname,groupname_len) == 0) return 0;
if (rioWriteBulkString(r,consumer->name,sdslen(consumer->name)) == 0) return 0;
return 1;
}
/* Emit the commands needed to rebuild a stream object. /* Emit the commands needed to rebuild a stream object.
* The function returns 0 on error, 1 on success. */ * The function returns 0 on error, 1 on success. */
int rewriteStreamObject(rio *r, robj *key, robj *o) { int rewriteStreamObject(rio *r, robj *key, robj *o) {
@ -1366,13 +1446,25 @@ int rewriteStreamObject(rio *r, robj *key, robj *o) {
} }
/* Generate XCLAIMs for each consumer that happens to /* Generate XCLAIMs for each consumer that happens to
* have pending entries. Empty consumers have no semantical * have pending entries. Empty consumers would be generated with
* value so they are discarded. */ * XGROUP CREATECONSUMER. */
raxIterator ri_cons; raxIterator ri_cons;
raxStart(&ri_cons,group->consumers); raxStart(&ri_cons,group->consumers);
raxSeek(&ri_cons,"^",NULL,0); raxSeek(&ri_cons,"^",NULL,0);
while(raxNext(&ri_cons)) { while(raxNext(&ri_cons)) {
streamConsumer *consumer = (streamConsumer*)ri_cons.data; streamConsumer *consumer = (streamConsumer*)ri_cons.data;
/* If there are no pending entries, just emit XGROUP CREATECONSUMER */
if (raxSize(consumer->pel) == 0) {
if (rioWriteStreamEmptyConsumer(r,key,(char*)ri.key,
ri.key_len,consumer) == 0)
{
raxStop(&ri_cons);
raxStop(&ri);
streamIteratorStop(&si);
return 0;
}
continue;
}
/* For the current consumer, iterate all the PEL entries /* For the current consumer, iterate all the PEL entries
* to emit the XCLAIM protocol. */ * to emit the XCLAIM protocol. */
raxIterator ri_pel; raxIterator ri_pel;
@ -1438,6 +1530,8 @@ int rewriteAppendOnlyFileRio(rio *aof) {
dictEntry *de; dictEntry *de;
size_t processed = 0; size_t processed = 0;
int j; int j;
long key_count = 0;
long long updated_time = 0;
for (j = 0; j < cserver.dbnum; j++) { for (j = 0; j < cserver.dbnum; j++) {
char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n"; char selectcmd[] = "*2\r\n$6\r\nSELECT\r\n";
@ -1509,6 +1603,17 @@ int rewriteAppendOnlyFileRio(rio *aof) {
processed = aof->processed_bytes; processed = aof->processed_bytes;
aofReadDiffFromParent(); aofReadDiffFromParent();
} }
/* Update info every 1 second (approximately).
* in order to avoid calling mstime() on each iteration, we will
* check the diff every 1024 keys */
if ((key_count++ & 1023) == 0) {
long long now = mstime();
if (now - updated_time >= 1000) {
sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, "AOF rewrite");
updated_time = now;
}
}
} }
dictReleaseIterator(di); dictReleaseIterator(di);
di = NULL; di = NULL;
@ -1535,6 +1640,7 @@ int rewriteAppendOnlyFile(char *filename) {
int nodata = 0; int nodata = 0;
mstime_t start = 0; mstime_t start = 0;
{ // BEGIN GOTO SCOPED VARIABLES
/* Note that we have to use a different temp name here compared to the /* Note that we have to use a different temp name here compared to the
* one used by rewriteAppendOnlyFileBackground() function. */ * one used by rewriteAppendOnlyFileBackground() function. */
snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid()); snprintf(tmpfile,256,"temp-rewriteaof-%d.aof", (int) getpid());
@ -1603,9 +1709,32 @@ int rewriteAppendOnlyFile(char *filename) {
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Concatenating %.2f MB of AOF diff received from parent.", "Concatenating %.2f MB of AOF diff received from parent.",
(double) sdslen(g_pserver->aof_child_diff) / (1024*1024)); (double) sdslen(g_pserver->aof_child_diff) / (1024*1024));
if (rioWrite(&aof,g_pserver->aof_child_diff,sdslen(g_pserver->aof_child_diff)) == 0)
/* Now we write the entire AOF buffer we received from the parent
* via the pipe during the life of this fork child.
* once a second, we'll take a break and send updated COW info to the parent */
size_t bytes_to_write = sdslen(g_pserver->aof_child_diff);
const char *buf = g_pserver->aof_child_diff;
long long cow_updated_time = mstime();
long long key_count = dbTotalServerKeyCount();
while (bytes_to_write) {
/* We write the AOF buffer in chunk of 8MB so that we can check the time in between them */
size_t chunk_size = bytes_to_write < (8<<20) ? bytes_to_write : (8<<20);
if (rioWrite(&aof,buf,chunk_size) == 0)
goto werr; goto werr;
bytes_to_write -= chunk_size;
buf += chunk_size;
/* Update COW info */
long long now = mstime();
if (now - cow_updated_time >= 1000) {
sendChildInfo(CHILD_INFO_TYPE_CURRENT_INFO, key_count, "AOF rewrite");
cow_updated_time = now;
}
}
/* Make sure data will not remain on the OS's output buffers */ /* Make sure data will not remain on the OS's output buffers */
if (fflush(fp)) goto werr; if (fflush(fp)) goto werr;
if (fsync(fileno(fp))) goto werr; if (fsync(fileno(fp))) goto werr;
@ -1623,6 +1752,7 @@ int rewriteAppendOnlyFile(char *filename) {
serverLog(LL_NOTICE,"SYNC append only file rewrite performed"); serverLog(LL_NOTICE,"SYNC append only file rewrite performed");
stopSaving(1); stopSaving(1);
return C_OK; return C_OK;
} // END GOTO SCOPED VARIABLES
werr: werr:
serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno)); serverLog(LL_WARNING,"Write error writing append only file on disk: %s", strerror(errno));
@ -1739,7 +1869,6 @@ int rewriteAppendOnlyFileBackground(void) {
if (hasActiveChildProcess()) return C_ERR; if (hasActiveChildProcess()) return C_ERR;
if (aofCreatePipes() != C_OK) return C_ERR; if (aofCreatePipes() != C_OK) return C_ERR;
openChildInfoPipe();
if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) { if ((childpid = redisFork(CHILD_TYPE_AOF)) == 0) {
char tmpfile[256]; char tmpfile[256];
@ -1748,7 +1877,7 @@ int rewriteAppendOnlyFileBackground(void) {
redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist); redisSetCpuAffinity(g_pserver->aof_rewrite_cpulist);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid()); snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", (int) getpid());
if (rewriteAppendOnlyFile(tmpfile) == C_OK) { if (rewriteAppendOnlyFile(tmpfile) == C_OK) {
sendChildCOWInfo(CHILD_TYPE_AOF, "AOF rewrite"); sendChildCowInfo(CHILD_INFO_TYPE_AOF_COW_SIZE, "AOF rewrite");
exitFromChild(0); exitFromChild(0);
} else { } else {
exitFromChild(1); exitFromChild(1);
@ -1756,7 +1885,6 @@ int rewriteAppendOnlyFileBackground(void) {
} else { } else {
/* Parent */ /* Parent */
if (childpid == -1) { if (childpid == -1) {
closeChildInfoPipe();
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Can't rewrite append only file in background: fork: %s", "Can't rewrite append only file in background: fork: %s",
strerror(errno)); strerror(errno));
@ -1764,10 +1892,9 @@ int rewriteAppendOnlyFileBackground(void) {
return C_ERR; return C_ERR;
} }
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Background append only file rewriting started by pid %d",childpid); "Background append only file rewriting started by pid %ld",(long)childpid);
g_pserver->aof_rewrite_scheduled = 0; g_pserver->aof_rewrite_scheduled = 0;
g_pserver->aof_rewrite_time_start = time(NULL); g_pserver->aof_rewrite_time_start = time(NULL);
g_pserver->aof_child_pid = childpid;
updateDictResizePolicy(); updateDictResizePolicy();
/* We set appendseldb to -1 in order to force the next call to the /* We set appendseldb to -1 in order to force the next call to the
* feedAppendOnlyFile() to issue a SELECT command, so the differences * feedAppendOnlyFile() to issue a SELECT command, so the differences
@ -1781,7 +1908,7 @@ int rewriteAppendOnlyFileBackground(void) {
} }
void bgrewriteaofCommand(client *c) { void bgrewriteaofCommand(client *c) {
if (g_pserver->aof_child_pid != -1) { if (g_pserver->child_type == CHILD_TYPE_AOF) {
addReplyError(c,"Background append only file rewriting already in progress"); addReplyError(c,"Background append only file rewriting already in progress");
} else if (hasActiveChildProcess()) { } else if (hasActiveChildProcess()) {
g_pserver->aof_rewrite_scheduled = 1; g_pserver->aof_rewrite_scheduled = 1;
@ -1839,7 +1966,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
* rewritten AOF. */ * rewritten AOF. */
latencyStartMonitor(latency); latencyStartMonitor(latency);
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof", snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
(int)g_pserver->aof_child_pid); (int)g_pserver->child_pid);
newfd = open(tmpfile,O_WRONLY|O_APPEND); newfd = open(tmpfile,O_WRONLY|O_APPEND);
if (newfd == -1) { if (newfd == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
@ -1856,6 +1983,20 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
latencyEndMonitor(latency); latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency); latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency);
if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC) {
aof_background_fsync(newfd);
} else if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS) {
latencyStartMonitor(latency);
if (redis_fsync(newfd) == -1) {
serverLog(LL_WARNING,
"Error trying to fsync the parent diff to the rewritten AOF: %s", strerror(errno));
close(newfd);
goto cleanup;
}
latencyEndMonitor(latency);
latencyAddSampleIfNeeded("aof-rewrite-done-fsync",latency);
}
serverLog(LL_NOTICE, serverLog(LL_NOTICE,
"Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024)); "Residual parent diff successfully flushed to the rewritten AOF (%.2f MB)", (double) aofRewriteBufferSize() / (1024*1024));
@ -1922,14 +2063,11 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
/* AOF enabled, replace the old fd with the new one. */ /* AOF enabled, replace the old fd with the new one. */
oldfd = g_pserver->aof_fd; oldfd = g_pserver->aof_fd;
g_pserver->aof_fd = newfd; g_pserver->aof_fd = newfd;
if (g_pserver->aof_fsync == AOF_FSYNC_ALWAYS)
redis_fsync(newfd);
else if (g_pserver->aof_fsync == AOF_FSYNC_EVERYSEC)
aof_background_fsync(newfd);
g_pserver->aof_selected_db = -1; /* Make sure SELECT is re-issued */ g_pserver->aof_selected_db = -1; /* Make sure SELECT is re-issued */
aofUpdateCurrentSize(); aofUpdateCurrentSize();
g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size; g_pserver->aof_rewrite_base_size = g_pserver->aof_current_size;
g_pserver->aof_fsync_offset = g_pserver->aof_current_size; g_pserver->aof_fsync_offset = g_pserver->aof_current_size;
g_pserver->aof_last_fsync = g_pserver->unixtime;
/* Clear regular AOF buffer since its contents was just written to /* Clear regular AOF buffer since its contents was just written to
* the new AOF from the background rewrite buffer. */ * the new AOF from the background rewrite buffer. */
@ -1945,7 +2083,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
g_pserver->aof_state = AOF_ON; g_pserver->aof_state = AOF_ON;
/* Asynchronously close the overwritten AOF. */ /* Asynchronously close the overwritten AOF. */
if (oldfd != -1) bioCreateBackgroundJob(BIO_CLOSE_FILE,(void*)(long)oldfd,NULL,NULL); if (oldfd != -1) bioCreateCloseJob(oldfd);
serverLog(LL_VERBOSE, serverLog(LL_VERBOSE,
"Background AOF rewrite signal handler took %lldus", ustime()-now); "Background AOF rewrite signal handler took %lldus", ustime()-now);
@ -1967,8 +2105,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
cleanup: cleanup:
aofClosePipes(); aofClosePipes();
aofRewriteBufferReset(); aofRewriteBufferReset();
aofRemoveTempFile(g_pserver->aof_child_pid); aofRemoveTempFile(g_pserver->child_pid);
g_pserver->aof_child_pid = -1;
g_pserver->aof_rewrite_time_last = time(NULL)-g_pserver->aof_rewrite_time_start; g_pserver->aof_rewrite_time_last = time(NULL)-g_pserver->aof_rewrite_time_start;
g_pserver->aof_rewrite_time_start = -1; g_pserver->aof_rewrite_time_start = -1;
/* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */ /* Schedule a new rewrite if we are waiting for it to switch the AOF ON. */

View File

@ -1,5 +1,5 @@
/* This file implements atomic counters using __atomic or __sync macros if /* This file implements atomic counters using c11 _Atomic, __atomic or __sync
* available, otherwise synchronizing different threads using a mutex. * macros if available, otherwise we will throw an error when compile.
* *
* The exported interface is composed of three macros: * The exported interface is composed of three macros:
* *
@ -8,16 +8,8 @@
* atomicDecr(var,count) -- Decrement the atomic counter * atomicDecr(var,count) -- Decrement the atomic counter
* atomicGet(var,dstvar) -- Fetch the atomic counter value * atomicGet(var,dstvar) -- Fetch the atomic counter value
* atomicSet(var,value) -- Set the atomic counter value * atomicSet(var,value) -- Set the atomic counter value
* * atomicGetWithSync(var,value) -- 'atomicGet' with inter-thread synchronization
* The variable 'var' should also have a declared mutex with the same * atomicSetWithSync(var,value) -- 'atomicSet' with inter-thread synchronization
* name and the "_mutex" postfix, for instance:
*
* long myvar;
* pthread_mutex_t myvar_mutex;
* atomicSet(myvar,12345);
*
* If atomic primitives are available (tested in config.h) the mutex
* is not used.
* *
* Never use return value from the macros, instead use the AtomicGetIncr() * Never use return value from the macros, instead use the AtomicGetIncr()
* if you need to get the current value and increment it atomically, like * if you need to get the current value and increment it atomically, like
@ -58,17 +50,64 @@
*/ */
#include <pthread.h> #include <pthread.h>
#include "config.h"
#ifndef __ATOMIC_VAR_H #ifndef __ATOMIC_VAR_H
#define __ATOMIC_VAR_H #define __ATOMIC_VAR_H
/* Define redisAtomic for atomic variable. */
#define redisAtomic
/* To test Redis with Helgrind (a Valgrind tool) it is useful to define /* To test Redis with Helgrind (a Valgrind tool) it is useful to define
* the following macro, so that __sync macros are used: those can be detected * the following macro, so that __sync macros are used: those can be detected
* by Helgrind (even if they are less efficient) so that no false positive * by Helgrind (even if they are less efficient) so that no false positive
* is reported. */ * is reported. */
// #define __ATOMIC_VAR_FORCE_SYNC_MACROS // #define __ATOMIC_VAR_FORCE_SYNC_MACROS
#if !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && defined(__ATOMIC_RELAXED) && !defined(__sun) && (!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057) /* There will be many false positives if we test Redis with Helgrind, since
* Helgrind can't understand we have imposed ordering on the program, so
* we use macros in helgrind.h to tell Helgrind inter-thread happens-before
* relationship explicitly for avoiding false positives.
*
* For more details, please see: valgrind/helgrind.h and
* https://www.valgrind.org/docs/manual/hg-manual.html#hg-manual.effective-use
*
* These macros take effect only when 'make helgrind', and you must first
* install Valgrind in the default path configuration. */
#ifdef __ATOMIC_VAR_FORCE_SYNC_MACROS
#include <valgrind/helgrind.h>
#else
#define ANNOTATE_HAPPENS_BEFORE(v) ((void) v)
#define ANNOTATE_HAPPENS_AFTER(v) ((void) v)
#endif
#if !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && defined(__STDC_VERSION__) && \
(__STDC_VERSION__ >= 201112L) && !defined(__STDC_NO_ATOMICS__)
/* Use '_Atomic' keyword if the compiler supports. */
#undef redisAtomic
#define redisAtomic _Atomic
/* Implementation using _Atomic in C11. */
#include <stdatomic.h>
#define atomicIncr(var,count) atomic_fetch_add_explicit(&var,(count),memory_order_relaxed)
#define atomicGetIncr(var,oldvalue_var,count) do { \
oldvalue_var = atomic_fetch_add_explicit(&var,(count),memory_order_relaxed); \
} while(0)
#define atomicDecr(var,count) atomic_fetch_sub_explicit(&var,(count),memory_order_relaxed)
#define atomicGet(var,dstvar) do { \
dstvar = atomic_load_explicit(&var,memory_order_relaxed); \
} while(0)
#define atomicSet(var,value) atomic_store_explicit(&var,value,memory_order_relaxed)
#define atomicGetWithSync(var,dstvar) do { \
dstvar = atomic_load_explicit(&var,memory_order_seq_cst); \
} while(0)
#define atomicSetWithSync(var,value) \
atomic_store_explicit(&var,value,memory_order_seq_cst)
#define REDIS_ATOMIC_API "c11-builtin"
#elif !defined(__ATOMIC_VAR_FORCE_SYNC_MACROS) && \
(!defined(__clang__) || !defined(__APPLE__) || __apple_build_version__ > 4210057) && \
defined(__ATOMIC_RELAXED) && defined(__ATOMIC_SEQ_CST)
/* Implementation using __atomic macros. */ /* Implementation using __atomic macros. */
#define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED) #define atomicIncr(var,count) __atomic_add_fetch(&var,(count),__ATOMIC_RELAXED)
@ -80,6 +119,11 @@
dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \ dstvar = __atomic_load_n(&var,__ATOMIC_RELAXED); \
} while(0) } while(0)
#define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED) #define atomicSet(var,value) __atomic_store_n(&var,value,__ATOMIC_RELAXED)
#define atomicGetWithSync(var,dstvar) do { \
dstvar = __atomic_load_n(&var,__ATOMIC_SEQ_CST); \
} while(0)
#define atomicSetWithSync(var,value) \
__atomic_store_n(&var,value,__ATOMIC_SEQ_CST)
#define REDIS_ATOMIC_API "atomic-builtin" #define REDIS_ATOMIC_API "atomic-builtin"
#elif defined(HAVE_ATOMIC) #elif defined(HAVE_ATOMIC)
@ -96,38 +140,19 @@
#define atomicSet(var,value) do { \ #define atomicSet(var,value) do { \
while(!__sync_bool_compare_and_swap(&var,var,value)); \ while(!__sync_bool_compare_and_swap(&var,var,value)); \
} while(0) } while(0)
/* Actually the builtin issues a full memory barrier by default. */
#define atomicGetWithSync(var,dstvar) { \
dstvar = __sync_sub_and_fetch(&var,0,__sync_synchronize); \
ANNOTATE_HAPPENS_AFTER(&var); \
} while(0)
#define atomicSetWithSync(var,value) do { \
ANNOTATE_HAPPENS_BEFORE(&var); \
while(!__sync_bool_compare_and_swap(&var,var,value,__sync_synchronize)); \
} while(0)
#define REDIS_ATOMIC_API "sync-builtin" #define REDIS_ATOMIC_API "sync-builtin"
#else #else
/* Implementation using pthread mutex. */ #error "Unable to determine atomic operations for your platform"
#define atomicIncr(var,count) do { \
pthread_mutex_lock(&var ## _mutex); \
var += (count); \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
#define atomicGetIncr(var,oldvalue_var,count) do { \
pthread_mutex_lock(&var ## _mutex); \
oldvalue_var = var; \
var += (count); \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
#define atomicDecr(var,count) do { \
pthread_mutex_lock(&var ## _mutex); \
var -= (count); \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
#define atomicGet(var,dstvar) do { \
pthread_mutex_lock(&var ## _mutex); \
dstvar = var; \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
#define atomicSet(var,value) do { \
pthread_mutex_lock(&var ## _mutex); \
var = value; \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
#define REDIS_ATOMIC_API "pthread-mutex"
#endif #endif
#endif /* __ATOMIC_VAR_H */ #endif /* __ATOMIC_VAR_H */

View File

@ -78,15 +78,13 @@ static unsigned long long bio_pending[BIO_NUM_OPS];
* file as the API does not expose the internals at all. */ * file as the API does not expose the internals at all. */
struct bio_job { struct bio_job {
time_t time; /* Time at which the job was created. */ time_t time; /* Time at which the job was created. */
/* Job specific arguments pointers. If we need to pass more than three /* Job specific arguments.*/
* arguments we can just pass a pointer to a structure or alike. */ int fd; /* Fd for file based background jobs */
void *arg1, *arg2, *arg3; lazy_free_fn *free_fn; /* Function that will free the provided arguments */
void *free_args[]; /* List of arguments to be passed to the free function */
}; };
void *bioProcessBackgroundJobs(void *arg); void *bioProcessBackgroundJobs(void *arg);
void lazyfreeFreeObjectFromBioThread(robj *o);
void lazyfreeFreeDatabaseFromBioThread(dict *ht1, expireset *set);
void lazyfreeFreeSlotsMapFromBioThread(rax *rt);
/* Make sure we have enough stack to perform all the things we do in the /* Make sure we have enough stack to perform all the things we do in the
* main thread. */ * main thread. */
@ -128,13 +126,8 @@ void bioInit(void) {
} }
} }
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) { void bioSubmitJob(int type, struct bio_job *job) {
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job), MALLOC_LOCAL);
job->time = time(NULL); job->time = time(NULL);
job->arg1 = arg1;
job->arg2 = arg2;
job->arg3 = arg3;
pthread_mutex_lock(&bio_mutex[type]); pthread_mutex_lock(&bio_mutex[type]);
listAddNodeTail(bio_jobs[type],job); listAddNodeTail(bio_jobs[type],job);
bio_pending[type]++; bio_pending[type]++;
@ -142,6 +135,35 @@ void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3) {
pthread_mutex_unlock(&bio_mutex[type]); pthread_mutex_unlock(&bio_mutex[type]);
} }
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...) {
va_list valist;
/* Allocate memory for the job structure and all required
* arguments */
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job) + sizeof(void *) * (arg_count));
job->free_fn = free_fn;
va_start(valist, arg_count);
for (int i = 0; i < arg_count; i++) {
job->free_args[i] = va_arg(valist, void *);
}
va_end(valist);
bioSubmitJob(BIO_LAZY_FREE, job);
}
void bioCreateCloseJob(int fd) {
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job));
job->fd = fd;
bioSubmitJob(BIO_CLOSE_FILE, job);
}
void bioCreateFsyncJob(int fd) {
struct bio_job *job = (bio_job*)zmalloc(sizeof(*job));
job->fd = fd;
bioSubmitJob(BIO_AOF_FSYNC, job);
}
void *bioProcessBackgroundJobs(void *arg) { void *bioProcessBackgroundJobs(void *arg) {
struct bio_job *job; struct bio_job *job;
unsigned long type = (unsigned long) arg; unsigned long type = (unsigned long) arg;
@ -196,20 +218,11 @@ void *bioProcessBackgroundJobs(void *arg) {
/* Process the job accordingly to its type. */ /* Process the job accordingly to its type. */
if (type == BIO_CLOSE_FILE) { if (type == BIO_CLOSE_FILE) {
close((long)job->arg1); close(job->fd);
} else if (type == BIO_AOF_FSYNC) { } else if (type == BIO_AOF_FSYNC) {
redis_fsync((long)job->arg1); redis_fsync(job->fd);
} else if (type == BIO_LAZY_FREE) { } else if (type == BIO_LAZY_FREE) {
/* What we free changes depending on what arguments are set: job->free_fn(job->free_args);
* arg1 -> free the object at pointer.
* arg2 & arg3 -> free two dictionaries (a Redis DB).
* only arg3 -> free the radix tree. */
if (job->arg1)
lazyfreeFreeObjectFromBioThread((robj*)job->arg1);
else if (job->arg2 && job->arg3)
lazyfreeFreeDatabaseFromBioThread((dict*)job->arg2,(expireset*)job->arg3);
else if (job->arg3)
lazyfreeFreeSlotsMapFromBioThread((rax*)job->arg3);
} else { } else {
serverPanic("Wrong job type in bioProcessBackgroundJobs()."); serverPanic("Wrong job type in bioProcessBackgroundJobs().");
} }

View File

@ -32,13 +32,17 @@
extern "C" { extern "C" {
#endif #endif
typedef void lazy_free_fn(void *args[]);
/* Exported API */ /* Exported API */
void bioInit(void); void bioInit(void);
void bioCreateBackgroundJob(int type, void *arg1, void *arg2, void *arg3);
unsigned long long bioPendingJobsOfType(int type); unsigned long long bioPendingJobsOfType(int type);
unsigned long long bioWaitStepOfType(int type); unsigned long long bioWaitStepOfType(int type);
time_t bioOlderJobOfType(int type); time_t bioOlderJobOfType(int type);
void bioKillThreads(void); void bioKillThreads(void);
void bioCreateCloseJob(int fd);
void bioCreateFsyncJob(int fd);
void bioCreateLazyFreeJob(lazy_free_fn free_fn, int arg_count, ...);
/* Background job opcodes */ /* Background job opcodes */
#define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */ #define BIO_CLOSE_FILE 0 /* Deferred close(2) syscall. */

View File

@ -36,7 +36,7 @@
/* Count number of bits set in the binary array pointed by 's' and long /* Count number of bits set in the binary array pointed by 's' and long
* 'count' bytes. The implementation of this function is required to * 'count' bytes. The implementation of this function is required to
* work with an input string length up to 512 MB. */ * work with an input string length up to 512 MB or more (server.proto_max_bulk_len) */
size_t redisPopcount(const void *s, long count) { size_t redisPopcount(const void *s, long count) {
size_t bits = 0; size_t bits = 0;
unsigned char *p = (unsigned char*)s; unsigned char *p = (unsigned char*)s;
@ -407,7 +407,7 @@ void printBits(unsigned char *p, unsigned long count) {
/* This helper function used by GETBIT / SETBIT parses the bit offset argument /* This helper function used by GETBIT / SETBIT parses the bit offset argument
* making sure an error is returned if it is negative or if it overflows * making sure an error is returned if it is negative or if it overflows
* Redis 512 MB limit for the string value. * Redis 512 MB limit for the string value or more (server.proto_max_bulk_len).
* *
* If the 'hash' argument is true, and 'bits is positive, then the command * If the 'hash' argument is true, and 'bits is positive, then the command
* will also parse bit offsets prefixed by "#". In such a case the offset * will also parse bit offsets prefixed by "#". In such a case the offset
@ -430,8 +430,8 @@ int getBitOffsetFromArgument(client *c, robj *o, size_t *offset, int hash, int b
/* Adjust the offset by 'bits' for #<offset> form. */ /* Adjust the offset by 'bits' for #<offset> form. */
if (usehash) loffset *= bits; if (usehash) loffset *= bits;
/* Limit offset to 512MB in bytes */ /* Limit offset to server.proto_max_bulk_len (512MB in bytes by default) */
if ((loffset < 0) || ((unsigned long long)loffset >> 3) >= (512*1024*1024)) if ((loffset < 0) || (loffset >> 3) >= g_pserver->proto_max_bulk_len)
{ {
addReplyError(c,err); addReplyError(c,err);
return C_ERR; return C_ERR;
@ -482,12 +482,12 @@ int getBitfieldTypeFromArgument(client *c, robj *o, int *sign, int *bits) {
robj *lookupStringForBitCommand(client *c, size_t maxbit) { robj *lookupStringForBitCommand(client *c, size_t maxbit) {
size_t byte = maxbit >> 3; size_t byte = maxbit >> 3;
robj *o = lookupKeyWrite(c->db,c->argv[1]); robj *o = lookupKeyWrite(c->db,c->argv[1]);
if (checkType(c,o,OBJ_STRING)) return NULL;
if (o == NULL) { if (o == NULL) {
o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1)); o = createObject(OBJ_STRING,sdsnewlen(NULL, byte+1));
dbAdd(c->db,c->argv[1],o); dbAdd(c->db,c->argv[1],o);
} else { } else {
if (checkType(c,o,OBJ_STRING)) return NULL;
o = dbUnshareStringValue(c->db,c->argv[1],o); o = dbUnshareStringValue(c->db,c->argv[1],o);
o->m_ptr = sdsgrowzero(szFromObj(o),byte+1); o->m_ptr = sdsgrowzero(szFromObj(o),byte+1);
} }
@ -619,7 +619,7 @@ void bitopCommand(client *c) {
else if (!strcasecmp(opname, "rshift")) else if (!strcasecmp(opname, "rshift"))
op = BITOP_RSHIFT; op = BITOP_RSHIFT;
else { else {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
@ -731,7 +731,6 @@ void bitopCommand(client *c) {
/* Compute the bit operation, if at least one string is not empty. */ /* Compute the bit operation, if at least one string is not empty. */
if (maxlen) { if (maxlen) {
res = (unsigned char*) sdsnewlen(NULL,maxlen); res = (unsigned char*) sdsnewlen(NULL,maxlen);
unsigned char output, byte;
unsigned long i; unsigned long i;
/* Fast path: as far as we have data for all the input bitmaps we /* Fast path: as far as we have data for all the input bitmaps we
@ -801,24 +800,35 @@ void bitopCommand(client *c) {
} }
} }
} }
}
#endif #endif
/* j is set to the next byte to process by the previous loop. */ /* j is set to the next byte to process by the previous loop. */
for (; j < maxlen; j++) { for (; j < maxlen; j++) {
output = (len[0] <= j) ? 0 : src[0][j]; auto output = (len[0] <= j) ? 0 : src[0][j];
if (op == BITOP_NOT) output = ~output; if (op == BITOP_NOT) output = ~output;
for (i = 1; i < numkeys; i++) { for (unsigned long i = 1; i < numkeys; i++) {
byte = (len[i] <= j) ? 0 : src[i][j]; int skip = 0;
auto byte = (len[i] <= j) ? 0 : src[i][j];
switch(op) { switch(op) {
case BITOP_AND: output &= byte; break; case BITOP_AND:
case BITOP_OR: output |= byte; break; output &= byte;
skip = (output == 0);
break;
case BITOP_OR:
output |= byte;
skip = (output == 0xff);
break;
case BITOP_XOR: output ^= byte; break; case BITOP_XOR: output ^= byte; break;
} }
if (skip) {
break;
}
} }
res[j] = output; res[j] = output;
} }
} }
}
for (j = 0; j < numkeys; j++) { for (j = 0; j < numkeys; j++) {
if (objects[j]) if (objects[j])
decrRefCount(objects[j]); decrRefCount(objects[j]);
@ -876,7 +886,7 @@ void bitcountCommand(client *c) {
end = strlen-1; end = strlen-1;
} else { } else {
/* Syntax error. */ /* Syntax error. */
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
@ -941,7 +951,7 @@ void bitposCommand(client *c) {
end = strlen-1; end = strlen-1;
} else { } else {
/* Syntax error. */ /* Syntax error. */
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
@ -1033,7 +1043,7 @@ void bitfieldGeneric(client *c, int flags) {
} }
continue; continue;
} else { } else {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
zfree(ops); zfree(ops);
return; return;
} }

View File

@ -61,9 +61,13 @@
*/ */
#include "server.h" #include "server.h"
#include "slowlog.h"
#include "latency.h"
#include "monotonic.h"
#include <mutex> #include <mutex>
int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int where); int serveClientBlockedOnList(client *receiver, robj *key, robj *dstkey, redisDb *db, robj *value, int wherefrom, int whereto);
int getListPositionFromObjectOrReply(client *c, robj *arg, int *position);
/* This structure represents the blocked key information that we store /* This structure represents the blocked key information that we store
* in the client structure. Each client blocked on keys, has a * in the client structure. Each client blocked on keys, has a
@ -90,6 +94,26 @@ void blockClient(client *c, int btype) {
g_pserver->blocked_clients++; g_pserver->blocked_clients++;
g_pserver->blocked_clients_by_type[btype]++; g_pserver->blocked_clients_by_type[btype]++;
addClientToTimeoutTable(c); addClientToTimeoutTable(c);
if (btype == BLOCKED_PAUSE) {
listAddNodeTail(g_pserver->paused_clients, c);
c->paused_list_node = listLast(g_pserver->paused_clients);
/* Mark this client to execute its command */
c->flags |= CLIENT_PENDING_COMMAND;
}
}
/* This function is called after a client has finished a blocking operation
* in order to update the total command duration, log the command into
* the Slow log if needed, and log the reply duration event if needed. */
void updateStatsOnUnblock(client *c, long blocked_us, long reply_us){
const ustime_t total_cmd_duration = c->duration + blocked_us + reply_us;
c->lastcmd->microseconds += total_cmd_duration;
/* Log the command into the Slow log if needed. */
if (!(c->lastcmd->flags & CMD_SKIP_SLOWLOG)) {
slowlogPushEntryIfNeeded(c,c->argv,c->argc,total_cmd_duration);
/* Log the reply duration event. */
latencyAddSampleIfNeeded("command-unblocking",reply_us/1000);
}
} }
/* This function is called in the beforeSleep() function of the event loop /* This function is called in the beforeSleep() function of the event loop
@ -118,6 +142,11 @@ void processUnblockedClients(int iel) {
* client is not blocked before to proceed, but things may change and * client is not blocked before to proceed, but things may change and
* the code is conceptually more correct this way. */ * the code is conceptually more correct this way. */
if (!(c->flags & CLIENT_BLOCKED)) { if (!(c->flags & CLIENT_BLOCKED)) {
/* If we have a queued command, execute it now. */
if (processPendingCommandsAndResetClient(c, CMD_CALL_FULL) == C_ERR) {
continue;
}
/* Then process client if it has more data in it's buffer. */
if (c->querybuf && sdslen(c->querybuf) > 0) { if (c->querybuf && sdslen(c->querybuf) > 0) {
processInputBuffer(c, CMD_CALL_FULL); processInputBuffer(c, CMD_CALL_FULL);
} }
@ -168,6 +197,9 @@ void unblockClient(client *c) {
} else if (c->btype == BLOCKED_MODULE) { } else if (c->btype == BLOCKED_MODULE) {
if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c); if (moduleClientIsBlockedOnKeys(c)) unblockClientWaitingData(c);
unblockClientFromModule(c); unblockClientFromModule(c);
} else if (c->btype == BLOCKED_PAUSE) {
listDelNode(g_pserver->paused_clients,c->paused_list_node);
c->paused_list_node = NULL;
} else { } else {
serverPanic("Unknown btype in unblockClient()."); serverPanic("Unknown btype in unblockClient().");
} }
@ -216,9 +248,16 @@ void disconnectAllBlockedClients(void) {
fastlock_lock(&c->lock); fastlock_lock(&c->lock);
if (c->flags & CLIENT_BLOCKED) { if (c->flags & CLIENT_BLOCKED) {
addReplySds(c,sdsnew( /* PAUSED clients are an exception, when they'll be unblocked, the
* command processing will start from scratch, and the command will
* be either executed or rejected. (unlike LIST blocked clients for
* which the command is already in progress in a way. */
if (c->btype == BLOCKED_PAUSE)
continue;
addReplyError(c,
"-UNBLOCKED force unblock from blocking operation, " "-UNBLOCKED force unblock from blocking operation, "
"instance state changed (master -> replica?)\r\n")); "instance state changed (master -> replica?)");
unblockClient(c); unblockClient(c);
c->flags |= CLIENT_CLOSE_AFTER_REPLY; c->flags |= CLIENT_CLOSE_AFTER_REPLY;
} }
@ -250,10 +289,9 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
} }
robj *dstkey = receiver->bpop.target; robj *dstkey = receiver->bpop.target;
int where = (receiver->lastcmd && int wherefrom = receiver->bpop.listpos.wherefrom;
receiver->lastcmd->proc == blpopCommand) ? int whereto = receiver->bpop.listpos.whereto;
LIST_HEAD : LIST_TAIL; robj *value = listTypePop(o, wherefrom);
robj *value = listTypePop(o,where);
if (value) { if (value) {
/* Protect receiver->bpop.target, that will be /* Protect receiver->bpop.target, that will be
@ -262,14 +300,17 @@ void serveClientsBlockedOnListKey(robj *o, readyList *rl) {
if (dstkey) incrRefCount(dstkey); if (dstkey) incrRefCount(dstkey);
unblockClient(receiver); unblockClient(receiver);
monotime replyTimer;
elapsedStart(&replyTimer);
if (serveClientBlockedOnList(receiver, if (serveClientBlockedOnList(receiver,
rl->key,dstkey,rl->db,value, rl->key,dstkey,rl->db,value,
where) == C_ERR) wherefrom, whereto) == C_ERR)
{ {
/* If we failed serving the client we need /* If we failed serving the client we need
* to also undo the POP operation. */ * to also undo the POP operation. */
listTypePush(o,value,where); listTypePush(o,value,wherefrom);
} }
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
if (dstkey) decrRefCount(dstkey); if (dstkey) decrRefCount(dstkey);
decrRefCount(value); decrRefCount(value);
@ -315,7 +356,10 @@ void serveClientsBlockedOnSortedSetKey(robj *o, readyList *rl) {
receiver->lastcmd->proc == bzpopminCommand) receiver->lastcmd->proc == bzpopminCommand)
? ZSET_MIN : ZSET_MAX; ? ZSET_MIN : ZSET_MAX;
unblockClient(receiver); unblockClient(receiver);
monotime replyTimer;
elapsedStart(&replyTimer);
genericZpopCommand(receiver,&rl->key,1,where,1,NULL); genericZpopCommand(receiver,&rl->key,1,where,1,NULL);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
zcard--; zcard--;
/* Replicate the command. */ /* Replicate the command. */
@ -392,13 +436,22 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
int noack = 0; int noack = 0;
if (group) { if (group) {
int created = 0;
consumer = consumer =
streamLookupConsumer(group, streamLookupConsumer(group,
szFromObj(receiver->bpop.xread_consumer), szFromObj(receiver->bpop.xread_consumer),
SLC_NONE); SLC_NONE,
&created);
noack = receiver->bpop.xread_group_noack; noack = receiver->bpop.xread_group_noack;
if (created && noack) {
streamPropagateConsumerCreation(receiver,rl->key,
receiver->bpop.xread_group,
consumer->name);
}
} }
monotime replyTimer;
elapsedStart(&replyTimer);
/* Emit the two elements sub-array consisting of /* Emit the two elements sub-array consisting of
* the name of the stream and the data we * the name of the stream and the data we
* extracted from it. Wrapped in a single-item * extracted from it. Wrapped in a single-item
@ -418,6 +471,7 @@ void serveClientsBlockedOnStreamKey(robj *o, readyList *rl) {
streamReplyWithRange(receiver,s,&start,NULL, streamReplyWithRange(receiver,s,&start,NULL,
receiver->bpop.xread_count, receiver->bpop.xread_count,
0, group, consumer, noack, &pi); 0, group, consumer, noack, &pi);
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
/* Note that after we unblock the client, 'gt' /* Note that after we unblock the client, 'gt'
* and other receiver->bpop stuff are no longer * and other receiver->bpop stuff are no longer
@ -464,7 +518,10 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
* different modules with different triggers to consider if a key * different modules with different triggers to consider if a key
* is ready or not. This means we can't exit the loop but need * is ready or not. This means we can't exit the loop but need
* to continue after the first failure. */ * to continue after the first failure. */
monotime replyTimer;
elapsedStart(&replyTimer);
if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue; if (!moduleTryServeClientBlockedOnKey(receiver, rl->key)) continue;
updateStatsOnUnblock(receiver, 0, elapsedUs(replyTimer));
moduleUnblockClient(receiver); moduleUnblockClient(receiver);
} }
@ -480,8 +537,8 @@ void serveClientsBlockedOnKeyByModule(readyList *rl) {
* one new element via some write operation are accumulated into * one new element via some write operation are accumulated into
* the g_pserver->ready_keys list. This function will run the list and will * the g_pserver->ready_keys list. This function will run the list and will
* serve clients accordingly. Note that the function will iterate again and * serve clients accordingly. Note that the function will iterate again and
* again as a result of serving BRPOPLPUSH we can have new blocking clients * again as a result of serving BLMOVE we can have new blocking clients
* to serve because of the PUSH side of BRPOPLPUSH. * to serve because of the PUSH side of BLMOVE.
* *
* This function is normally "fair", that is, it will server clients * This function is normally "fair", that is, it will server clients
* using a FIFO behavior. However this fairness is violated in certain * using a FIFO behavior. However this fairness is violated in certain
@ -515,7 +572,7 @@ void handleClientsBlockedOnKeys(void) {
/* Even if we are not inside call(), increment the call depth /* Even if we are not inside call(), increment the call depth
* in order to make sure that keys are expired against a fixed * in order to make sure that keys are expired against a fixed
* reference time, and not against the wallclock time. This * reference time, and not against the wallclock time. This
* way we can lookup an object multiple times (BRPOPLPUSH does * way we can lookup an object multiple times (BLMOVE does
* that) without the risk of it being freed in the second * that) without the risk of it being freed in the second
* lookup, invalidating the first one. * lookup, invalidating the first one.
* See https://github.com/antirez/redis/pull/6554. */ * See https://github.com/antirez/redis/pull/6554. */
@ -575,7 +632,7 @@ void handleClientsBlockedOnKeys(void) {
* stream keys, we also provide an array of streamID structures: clients will * stream keys, we also provide an array of streamID structures: clients will
* be unblocked only when items with an ID greater or equal to the specified * be unblocked only when items with an ID greater or equal to the specified
* one is appended to the stream. */ * one is appended to the stream. */
void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, streamID *ids) { void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeout, robj *target, struct listPos *listpos, streamID *ids) {
dictEntry *de; dictEntry *de;
list *l; list *l;
int j; int j;
@ -583,6 +640,8 @@ void blockForKeys(client *c, int btype, robj **keys, int numkeys, mstime_t timeo
c->bpop.timeout = timeout; c->bpop.timeout = timeout;
c->bpop.target = target; c->bpop.target = target;
if (listpos != NULL) c->bpop.listpos = *listpos;
if (target != NULL) incrRefCount(target); if (target != NULL) incrRefCount(target);
for (j = 0; j < numkeys; j++) { for (j = 0; j < numkeys; j++) {
@ -656,6 +715,16 @@ void unblockClientWaitingData(client *c) {
} }
} }
static int getBlockedTypeByType(int type) {
switch (type) {
case OBJ_LIST: return BLOCKED_LIST;
case OBJ_ZSET: return BLOCKED_ZSET;
case OBJ_MODULE: return BLOCKED_MODULE;
case OBJ_STREAM: return BLOCKED_STREAM;
default: return BLOCKED_NONE;
}
}
/* If the specified key has clients blocked waiting for list pushes, this /* If the specified key has clients blocked waiting for list pushes, this
* function will put the key reference into the g_pserver->ready_keys list. * function will put the key reference into the g_pserver->ready_keys list.
* Note that db->ready_keys is a hash table that allows us to avoid putting * Note that db->ready_keys is a hash table that allows us to avoid putting
@ -663,9 +732,24 @@ void unblockClientWaitingData(client *c) {
* made by a script or in the context of MULTI/EXEC. * made by a script or in the context of MULTI/EXEC.
* *
* The list will be finally processed by handleClientsBlockedOnKeys() */ * The list will be finally processed by handleClientsBlockedOnKeys() */
void signalKeyAsReady(redisDb *db, robj *key) { void signalKeyAsReady(redisDb *db, robj *key, int type) {
readyList *rl; readyList *rl;
/* Quick returns. */
int btype = getBlockedTypeByType(type);
if (btype == BLOCKED_NONE) {
/* The type can never block. */
return;
}
if (!g_pserver->blocked_clients_by_type[btype] &&
!g_pserver->blocked_clients_by_type[BLOCKED_MODULE]) {
/* No clients block on this type. Note: Blocked modules are represented
* by BLOCKED_MODULE, even if the intention is to wake up by normal
* types (list, zset, stream), so we need to check that there are no
* blocked modules before we do a quick return here. */
return;
}
/* No clients blocking for this key? No need to queue it. */ /* No clients blocking for this key? No need to queue it. */
if (dictFind(db->blocking_keys,key) == NULL) return; if (dictFind(db->blocking_keys,key) == NULL) return;
@ -693,4 +777,8 @@ void signalKeyAsReady(redisDb *db, robj *key) {
serverAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK); serverAssert(dictAdd(db->ready_keys,key,NULL) == DICT_OK);
} }
void signalKeyAsReady(redisDb *db, sds key, int type) {
redisObjectStack o;
initStaticStringObject(o, key);
signalKeyAsReady(db, &o, type);
}

View File

@ -30,18 +30,25 @@
#include "server.h" #include "server.h"
#include <unistd.h> #include <unistd.h>
typedef struct {
size_t keys;
size_t cow;
double progress;
childInfoType information_type; /* Type of information */
} child_info_data;
/* Open a child-parent channel used in order to move information about the /* Open a child-parent channel used in order to move information about the
* RDB / AOF saving process from the child to the parent (for instance * RDB / AOF saving process from the child to the parent (for instance
* the amount of copy on write memory used) */ * the amount of copy on write memory used) */
void openChildInfoPipe(void) { void openChildInfoPipe(void) {
if (pipe(g_pserver->child_info_pipe) == -1) { if (pipe(g_pserver->child_info_pipe) == -1) {
/* On error our two file descriptors should be still set to -1, /* On error our two file descriptors should be still set to -1,
* but we call anyway cloesChildInfoPipe() since can't hurt. */ * but we call anyway closeChildInfoPipe() since can't hurt. */
closeChildInfoPipe(); closeChildInfoPipe();
} else if (anetNonBlock(NULL,g_pserver->child_info_pipe[0]) != ANET_OK) { } else if (anetNonBlock(NULL,g_pserver->child_info_pipe[0]) != ANET_OK) {
closeChildInfoPipe(); closeChildInfoPipe();
} else { } else {
memset(&g_pserver->child_info_data,0,sizeof(g_pserver->child_info_data)); g_pserver->child_info_nread = 0;
} }
} }
@ -54,34 +61,88 @@ void closeChildInfoPipe(void) {
close(g_pserver->child_info_pipe[1]); close(g_pserver->child_info_pipe[1]);
g_pserver->child_info_pipe[0] = -1; g_pserver->child_info_pipe[0] = -1;
g_pserver->child_info_pipe[1] = -1; g_pserver->child_info_pipe[1] = -1;
g_pserver->child_info_nread = 0;
} }
} }
/* Send COW data to parent. The child should call this function after populating /* Send save data to parent. */
* the corresponding fields it want to sent (according to the process type). */ void sendChildInfoGeneric(childInfoType info_type, size_t keys, double progress, const char *pname) {
void sendChildInfo(int ptype) {
if (g_pserver->child_info_pipe[1] == -1) return; if (g_pserver->child_info_pipe[1] == -1) return;
g_pserver->child_info_data.magic = CHILD_INFO_MAGIC;
g_pserver->child_info_data.process_type = ptype; child_info_data data = {0}; /* zero everything, including padding to sattisfy valgrind */
ssize_t wlen = sizeof(g_pserver->child_info_data); data.information_type = info_type;
if (write(g_pserver->child_info_pipe[1],&g_pserver->child_info_data,wlen) != wlen) { data.keys = keys;
data.cow = zmalloc_get_private_dirty(-1);
data.progress = progress;
if (data.cow) {
serverLog((info_type == CHILD_INFO_TYPE_CURRENT_INFO) ? LL_VERBOSE : LL_NOTICE,
"%s: %zu MB of memory used by copy-on-write",
pname, data.cow/(1024*1024));
}
ssize_t wlen = sizeof(data);
if (write(g_pserver->child_info_pipe[1], &data, wlen) != wlen) {
/* Nothing to do on error, this will be detected by the other side. */ /* Nothing to do on error, this will be detected by the other side. */
} }
} }
/* Receive COW data from parent. */ /* Update Child info. */
void updateChildInfo(childInfoType information_type, size_t cow, size_t keys, double progress) {
if (information_type == CHILD_INFO_TYPE_CURRENT_INFO) {
g_pserver->stat_current_cow_bytes = cow;
g_pserver->stat_current_save_keys_processed = keys;
if (progress != -1) g_pserver->stat_module_progress = progress;
} else if (information_type == CHILD_INFO_TYPE_AOF_COW_SIZE) {
g_pserver->stat_aof_cow_bytes = cow;
} else if (information_type == CHILD_INFO_TYPE_RDB_COW_SIZE) {
g_pserver->stat_rdb_cow_bytes = cow;
} else if (information_type == CHILD_INFO_TYPE_MODULE_COW_SIZE) {
g_pserver->stat_module_cow_bytes = cow;
}
}
/* Read child info data from the pipe.
* if complete data read into the buffer,
* data is stored into *buffer, and returns 1.
* otherwise, the partial data is left in the buffer, waiting for the next read, and returns 0. */
int readChildInfo(childInfoType *information_type, size_t *cow, size_t *keys, double* progress) {
/* We are using here a static buffer in combination with the server.child_info_nread to handle short reads */
static child_info_data buffer;
ssize_t wlen = sizeof(buffer);
/* Do not overlap */
if (g_pserver->child_info_nread == wlen) g_pserver->child_info_nread = 0;
int nread = read(g_pserver->child_info_pipe[0], (char *)&buffer + g_pserver->child_info_nread, wlen - g_pserver->child_info_nread);
if (nread > 0) {
g_pserver->child_info_nread += nread;
}
/* We have complete child info */
if (g_pserver->child_info_nread == wlen) {
*information_type = buffer.information_type;
*cow = buffer.cow;
*keys = buffer.keys;
*progress = buffer.progress;
return 1;
} else {
return 0;
}
}
/* Receive info data from child. */
void receiveChildInfo(void) { void receiveChildInfo(void) {
if (g_pserver->child_info_pipe[0] == -1) return; if (g_pserver->child_info_pipe[0] == -1) return;
ssize_t wlen = sizeof(g_pserver->child_info_data);
if (read(g_pserver->child_info_pipe[0],&g_pserver->child_info_data,wlen) == wlen && size_t cow;
g_pserver->child_info_data.magic == CHILD_INFO_MAGIC) size_t keys;
{ double progress;
if (g_pserver->child_info_data.process_type == CHILD_TYPE_RDB) { childInfoType information_type;
g_pserver->stat_rdb_cow_bytes = g_pserver->child_info_data.cow_size;
} else if (g_pserver->child_info_data.process_type == CHILD_TYPE_AOF) { /* Drain the pipe and update child info so that we get the final message. */
g_pserver->stat_aof_cow_bytes = g_pserver->child_info_data.cow_size; while (readChildInfo(&information_type, &cow, &keys, &progress)) {
} else if (g_pserver->child_info_data.process_type == CHILD_TYPE_MODULE) { updateChildInfo(information_type, cow, keys, progress);
g_pserver->stat_module_cow_bytes = g_pserver->child_info_data.cow_size;
}
} }
} }

194
src/cli_common.c Normal file
View File

@ -0,0 +1,194 @@
/* CLI (command line interface) common methods
*
* Copyright (c) 2020, Redis Labs
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "cli_common.h"
#include <errno.h>
#include <hiredis.h>
#include <sdscompat.h> /* Use hiredis' sds compat header that maps sds calls to their hi_ variants */
#include <sds.h> /* use sds.h from hiredis, so that only one set of sds functions will be present in the binary */
#ifdef USE_OPENSSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <hiredis_ssl.h>
#endif
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
* not building with TLS support.
*/
int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err) {
#ifdef USE_OPENSSL
static SSL_CTX *ssl_ctx = NULL;
if (!ssl_ctx) {
ssl_ctx = SSL_CTX_new(SSLv23_client_method());
if (!ssl_ctx) {
*err = "Failed to create SSL_CTX";
goto error;
}
SSL_CTX_set_options(ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
SSL_CTX_set_verify(ssl_ctx, config.skip_cert_verify ? SSL_VERIFY_NONE : SSL_VERIFY_PEER, NULL);
if (config.cacert || config.cacertdir) {
if (!SSL_CTX_load_verify_locations(ssl_ctx, config.cacert, config.cacertdir)) {
*err = "Invalid CA Certificate File/Directory";
goto error;
}
} else {
if (!SSL_CTX_set_default_verify_paths(ssl_ctx)) {
*err = "Failed to use default CA paths";
goto error;
}
}
if (config.cert && !SSL_CTX_use_certificate_chain_file(ssl_ctx, config.cert)) {
*err = "Invalid client certificate";
goto error;
}
if (config.key && !SSL_CTX_use_PrivateKey_file(ssl_ctx, config.key, SSL_FILETYPE_PEM)) {
*err = "Invalid private key";
goto error;
}
if (config.ciphers && !SSL_CTX_set_cipher_list(ssl_ctx, config.ciphers)) {
*err = "Error while configuring ciphers";
goto error;
}
#ifdef TLS1_3_VERSION
if (config.ciphersuites && !SSL_CTX_set_ciphersuites(ssl_ctx, config.ciphersuites)) {
*err = "Error while setting cypher suites";
goto error;
}
#endif
}
SSL *ssl = SSL_new(ssl_ctx);
if (!ssl) {
*err = "Failed to create SSL object";
return REDIS_ERR;
}
if (config.sni && !SSL_set_tlsext_host_name(ssl, config.sni)) {
*err = "Failed to configure SNI";
SSL_free(ssl);
return REDIS_ERR;
}
return redisInitiateSSL(c, ssl);
error:
SSL_CTX_free(ssl_ctx);
ssl_ctx = NULL;
return REDIS_ERR;
#else
(void) config;
(void) c;
(void) err;
return REDIS_OK;
#endif
}
/* Wrapper around hiredis to allow arbitrary reads and writes.
*
* We piggybacks on top of hiredis to achieve transparent TLS support,
* and use its internal buffers so it can co-exist with commands
* previously/later issued on the connection.
*
* Interface is close to enough to read()/write() so things should mostly
* work transparently.
*/
/* Write a raw buffer through a redisContext. If we already have something
* in the buffer (leftovers from hiredis operations) it will be written
* as well.
*/
ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len)
{
int done = 0;
/* Append data to buffer which is *usually* expected to be empty
* but we don't assume that, and write.
*/
c->obuf = sdscatlen(c->obuf, buf, buf_len);
if (redisBufferWrite(c, &done) == REDIS_ERR) {
if (!(c->flags & REDIS_BLOCK))
errno = EAGAIN;
/* On error, we assume nothing was written and we roll back the
* buffer to its original state.
*/
if (sdslen(c->obuf) > buf_len)
sdsrange(c->obuf, 0, -(buf_len+1));
else
sdsclear(c->obuf);
return -1;
}
/* If we're done, free up everything. We may have written more than
* buf_len (if c->obuf was not initially empty) but we don't have to
* tell.
*/
if (done) {
sdsclear(c->obuf);
return buf_len;
}
/* Write was successful but we have some leftovers which we should
* remove from the buffer.
*
* Do we still have data that was there prior to our buf? If so,
* restore buffer to it's original state and report no new data was
* writen.
*/
if (sdslen(c->obuf) > buf_len) {
sdsrange(c->obuf, 0, -(buf_len+1));
return 0;
}
/* At this point we're sure no prior data is left. We flush the buffer
* and report how much we've written.
*/
size_t left = sdslen(c->obuf);
sdsclear(c->obuf);
return buf_len - left;
}
/* Wrapper around OpenSSL (libssl and libcrypto) initialisation
*/
int cliSecureInit()
{
#ifdef USE_OPENSSL
ERR_load_crypto_strings();
SSL_load_error_strings();
SSL_library_init();
#endif
return REDIS_OK;
}

58
src/cli_common.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef __CLICOMMON_H
#define __CLICOMMON_H
#include <hiredis.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct cliSSLconfig {
/* Requested SNI, or NULL */
char *sni;
/* CA Certificate file, or NULL */
char *cacert;
/* Directory where trusted CA certificates are stored, or NULL */
char *cacertdir;
/* Skip server certificate verification. */
int skip_cert_verify;
/* Client certificate to authenticate with, or NULL */
char *cert;
/* Private key file to authenticate with, or NULL */
char *key;
/* Prefered cipher list, or NULL (applies only to <= TLSv1.2) */
char* ciphers;
/* Prefered ciphersuites list, or NULL (applies only to TLSv1.3) */
char* ciphersuites;
} cliSSLconfig;
/* Wrapper around redisSecureConnection to avoid hiredis_ssl dependencies if
* not building with TLS support.
*/
int cliSecureConnection(redisContext *c, cliSSLconfig config, const char **err);
/* Wrapper around hiredis to allow arbitrary reads and writes.
*
* We piggybacks on top of hiredis to achieve transparent TLS support,
* and use its internal buffers so it can co-exist with commands
* previously/later issued on the connection.
*
* Interface is close to enough to read()/write() so things should mostly
* work transparently.
*/
/* Write a raw buffer through a redisContext. If we already have something
* in the buffer (leftovers from hiredis operations) it will be written
* as well.
*/
ssize_t cliWriteConn(redisContext *c, const char *buf, size_t buf_len);
/* Wrapper around OpenSSL (libssl and libcrypto) initialisation.
*/
int cliSecureInit();
#ifdef __cplusplus
}
#endif
#endif /* __CLICOMMON_H */

View File

@ -47,7 +47,7 @@
clusterNode *myself = NULL; clusterNode *myself = NULL;
clusterNode *createClusterNode(char *nodename, int flags); clusterNode *createClusterNode(char *nodename, int flags);
int clusterAddNode(clusterNode *node); void clusterAddNode(clusterNode *node);
void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask); void clusterAcceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
void clusterReadHandler(connection *conn); void clusterReadHandler(connection *conn);
void clusterSendPing(clusterLink *link, int type); void clusterSendPing(clusterLink *link, int type);
@ -415,7 +415,7 @@ int clusterLockConfig(char *filename) {
/* To lock it, we need to open the file in a way it is created if /* To lock it, we need to open the file in a way it is created if
* it does not exist, otherwise there is a race condition with other * it does not exist, otherwise there is a race condition with other
* processes. */ * processes. */
int fd = open(filename,O_WRONLY|O_CREAT,0644); int fd = open(filename,O_WRONLY|O_CREAT|O_CLOEXEC,0644);
if (fd == -1) { if (fd == -1) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Can't open %s in order to acquire a lock: %s", "Can't open %s in order to acquire a lock: %s",
@ -519,7 +519,7 @@ void clusterInit(void) {
if (saveconf) clusterSaveConfigOrDie(1); if (saveconf) clusterSaveConfigOrDie(1);
/* We need a listening TCP port for our cluster messaging needs. */ /* We need a listening TCP port for our cluster messaging needs. */
g_pserver->cfd_count = 0; g_pserver->cfd.count = 0;
/* Port sanity check II /* Port sanity check II
* The other handshake port check is triggered too late to stop * The other handshake port check is triggered too late to stop
@ -529,23 +529,16 @@ void clusterInit(void) {
serverLog(LL_WARNING, "KeyDB port number too high. " serverLog(LL_WARNING, "KeyDB port number too high. "
"Cluster communication port is 10,000 port " "Cluster communication port is 10,000 port "
"numbers higher than your KeyDB port. " "numbers higher than your KeyDB port. "
"Your KeyDB port number must be " "Your KeyDB port number must be 55535 or less.");
"lower than 55535.");
exit(1); exit(1);
} }
if (listenToPort(port+CLUSTER_PORT_INCR, if (listenToPort(port+CLUSTER_PORT_INCR, &g_pserver->cfd, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR) {
g_pserver->cfd,&g_pserver->cfd_count, 0 /*fReusePort*/, 0 /*fFirstListen*/) == C_ERR)
{
exit(1); exit(1);
} else { }
int j;
for (j = 0; j < g_pserver->cfd_count; j++) { serverAssert(serverTL == &g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN]);
if (aeCreateFileEvent(g_pserver->rgthreadvar[IDX_EVENT_LOOP_MAIN].el, g_pserver->cfd[j], AE_READABLE, if (createSocketAcceptHandler(&g_pserver->cfd, clusterAcceptHandler) != C_OK) {
clusterAcceptHandler, NULL) == AE_ERR) serverPanic("Unrecoverable error creating Redis Cluster socket accept handler.");
serverPanic("Unrecoverable error creating Redis Cluster "
"file event.");
}
} }
/* The slots -> keys map is a radix tree. Initialize it here. */ /* The slots -> keys map is a radix tree. Initialize it here. */
@ -815,6 +808,7 @@ clusterNode *createClusterNode(char *nodename, int flags) {
node->configEpoch = 0; node->configEpoch = 0;
node->flags = flags; node->flags = flags;
memset(node->slots,0,sizeof(node->slots)); memset(node->slots,0,sizeof(node->slots));
node->slots_info = NULL;
node->numslots = 0; node->numslots = 0;
node->numslaves = 0; node->numslaves = 0;
node->slaves = NULL; node->slaves = NULL;
@ -997,12 +991,12 @@ void freeClusterNode(clusterNode *n) {
} }
/* Add a node to the nodes hash table */ /* Add a node to the nodes hash table */
int clusterAddNode(clusterNode *node) { void clusterAddNode(clusterNode *node) {
int retval; int retval;
retval = dictAdd(g_pserver->cluster->nodes, retval = dictAdd(g_pserver->cluster->nodes,
sdsnewlen(node->name,CLUSTER_NAMELEN), node); sdsnewlen(node->name,CLUSTER_NAMELEN), node);
return (retval == DICT_OK) ? C_OK : C_ERR; serverAssert(retval == DICT_OK);
} }
/* Remove a node from the cluster. The function performs the high level /* Remove a node from the cluster. The function performs the high level
@ -1862,6 +1856,7 @@ int clusterProcessPacket(clusterLink *link) {
g_pserver->cluster->mf_master_offset == 0) g_pserver->cluster->mf_master_offset == 0)
{ {
g_pserver->cluster->mf_master_offset = sender->repl_offset; g_pserver->cluster->mf_master_offset = sender->repl_offset;
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Received replication offset for paused " "Received replication offset for paused "
"master manual failover: %lld", "master manual failover: %lld",
@ -2165,7 +2160,7 @@ int clusterProcessPacket(clusterLink *link) {
/* Don't bother creating useless objects if there are no /* Don't bother creating useless objects if there are no
* Pub/Sub subscribers. */ * Pub/Sub subscribers. */
if (dictSize(g_pserver->pubsub_channels) || if (dictSize(g_pserver->pubsub_channels) ||
listLength(g_pserver->pubsub_patterns)) dictSize(g_pserver->pubsub_patterns))
{ {
channel_len = ntohl(hdr->data.publish.msg.channel_len); channel_len = ntohl(hdr->data.publish.msg.channel_len);
message_len = ntohl(hdr->data.publish.msg.message_len); message_len = ntohl(hdr->data.publish.msg.message_len);
@ -2203,9 +2198,15 @@ int clusterProcessPacket(clusterLink *link) {
resetManualFailover(); resetManualFailover();
g_pserver->cluster->mf_end = now + CLUSTER_MF_TIMEOUT; g_pserver->cluster->mf_end = now + CLUSTER_MF_TIMEOUT;
g_pserver->cluster->mf_slave = sender; g_pserver->cluster->mf_slave = sender;
pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT)); pauseClients(now+(CLUSTER_MF_TIMEOUT*CLUSTER_MF_PAUSE_MULT),CLIENT_PAUSE_WRITE);
serverLog(LL_WARNING,"Manual failover requested by replica %.40s.", serverLog(LL_WARNING,"Manual failover requested by replica %.40s.",
sender->name); sender->name);
/* We need to send a ping message to the replica, as it would carry
* `server.cluster->mf_master_offset`, which means the master paused clients
* at offset `server.cluster->mf_master_offset`, so that the replica would
* know that it is safe to set its `server.cluster->mf_can_start` to 1 so as
* to complete failover as quickly as possible. */
clusterSendPing(link, CLUSTERMSG_TYPE_PING);
} else if (type == CLUSTERMSG_TYPE_UPDATE) { } else if (type == CLUSTERMSG_TYPE_UPDATE) {
clusterNode *n; /* The node the update is about. */ clusterNode *n; /* The node the update is about. */
uint64_t reportedConfigEpoch = uint64_t reportedConfigEpoch =
@ -2856,7 +2857,7 @@ void clusterPropagatePublish(robj *channel, robj *message) {
* SLAVE node specific functions * SLAVE node specific functions
* -------------------------------------------------------------------------- */ * -------------------------------------------------------------------------- */
/* This function sends a FAILOVE_AUTH_REQUEST message to every node in order to /* This function sends a FAILOVER_AUTH_REQUEST message to every node in order to
* see if there is the quorum for this slave instance to failover its failing * see if there is the quorum for this slave instance to failover its failing
* master. * master.
* *
@ -3462,9 +3463,8 @@ void clusterHandleSlaveMigration(int max_slaves) {
* The function can be used both to initialize the manual failover state at * The function can be used both to initialize the manual failover state at
* startup or to abort a manual failover in progress. */ * startup or to abort a manual failover in progress. */
void resetManualFailover(void) { void resetManualFailover(void) {
if (g_pserver->cluster->mf_end && clientsArePaused()) { if (g_pserver->cluster->mf_end) {
g_pserver->clients_pause_end_time = 0; checkClientPauseTimeoutAndReturnIfPaused();
unpauseClientsIfNecessary();
} }
g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */ g_pserver->cluster->mf_end = 0; /* No manual failover in progress. */
g_pserver->cluster->mf_can_start = 0; g_pserver->cluster->mf_can_start = 0;
@ -3499,7 +3499,10 @@ void clusterHandleManualFailover(void) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"All master replication stream processed, " "All master replication stream processed, "
"manual failover can start."); "manual failover can start.");
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_FAILOVER);
return;
} }
clusterDoBeforeSleep(CLUSTER_TODO_HANDLE_MANUALFAILOVER);
} }
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
@ -3774,25 +3777,35 @@ void clusterCron(void) {
* handlers, or to perform potentially expansive tasks that we need to do * handlers, or to perform potentially expansive tasks that we need to do
* a single time before replying to clients. */ * a single time before replying to clients. */
void clusterBeforeSleep(void) { void clusterBeforeSleep(void) {
/* Handle failover, this is needed when it is likely that there is already int flags = g_pserver->cluster->todo_before_sleep;
* the quorum from masters in order to react fast. */
if (g_pserver->cluster->todo_before_sleep & CLUSTER_TODO_HANDLE_FAILOVER)
clusterHandleSlaveFailover();
/* Update the cluster state. */
if (g_pserver->cluster->todo_before_sleep & CLUSTER_TODO_UPDATE_STATE)
clusterUpdateState();
/* Save the config, possibly using fsync. */
if (g_pserver->cluster->todo_before_sleep & CLUSTER_TODO_SAVE_CONFIG) {
int fsync = g_pserver->cluster->todo_before_sleep &
CLUSTER_TODO_FSYNC_CONFIG;
clusterSaveConfigOrDie(fsync);
}
/* Reset our flags (not strictly needed since every single function /* Reset our flags (not strictly needed since every single function
* called for flags set should be able to clear its flag). */ * called for flags set should be able to clear its flag). */
g_pserver->cluster->todo_before_sleep = 0; g_pserver->cluster->todo_before_sleep = 0;
if (flags & CLUSTER_TODO_HANDLE_MANUALFAILOVER) {
/* Handle manual failover as soon as possible so that won't have a 100ms
* as it was handled only in clusterCron */
if(nodeIsSlave(myself)) {
clusterHandleManualFailover();
if (!(g_pserver->cluster_module_flags & CLUSTER_MODULE_FLAG_NO_FAILOVER))
clusterHandleSlaveFailover();
}
} else if (flags & CLUSTER_TODO_HANDLE_FAILOVER) {
/* Handle failover, this is needed when it is likely that there is already
* the quorum from masters in order to react fast. */
clusterHandleSlaveFailover();
}
/* Update the cluster state. */
if (flags & CLUSTER_TODO_UPDATE_STATE)
clusterUpdateState();
/* Save the config, possibly using fsync. */
if (flags & CLUSTER_TODO_SAVE_CONFIG) {
int fsync = flags & CLUSTER_TODO_FSYNC_CONFIG;
clusterSaveConfigOrDie(fsync);
}
} }
void clusterDoBeforeSleep(int flags) { void clusterDoBeforeSleep(int flags) {
@ -4173,8 +4186,8 @@ sds clusterGenNodeDescription(clusterNode *node) {
sds ci; sds ci;
/* Node coordinates */ /* Node coordinates */
ci = sdscatprintf(sdsempty(),"%.40s %s:%d@%d ", ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN);
node->name, ci = sdscatfmt(ci," %s:%i@%i ",
node->ip, node->ip,
node->port, node->port,
node->cport); node->cport);
@ -4183,24 +4196,29 @@ sds clusterGenNodeDescription(clusterNode *node) {
ci = representClusterNodeFlags(ci, node->flags); ci = representClusterNodeFlags(ci, node->flags);
/* Slave of... or just "-" */ /* Slave of... or just "-" */
ci = sdscatlen(ci," ",1);
if (node->slaveof) if (node->slaveof)
ci = sdscatprintf(ci," %.40s ",node->slaveof->name); ci = sdscatlen(ci,node->slaveof->name,CLUSTER_NAMELEN);
else else
ci = sdscatlen(ci," - ",3); ci = sdscatlen(ci,"-",1);
unsigned long long nodeEpoch = node->configEpoch; unsigned long long nodeEpoch = node->configEpoch;
if (nodeIsSlave(node) && node->slaveof) { if (nodeIsSlave(node) && node->slaveof) {
nodeEpoch = node->slaveof->configEpoch; nodeEpoch = node->slaveof->configEpoch;
} }
/* Latency from the POV of this node, config epoch, link status */ /* Latency from the POV of this node, config epoch, link status */
ci = sdscatprintf(ci,"%lld %lld %llu %s", ci = sdscatfmt(ci," %I %I %U %s",
(long long) node->ping_sent, (long long) node->ping_sent,
(long long) node->pong_received, (long long) node->pong_received,
nodeEpoch, nodeEpoch,
(node->link || node->flags & CLUSTER_NODE_MYSELF) ? (node->link || node->flags & CLUSTER_NODE_MYSELF) ?
"connected" : "disconnected"); "connected" : "disconnected");
/* Slots served by this instance */ /* Slots served by this instance. If we already have slots info,
* append it diretly, otherwise, generate slots only if it has. */
if (node->slots_info) {
ci = sdscatsds(ci, node->slots_info);
} else if (node->numslots > 0) {
start = -1; start = -1;
for (j = 0; j < CLUSTER_SLOTS; j++) { for (j = 0; j < CLUSTER_SLOTS; j++) {
int bit; int bit;
@ -4212,13 +4230,14 @@ sds clusterGenNodeDescription(clusterNode *node) {
if (bit && j == CLUSTER_SLOTS-1) j++; if (bit && j == CLUSTER_SLOTS-1) j++;
if (start == j-1) { if (start == j-1) {
ci = sdscatprintf(ci," %d",start); ci = sdscatfmt(ci," %i",start);
} else { } else {
ci = sdscatprintf(ci," %d-%d",start,j-1); ci = sdscatfmt(ci," %i-%i",start,j-1);
} }
start = -1; start = -1;
} }
} }
}
/* Just for MYSELF node we also dump info about slots that /* Just for MYSELF node we also dump info about slots that
* we are migrating to other instances or importing from other * we are migrating to other instances or importing from other
@ -4237,6 +4256,41 @@ sds clusterGenNodeDescription(clusterNode *node) {
return ci; return ci;
} }
/* Generate the slot topology for all nodes and store the string representation
* in the slots_info struct on the node. This is used to improve the efficiency
* of clusterGenNodesDescription() because it removes looping of the slot space
* for generating the slot info for each node individually. */
void clusterGenNodesSlotsInfo(int filter) {
clusterNode *n = NULL;
int start = -1;
for (int i = 0; i <= CLUSTER_SLOTS; i++) {
/* Find start node and slot id. */
if (n == NULL) {
if (i == CLUSTER_SLOTS) break;
n = g_pserver->cluster->slots[i];
start = i;
continue;
}
/* Generate slots info when occur different node with start
* or end of slot. */
if (i == CLUSTER_SLOTS || n != g_pserver->cluster->slots[i]) {
if (!(n->flags & filter)) {
if (n->slots_info == NULL) n->slots_info = sdsempty();
if (start == i-1) {
n->slots_info = sdscatfmt(n->slots_info," %i",start);
} else {
n->slots_info = sdscatfmt(n->slots_info," %i-%i",start,i-1);
}
}
if (i == CLUSTER_SLOTS) break;
n = g_pserver->cluster->slots[i];
start = i;
}
}
}
/* Generate a csv-alike representation of the nodes we are aware of, /* Generate a csv-alike representation of the nodes we are aware of,
* including the "myself" node, and return an SDS string containing the * including the "myself" node, and return an SDS string containing the
* representation (it is up to the caller to free it). * representation (it is up to the caller to free it).
@ -4254,6 +4308,9 @@ sds clusterGenNodesDescription(int filter) {
dictIterator *di; dictIterator *di;
dictEntry *de; dictEntry *de;
/* Generate all nodes slots info firstly. */
clusterGenNodesSlotsInfo(filter);
di = dictGetSafeIterator(g_pserver->cluster->nodes); di = dictGetSafeIterator(g_pserver->cluster->nodes);
while((de = dictNext(di)) != NULL) { while((de = dictNext(di)) != NULL) {
clusterNode *node = (clusterNode*)dictGetVal(de); clusterNode *node = (clusterNode*)dictGetVal(de);
@ -4263,6 +4320,12 @@ sds clusterGenNodesDescription(int filter) {
ci = sdscatsds(ci,ni); ci = sdscatsds(ci,ni);
sdsfree(ni); sdsfree(ni);
ci = sdscatlen(ci,"\n",1); ci = sdscatlen(ci,"\n",1);
/* Release slots info. */
if (node->slots_info) {
sdsfree(node->slots_info);
node->slots_info = NULL;
}
} }
dictReleaseIterator(di); dictReleaseIterator(di);
return ci; return ci;
@ -4385,28 +4448,49 @@ void clusterCommand(client *c) {
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) { if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
const char *help[] = { const char *help[] = {
"ADDSLOTS <slot> [slot ...] -- Assign slots to current node.", "ADDSLOTS <slot> [<slot> ...]",
"BUMPEPOCH -- Advance the cluster config epoch.", " Assign slots to current node.",
"COUNT-failure-reports <node-id> -- Return number of failure reports for <node-id>.", "BUMPEPOCH",
"COUNTKEYSINSLOT <slot> - Return the number of keys in <slot>.", " Advance the cluster config epoch.",
"DELSLOTS <slot> [slot ...] -- Delete slots information from current node.", "COUNT-FAILURE-REPORTS <node-id>",
"FAILOVER [force|takeover] -- Promote current replica node to being a master.", " Return number of failure reports for <node-id>.",
"FORGET <node-id> -- Remove a node from the cluster.", "COUNTKEYSINSLOT <slot>",
"GETKEYSINSLOT <slot> <count> -- Return key names stored by current node in a slot.", " Return the number of keys in <slot>.",
"FLUSHSLOTS -- Delete current node own slots information.", "DELSLOTS <slot> [<slot> ...]",
"INFO - Return information about the cluster.", " Delete slots information from current node.",
"KEYSLOT <key> -- Return the hash slot for <key>.", "FAILOVER [FORCE|TAKEOVER]",
"MEET <ip> <port> [bus-port] -- Connect nodes into a working cluster.", " Promote current replica node to being a master.",
"MYID -- Return the node id.", "FORGET <node-id>",
"NODES -- Return cluster configuration seen by node. Output format:", " Remove a node from the cluster.",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ... <slot>", "GETKEYSINSLOT <slot> <count>",
"REPLICATE <node-id> -- Configure current node as replica to <node-id>.", " Return key names stored by current node in a slot.",
"RESET [hard|soft] -- Reset current node (default: soft).", "FLUSHSLOTS",
"SET-config-epoch <epoch> - Set config epoch of current node.", " Delete current node own slots information.",
"SETSLOT <slot> (importing|migrating|stable|node <node-id>) -- Set slot state.", "INFO",
"REPLICAS <node-id> -- Return <node-id> replicas.", " Return information about the cluster.",
"SAVECONFIG - Force saving cluster configuration on disk.", "KEYSLOT <key>",
"SLOTS -- Return information about slots range mappings. Each range is made of:", " Return the hash slot for <key>.",
"MEET <ip> <port> [<bus-port>]",
" Connect nodes into a working cluster.",
"MYID",
" Return the node id.",
"NODES",
" Return cluster configuration seen by node. Output format:",
" <id> <ip:port> <flags> <master> <pings> <pongs> <epoch> <link> <slot> ...",
"REPLICATE <node-id>",
" Configure current node as replica to <node-id>.",
"RESET [HARD|SOFT]",
" Reset current node (default: soft).",
"SET-CONFIG-EPOCH <epoch>",
" Set config epoch of current node.",
"SETSLOT <slot> (IMPORTING|MIGRATING|STABLE|NODE <node-id>)",
" Set slot state.",
"REPLICAS <node-id>",
" Return <node-id> replicas.",
"SAVECONFIG",
" Force saving cluster configuration on disk.",
"SLOTS",
" Return information about slots range mappings. Each range is made of:",
" start, end, master and replicas IP addresses, ports and ids", " start, end, master and replicas IP addresses, ports and ids",
NULL NULL
}; };
@ -4577,6 +4661,9 @@ NULL
g_pserver->cluster->migrating_slots_to[slot]) g_pserver->cluster->migrating_slots_to[slot])
g_pserver->cluster->migrating_slots_to[slot] = NULL; g_pserver->cluster->migrating_slots_to[slot] = NULL;
clusterDelSlot(slot);
clusterAddSlot(n,slot);
/* If this node was importing this slot, assigning the slot to /* If this node was importing this slot, assigning the slot to
* itself also clears the importing status. */ * itself also clears the importing status. */
if (n == myself && if (n == myself &&
@ -4596,9 +4683,10 @@ NULL
"configEpoch updated after importing slot %d", slot); "configEpoch updated after importing slot %d", slot);
} }
g_pserver->cluster->importing_slots_from[slot] = NULL; g_pserver->cluster->importing_slots_from[slot] = NULL;
/* After importing this slot, let the other nodes know as
* soon as possible. */
clusterBroadcastPong(CLUSTER_BROADCAST_ALL);
} }
clusterDelSlot(slot);
clusterAddSlot(n,slot);
} else { } else {
addReplyError(c, addReplyError(c,
"Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP"); "Invalid CLUSTER SETSLOT action or number of arguments. Try CLUSTER HELP");
@ -4844,7 +4932,7 @@ NULL
takeover = 1; takeover = 1;
force = 1; /* Takeover also implies force. */ force = 1; /* Takeover also implies force. */
} else { } else {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
} }
@ -4935,7 +5023,7 @@ NULL
} else if (!strcasecmp(szFromObj(c->argv[2]),"soft")) { } else if (!strcasecmp(szFromObj(c->argv[2]),"soft")) {
hard = 0; hard = 0;
} else { } else {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
} }
@ -5007,6 +5095,9 @@ int verifyDumpPayload(unsigned char *p, size_t len) {
rdbver = (footer[1] << 8) | footer[0]; rdbver = (footer[1] << 8) | footer[0];
if (rdbver > RDB_VERSION) return C_ERR; if (rdbver > RDB_VERSION) return C_ERR;
if (cserver.skip_checksum_validation)
return C_OK;
/* Verify CRC64 */ /* Verify CRC64 */
crc = crc64(0,p,len-8); crc = crc64(0,p,len-8);
memrev64ifbe(&crc); memrev64ifbe(&crc);
@ -5066,7 +5157,7 @@ void mvccrestoreCommand(client *c) {
setMvccTstamp(obj, mvcc); setMvccTstamp(obj, mvcc);
/* Create the key and set the TTL if any */ /* Create the key and set the TTL if any */
if (dbMerge(c->db,key,obj,true)) { if (dbMerge(c->db,szFromObj(key),obj,true)) {
if (expire >= 0) { if (expire >= 0) {
setExpire(c,c->db,key,nullptr,expire); setExpire(c,c->db,key,nullptr,expire);
} }
@ -5115,7 +5206,7 @@ void restoreCommand(client *c) {
} }
j++; /* Consume additional arg. */ j++; /* Consume additional arg. */
} else { } else {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
} }
@ -5123,7 +5214,7 @@ void restoreCommand(client *c) {
/* Make sure this key does not already exist here... */ /* Make sure this key does not already exist here... */
robj *key = c->argv[1]; robj *key = c->argv[1];
if (!replace && lookupKeyWrite(c->db,key) != NULL) { if (!replace && lookupKeyWrite(c->db,key) != NULL) {
addReply(c,shared.busykeyerr); addReplyErrorObject(c,shared.busykeyerr);
return; return;
} }
@ -5236,8 +5327,7 @@ migrateCachedSocket* migrateGetSocket(client *c, robj *host, robj *port, long ti
conn = g_pserver->tls_cluster ? connCreateTLS() : connCreateSocket(); conn = g_pserver->tls_cluster ? connCreateTLS() : connCreateSocket();
if (connBlockingConnect(conn, szFromObj(c->argv[1]), atoi(szFromObj(c->argv[2])), timeout) if (connBlockingConnect(conn, szFromObj(c->argv[1]), atoi(szFromObj(c->argv[2])), timeout)
!= C_OK) { != C_OK) {
addReplySds(c, addReplyError(c,"-IOERR error or timeout connecting to the client");
sdsnew("-IOERR error or timeout connecting to the client\r\n"));
connClose(conn); connClose(conn);
sdsfree(name); sdsfree(name);
return NULL; return NULL;
@ -5324,7 +5414,7 @@ void migrateCommand(client *c) {
replace = 1; replace = 1;
} else if (!strcasecmp(szFromObj(c->argv[j]),"auth")) { } else if (!strcasecmp(szFromObj(c->argv[j]),"auth")) {
if (!moreargs) { if (!moreargs) {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
j++; j++;
@ -5347,7 +5437,7 @@ void migrateCommand(client *c) {
num_keys = c->argc - j - 1; num_keys = c->argc - j - 1;
break; /* All the remaining args are keys. */ break; /* All the remaining args are keys. */
} else { } else {
addReply(c,shared.syntaxerr); addReplyErrorObject(c,shared.syntaxerr);
return; return;
} }
} }
@ -5840,7 +5930,7 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
* cluster is down. */ * cluster is down. */
if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE; if (error_code) *error_code = CLUSTER_REDIR_DOWN_STATE;
return NULL; return NULL;
} else if (!(cmd->flags & CMD_READONLY) && !(cmd->proc == evalCommand) } else if ((cmd->flags & CMD_WRITE) && !(cmd->proc == evalCommand)
&& !(cmd->proc == evalShaCommand)) && !(cmd->proc == evalShaCommand))
{ {
/* The cluster is configured to allow read only commands /* The cluster is configured to allow read only commands
@ -5889,11 +5979,10 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
/* Handle the read-only client case reading from a slave: if this /* Handle the read-only client case reading from a slave: if this
* node is a slave and the request is about a hash slot our master * node is a slave and the request is about a hash slot our master
* is serving, we can reply without redirection. */ * is serving, we can reply without redirection. */
int is_readonly_command = (c->cmd->flags & CMD_READONLY) || int is_write_command = (c->cmd->flags & CMD_WRITE) ||
(c->cmd->proc == execCommand && !(c->mstate.cmd_inv_flags & CMD_READONLY)); (c->cmd->proc == execCommand && (c->mstate.cmd_flags & CMD_WRITE));
if (c->flags & CLIENT_READONLY && if (c->flags & CLIENT_READONLY &&
(is_readonly_command || cmd->proc == evalCommand || (!is_write_command || cmd->proc == evalCommand || cmd->proc == evalShaCommand) &&
cmd->proc == evalShaCommand) &&
nodeIsSlave(myself) && nodeIsSlave(myself) &&
myself->slaveof == n) myself->slaveof == n)
{ {
@ -5915,23 +6004,23 @@ clusterNode *getNodeByQuery(client *c, struct redisCommand *cmd, robj **argv, in
* be set to the hash slot that caused the redirection. */ * be set to the hash slot that caused the redirection. */
void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) { void clusterRedirectClient(client *c, clusterNode *n, int hashslot, int error_code) {
if (error_code == CLUSTER_REDIR_CROSS_SLOT) { if (error_code == CLUSTER_REDIR_CROSS_SLOT) {
addReplySds(c,sdsnew("-CROSSSLOT Keys in request don't hash to the same slot\r\n")); addReplyError(c,"-CROSSSLOT Keys in request don't hash to the same slot");
} else if (error_code == CLUSTER_REDIR_UNSTABLE) { } else if (error_code == CLUSTER_REDIR_UNSTABLE) {
/* The request spawns multiple keys in the same slot, /* The request spawns multiple keys in the same slot,
* but the slot is not "stable" currently as there is * but the slot is not "stable" currently as there is
* a migration or import in progress. */ * a migration or import in progress. */
addReplySds(c,sdsnew("-TRYAGAIN Multiple keys request during rehashing of slot\r\n")); addReplyError(c,"-TRYAGAIN Multiple keys request during rehashing of slot");
} else if (error_code == CLUSTER_REDIR_DOWN_STATE) { } else if (error_code == CLUSTER_REDIR_DOWN_STATE) {
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down\r\n")); addReplyError(c,"-CLUSTERDOWN The cluster is down");
} else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) { } else if (error_code == CLUSTER_REDIR_DOWN_RO_STATE) {
addReplySds(c,sdsnew("-CLUSTERDOWN The cluster is down and only accepts read commands\r\n")); addReplyError(c,"-CLUSTERDOWN The cluster is down and only accepts read commands");
} else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) { } else if (error_code == CLUSTER_REDIR_DOWN_UNBOUND) {
addReplySds(c,sdsnew("-CLUSTERDOWN Hash slot not served\r\n")); addReplyError(c,"-CLUSTERDOWN Hash slot not served");
} else if (error_code == CLUSTER_REDIR_MOVED || } else if (error_code == CLUSTER_REDIR_MOVED ||
error_code == CLUSTER_REDIR_ASK) error_code == CLUSTER_REDIR_ASK)
{ {
addReplySds(c,sdscatprintf(sdsempty(), addReplyErrorSds(c,sdscatprintf(sdsempty(),
"-%s %d %s:%d\r\n", "-%s %d %s:%d",
(error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED", (error_code == CLUSTER_REDIR_ASK) ? "ASK" : "MOVED",
hashslot,n->ip,n->port)); hashslot,n->ip,n->port));
} else { } else {
@ -5985,6 +6074,15 @@ int clusterRedirectBlockedClientIfNeeded(client *c) {
node = myself; node = myself;
} }
/* if the client is read-only and attempting to access key that our
* replica can handle, allow it. */
if ((c->flags & CLIENT_READONLY) &&
!(c->lastcmd->flags & CMD_WRITE) &&
nodeIsSlave(myself) && myself->slaveof == node)
{
node = myself;
}
/* We send an error and unblock the client if: /* We send an error and unblock the client if:
* 1) The slot is unassigned, emitting a cluster down error. * 1) The slot is unassigned, emitting a cluster down error.
* 2) The slot is not handled by this node, nor being imported. */ * 2) The slot is not handled by this node, nor being imported. */

View File

@ -83,6 +83,7 @@ typedef struct clusterLink {
#define CLUSTER_TODO_UPDATE_STATE (1<<1) #define CLUSTER_TODO_UPDATE_STATE (1<<1)
#define CLUSTER_TODO_SAVE_CONFIG (1<<2) #define CLUSTER_TODO_SAVE_CONFIG (1<<2)
#define CLUSTER_TODO_FSYNC_CONFIG (1<<3) #define CLUSTER_TODO_FSYNC_CONFIG (1<<3)
#define CLUSTER_TODO_HANDLE_MANUALFAILOVER (1<<4)
/* Message types. /* Message types.
* *
@ -121,6 +122,7 @@ typedef struct clusterNode {
int flags; /* CLUSTER_NODE_... */ int flags; /* CLUSTER_NODE_... */
uint64_t configEpoch; /* Last configEpoch observed for this node */ uint64_t configEpoch; /* Last configEpoch observed for this node */
unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */ unsigned char slots[CLUSTER_SLOTS/8]; /* slots handled by this node */
sds slots_info; /* Slots info represented by string. */
int numslots; /* Number of slots handled by this node */ int numslots; /* Number of slots handled by this node */
int numslaves; /* Number of slave nodes, if this is a master */ int numslaves; /* Number of slave nodes, if this is a master */
struct clusterNode **slaves; /* pointers to slave nodes */ struct clusterNode **slaves; /* pointers to slave nodes */

View File

@ -73,7 +73,7 @@ public:
m_max = m_max + 4; m_max = m_max + 4;
m_data = (T*)zrealloc(m_data, sizeof(T) * m_max, MALLOC_LOCAL); m_data = (T*)zrealloc(m_data, sizeof(T) * m_max, MALLOC_LOCAL);
m_max = zmalloc_usable(m_data) / sizeof(T); m_max = zmalloc_usable_size(m_data) / sizeof(T);
} }
assert(idx < m_max); assert(idx < m_max);
where = m_data + idx; where = m_data + idx;

View File

@ -115,6 +115,19 @@ configEnum oom_score_adj_enum[] = {
{NULL, 0} {NULL, 0}
}; };
configEnum acl_pubsub_default_enum[] = {
{"allchannels", USER_FLAG_ALLCHANNELS},
{"resetchannels", 0},
{NULL, 0}
};
configEnum sanitize_dump_payload_enum[] = {
{"no", SANITIZE_DUMP_NO},
{"yes", SANITIZE_DUMP_YES},
{"clients", SANITIZE_DUMP_CLIENTS},
{NULL, 0}
};
/* Output buffer limits presets. */ /* Output buffer limits presets. */
clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = { clientBufferLimitsConfig clientBufferLimitsDefaults[CLIENT_TYPE_OBUF_COUNT] = {
{0, 0, 0}, /* normal */ {0, 0, 0}, /* normal */
@ -155,6 +168,15 @@ typedef struct stringConfigData {
be stored as a NULL value. */ be stored as a NULL value. */
} stringConfigData; } stringConfigData;
typedef struct sdsConfigData {
sds *config; /* Pointer to the server config this value is stored in. */
const char *default_value; /* Default value of the config on rewrite. */
int (*is_valid_fn)(sds val, const char **err); /* Optional function to check validity of new value (generic doc above) */
int (*update_fn)(sds val, sds prev, const char **err); /* Optional function to apply new value at runtime (generic doc above) */
int convert_empty_to_null; /* Boolean indicating if empty SDS strings should
be stored as a NULL value. */
} sdsConfigData;
typedef struct enumConfigData { typedef struct enumConfigData {
int *config; /* The pointer to the server config this value is stored in */ int *config; /* The pointer to the server config this value is stored in */
configEnum *enum_value; /* The underlying enum type this data represents */ configEnum *enum_value; /* The underlying enum type this data represents */
@ -201,6 +223,7 @@ typedef struct numericConfigData {
typedef union typeData { typedef union typeData {
boolConfigData yesno; boolConfigData yesno;
stringConfigData string; stringConfigData string;
sdsConfigData sds;
enumConfigData enumd; enumConfigData enumd;
numericConfigData numeric; numericConfigData numeric;
} typeData; } typeData;
@ -375,6 +398,7 @@ void loadServerConfigFromString(char *config) {
int linenum = 0, totlines, i; int linenum = 0, totlines, i;
int slaveof_linenum = 0; int slaveof_linenum = 0;
sds *lines; sds *lines;
int save_loaded = 0;
lines = sdssplitlen(config,strlen(config),"\n",1,&totlines); lines = sdssplitlen(config,strlen(config),"\n",1,&totlines);
@ -447,6 +471,14 @@ void loadServerConfigFromString(char *config) {
err = "Invalid socket file permissions"; goto loaderr; err = "Invalid socket file permissions"; goto loaderr;
} }
} else if (!strcasecmp(argv[0],"save")) { } else if (!strcasecmp(argv[0],"save")) {
/* We don't reset save params before loading, because if they're not part
* of the file the defaults should be used.
*/
if (!save_loaded) {
save_loaded = 1;
resetServerSaveParams();
}
if (argc == 3) { if (argc == 3) {
int seconds = atoi(argv[1]); int seconds = atoi(argv[1]);
int changes = atoi(argv[2]); int changes = atoi(argv[2]);
@ -480,9 +512,7 @@ void loadServerConfigFromString(char *config) {
fclose(logfp); fclose(logfp);
} }
} else if (!strcasecmp(argv[0],"include") && argc == 2) { } else if (!strcasecmp(argv[0],"include") && argc == 2) {
loadServerConfig(argv[1],NULL); loadServerConfig(argv[1], 0, NULL);
} else if ((!strcasecmp(argv[0],"client-query-buffer-limit")) && argc == 2) {
cserver.client_max_querybuf_len = memtoll(argv[1],NULL);
} else if ((!strcasecmp(argv[0],"slaveof") || } else if ((!strcasecmp(argv[0],"slaveof") ||
!strcasecmp(argv[0],"replicaof")) && argc == 3) { !strcasecmp(argv[0],"replicaof")) && argc == 3) {
slaveof_linenum = linenum; slaveof_linenum = linenum;
@ -494,7 +524,7 @@ void loadServerConfigFromString(char *config) {
while ((ln = listNext(&li))) while ((ln = listNext(&li)))
{ {
struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln); struct redisMaster *mi = (struct redisMaster*)listNodeValue(ln);
zfree(mi->masterauth); sdsfree(mi->masterauth);
zfree(mi->masteruser); zfree(mi->masteruser);
zfree(mi->repl_transfer_tmpfile); zfree(mi->repl_transfer_tmpfile);
delete mi->staleKeyMap; delete mi->staleKeyMap;
@ -515,21 +545,6 @@ void loadServerConfigFromString(char *config) {
err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN"; err = "Password is longer than CONFIG_AUTHPASS_MAX_LEN";
goto loaderr; goto loaderr;
} }
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLSetUser(DefaultUser,"resetpass",-1);
sdsfree(g_pserver->requirepass);
g_pserver->requirepass = NULL;
if (sdslen(argv[1])) {
sds aclop = sdscatprintf(sdsempty(),">%s",argv[1]);
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
g_pserver->requirepass = sdsnew(argv[1]);
} else {
ACLSetUser(DefaultUser,"nopass",-1);
}
} else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){ } else if (!strcasecmp(argv[0],"list-max-ziplist-entries") && argc == 2){
/* DEAD OPTION */ /* DEAD OPTION */
} else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) { } else if (!strcasecmp(argv[0],"list-max-ziplist-value") && argc == 2) {
@ -613,8 +628,7 @@ void loadServerConfigFromString(char *config) {
err = "sentinel directive while not in sentinel mode"; err = "sentinel directive while not in sentinel mode";
goto loaderr; goto loaderr;
} }
err = sentinelHandleConfiguration(argv+1,argc-1); queueSentinelConfig(argv+1,argc-1,linenum,lines[i]);
if (err) goto loaderr;
} }
} else if (!strcasecmp(argv[0],"scratch-file-path")) { } else if (!strcasecmp(argv[0],"scratch-file-path")) {
#ifdef USE_MEMKIND #ifdef USE_MEMKIND
@ -697,28 +711,31 @@ loaderr:
* Both filename and options can be NULL, in such a case are considered * Both filename and options can be NULL, in such a case are considered
* empty. This way loadServerConfig can be used to just load a file or * empty. This way loadServerConfig can be used to just load a file or
* just load a string. */ * just load a string. */
void loadServerConfig(char *filename, char *options) { void loadServerConfig(char *filename, char config_from_stdin, char *options) {
sds config = sdsempty(); sds config = sdsempty();
char buf[CONFIG_MAX_LINE+1]; char buf[CONFIG_MAX_LINE+1];
FILE *fp;
/* Load the file content */ /* Load the file content */
if (filename) { if (filename) {
FILE *fp;
if (filename[0] == '-' && filename[1] == '\0') {
fp = stdin;
} else {
if ((fp = fopen(filename,"r")) == NULL) { if ((fp = fopen(filename,"r")) == NULL) {
serverLog(LL_WARNING, serverLog(LL_WARNING,
"Fatal error, can't open config file '%s': %s", "Fatal error, can't open config file '%s': %s",
filename, strerror(errno)); filename, strerror(errno));
exit(1); exit(1);
} }
}
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL) while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
config = sdscat(config,buf); config = sdscat(config,buf);
if (fp != stdin) fclose(fp); fclose(fp);
} }
/* Append content from stdin */
if (config_from_stdin) {
serverLog(LL_WARNING,"Reading config from stdin");
fp = stdin;
while(fgets(buf,CONFIG_MAX_LINE+1,fp) != NULL)
config = sdscat(config,buf);
}
/* Append the additional options */ /* Append the additional options */
if (options) { if (options) {
config = sdscat(config,"\n"); config = sdscat(config,"\n");
@ -785,23 +802,38 @@ void configSetCommand(client *c) {
if (0) { /* this starts the config_set macros else-if chain. */ if (0) { /* this starts the config_set macros else-if chain. */
/* Special fields that can't be handled with general macros. */ /* Special fields that can't be handled with general macros. */
config_set_special_field("requirepass") { config_set_special_field("bind") {
if (sdslen(szFromObj(o)) > CONFIG_AUTHPASS_MAX_LEN) goto badfmt; int vlen;
/* The old "requirepass" directive just translates to setting sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this if (vlen < 1 || vlen > CONFIG_BINDADDR_MAX) {
* case, for backward compatibility with Redis <= 5. */ addReplyError(c, "Too many bind addresses specified.");
ACLSetUser(DefaultUser,"resetpass",-1); sdsfreesplitres(v, vlen);
sdsfree(g_pserver->requirepass); return;
g_pserver->requirepass = NULL;
if (sdslen(szFromObj(o))) {
sds aclop = sdscatprintf(sdsempty(),">%s",(char*)ptrFromObj(o));
ACLSetUser(DefaultUser,aclop,sdslen(aclop));
sdsfree(aclop);
g_pserver->requirepass = sdsnew(szFromObj(o));
} else {
ACLSetUser(DefaultUser,"nopass",-1);
} }
if (changeBindAddr(v, vlen, true) == C_ERR) {
addReplyError(c, "Failed to bind to specified addresses.");
sdsfreesplitres(v, vlen);
return;
}
// Now run the config change on the other threads
for (int ithread = 0; ithread < cserver.cthreads; ++ithread) {
if (&g_pserver->rgthreadvar[ithread] != serverTL) {
incrRefCount(o);
aePostFunction(g_pserver->rgthreadvar[ithread].el, [o]{
int vlen;
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
if (changeBindAddr(v, vlen, false) == C_ERR) {
serverLog(LL_WARNING, "Failed to change the bind address for a thread. Server will still be listening on old addresses.");
}
sdsfreesplitres(v, vlen);
decrRefCount(o);
});
}
}
sdsfreesplitres(v, vlen);
} config_set_special_field("save") { } config_set_special_field("save") {
int vlen, j; int vlen, j;
sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen); sds *v = sdssplitlen(szFromObj(o),sdslen(szFromObj(o))," ",1,&vlen);
@ -910,10 +942,6 @@ void configSetCommand(client *c) {
enableWatchdog(ll); enableWatchdog(ll);
else else
disableWatchdog(); disableWatchdog();
/* Memory fields.
* config_set_memory_field(name,var) */
} config_set_memory_field(
"client-query-buffer-limit",cserver.client_max_querybuf_len) {
/* Everything else is an error... */ /* Everything else is an error... */
} config_set_else { } config_set_else {
addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s", addReplyErrorFormat(c,"Unsupported CONFIG parameter: %s",
@ -948,7 +976,7 @@ badfmt: /* Bad format errors */
addReplyBulkCString(c,_var ? _var : ""); \ addReplyBulkCString(c,_var ? _var : ""); \
matches++; \ matches++; \
} \ } \
} while(0); } while(0)
#define config_get_bool_field(_name,_var) do { \ #define config_get_bool_field(_name,_var) do { \
if (stringmatch(pattern,_name,1)) { \ if (stringmatch(pattern,_name,1)) { \
@ -956,7 +984,7 @@ badfmt: /* Bad format errors */
addReplyBulkCString(c,_var ? "yes" : "no"); \ addReplyBulkCString(c,_var ? "yes" : "no"); \
matches++; \ matches++; \
} \ } \
} while(0); } while(0)
#define config_get_numerical_field(_name,_var) do { \ #define config_get_numerical_field(_name,_var) do { \
if (stringmatch(pattern,_name,1)) { \ if (stringmatch(pattern,_name,1)) { \
@ -965,8 +993,7 @@ badfmt: /* Bad format errors */
addReplyBulkCString(c,buf); \ addReplyBulkCString(c,buf); \
matches++; \ matches++; \
} \ } \
} while(0); } while(0)
void configGetCommand(client *c) { void configGetCommand(client *c) {
robj *o = c->argv[2]; robj *o = c->argv[2];
@ -994,7 +1021,6 @@ void configGetCommand(client *c) {
config_get_string_field("logfile",g_pserver->logfile); config_get_string_field("logfile",g_pserver->logfile);
/* Numerical values */ /* Numerical values */
config_get_numerical_field("client-query-buffer-limit",cserver.client_max_querybuf_len);
config_get_numerical_field("watchdog-period",g_pserver->watchdog_period); config_get_numerical_field("watchdog-period",g_pserver->watchdog_period);
/* Everything we can't handle with macros follows. */ /* Everything we can't handle with macros follows. */
@ -1045,7 +1071,7 @@ void configGetCommand(client *c) {
} }
if (stringmatch(pattern,"unixsocketperm",1)) { if (stringmatch(pattern,"unixsocketperm",1)) {
char buf[32]; char buf[32];
snprintf(buf,sizeof(buf),"%o",g_pserver->unixsocketperm); snprintf(buf,sizeof(buf),"%lo",(unsigned long)g_pserver->unixsocketperm);
addReplyBulkCString(c,"unixsocketperm"); addReplyBulkCString(c,"unixsocketperm");
addReplyBulkCString(c,buf); addReplyBulkCString(c,buf);
matches++; matches++;
@ -1099,16 +1125,6 @@ void configGetCommand(client *c) {
sdsfree(aux); sdsfree(aux);
matches++; matches++;
} }
if (stringmatch(pattern,"requirepass",1)) {
addReplyBulkCString(c,"requirepass");
sds password = g_pserver->requirepass;
if (password) {
addReplyBulkCBuffer(c,password,sdslen(password));
} else {
addReplyBulkCString(c,"");
}
matches++;
}
if (stringmatch(pattern,"oom-score-adj-values",0)) { if (stringmatch(pattern,"oom-score-adj-values",0)) {
sds buf = sdsempty(); sds buf = sdsempty();
int j; int j;
@ -1157,7 +1173,8 @@ dictType optionToLineDictType = {
NULL, /* val dup */ NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */ dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */ dictSdsDestructor, /* key destructor */
dictListDestructor /* val destructor */ dictListDestructor, /* val destructor */
NULL /* allow to expand */
}; };
dictType optionSetDictType = { dictType optionSetDictType = {
@ -1166,7 +1183,8 @@ dictType optionSetDictType = {
NULL, /* val dup */ NULL, /* val dup */
dictSdsKeyCaseCompare, /* key compare */ dictSdsKeyCaseCompare, /* key compare */
dictSdsDestructor, /* key destructor */ dictSdsDestructor, /* key destructor */
NULL /* val destructor */ NULL, /* val destructor */
NULL /* allow to expand */
}; };
/* The config rewrite state. */ /* The config rewrite state. */
@ -1270,13 +1288,22 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
char *p = strstr(argv[0],"slave"); char *p = strstr(argv[0],"slave");
if (p) { if (p) {
sds alt = sdsempty(); sds alt = sdsempty();
alt = sdscatlen(alt,argv[0],p-argv[0]);; alt = sdscatlen(alt,argv[0],p-argv[0]);
alt = sdscatlen(alt,"replica",7); alt = sdscatlen(alt,"replica",7);
alt = sdscatlen(alt,p+5,strlen(p+5)); alt = sdscatlen(alt,p+5,strlen(p+5));
sdsfree(argv[0]); sdsfree(argv[0]);
argv[0] = alt; argv[0] = alt;
} }
/* If this is sentinel config, we use sentinel "sentinel <config>" as option
to avoid messing up the sequence. */
if (g_pserver->sentinel_mode && argc > 1 && !strcasecmp(argv[0],"sentinel")) {
sds sentinelOption = sdsempty();
sentinelOption = sdscatfmt(sentinelOption,"%S %S",argv[0],argv[1]);
rewriteConfigAddLineNumberToOption(state,sentinelOption,linenum);
sdsfree(sentinelOption);
} else {
rewriteConfigAddLineNumberToOption(state,argv[0],linenum); rewriteConfigAddLineNumberToOption(state,argv[0],linenum);
}
sdsfreesplitres(argv,argc); sdsfreesplitres(argv,argc);
} }
fclose(fp); fclose(fp);
@ -1394,6 +1421,28 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, const char *opt
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
/* Rewrite a SDS string option. */
void rewriteConfigSdsOption(struct rewriteConfigState *state, const char *option, sds value, const sds defvalue) {
int force = 1;
sds line;
/* If there is no value set, we don't want the SDS option
* to be present in the configuration at all. */
if (value == NULL) {
rewriteConfigMarkAsProcessed(state, option);
return;
}
/* Set force to zero if the value is set to its default. */
if (defvalue && sdscmp(value, defvalue) == 0) force = 0;
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatrepr(line, value, sdslen(value));
rewriteConfigRewriteLine(state, option, line, force);
}
/* Rewrite a numerical (long long range) option. */ /* Rewrite a numerical (long long range) option. */
void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) { void rewriteConfigNumericalOption(struct rewriteConfigState *state, const char *option, long long value, long long defvalue) {
int force = value != defvalue; int force = value != defvalue;
@ -1597,26 +1646,6 @@ void rewriteConfigBindOption(struct rewriteConfigState *state) {
rewriteConfigRewriteLine(state,option,line,force); rewriteConfigRewriteLine(state,option,line,force);
} }
/* Rewrite the requirepass option. */
void rewriteConfigRequirepassOption(struct rewriteConfigState *state, const char *option) {
int force = 1;
sds line;
sds password = g_pserver->requirepass;
/* If there is no password set, we don't want the requirepass option
* to be present in the configuration at all. */
if (password == NULL) {
rewriteConfigMarkAsProcessed(state,option);
return;
}
line = sdsnew(option);
line = sdscatlen(line, " ", 1);
line = sdscatsds(line, password);
rewriteConfigRewriteLine(state,option,line,force);
}
/* Glue together the configuration lines in the current configuration /* Glue together the configuration lines in the current configuration
* rewrite state into a single string, stripping multiple empty lines. */ * rewrite state into a single string, stripping multiple empty lines. */
sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) { sds rewriteConfigGetContentFromState(struct rewriteConfigState *state) {
@ -1724,7 +1753,7 @@ int rewriteConfigOverwriteFile(char *configfile, sds content) {
if (fsync(fd)) if (fsync(fd))
serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno)); serverLog(LL_WARNING, "Could not sync tmp config file to disk (%s)", strerror(errno));
else if (fchmod(fd, 0644) == -1) else if (fchmod(fd, 0644 & ~g_pserver->umask) == -1)
serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno)); serverLog(LL_WARNING, "Could not chmod config file (%s)", strerror(errno));
else if (rename(tmp_conffile, configfile) == -1) else if (rename(tmp_conffile, configfile) == -1)
serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno)); serverLog(LL_WARNING, "Could not rename tmp config file (%s)", strerror(errno));
@ -1773,8 +1802,6 @@ int rewriteConfig(char *path, int force_all) {
rewriteConfigUserOption(state); rewriteConfigUserOption(state);
rewriteConfigDirOption(state); rewriteConfigDirOption(state);
rewriteConfigSlaveofOption(state,"replicaof"); rewriteConfigSlaveofOption(state,"replicaof");
rewriteConfigRequirepassOption(state,"requirepass");
rewriteConfigBytesOption(state,"client-query-buffer-limit",cserver.client_max_querybuf_len,PROTO_MAX_QUERYBUF_LEN);
rewriteConfigStringOption(state,"cluster-config-file",g_pserver->cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE); rewriteConfigStringOption(state,"cluster-config-file",g_pserver->cluster_configfile,CONFIG_DEFAULT_CLUSTER_CONFIG_FILE);
rewriteConfigNotifykeyspaceeventsOption(state); rewriteConfigNotifykeyspaceeventsOption(state);
rewriteConfigClientoutputbufferlimitOption(state); rewriteConfigClientoutputbufferlimitOption(state);
@ -1875,22 +1902,14 @@ constexpr standardConfig createBoolConfig(const char *name, const char *alias, i
/* String Configs */ /* String Configs */
static void stringConfigInit(typeData data) { static void stringConfigInit(typeData data) {
if (data.string.convert_empty_to_null) { *data.string.config = (data.string.convert_empty_to_null && !data.string.default_value) ? NULL : zstrdup(data.string.default_value);
*data.string.config = data.string.default_value ? zstrdup(data.string.default_value) : NULL;
} else {
*data.string.config = zstrdup(data.string.default_value);
}
} }
static int stringConfigSet(typeData data, sds value, int update, const char **err) { static int stringConfigSet(typeData data, sds value, int update, const char **err) {
if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err)) if (data.string.is_valid_fn && !data.string.is_valid_fn(value, err))
return 0; return 0;
char *prev = *data.string.config; char *prev = *data.string.config;
if (data.string.convert_empty_to_null) { *data.string.config = (data.string.convert_empty_to_null && !value[0]) ? NULL : zstrdup(value);
*data.string.config = value[0] ? zstrdup(value) : NULL;
} else {
*data.string.config = zstrdup(value);
}
if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) { if (update && data.string.update_fn && !data.string.update_fn(*data.string.config, prev, err)) {
zfree(*data.string.config); zfree(*data.string.config);
*data.string.config = prev; *data.string.config = prev;
@ -1908,6 +1927,38 @@ static void stringConfigRewrite(typeData data, const char *name, struct rewriteC
rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value); rewriteConfigStringOption(state, name,*(data.string.config), data.string.default_value);
} }
/* SDS Configs */
static void sdsConfigInit(typeData data) {
*data.sds.config = (data.sds.convert_empty_to_null && !data.sds.default_value) ? NULL: sdsnew(data.sds.default_value);
}
static int sdsConfigSet(typeData data, sds value, int update, const char **err) {
if (data.sds.is_valid_fn && !data.sds.is_valid_fn(value, err))
return 0;
sds prev = *data.sds.config;
*data.sds.config = (data.sds.convert_empty_to_null && (sdslen(value) == 0)) ? NULL : sdsdup(value);
if (update && data.sds.update_fn && !data.sds.update_fn(*data.sds.config, prev, err)) {
sdsfree(*data.sds.config);
*data.sds.config = prev;
return 0;
}
sdsfree(prev);
return 1;
}
static void sdsConfigGet(client *c, typeData data) {
if (*data.sds.config) {
addReplyBulkSds(c, sdsdup(*data.sds.config));
} else {
addReplyBulkCString(c, "");
}
}
static void sdsConfigRewrite(typeData data, const char *name, struct rewriteConfigState *state) {
rewriteConfigSdsOption(state, name, *(data.sds.config), data.sds.default_value ? sdsnew(data.sds.default_value) : NULL);
}
#define ALLOW_EMPTY_STRING 0 #define ALLOW_EMPTY_STRING 0
#define EMPTY_STRING_IS_NULL 1 #define EMPTY_STRING_IS_NULL 1
@ -1926,6 +1977,21 @@ constexpr standardConfig createStringConfig(const char *name, const char *alias,
return conf; return conf;
} }
constexpr standardConfig createSDSConfig(const char *name, const char *alias, int modifiable, int empty_to_null, sds &config_addr, const char *defaultValue, int (*is_valid)(char*,const char**), int (*update)(char*,char*,const char**)) {
standardConfig conf = {
embedCommonConfig(name, alias, modifiable)
embedConfigInterface(sdsConfigInit, sdsConfigSet, sdsConfigGet, sdsConfigRewrite)
};
conf.data.sds = {
&(config_addr),
(defaultValue),
(is_valid),
(update),
(empty_to_null),
};
return conf;
}
/* Enum configs */ /* Enum configs */
static void enumConfigInit(typeData data) { static void enumConfigInit(typeData data) {
*data.enumd.config = data.enumd.default_value; *data.enumd.config = data.enumd.default_value;
@ -1944,7 +2010,7 @@ static int enumConfigSet(typeData data, sds value, int update, const char **err)
} }
sdsrange(enumerr,0,-3); /* Remove final ", ". */ sdsrange(enumerr,0,-3); /* Remove final ", ". */
strncpy(loadbuf, enumerr, LOADBUF_SIZE-1); strncpy(loadbuf, enumerr, LOADBUF_SIZE);
loadbuf[LOADBUF_SIZE - 1] = '\0'; loadbuf[LOADBUF_SIZE - 1] = '\0';
sdsfree(enumerr); sdsfree(enumerr);
@ -2246,6 +2312,25 @@ static int isValidAOFfilename(char *val, const char **err) {
return 1; return 1;
} }
/* Validate specified string is a valid proc-title-template */
static int isValidProcTitleTemplate(char *val, const char **err) {
if (!validateProcTitleTemplate(val)) {
*err = "template format is invalid or contains unknown variables";
return 0;
}
return 1;
}
static int updateProcTitleTemplate(char *val, char *prev, const char **err) {
UNUSED(val);
UNUSED(prev);
if (redisSetProcTitle(NULL) == C_ERR) {
*err = "failed to set process title";
return 0;
}
return 1;
}
static int updateHZ(long long val, long long prev, const char **err) { static int updateHZ(long long val, long long prev, const char **err) {
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
@ -2258,6 +2343,32 @@ static int updateHZ(long long val, long long prev, const char **err) {
return 1; return 1;
} }
static int updatePort(long long val, long long prev, const char **err) {
/* Do nothing if port is unchanged */
if (val == prev) {
return 1;
}
// Run this thread to make sure its valid
if (changeListenPort(val, &serverTL->ipfd, acceptTcpHandler, true) == C_ERR) {
*err = "Unable to listen on this port. Check server logs.";
return 0;
}
// Now run the config change on the other threads
for (int ithread = 0; ithread < cserver.cthreads; ++ithread) {
if (&g_pserver->rgthreadvar[ithread] != serverTL) {
aePostFunction(g_pserver->rgthreadvar[ithread].el, [val]{
if (changeListenPort(val, &serverTL->ipfd, acceptTcpHandler, false) == C_ERR) {
serverLog(LL_WARNING, "Failed to change the listen port for a thread. Server will still be listening on old ports.");
}
});
}
}
return 1;
}
static int updateJemallocBgThread(int val, int prev, const char **err) { static int updateJemallocBgThread(int val, int prev, const char **err) {
UNUSED(prev); UNUSED(prev);
UNUSED(err); UNUSED(err);
@ -2282,7 +2393,7 @@ static int updateMaxmemory(long long val, long long prev, const char **err) {
if ((unsigned long long)val < used) { if ((unsigned long long)val < used) {
serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", g_pserver->maxmemory, used); serverLog(LL_WARNING,"WARNING: the new maxmemory value set via CONFIG SET (%llu) is smaller than the current memory usage (%zu). This will result in key eviction and/or the inability to accept new write commands depending on the maxmemory-policy.", g_pserver->maxmemory, used);
} }
freeMemoryIfNeededAndSafe(); performEvictions();
} }
return 1; return 1;
} }
@ -2295,7 +2406,7 @@ static int updateGoodSlaves(long long val, long long prev, const char **err) {
return 1; return 1;
} }
static int updateMasterAuthConfig(char *, char *, const char **) { static int updateMasterAuthConfig(sds, sds, const char **) {
updateMasterAuth(); updateMasterAuth();
return 1; return 1;
} }
@ -2313,6 +2424,16 @@ static int updateAppendonly(int val, int prev, const char **err) {
return 1; return 1;
} }
static int updateSighandlerEnabled(int val, int prev, const char **err) {
UNUSED(err);
UNUSED(prev);
if (val)
setupSignalHandlers();
else
removeSignalHandlers();
return 1;
}
static int updateMaxclients(long long val, long long prev, const char **err) { static int updateMaxclients(long long val, long long prev, const char **err) {
/* Try to check if the OS is capable of supporting so many FDs. */ /* Try to check if the OS is capable of supporting so many FDs. */
if (val > prev) { if (val > prev) {
@ -2389,6 +2510,17 @@ static int updateOOMScoreAdj(int val, int prev, const char **err) {
return 1; return 1;
} }
int updateRequirePass(sds val, sds prev, const char **err) {
UNUSED(prev);
UNUSED(err);
/* The old "requirepass" directive just translates to setting
* a password to the default user. The only thing we do
* additionally is to remember the cleartext password in this
* case, for backward compatibility with Redis <= 5. */
ACLUpdateDefaultUserPassword(val);
return 1;
}
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
static int updateTlsCfg(char *val, char *prev, const char **err) { static int updateTlsCfg(char *val, char *prev, const char **err) {
UNUSED(val); UNUSED(val);
@ -2414,6 +2546,27 @@ static int updateTlsCfgInt(long long val, long long prev, const char **err) {
UNUSED(prev); UNUSED(prev);
return updateTlsCfg(NULL, NULL, err); return updateTlsCfg(NULL, NULL, err);
} }
static int updateTLSPort(long long val, long long prev, const char **err) {
/* Do nothing if port is unchanged */
if (val == prev) {
return 1;
}
/* Configure TLS if tls is enabled */
if (prev == 0 && tlsConfigure(&server.tls_ctx_config) == C_ERR) {
*err = "Unable to update TLS configuration. Check server logs.";
return 0;
}
asdsa
if (changeListenPort(val, &server.tlsfd, acceptTLSHandler) == C_ERR) {
*err = "Unable to listen on this port. Check server logs.";
return 0;
}
return 1;
}
#endif /* USE_OPENSSL */ #endif /* USE_OPENSSL */
int fDummy = false; int fDummy = false;
@ -2429,11 +2582,13 @@ standardConfig configs[] = {
createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_del_sync_files, 0, NULL, NULL), createBoolConfig("rdb-del-sync-files", NULL, MODIFIABLE_CONFIG, g_pserver->rdb_del_sync_files, 0, NULL, NULL),
createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, g_pserver->activerehashing, 1, NULL, NULL), createBoolConfig("activerehashing", NULL, MODIFIABLE_CONFIG, g_pserver->activerehashing, 1, NULL, NULL),
createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, g_pserver->stop_writes_on_bgsave_err, 1, NULL, NULL), createBoolConfig("stop-writes-on-bgsave-error", NULL, MODIFIABLE_CONFIG, g_pserver->stop_writes_on_bgsave_err, 1, NULL, NULL),
createBoolConfig("set-proc-title", NULL, IMMUTABLE_CONFIG, cserver.set_proc_title, 1, NULL, NULL), /* Should setproctitle be used? */
createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, g_pserver->dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/ createBoolConfig("dynamic-hz", NULL, MODIFIABLE_CONFIG, g_pserver->dynamic_hz, 1, NULL, NULL), /* Adapt hz to # of clients.*/
createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_eviction, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-eviction", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_eviction, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_expire, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-expire", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_expire, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_server_del, 0, NULL, NULL), createBoolConfig("lazyfree-lazy-server-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_server_del, 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_user_del , 0, NULL, NULL), createBoolConfig("lazyfree-lazy-user-del", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_user_del , 0, NULL, NULL),
createBoolConfig("lazyfree-lazy-user-flush", NULL, MODIFIABLE_CONFIG, g_pserver->lazyfree_lazy_user_flush , 0, NULL, NULL),
createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, g_pserver->repl_disable_tcp_nodelay, 0, NULL, NULL), createBoolConfig("repl-disable-tcp-nodelay", NULL, MODIFIABLE_CONFIG, g_pserver->repl_disable_tcp_nodelay, 0, NULL, NULL),
createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, g_pserver->repl_diskless_sync, 0, NULL, NULL), createBoolConfig("repl-diskless-sync", NULL, MODIFIABLE_CONFIG, g_pserver->repl_diskless_sync, 0, NULL, NULL),
createBoolConfig("aof-rewrite-incremental-fsync", NULL, MODIFIABLE_CONFIG, g_pserver->aof_rewrite_incremental_fsync, 1, NULL, NULL), createBoolConfig("aof-rewrite-incremental-fsync", NULL, MODIFIABLE_CONFIG, g_pserver->aof_rewrite_incremental_fsync, 1, NULL, NULL),
@ -2454,9 +2609,11 @@ standardConfig configs[] = {
createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, g_pserver->cluster_enabled, 0, NULL, NULL), createBoolConfig("cluster-enabled", NULL, IMMUTABLE_CONFIG, g_pserver->cluster_enabled, 0, NULL, NULL),
createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, g_pserver->aof_enabled, 0, NULL, updateAppendonly), createBoolConfig("appendonly", NULL, MODIFIABLE_CONFIG, g_pserver->aof_enabled, 0, NULL, updateAppendonly),
createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_reads_when_down, 0, NULL, NULL), createBoolConfig("cluster-allow-reads-when-down", NULL, MODIFIABLE_CONFIG, g_pserver->cluster_allow_reads_when_down, 0, NULL, NULL),
createBoolConfig("multi-master-no-forward", NULL, MODIFIABLE_CONFIG, cserver.multimaster_no_forward, 0, validateMultiMasterNoForward, NULL),
createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL),
createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL), createBoolConfig("io-threads-do-reads", NULL, IMMUTABLE_CONFIG, fDummy, 0, NULL, NULL),
createBoolConfig("crash-log-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->crashlog_enabled, 1, NULL, updateSighandlerEnabled),
createBoolConfig("crash-memcheck-enabled", NULL, MODIFIABLE_CONFIG, g_pserver->memcheck_enabled, 1, NULL, NULL),
createBoolConfig("use-exit-on-panic", NULL, MODIFIABLE_CONFIG, g_pserver->use_exit_on_panic, 0, NULL, NULL),
createBoolConfig("disable-thp", NULL, MODIFIABLE_CONFIG, g_pserver->disable_thp, 1, NULL, NULL),
/* String Configs */ /* String Configs */
createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->acl_filename, "", NULL, NULL), createStringConfig("aclfile", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->acl_filename, "", NULL, NULL),
@ -2464,7 +2621,6 @@ standardConfig configs[] = {
createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.pidfile, NULL, NULL, NULL), createStringConfig("pidfile", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.pidfile, NULL, NULL, NULL),
createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->slave_announce_ip, NULL, NULL, NULL), createStringConfig("replica-announce-ip", "slave-announce-ip", MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->slave_announce_ip, NULL, NULL, NULL),
createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masteruser, NULL, NULL, updateMasterAuthConfig), createStringConfig("masteruser", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masteruser, NULL, NULL, updateMasterAuthConfig),
createStringConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masterauth, NULL, NULL, updateMasterAuthConfig),
createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL), createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->cluster_announce_ip, NULL, NULL, NULL),
createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL), createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->syslog_ident, "redis", NULL, NULL),
createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL), createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->rdb_filename, CONFIG_DEFAULT_RDB_FILENAME, isValidDBfilename, NULL),
@ -2473,7 +2629,12 @@ standardConfig configs[] = {
createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL), createStringConfig("bio_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bio_cpulist, NULL, NULL, NULL),
createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->aof_rewrite_cpulist, NULL, NULL, NULL), createStringConfig("aof_rewrite_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->aof_rewrite_cpulist, NULL, NULL, NULL),
createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bgsave_cpulist, NULL, NULL, NULL), createStringConfig("bgsave_cpulist", NULL, IMMUTABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->bgsave_cpulist, NULL, NULL, NULL),
createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->ignore_warnings, "ARM64-COW-BUG", NULL, NULL), createStringConfig("ignore-warnings", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, g_pserver->ignore_warnings, "", NULL, NULL),
createStringConfig("proc-title-template", NULL, MODIFIABLE_CONFIG, ALLOW_EMPTY_STRING, cserver.proc_title_template, CONFIG_DEFAULT_PROC_TITLE_TEMPLATE, isValidProcTitleTemplate, updateProcTitleTemplate),
/* SDS Configs */
createSDSConfig("masterauth", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, cserver.default_masterauth, NULL, NULL, updateMasterAuthConfig),
createSDSConfig("requirepass", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->requirepass, NULL, NULL, updateRequirePass),
/* Enum Configs */ /* Enum Configs */
createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL), createEnumConfig("supervised", NULL, IMMUTABLE_CONFIG, supervised_mode_enum, cserver.supervised_mode, SUPERVISED_NONE, NULL, NULL),
@ -2483,10 +2644,12 @@ standardConfig configs[] = {
createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, g_pserver->maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL), createEnumConfig("maxmemory-policy", NULL, MODIFIABLE_CONFIG, maxmemory_policy_enum, g_pserver->maxmemory_policy, MAXMEMORY_NO_EVICTION, NULL, NULL),
createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, g_pserver->aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL), createEnumConfig("appendfsync", NULL, MODIFIABLE_CONFIG, aof_fsync_enum, g_pserver->aof_fsync, AOF_FSYNC_EVERYSEC, NULL, NULL),
createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, g_pserver->oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj), createEnumConfig("oom-score-adj", NULL, MODIFIABLE_CONFIG, oom_score_adj_enum, g_pserver->oom_score_adj, OOM_SCORE_ADJ_NO, NULL, updateOOMScoreAdj),
createEnumConfig("acl-pubsub-default", NULL, MODIFIABLE_CONFIG, acl_pubsub_default_enum, g_pserver->acl_pubusub_default, USER_FLAG_ALLCHANNELS, NULL, NULL),
createEnumConfig("sanitize-dump-payload", NULL, MODIFIABLE_CONFIG, sanitize_dump_payload_enum, cserver.sanitize_dump_payload, SANITIZE_DUMP_NO, NULL, NULL),
/* Integer configs */ /* Integer configs */
createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, cserver.dbnum, 16, INTEGER_CONFIG, NULL, NULL), createIntConfig("databases", NULL, IMMUTABLE_CONFIG, 1, INT_MAX, cserver.dbnum, 16, INTEGER_CONFIG, NULL, NULL),
createIntConfig("port", NULL, IMMUTABLE_CONFIG, 0, 65535, g_pserver->port, 6379, INTEGER_CONFIG, NULL, NULL), /* TCP port. */ createIntConfig("port", NULL, MODIFIABLE_CONFIG, 0, 65535, g_pserver->port, 6379, INTEGER_CONFIG, NULL, updatePort), /* TCP port. */
createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("auto-aof-rewrite-percentage", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->aof_rewrite_perc, 100, INTEGER_CONFIG, NULL, NULL),
createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */ createIntConfig("cluster-replica-validity-factor", "cluster-slave-validity-factor", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->cluster_slave_validity_factor, 10, INTEGER_CONFIG, NULL, NULL), /* Slave max data age factor. */
createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL), createIntConfig("list-max-ziplist-size", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->list_max_ziplist_size, -2, INTEGER_CONFIG, NULL, NULL),
@ -2501,6 +2664,7 @@ standardConfig configs[] = {
createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->slave_priority, 100, INTEGER_CONFIG, NULL, NULL), createIntConfig("replica-priority", "slave-priority", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->slave_priority, 100, INTEGER_CONFIG, NULL, NULL),
createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-diskless-sync-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_diskless_sync_delay, 5, INTEGER_CONFIG, NULL, NULL),
createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL), createIntConfig("maxmemory-samples", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->maxmemory_samples, 5, INTEGER_CONFIG, NULL, NULL),
createIntConfig("maxmemory-eviction-tenacity", NULL, MODIFIABLE_CONFIG, 0, 100, g_pserver->maxmemory_eviction_tenacity, 10, INTEGER_CONFIG, NULL, NULL),
createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, cserver.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */ createIntConfig("timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, cserver.maxidletime, 0, INTEGER_CONFIG, NULL, NULL), /* Default client timeout: infinite */
createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, g_pserver->slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("replica-announce-port", "slave-announce-port", MODIFIABLE_CONFIG, 0, 65535, g_pserver->slave_announce_port, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, g_pserver->tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */ createIntConfig("tcp-backlog", NULL, IMMUTABLE_CONFIG, 0, INT_MAX, g_pserver->tcp_backlog, 511, INTEGER_CONFIG, NULL, NULL), /* TCP listen backlog. */
@ -2509,8 +2673,8 @@ standardConfig configs[] = {
createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_timeout, 60, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-timeout", NULL, MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_timeout, 60, INTEGER_CONFIG, NULL, NULL),
createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL), createIntConfig("repl-ping-replica-period", "repl-ping-slave-period", MODIFIABLE_CONFIG, 1, INT_MAX, g_pserver->repl_ping_slave_period, 10, INTEGER_CONFIG, NULL, NULL),
createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("list-compress-depth", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->list_compress_depth, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("rdb-key-save-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->rdb_key_save_delay, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL), createIntConfig("key-load-delay", NULL, MODIFIABLE_CONFIG, INT_MIN, INT_MAX, g_pserver->key_load_delay, 0, INTEGER_CONFIG, NULL, NULL),
createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */ createIntConfig("active-expire-effort", NULL, MODIFIABLE_CONFIG, 1, 10, cserver.active_expire_effort, 1, INTEGER_CONFIG, NULL, NULL), /* From 1 to 10. */
createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ), createIntConfig("hz", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->config_hz, CONFIG_DEFAULT_HZ, INTEGER_CONFIG, NULL, updateHZ),
createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves), createIntConfig("min-replicas-to-write", "min-slaves-to-write", MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->repl_min_slaves_to_write, 0, INTEGER_CONFIG, NULL, updateGoodSlaves),
@ -2522,7 +2686,6 @@ standardConfig configs[] = {
createUIntConfig("loading-process-events-interval-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_keys, 8192, MEMORY_CONFIG, NULL, NULL), createUIntConfig("loading-process-events-interval-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_keys, 8192, MEMORY_CONFIG, NULL, NULL),
/* Unsigned Long configs */ /* Unsigned Long configs */
createULongConfig("loading-process-events-interval-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_bytes, 2*1024*1024, MEMORY_CONFIG, NULL, NULL),
createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */ createULongConfig("active-defrag-max-scan-fields", NULL, MODIFIABLE_CONFIG, 1, LONG_MAX, cserver.active_defrag_max_scan_fields, 1000, INTEGER_CONFIG, NULL, NULL), /* Default: keys with more than 1000 fields will be processed separately */
createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL), createULongConfig("slowlog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->slowlog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL), createULongConfig("acllog-max-len", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->acllog_max_len, 128, INTEGER_CONFIG, NULL, NULL),
@ -2549,28 +2712,36 @@ standardConfig configs[] = {
createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("zset-max-ziplist-value", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->zset_max_ziplist_value, 64, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL), createSizeTConfig("hll-sparse-max-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->hll_sparse_max_bytes, 3000, MEMORY_CONFIG, NULL, NULL),
createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */ createSizeTConfig("tracking-table-max-keys", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->tracking_table_max_keys, 1000000, INTEGER_CONFIG, NULL, NULL), /* Default: 1 million keys max. */
createSizeTConfig("client-query-buffer-limit", NULL, MODIFIABLE_CONFIG, 1024*1024, LONG_MAX, cserver.client_max_querybuf_len, 1024*1024*1024, MEMORY_CONFIG, NULL, NULL), /* Default: 1GB max query buffer. */
/* Other configs */ /* Other configs */
createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */ createTimeTConfig("repl-backlog-ttl", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->repl_backlog_time_limit, 60*60, INTEGER_CONFIG, NULL, NULL), /* Default: 1 hour */
createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL), createOffTConfig("auto-aof-rewrite-min-size", NULL, MODIFIABLE_CONFIG, 0, LLONG_MAX, g_pserver->aof_rewrite_min_size, 64*1024*1024, MEMORY_CONFIG, NULL, NULL),
/* KeyDB Specific Configs */
createULongConfig("loading-process-events-interval-bytes", NULL, MODIFIABLE_CONFIG, 0, LONG_MAX, g_pserver->loading_process_events_interval_bytes, 2*1024*1024, MEMORY_CONFIG, NULL, NULL),
createBoolConfig("multi-master-no-forward", NULL, MODIFIABLE_CONFIG, cserver.multimaster_no_forward, 0, validateMultiMasterNoForward, NULL),
createBoolConfig("allow-write-during-load", NULL, MODIFIABLE_CONFIG, g_pserver->fWriteDuringActiveLoad, 0, NULL, NULL),
#ifdef USE_OPENSSL #ifdef USE_OPENSSL
createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, g_pserver->tls_port, 0, INTEGER_CONFIG, NULL, NULL), /* TCP port. */ createIntConfig("tls-port", NULL, IMMUTABLE_CONFIG, 0, 65535, server.tls_port, 0, INTEGER_CONFIG, NULL, updateTLSPort), /* TCP port. */
createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt), createIntConfig("tls-session-cache-size", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_size, 20*1024, INTEGER_CONFIG, NULL, updateTlsCfgInt),
createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, g_pserver->tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt), createIntConfig("tls-session-cache-timeout", NULL, MODIFIABLE_CONFIG, 0, INT_MAX, server.tls_ctx_config.session_cache_timeout, 300, INTEGER_CONFIG, NULL, updateTlsCfgInt),
createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, g_pserver->tls_cluster, 0, NULL, NULL), createBoolConfig("tls-cluster", NULL, MODIFIABLE_CONFIG, server.tls_cluster, 0, NULL, updateTlsCfgBool),
createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, g_pserver->tls_replication, 0, NULL, NULL), createBoolConfig("tls-replication", NULL, MODIFIABLE_CONFIG, server.tls_replication, 0, NULL, updateTlsCfgBool),
createEnumConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, g_pserver->tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL), createEnumConfig("tls-auth-clients", NULL, MODIFIABLE_CONFIG, tls_auth_clients_enum, server.tls_auth_clients, TLS_CLIENT_AUTH_YES, NULL, NULL),
createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, g_pserver->tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool), createBoolConfig("tls-prefer-server-ciphers", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.prefer_server_ciphers, 0, NULL, updateTlsCfgBool),
createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, g_pserver->tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool), createBoolConfig("tls-session-caching", NULL, MODIFIABLE_CONFIG, server.tls_ctx_config.session_caching, 1, NULL, updateTlsCfgBool),
createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.key_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.key_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-client-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg), createStringConfig("tls-client-key-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.client_key_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg), createStringConfig("tls-dh-params-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.dh_params_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.protocols, NULL, NULL, updateTlsCfg), createStringConfig("tls-ca-cert-file", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_file, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ciphers, NULL, NULL, updateTlsCfg), createStringConfig("tls-ca-cert-dir", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ca_cert_dir, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, g_pserver->tls_ctx_config.ciphersuites, NULL, NULL, updateTlsCfg), createStringConfig("tls-protocols", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.protocols, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ciphers", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphers, NULL, NULL, updateTlsCfg),
createStringConfig("tls-ciphersuites", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.tls_ctx_config.ciphersuites, NULL, NULL, updateTlsCfg),
#endif #endif
/* NULL Terminator */ /* NULL Terminator */
@ -2590,12 +2761,17 @@ void configCommand(client *c) {
if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) { if (c->argc == 2 && !strcasecmp(szFromObj(c->argv[1]),"help")) {
const char *help[] = { const char *help[] = {
"GET <pattern> -- Return parameters matching the glob-like <pattern> and their values.", "GET <pattern>",
"SET <parameter> <value> -- Set parameter to value.", " Return parameters matching the glob-like <pattern> and their values.",
"RESETSTAT -- Reset statistics reported by INFO.", "SET <directive> <value>",
"REWRITE -- Rewrite the configuration file.", " Set the configuration <directive> to <value>.",
"RESETSTAT",
" Reset statistics reported by the INFO command.",
"REWRITE",
" Rewrite the configuration file.",
NULL NULL
}; };
addReplyHelp(c, help); addReplyHelp(c, help);
} else if (!strcasecmp(szFromObj(c->argv[1]),"set") && c->argc == 4) { } else if (!strcasecmp(szFromObj(c->argv[1]),"set") && c->argc == 4) {
configSetCommand(c); configSetCommand(c);
@ -2604,6 +2780,7 @@ NULL
} else if (!strcasecmp(szFromObj(c->argv[1]),"resetstat") && c->argc == 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"resetstat") && c->argc == 2) {
resetServerStats(); resetServerStats();
resetCommandTableStats(); resetCommandTableStats();
resetErrorTableStats();
addReply(c,shared.ok); addReply(c,shared.ok);
} else if (!strcasecmp(szFromObj(c->argv[1]),"rewrite") && c->argc == 2) { } else if (!strcasecmp(szFromObj(c->argv[1]),"rewrite") && c->argc == 2) {
if (cserver.configfile == NULL) { if (cserver.configfile == NULL) {

View File

@ -35,10 +35,11 @@
#endif #endif
#ifdef __linux__ #ifdef __linux__
#include <linux/version.h>
#include <features.h> #include <features.h>
#endif #endif
#define CONFIG_DEFAULT_RDB_FILENAME "dump.rdb"
/* Define redis_fstat to fstat or fstat64() */ /* Define redis_fstat to fstat or fstat64() */
#if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6) #if defined(__APPLE__) && !defined(MAC_OS_X_VERSION_10_6)
#define redis_fstat fstat64 #define redis_fstat fstat64
@ -65,7 +66,7 @@
/* Test for backtrace() */ /* Test for backtrace() */
#if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \ #if defined(__APPLE__) || (defined(__linux__) && defined(__GLIBC__)) || \
defined(__FreeBSD__) || ((defined(__OpenBSD__) || defined(__NetBSD__)) && defined(USE_BACKTRACE))\ defined(__FreeBSD__) || ((defined(__OpenBSD__) || defined(__NetBSD__)) && defined(USE_BACKTRACE))\
|| defined(__DragonFly__) || defined(__DragonFly__) || (defined(__UCLIBC__) && defined(__UCLIBC_HAS_BACKTRACE__))
#define HAVE_BACKTRACE 1 #define HAVE_BACKTRACE 1
#endif #endif
@ -87,6 +88,7 @@
#include <sys/feature_tests.h> #include <sys/feature_tests.h>
#ifdef _DTRACE_VERSION #ifdef _DTRACE_VERSION
#define HAVE_EVPORT 1 #define HAVE_EVPORT 1
#define HAVE_PSINFO 1
#endif #endif
#endif #endif
@ -97,21 +99,23 @@
#define redis_fsync fsync #define redis_fsync fsync
#endif #endif
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use #if __GNUC__ >= 4
* the plain fsync() call. */ #define redis_unreachable __builtin_unreachable
#ifdef __linux__
#if defined(__GLIBC__) && defined(__GLIBC_PREREQ)
#if (LINUX_VERSION_CODE >= 0x020611 && __GLIBC_PREREQ(2, 6))
#define HAVE_SYNC_FILE_RANGE 1
#endif
#else #else
#if (LINUX_VERSION_CODE >= 0x020611) #define redis_unreachable abort
#define HAVE_SYNC_FILE_RANGE 1
#endif
#endif
#endif #endif
#ifdef HAVE_SYNC_FILE_RANGE #if __GNUC__ >= 3
#define likely(x) __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)
#else
#define likely(x) (x)
#define unlikely(x) (x)
#endif
/* Define rdb_fsync_range to sync_file_range() on Linux, otherwise we use
* the plain fsync() call. */
#if (defined(__linux__) && defined(SYNC_FILE_RANGE_WAIT_BEFORE))
#define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE) #define rdb_fsync_range(fd,off,size) sync_file_range(fd,off,size,SYNC_FILE_RANGE_WAIT_BEFORE|SYNC_FILE_RANGE_WRITE)
#else #else
#define rdb_fsync_range(fd,off,size) fsync(fd) #define rdb_fsync_range(fd,off,size) fsync(fd)
@ -128,7 +132,7 @@
#define ESOCKTNOSUPPORT 0 #define ESOCKTNOSUPPORT 0
#endif #endif
#if ((defined __linux && defined(__GLIBC__)) || defined __APPLE__) #if (defined __linux || defined __APPLE__)
#define USE_SETPROCTITLE #define USE_SETPROCTITLE
#define INIT_SETPROCTITLE_REPLACEMENT #define INIT_SETPROCTITLE_REPLACEMENT
#ifdef __cplusplus #ifdef __cplusplus
@ -247,11 +251,11 @@ void setproctitle(const char *fmt, ...);
#elif defined __NetBSD__ #elif defined __NetBSD__
#include <pthread.h> #include <pthread.h>
#define redis_set_thread_title(name) pthread_setname_np(pthread_self(), "%s", name) #define redis_set_thread_title(name) pthread_setname_np(pthread_self(), "%s", name)
#elif defined __HAIKU__
#include <kernel/OS.h>
#define redis_set_thread_title(name) rename_thread(find_thread(0), name)
#else #else
#if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7)) #if (defined __APPLE__ && defined(MAC_OS_X_VERSION_10_7))
#ifdef __cplusplus
extern "C"
#endif
int pthread_setname_np(const char *name); int pthread_setname_np(const char *name);
#include <pthread.h> #include <pthread.h>
#define redis_set_thread_title(name) pthread_setname_np(name) #define redis_set_thread_title(name) pthread_setname_np(name)

View File

@ -147,8 +147,7 @@ void *connGetPrivateData(connection *conn) {
/* Close the connection and free resources. */ /* Close the connection and free resources. */
static void connSocketClose(connection *conn) { static void connSocketClose(connection *conn) {
if (conn->fd != -1) { if (conn->fd != -1) {
aeDeleteFileEvent(serverTL->el,conn->fd,AE_READABLE); aeDeleteFileEvent(serverTL->el,conn->fd,AE_READABLE | AE_WRITABLE);
aeDeleteFileEvent(serverTL->el,conn->fd,AE_WRITABLE);
close(conn->fd); close(conn->fd);
conn->fd = -1; conn->fd = -1;
} }
@ -384,15 +383,15 @@ int connGetSocketError(connection *conn) {
} }
int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) { int connPeerToString(connection *conn, char *ip, size_t ip_len, int *port) {
return anetPeerToString(conn ? conn->fd : -1, ip, ip_len, port); return anetFdToString(conn ? conn->fd : -1, ip, ip_len, port, FD_TO_PEER_NAME);
}
int connFormatPeer(connection *conn, char *buf, size_t buf_len) {
return anetFormatPeer(conn ? conn->fd : -1, buf, buf_len);
} }
int connSockName(connection *conn, char *ip, size_t ip_len, int *port) { int connSockName(connection *conn, char *ip, size_t ip_len, int *port) {
return anetSockName(conn->fd, ip, ip_len, port); return anetFdToString(conn->fd, ip, ip_len, port, FD_TO_SOCK_NAME);
}
int connFormatFdAddr(connection *conn, char *buf, size_t buf_len, int fd_to_str_type) {
return anetFormatFdAddr(conn ? conn->fd : -1, buf, buf_len, fd_to_str_type);
} }
int connBlock(connection *conn) { int connBlock(connection *conn) {
@ -447,7 +446,7 @@ void connSetThreadAffinity(connection *conn, int cpu) {
* For sockets, we always return "fd=<fdnum>" to maintain compatibility. * For sockets, we always return "fd=<fdnum>" to maintain compatibility.
*/ */
const char *connGetInfo(connection *conn, char *buf, size_t buf_len) { const char *connGetInfo(connection *conn, char *buf, size_t buf_len) {
snprintf(buf, buf_len-1, "fd=%i", conn->fd); snprintf(buf, buf_len-1, "fd=%i", conn == NULL ? -1 : conn->fd);
return buf; return buf;
} }

Some files were not shown because too many files have changed in this diff Show More