Add files to cetus project

This commit is contained in:
lazio579 2018-03-06 14:00:39 +08:00
parent d2b2d5a7ba
commit 0af842272e
140 changed files with 51499 additions and 1 deletions

282
CMakeLists.txt Normal file
View File

@ -0,0 +1,282 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
PROJECT(cetus C)
SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
CMAKE_MINIMUM_REQUIRED(VERSION 2.6)
INCLUDE(CheckIncludeFiles)
INCLUDE(CheckFunctionExists)
INCLUDE(CheckLibraryExists)
INCLUDE(FindPkgConfig)
INCLUDE(CheckTypeSize)
INCLUDE(ChassisPlugin)
INCLUDE(ChassisInstall)
INCLUDE(CTest)
INCLUDE(Tar)
ENABLE_TESTING()
set(CETUS_TOOLS_DIR ${PROJECT_SOURCE_DIR}/tools)
OPTION(ENABLE_GCOV "Enable gcov (debug, Linux builds only)" OFF)
IF (ENABLE_GCOV AND NOT WIN32 AND NOT APPLE)
MESSAGE(STATUS "***************building gcov version*************")
SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
SET(CMAKE_EXE_LINKER_FLAGS_DEBUG
"${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage -lgcov")
ENDIF()
option(NETWORK_DEBUG_TRACE_IO "if NETWORK_DEBUG_TRACE_IO is defined, the network layer will log the raw MySQL packets as log-level debug")
option(NETWORK_DEBUG_TRACE_EVENT "if NETWORK_DEBUG_TRACE_EVENT is defined, cetus will log the abnormal event")
option(NETWORK_DEBUG_TRACE_STATE_CHANGES "if NETWORK_DEBUG_TRACE_STATE_CHANGES is defined the state engine for the mysql protocol will log all state changes")
option(USE_GLIB_DEBUG_LOG "g_debug() is default defined to nothing, change to ON if you want to use g_debug()")
EXECUTE_PROCESS(COMMAND git describe --tags
TIMEOUT 5
OUTPUT_VARIABLE GIT_REVISION
OUTPUT_STRIP_TRAILING_WHITESPACE
)
MESSAGE(STATUS "building from ${GIT_REVISION}")
SET(CPACK_PACKAGE_VERSION_MAJOR "0")
SET(CPACK_PACKAGE_VERSION_MINOR "8")
SET(CPACK_PACKAGE_VERSION_PATCH "8")
SET(CPACK_PACKAGE_VENDOR "MySQL")
SET(PACKAGE_NAME cetus)
## SET(CPACK_*) before the INCLUDE(CPack)
INCLUDE(CPack)
MESSAGE(STATUS "libevent at libs:${EVENT_LIBRARY_DIRS} incl:${EVENT_INCLUDE_DIRS}")
MESSAGE(STATUS "gthread at libs:${GTHREAD_LIBRARY_DIRS} incl:${GTHREAD_INCLUDE_DIRS}")
MESSAGE(STATUS "gmodule at libs:${GMODULE_LIBRARY_DIRS} incl:${GMODULE_INCLUDE_DIRS}")
## if the paths are not set, use pkg-config to fetch them
IF(NOT GLIB_DEV_BASE_DIR)
SET(GMODULE_INCLUDE_DIRS CACHE PATH "gmodule-2.0 include dir [see pkg-config gmodule-2.0 --cflags]")
SET(GMODULE_LIBRARY_DIRS CACHE PATH "gmodule-2.0 library dir [see pkg-config gmodule-2.0 --libs]")
SET(GTHREAD_INCLUDE_DIRS CACHE PATH "gthread-2.0 include dir [see pkg-config gthread-2.0 --cflags]")
SET(GTHREAD_LIBRARY_DIRS CACHE PATH "gthread-2.0 library dir [see pkg-config gthread-2.0 --libs]")
ENDIF(NOT GLIB_DEV_BASE_DIR)
SET(MYSQL_INCLUDE_DIRS CACHE PATH "MySQL include dir")
SET(MYSQL_LIBRARY_DIRS CACHE PATH "MySQL library dir")
IF (NOT EVENT_BASE_DIR)
SET(EVENT_INCLUDE_DIRS CACHE PATH "libevent include dir")
SET(EVENT_LIBRARY_DIRS CACHE PATH "libevent library dir")
ENDIF(NOT EVENT_BASE_DIR)
MESSAGE(STATUS "libevent at libs:${EVENT_LIBRARY_DIRS} incl:${EVENT_INCLUDE_DIRS}")
MESSAGE(STATUS "glib2.0 at libs:${GLIB_LIBRARY_DIRS} incl:${GLIB_INCLUDE_DIRS}")
MESSAGE(STATUS "gthread at libs:${GTHREAD_LIBRARY_DIRS} incl:${GTHREAD_INCLUDE_DIRS}")
MESSAGE(STATUS "gmodule at libs:${GMODULE_LIBRARY_DIRS} incl:${GMODULE_INCLUDE_DIRS}")
IF(NOT GLIB_INCLUDE_DIRS)
SET(__pkg_config_checked_GLIB 0)
PKG_CHECK_MODULES(GLIB REQUIRED glib-2.0>=2.16)
ADD_DEFINITIONS(-DHAVE_GLIB)
ENDIF(NOT GLIB_INCLUDE_DIRS)
IF(NOT GMODULE_INCLUDE_DIRS)
PKG_CHECK_MODULES(GMODULE REQUIRED gmodule-2.0>=2.16)
ADD_DEFINITIONS(-DHAVE_GMODULE)
ENDIF(NOT GMODULE_INCLUDE_DIRS)
IF(NOT GTHREAD_INCLUDE_DIRS)
PKG_CHECK_MODULES(GTHREAD REQUIRED gthread-2.0>=2.16)
ENDIF(NOT GTHREAD_INCLUDE_DIRS)
MACRO(_mysql_config VAR _regex _opt)
EXECUTE_PROCESS(COMMAND ${MYSQL_CONFIG_EXECUTABLE} ${_opt}
OUTPUT_VARIABLE _mysql_config_output
)
SET(_var ${_mysql_config_output})
STRING(REGEX MATCHALL "${_regex}([^ ]+)" _mysql_config_output "${_mysql_config_output}")
STRING(REGEX REPLACE "^[ \t]+" "" _mysql_config_output "${_mysql_config_output}")
STRING(REGEX REPLACE "[\r\n]$" "" _mysql_config_output "${_mysql_config_output}")
STRING(REGEX REPLACE "${_regex}" "" _mysql_config_output "${_mysql_config_output}")
SEPARATE_ARGUMENTS(_mysql_config_output)
SET(${VAR} ${_mysql_config_output})
ENDMACRO(_mysql_config _regex _opt)
IF(NOT MYSQL_INCLUDE_DIRS)
FIND_PROGRAM(MYSQL_CONFIG_EXECUTABLE NAMES mysql_config DOC "full path of mysql_config")
IF(NOT MYSQL_CONFIG_EXECUTABLE)
MESSAGE(SEND_ERROR "mysql_config wasn't found, -DMYSQL_CONFIG_EXECUTABLE=...")
ENDIF(NOT MYSQL_CONFIG_EXECUTABLE)
_MYSQL_CONFIG(MYSQL_INCLUDE_DIRS "(^| )-I" "--include")
_MYSQL_CONFIG(MYSQL_LIBRARIES "(^| )-l" "--libs")
_MYSQL_CONFIG(MYSQL_LIBRARY_DIRS "(^| )-L" "--libs")
ELSE(NOT MYSQL_INCLUDE_DIRS)
SET(MYSQL_LIBRARIES libmysql)
ENDIF(NOT MYSQL_INCLUDE_DIRS)
SET(CMAKE_REQUIRED_INCLUDES
${EVENT_INCLUDE_DIRS}
${MYSQL_INCLUDE_DIRS}
${GLIB_INCLUDE_DIRS}
${GTHREAD_INCLUDE_DIRS}
)
MESSAGE(STATUS "checking in dirs: ${CMAKE_REQUIRED_INCLUDES}")
CHECK_INCLUDE_FILES("sys/types.h;event.h" HAVE_EVENT_H)
CHECK_INCLUDE_FILES(inttypes.h HAVE_INTTYPES_H)
CHECK_INCLUDE_FILES(netinet/in.h HAVE_NETINET_IN_H)
CHECK_INCLUDE_FILES(net/if.h HAVE_NET_IF_H)
CHECK_INCLUDE_FILES(net/if_dl.h HAVE_NET_IF_DL_H)
CHECK_INCLUDE_FILES(stddef.h HAVE_STDDEF_H)
CHECK_INCLUDE_FILES(stdint.h HAVE_STDINT_H)
CHECK_INCLUDE_FILES(stdlib.h HAVE_STDLIB_H)
CHECK_INCLUDE_FILES(signal.h HAVE_SIGNAL_H)
CHECK_INCLUDE_FILES(syslog.h HAVE_SYSLOG_H)
CHECK_INCLUDE_FILES(sys/filio.h HAVE_SYS_FILIO_H)
CHECK_INCLUDE_FILES(sys/ioctl.h HAVE_SYS_IOCTL_H)
CHECK_INCLUDE_FILES(sys/param.h HAVE_SYS_PARAM_H)
CHECK_INCLUDE_FILES(sys/resource.h HAVE_SYS_RESOURCE_H)
CHECK_INCLUDE_FILES(sys/socket.h HAVE_SYS_SOCKET_H)
CHECK_INCLUDE_FILES(sys/sockio.h HAVE_SYS_SOCKIO_H)
CHECK_INCLUDE_FILES(sys/time.h HAVE_SYS_TIME_H)
CHECK_INCLUDE_FILES(sys/types.h HAVE_SYS_TYPES_H)
CHECK_INCLUDE_FILES(sys/uio.h HAVE_SYS_UIO_H)
CHECK_INCLUDE_FILES(sys/un.h HAVE_SYS_UN_H)
CHECK_INCLUDE_FILES(time.h HAVE_TIME_H)
CHECK_INCLUDE_FILES(unistd.h HAVE_UNISTD_H)
CHECK_INCLUDE_FILES(mysql.h HAVE_MYSQL_H)
CHECK_INCLUDE_FILES(glib.h HAVE_GLIB_H)
CHECK_INCLUDE_FILES(glib/gthread.h HAVE_GTHREAD_H)
CHECK_INCLUDE_FILES(pwd.h HAVE_PWD_H)
CHECK_FUNCTION_EXISTS(inet_ntop HAVE_INET_NTOP)
CHECK_FUNCTION_EXISTS(getcwd HAVE_GETCWD)
CHECK_FUNCTION_EXISTS(signal HAVE_SIGNAL)
CHECK_FUNCTION_EXISTS(strerror HAVE_STRERROR)
CHECK_FUNCTION_EXISTS(srandom HAVE_SRANDOM)
CHECK_FUNCTION_EXISTS(writev HAVE_WRITEV)
CHECK_FUNCTION_EXISTS(sigaction HAVE_SIGACTION)
CHECK_FUNCTION_EXISTS(getaddrinfo HAVE_GETADDRINFO)
# check for gthread actually being present
CHECK_LIBRARY_EXISTS(gthread-2.0 g_thread_init "${GTHREAD_LIBRARY_DIRS}" HAVE_GTHREAD)
#SET(OLD_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
#SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${GTHREAD_LIBRARIES})
# fails for some reason use check_library_exists instead and hope it's better
#CHECK_FUNCTION_EXISTS(g_thread_init HAVE_GTHREAD)
#SET(CMAKE_REQUIRED_LIBRARIES ${OLD_CMAKE_REQUIRED_LIBRARIES})
find_package(OpenSSL QUIET)
if (OPENSSL_FOUND)
message("-- OpenSSL version: ${OPENSSL_VERSION}")
set(HAVE_OPENSSL 1)
else(OPENSSL_FOUND)
message("-- OpenSSL not found")
set(OPENSSL_LIBRARIES "")
endif(OPENSSL_FOUND)
IF(${HAVE_SYS_TYPES_H})
SET(CMAKE_EXTRA_INCLUDE_FILES sys/types.h)
CHECK_TYPE_SIZE(ulong HAVE_ULONG)
SET(CMAKE_EXTRA_INCLUDE_FILES )
ENDIF(${HAVE_SYS_TYPES_H})
IF(${HAVE_SYS_SOCKET_H})
SET(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h)
CHECK_TYPE_SIZE(socklen_t HAVE_SOCKLEN_T)
SET(CMAKE_EXTRA_INCLUDE_FILES )
ENDIF(${HAVE_SYS_SOCKET_H})
IF(${HAVE_SYS_RESOURCE_H})
SET(CMAKE_EXTRA_INCLUDE_FILES sys/resource.h)
CHECK_TYPE_SIZE(rlim_t SIZEOF_RLIM_T)
SET(CMAKE_EXTRA_INCLUDE_FILES )
ENDIF(${HAVE_SYS_RESOURCE_H})
IF(EVENT_LIBRARY_DIRS)
FIND_LIBRARY(EVENT_LIBRARIES
NAMES event
PATHS ${EVENT_LIBRARY_DIRS}
NO_DEFAULT_PATH
)
ELSE(EVENT_LIBRARY_DIRS)
FIND_LIBRARY(EVENT_LIBRARIES event)
ENDIF(EVENT_LIBRARY_DIRS)
SET(OLD_CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES})
SET(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${EVENT_LIBRARIES})
MESSAGE(STATUS "CMAKE_REQUIRED_LIBRARIES is ${CMAKE_REQUIRED_LIBRARIES}")
CHECK_FUNCTION_EXISTS(event_base_new HAVE_EVENT_BASE_NEW)
CHECK_FUNCTION_EXISTS(event_base_free HAVE_EVENT_BASE_FREE)
SET(CMAKE_REQUIRED_LIBRARIES ${OLD_CMAKE_REQUIRED_LIBRARIES})
IF(GIT_REVISION)
ADD_DEFINITIONS(-DCHASSIS_BUILD_TAG="${GIT_REVISION}")
ENDIF(GIT_REVISION)
CONFIGURE_FILE(config.h.cmake config.h)
ADD_DEFINITIONS(-DHAVE_CONFIG_H)
SET(PACKAGE_VERSION_ID "(${CPACK_PACKAGE_VERSION_MAJOR} << 16 | ${CPACK_PACKAGE_VERSION_MINOR} << 8 | ${CPACK_PACKAGE_VERSION_PATCH})")
SET(PACKAGE_VERSION_STRING "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
SET(PACKAGE_VERSION "${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.${CPACK_PACKAGE_VERSION_PATCH}")
SET(PACKAGE_STRING "${CMAKE_PROJECT_NAME} ${CPACK_PACKAGE_VERSION}")
ADD_DEFINITIONS(
-DPACKAGE_VERSION_ID=${PACKAGE_VERSION_ID}
-DPACKAGE_VERSION="${PACKAGE_VERSION}"
-DPACKAGE_STRING="${PACKAGE_STRING}"
-DPACKAGE="${CMAKE_PROJECT_NAME}"
)
ADD_SUBDIRECTORY(src)
ADD_SUBDIRECTORY(plugins)
IF(EXISTS tests)
ADD_SUBDIRECTORY(tests)
ENDIF(EXISTS tests)
IF(EXISTS examples)
ADD_SUBDIRECTORY(examples)
ENDIF(EXISTS examples)
ADD_SUBDIRECTORY(lib)
CONFIGURE_FILE(mysql-chassis.pc.cmake mysql-chassis.pc @ONLY)
CONFIGURE_FILE(cetus.pc.cmake cetus.pc @ONLY)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/mysql-chassis.pc
DESTINATION lib/pkgconfig/
)
INSTALL(FILES ${CMAKE_CURRENT_BINARY_DIR}/cetus.pc
DESTINATION lib/pkgconfig/
)
INSTALL(FILES README_XA
DESTINATION logs
)
install(FILES
doc/users.json.example
doc/variables.json.example
doc/sharding.json.example
doc/shard.conf.example
doc/proxy.conf.example
DESTINATION conf
)

339
COPYING Normal file
View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

343
COPYING.rtf Normal file
View File

@ -0,0 +1,343 @@
{\rtf\ansi\deff0
{\fonttbl{\f0\fswiss Courier New;}}
\viewkind4\uc1\pard\lang1031\f0\fs15
GNU GENERAL PUBLIC LICENSE\line
Version 2, June 1991\line
\par
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,\line
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\line
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.\line
\par
Preamble\line
\par
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.\line
\par
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.\line
\par
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.\line
\par
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.\line
\par
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.\line
\par
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.\line
\par
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.\line
\par
The precise terms and conditions for copying, distribution and
modification follow.\line
\par
GNU GENERAL PUBLIC LICENSE\line
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\line
\par
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".\line
\par
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.\line
\par
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.\line
\par
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.\line
\par
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:\line
\par
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.\line
\par
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.\line
\par
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)\line
\par
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.\line
\par
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.\line
\par
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.\line
\par
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:\line
\par
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,\line
\par
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,\line
\par
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)\line
\par
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.\line
\par
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.\line
\par
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.\line
\par
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.\line
\par
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.\line
\par
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.\line
\par
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.\line
\par
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.\line
\par
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.\line
\par
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.\line
\par
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.\line
\par
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.\line
\par
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
\par
NO WARRANTY\line
\par
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.\line
\par
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.\line
\par
END OF TERMS AND CONDITIONS\line
\par
How to Apply These Terms to Your New Programs\line
\par
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.\line
\par
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.\line
\par
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>\line
\par
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.\line
\par
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.\line
\par
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.\line
\par
Also add information on how to contact you by electronic and paper mail.\line
\par
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:\line
\par
Gnomovision version 69, Copyright (C) year name of author\line
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\line
This is free software, and you are welcome to redistribute it\line
under certain conditions; type `show c' for details.\line
\par
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.\line
\par
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:\line
\par
Yoyodyne, Inc., hereby disclaims all copyright interest in the program\line
`Gnomovision' (which makes passes at compilers) written by James Hacker.\line
\par
<signature of Ty Coon>, 1 April 1989\line
Ty Coon, President of Vice\line
\par
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.\line
}

View File

@ -1,2 +1 @@
# cetus
A powerful Proxy for MySQL

12
cetus.pc.cmake Normal file
View File

@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=@CMAKE_INSTALL_PREFIX@/lib
pkglibdir=${libdir}/cetus
plugindir=${pkglibdir}/plugins
Name: MySQL Proxy
Version: @PACKAGE_VERSION_STRING@
Description: MySQL Proxy
URL: http://forge.mysql.com/wiki/MySQL_Proxy
Requires: glib-2.0 >= 2.16, mysql-chassis >= @PACKAGE_VERSION_STRING@
Libs: -L${libdir} -lmysql-chassis-proxy

View File

@ -0,0 +1,79 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
## print a few properties of a target
MACRO(PRINT_TARGET_PROPERTIES target)
MESSAGE(STATUS "properties of target ${target}")
FOREACH(prop "COMPILE_FLAGS" "IMPORT_PREFIX" "IMPORT_SUFFIX" "TYPE" "LOCATION")
GET_TARGET_PROPERTY(value ${target} ${prop})
MESSAGE(STATUS " ${prop} = ${value}")
ENDFOREACH()
ENDMACRO(PRINT_TARGET_PROPERTIES target)
## install a shared-lib or executable target incl. .pdb files on win32
##
## works around a bug in cmake that doesn't include TARGETS in cpack
MACRO(CHASSIS_INSTALL_TARGET target)
IF(WIN32)
GET_TARGET_PROPERTY(built_location ${target} LOCATION)
GET_TARGET_PROPERTY(type ${target} TYPE)
STRING(REPLACE "$(OutDir)" "${CMAKE_BUILD_TYPE}" built_location ${built_location})
IF(type MATCHES "SHARED_LIBRARY")
STRING(REPLACE ".dll" ".lib" lib_location ${built_location})
INSTALL(FILES
${built_location}
DESTINATION bin
)
INSTALL(FILES
${lib_location}
DESTINATION lib
)
IF(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
STRING(REPLACE ".dll" ".pdb" pdb_location ${built_location})
INSTALL(FILES
${pdb_location}
DESTINATION bin
)
ENDIF()
ENDIF()
IF(type MATCHES "EXECUTABLE")
INSTALL(FILES
${built_location}
DESTINATION bin
)
IF(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
STRING(REPLACE ".exe" ".pdb" pdb_location ${built_location})
INSTALL(FILES
${pdb_location}
DESTINATION bin
)
ENDIF()
ENDIF()
ELSE(WIN32)
INSTALL(TARGETS ${target}
RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib
LIBRARY DESTINATION lib
)
ENDIF(WIN32)
ENDMACRO(CHASSIS_INSTALL_TARGET target)

43
cmake/ChassisPlugin.cmake Normal file
View File

@ -0,0 +1,43 @@
# $%BEGINLICENSE%$
# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
MACRO(CHASSIS_PLUGIN_INSTALL _plugin_name)
IF(NOT WIN32)
INSTALL(TARGETS ${_plugin_name}
DESTINATION lib/cetus/plugins)
ELSE(NOT WIN32)
## on win32 the chassis plugins gets prefixed with plugin- and end up in bin/
GET_TARGET_PROPERTY(built_location ${_plugin_name} LOCATION)
STRING(REPLACE "$(OutDir)" "${CMAKE_BUILD_TYPE}" built_location ${built_location})
INSTALL(FILES ${built_location}
DESTINATION bin/
RENAME plugin-${_plugin_name}${CMAKE_SHARED_LIBRARY_SUFFIX}
)
## install the .pdb too
IF(CMAKE_BUILD_TYPE MATCHES "RelWithDebInfo")
STRING(REPLACE ${CMAKE_SHARED_LIBRARY_SUFFIX} ".pdb" pdb_location ${built_location})
INSTALL(FILES
${pdb_location}
DESTINATION bin
)
ENDIF()
ENDIF(NOT WIN32)
ENDMACRO(CHASSIS_PLUGIN_INSTALL _plugin_name)

73
cmake/Tar.cmake Normal file
View File

@ -0,0 +1,73 @@
# $%BEGINLICENSE%$
# Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
MACRO(TAR_UNPACK _file _wd)
FIND_PROGRAM(GTAR_EXECUTABLE NAMES gtar tar)
FIND_PROGRAM(GZIP_EXECUTABLE NAMES gzip)
MESSAGE(STATUS "gtar: ${GTAR_EXECUTABLE}")
MESSAGE(STATUS "gzip: ${GZIP_EXECUTABLE}")
IF(GTAR_EXECUTABLE AND GZIP_EXECUTABLE)
# On Windows gzip -cd $file | tar xvf - sometimes reports
# "Broken pipe" which causes cmake to assume an error has occured
# In fact, this is harmless, but because of it, this is done in
# two steps
MESSAGE(STATUS "unzipping ${_file} with ${GZIP_EXECUTABLE}")
EXECUTE_PROCESS(COMMAND ${GZIP_EXECUTABLE} "-cd" ${_file}
OUTPUT_FILE "${_file}.tar"
WORKING_DIRECTORY ${_wd}
ERROR_VARIABLE _err)
IF(_err)
MESSAGE(SEND_ERROR "GZIP_UNPACK()-err: ${_err}")
ENDIF(_err)
# untar after successful unzip
MESSAGE(STATUS "untarring ${_file}.tar with ${GTAR_EXECUTABLE}")
EXECUTE_PROCESS(COMMAND ${GTAR_EXECUTABLE} "xvf" "-"
INPUT_FILE "${_file}.tar"
WORKING_DIRECTORY ${_wd}
OUTPUT_VARIABLE _out
ERROR_VARIABLE _err)
IF(_err)
MESSAGE(SEND_ERROR "TAR_UNPACK()-err: ${_err}")
ENDIF(_err)
IF(_out)
MESSAGE(DEBUG "TAR_UNPACK()-out: ${_out}")
ENDIF(_out)
FILE(REMOVE "${_file}.tar")
ELSE(GTAR_EXECUTABLE AND GZIP_EXECUTABLE)
MESSAGE(STATUS "gnutar not found")
ENDIF(GTAR_EXECUTABLE AND GZIP_EXECUTABLE)
ENDMACRO(TAR_UNPACK _file _wd)
MACRO(ZIP_UNPACK _file _wd)
FIND_PROGRAM(7Z_EXECUTABLE NAMES 7z 7za)
MESSAGE(STATUS "7zip: ${7Z_EXECUTABLE}")
IF(7Z_EXECUTABLE)
EXECUTE_PROCESS(COMMAND ${7Z_EXECUTABLE} x "-o${_wd}" "${_file}")
ELSE(7Z_EXECUTABLE)
MESSAGE(STATUS "7zip not found")
ENDIF(7Z_EXECUTABLE)
ENDMACRO(ZIP_UNPACK _file _wd)

48
config.h.cmake Normal file
View File

@ -0,0 +1,48 @@
#cmakedefine HAVE_EVENT_BASE_NEW
#cmakedefine HAVE_EVENT_BASE_FREE
#cmakedefine HAVE_EVENT_H
#cmakedefine HAVE_INTTYPES_H
#cmakedefine HAVE_MGMAPI_H
#cmakedefine HAVE_NETINET_IN_H
#cmakedefine HAVE_NET_IF_H
#cmakedefine HAVE_NET_IF_DL_H
#cmakedefine HAVE_PWD_H
#cmakedefine HAVE_SIGNAL_H
#cmakedefine HAVE_STDDEF_H
#cmakedefine HAVE_STDINT_H
#cmakedefine HAVE_STDLIB_H
#cmakedefine HAVE_SYSLOG_H
#cmakedefine HAVE_SYS_IOCTL_H
#cmakedefine HAVE_SYS_FILIO_H
#cmakedefine HAVE_SYS_PARAM_H
#cmakedefine HAVE_SYS_RESOURCE_H
#cmakedefine HAVE_SYS_SOCKET_H
#cmakedefine HAVE_SYS_SOCKIO_H
#cmakedefine HAVE_SYS_TIME_H
#cmakedefine HAVE_SYS_TYPES_H
#cmakedefine HAVE_SYS_UIO_H
#cmakedefine HAVE_SYS_UN_H
#cmakedefine HAVE_TIME_H
#cmakedefine HAVE_UNISTD_H
#cmakedefine HAVE_SYSLOG_H
#cmakedefine HAVE_INET_NTOP
#cmakedefine HAVE_GETCWD
#cmakedefine HAVE_SIGNAL
#cmakedefine HAVE_SRANDOM
#cmakedefine HAVE_STRERROR
#cmakedefine HAVE_WRITEV
#cmakedefine HAVE_SIGACTION
#cmakedefine HAVE_SOCKLEN_T
#cmakedefine HAVE_ULONG
#cmakedefine HAVE_GTHREAD
#cmakedefine HAVE_GTHREAD_H
#cmakedefine HAVE_OPENSSL
#define SIZEOF_RLIM_T @SIZEOF_RLIM_T@
#cmakedefine NETWORK_DEBUG_TRACE_IO 1
#cmakedefine NETWORK_DEBUG_TRACE_STATE_CHANGES 1
#cmakedefine USE_GLIB_DEBUG_LOG 1
#cmakedefine NETWORK_DEBUG_TRACE_EVENT 1

1
deps/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1 @@
# INCLUDE(deps/libevent.cmake)

25
deps/libevent.cmake vendored Normal file
View File

@ -0,0 +1,25 @@
IF(WIN32)
SET(LIBEVENT_SOURCE_DIR "${CMAKE_SOURCE_DIR}/deps/libevent-1.4.11-stable")
IF(EXISTS ${LIBEVENT_SOURCE_DIR})
## write CMake file for libevent
CONFIGURE_FILE(deps/libevent.config.h.cmake ${LIBEVENT_SOURCE_DIR}/config.h.cmake COPYONLY)
CONFIGURE_FILE(deps/libevent.event-config.h.cmake ${LIBEVENT_SOURCE_DIR}/event-config.h.cmake COPYONLY)
CONFIGURE_FILE(deps/libevent.CMakeLists.txt ${LIBEVENT_SOURCE_DIR}/CMakeLists.txt COPYONLY)
CONFIGURE_FILE(deps/libevent.def ${LIBEVENT_SOURCE_DIR}/libevent.def COPYONLY)
## CONFIGURE_FILE(deps/libevent.event.h.cmake ${LIBEVENT_SOURCE_DIR}/event.h COPYONLY)
## CONFIGURE_FILE(deps/libevent.evutil.h.cmake ${LIBEVENT_SOURCE_DIR}/evutil.h COPYONLY)
ADD_SUBDIRECTORY(${LIBEVENT_SOURCE_DIR} build-libevent)
SET(EVENT_INCLUDE_DIRS ${LIBEVENT_SOURCE_DIR} CACHE INTERNAL "")
IF(EXISTS ${CMAKE_BINARY_DIR}/build-libevent/${CMAKE_BUILD_TYPE}/)
SET(EVENT_LIBRARY_DIRS ${CMAKE_BINARY_DIR}/build-libevent/${CMAKE_BUILD_TYPE} CACHE INTERNAL "")
ELSE(EXISTS ${CMAKE_BINARY_DIR}/build-libevent/${CMAKE_BUILD_TYPE}/)
SET(EVENT_LIBRARY_DIRS ${CMAKE_BINARY_DIR}/build-libevent CACHE INTERNAL "")
ENDIF(EXISTS ${CMAKE_BINARY_DIR}/build-libevent/${CMAKE_BUILD_TYPE}/)
SET(EVENT_LIBRARIES event CACHE INTERNAL "")
ELSE(EXISTS ${LIBEVENT_SOURCE_DIR})
MESSAGE(FATAL_ERROR "Could not find dependency libevent-1.4.11-stable in ${LIBEVENT_SOURCE_DIR}")
ENDIF(EXISTS ${LIBEVENT_SOURCE_DIR})
ENDIF(WIN32)

35
deps/libevent.config.h.cmake vendored Normal file
View File

@ -0,0 +1,35 @@
#cmakedefine HAVE_STDINT_H
#cmakedefine HAVE_SYS_TIME_H
#cmakedefine HAVE_SYS_TREE_H
#cmakedefine HAVE_FCNTL_H
#cmakedefine HAVE_STDARG_H
#cmakedefine HAVE_INTTYPES_H
#cmakedefine HAVE_STDLIB_H
#cmakedefine HAVE_POLL_H
#cmakedefine HAVE_SIGNAL_H
#cmakedefine HAVE_UNISTD_H
#cmakedefine HAVE_SYS_DEVPOLL_H
#cmakedefine HAVE_PORT_H
#cmakedefine HAVE_KQUEUE_H
#cmakedefine HAVE_POLL
#cmakedefine HAVE_SELECT
#cmakedefine HAVE_GETTIMEOFDAY
#cmakedefine HAVE_VASPRINTF
#cmakedefine HAVE_FCNTL
#cmakedefine HAVE_CLOCK_GETTIME
#cmakedefine HAVE_STRTOK_R
#cmakedefine HAVE_STRSEP
#cmakedefine HAVE_GETADDRINFO
#cmakedefine HAVE_GETNAMEINFO
#cmakedefine HAVE_STRLCPY
#cmakedefine HAVE_INET_NTOP
#cmakedefine HAVE_SIGTIMEDWAIT
#cmakedefine HAVE_EPOLL
#cmakedefine HAVE_KQUEUE
#cmakedefine HAVE_TIMERADD
#cmakedefine HAVE_TIMERSUB
#cmakedefine HAVE_TIMERISSET
#cmakedefine HAVE_TIMERCLEAR
#define VERSION "libevent-1.4.11-stable"

35
deps/libevent.event-config.h.cmake vendored Normal file
View File

@ -0,0 +1,35 @@
#cmakedefine _EVENT_HAVE_STDINT_H
#cmakedefine _EVENT_HAVE_SYS_TIME_H
#cmakedefine _EVENT_HAVE_SYS_TREE_H
#cmakedefine _EVENT_HAVE_FCNTL_H
#cmakedefine _EVENT_HAVE_STDARG_H
#cmakedefine _EVENT_HAVE_INTTYPES_H
#cmakedefine _EVENT_HAVE_STDLIB_H
#cmakedefine _EVENT_HAVE_POLL_H
#cmakedefine _EVENT_HAVE_SIGNAL_H
#cmakedefine _EVENT_HAVE_UNISTD_H
#cmakedefine _EVENT_HAVE_SYS_DEVPOLL_H
#cmakedefine _EVENT_HAVE_PORT_H
#cmakedefine _EVENT_HAVE_KQUEUE_H
#cmakedefine _EVENT_HAVE_POLL
#cmakedefine _EVENT_HAVE_SELECT
#cmakedefine _EVENT_HAVE_GETTIMEOFDAY
#cmakedefine _EVENT_HAVE_VASPRINTF
#cmakedefine _EVENT_HAVE_FCNTL
#cmakedefine _EVENT_HAVE_CLOCK_GETTIME
#cmakedefine _EVENT_HAVE_STRTOK_R
#cmakedefine _EVENT_HAVE_STRSEP
#cmakedefine _EVENT_HAVE_GETADDRINFO
#cmakedefine _EVENT_HAVE_GETNAMEINFO
#cmakedefine _EVENT_HAVE_STRLCPY
#cmakedefine _EVENT_HAVE_INET_NTOP
#cmakedefine _EVENT_HAVE_SIGTIMEDWAIT
#cmakedefine _EVENT_HAVE_EPOLL
#cmakedefine _EVENT_HAVE_KQUEUE
#cmakedefine _EVENT_HAVE_TIMERADD
#cmakedefine _EVENT_HAVE_TIMERSUB
#cmakedefine _EVENT_HAVE_TIMERISSET
#cmakedefine _EVENT_HAVE_TIMERCLEAR
#define _EVENT_VERSION "libevent-1.4.11-stable"

1182
deps/libevent.event.h.cmake vendored Normal file

File diff suppressed because it is too large Load Diff

191
deps/libevent.evutil.h.cmake vendored Normal file
View File

@ -0,0 +1,191 @@
/*
* Copyright (c) 2007 Niels Provos <provos@citi.umich.edu>
* 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.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 _EVUTIL_H_
#define _EVUTIL_H_
/** @file evutil.h
Common convenience functions for cross-platform portability and
related socket manipulations.
*/
#ifdef __cplusplus
extern "C" {
#endif
#include <event-config.h>
#ifdef _EVENT_HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef _EVENT_HAVE_STDINT_H
#include <stdint.h>
#elif defined(_EVENT_HAVE_INTTYPES_H)
#include <inttypes.h>
#endif
#ifdef _EVENT_HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#include <stdarg.h>
#ifdef _EVENT_HAVE_UINT64_T
#define ev_uint64_t uint64_t
#define ev_int64_t int64_t
#elif defined(WIN32)
#define ev_uint64_t unsigned __int64
#define ev_int64_t signed __int64
#elif _EVENT_SIZEOF_LONG_LONG == 8
#define ev_uint64_t unsigned long long
#define ev_int64_t long long
#elif _EVENT_SIZEOF_LONG == 8
#define ev_uint64_t unsigned long
#define ev_int64_t long
#else
#error "No way to define ev_uint64_t"
#endif
#ifdef _EVENT_HAVE_UINT32_T
#define ev_uint32_t uint32_t
#elif defined(WIN32)
#define ev_uint32_t unsigned int
#elif _EVENT_SIZEOF_LONG == 4
#define ev_uint32_t unsigned long
#elif _EVENT_SIZEOF_INT == 4
#define ev_uint32_t unsigned int
#else
#error "No way to define ev_uint32_t"
#endif
#ifdef _EVENT_HAVE_UINT16_T
#define ev_uint16_t uint16_t
#elif defined(WIN32)
#define ev_uint16_t unsigned short
#elif _EVENT_SIZEOF_INT == 2
#define ev_uint16_t unsigned int
#elif _EVENT_SIZEOF_SHORT == 2
#define ev_uint16_t unsigned short
#else
#error "No way to define ev_uint16_t"
#endif
#ifdef _EVENT_HAVE_UINT8_T
#define ev_uint8_t uint8_t
#else
#define ev_uint8_t unsigned char
#endif
#ifdef WIN32
#ifdef BUILD_EVENT_DLL
#define EVEXPORT __declspec(dllexport)
#else
#define EVEXPORT __declspec(dllimport)
#endif
#endif
EVEXPORT int evutil_socketpair(int d, int type, int protocol, int sv[2]);
int evutil_make_socket_nonblocking(int sock);
#ifdef WIN32
#define EVUTIL_CLOSESOCKET(s) closesocket(s)
#else
#define EVUTIL_CLOSESOCKET(s) close(s)
#endif
#ifdef WIN32
#define EVUTIL_SOCKET_ERROR() WSAGetLastError()
#define EVUTIL_SET_SOCKET_ERROR(errcode) \
do { WSASetLastError(errcode); } while (0)
#else
#define EVUTIL_SOCKET_ERROR() (errno)
#define EVUTIL_SET_SOCKET_ERROR(errcode) \
do { errno = (errcode); } while (0)
#endif
/*
* Manipulation functions for struct timeval
*/
#ifdef _EVENT_HAVE_TIMERADD
#define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp))
#define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp))
#else
#define evutil_timeradd(tvp, uvp, vvp) \
do { \
(vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
(vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
if ((vvp)->tv_usec >= 1000000) { \
(vvp)->tv_sec++; \
(vvp)->tv_usec -= 1000000; \
} \
} while (0)
#define evutil_timersub(tvp, uvp, vvp) \
do { \
(vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
(vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
if ((vvp)->tv_usec < 0) { \
(vvp)->tv_sec--; \
(vvp)->tv_usec += 1000000; \
} \
} while (0)
#endif /* !_EVENT_HAVE_HAVE_TIMERADD */
#ifdef _EVENT_HAVE_TIMERCLEAR
#define evutil_timerclear(tvp) timerclear(tvp)
#else
#define evutil_timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0
#endif
#define evutil_timercmp(tvp, uvp, cmp) \
(((tvp)->tv_sec == (uvp)->tv_sec) ? \
((tvp)->tv_usec cmp (uvp)->tv_usec) : \
((tvp)->tv_sec cmp (uvp)->tv_sec))
#ifdef _EVENT_HAVE_TIMERISSET
#define evutil_timerisset(tvp) timerisset(tvp)
#else
#define evutil_timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
#endif
/* big-int related functions */
ev_int64_t evutil_strtoll(const char *s, char **endptr, int base);
#ifdef _EVENT_HAVE_GETTIMEOFDAY
#define evutil_gettimeofday(tv, tz) gettimeofday((tv), (tz))
#else
int evutil_gettimeofday(struct timeval *tv, struct timezone *tz);
#endif
int evutil_snprintf(char *buf, size_t buflen, const char *format, ...)
#ifdef __GNUC__
__attribute__((format(printf, 3, 4)))
#endif
;
int evutil_vsnprintf(char *buf, size_t buflen, const char *format, va_list ap);
#ifdef __cplusplus
}
#endif
#endif /* _EVUTIL_H_ */

284
doc/Admin.md Normal file
View File

@ -0,0 +1,284 @@
# Admin手册
## 写在前面
**有配置修改均能动态生效,配置更改后请务必修改原始配置文件,以确保下次重启时配置能够保留。**
## 后端配置
### 查看后端
`SELECT * FROM backends`
查看后端信息。
| backend_ndx | address | state | type | uuid | connected_clients |
|------------:|---------------------|-------|------|------|------------------:|
| 1 | 10.120.240.252:3309 | up | rw | NULL | 0 |
| 2 | 10.120.240.254:3310 | up | ro | NULL | 20 |
结果说明:
* backend_ndx: 后端序号,按照添加顺序排列;
* address: 后端地址IP:PORT格式
* state: 后端状态(unknown|up|down|maintaining|delete)
* type: 读写类型(rw|ro)
* uuid: 暂时无用;
* connected_clients: 客户查看后端连接状态端连接的数量(正在处理请求的连接)。
```
状态说明
unknown: 后端初始状态,还未建立连接;
up: 能与后端正常建立连接;
down: 与后端无法联通(如果开启后端状态检测能连通后自动变为UP);
maintaining: 后端正在维护,无法建立连接或自动切换状态(此状态由管理员手动设置);
delete: 后端已被删除,无法再建立连接。
```
### 查看后端连接状态
`SELECT CONN_DETAILS FROM backends`
查看每个用户占用和空闲的后端连接数。
| backend_ndx | username | idle_conns | total_used_conns |
|------------:|---------------|-----------:|-----------------:|
| 1 | lott_quanzi | 2 | 0 |
| 1 | zjhpettest | 11 | 0 |
结果说明:
* backend_ndx: 后端序号;
* username: 用户名;
* idle_conns: 空闲连接数;
* total_used_conns: 正在使用的连接数。
### 添加后端
`ADD MASTER [ip:port]`
添加一个读写类型的后端。
>ADD MASTER 127.0.0.1:3307
`ADD SLAVE [ip:port]`
添加一个只读类型的后端。
>ADD SLAVE 127.0.0.1:3360
`INSERT INTO backends VALUES ("ip:port", "ro|rw")`
添加一个后端,同时指定读写类型。
>INSERT INTO backends values ("127.0.0.1:3306", "rw");
### 删除后端
`REMOVE BACKEND [backend_ndx]`
`DELETE FROM BACKENDS where backend_ndx = [backend_ndx]`
删除一个指定序号的后端。
>remove backend 1
`DELETE FROM BACKENDS where address = '[IP:PORT]'`
删除一个指定地址的后端。
>delete from backends where address = '127.0.0.1:3306'
### 修改后端
`UPDATE BACKENDS set type|state=[value] where address|backend_ndx=[value]`
修改后端类型或状态。
>update backends set type="rw" where address="127.0.0.1:3306"
>update backends set state="up" where backend_ndx=1
## 基本配置
### 获取基本配置
`config get common`
能获取的配置包括:
* check_slave_delay: 是否启用从库延迟检查0为不检查
* slave_delay_down: 从库延迟大于该时间时设置从库状态为DOWN单位
* slave_delay_recover: 从库延迟小于等于该时间时设置从库状态为UP单位
### 修改基本配置
`config set common.[option] = [value]`
例如
>config set common.slave_delay_down = 3
## 连接池配置
### 获取连接池配置
`config get pool`
能获取的配置包括:
* max_pool_size: 连接池最大连接数,超出后将不再维持超出部分的连接
* default_pool_size: 默认连接数量,连接数达到此值之前会一直新建连接
* master_preferred: 设置为1时仅访问主库(读写)
* max_resp_len: 允许每个后端返回数据的最大值
### 修改连接池配置
`config set pool.[option] = [value]`
修改某项配置
>config set pool.max_pool_size = 200
## 查看连接信息
### 查看当前连接的详细信息
`SHOW CONNECTIONLIST`
将当前全部连接的详细内容按表格显示出来。
| User | Host | Server | db | Command | Time | Trans | State | Info |
|---------------|----------------------|--------|------------------|---------|------|-------|------------|------|
| ddz_develop | 10.120.241.198:53728 | NULL | ddz_tower_test | Sleep | 0 | N | READ_QUERY | NULL |
| mail | 10.120.241.183:37960 | NULL | tech_mail_new_db | Sleep | 0 | N | READ_QUERY | NULL |
结果说明:
* User: 用户名;
* Host: 客户端的IP和端口;
* Server: 后端地址;
* db: 数据库名称;
* Command: 执行的sql"Sleep"代表当前空闲;
* Time: 已执行的时间;
* Trans: 是否在事务中;
* State: 连接当前的状态,"READ_QUERY"代表在等待获取命令;
* Info: 暂未知。
## 用户/密码管理
### 密码查询
`SELECT * FROM user_pwd WHERE user=[username]`
查询某个用户的后端密码。
**注意由于密码是非明文的,仅能显示字节码。**
>select * from user_pwd where user="root";
`SELECT * FROM app_user_pwd WHERE user=[username]`
查询某个用户连接proxy的密码同样是非明文。
>select * from app_user_pwd where user="test";
### 密码添加/修改
`UPDATE user_pwd SET password=[password1] WHERE user=[username]`
添加或修改特定用户的后端密码(如果该用户不存在则添加,已存在则覆盖)。
**需要Flush config生效**
>update user_pwd set password="123456" where user="test";
>flush config;
`UPDATE app_user_pwd SET password=[password1] WHERE user=[username]`
添加或修改特定用户连接Proxy的密码(如果该用户不存在则添加,已存在则覆盖)。
**需要Flush config生效**
>update app_user_pwd set password="123456" where user="root";
>flush config;
### 密码删除
`DELETE FROM user_pwd where user=[username]`
删除特定用户的后端密码。
**需要Flush config生效**
>delete from user_pwd where user="root"
>flush config;
`DELETE FROM app_user_pwd where user=[username]`
删除特定用户连接Proxy的密码。
**需要Flush config生效**
>delete from app_user_pwd where user="root"
>flush config;
### 查看未提交的修改
`SHOW CHANGES`
查看已修改但未flush的变更。
### 清除未提交的修改
`CLEAR CHANGES`
清除已修改但未flush的变更。
## IP许可
### 查看IP许可
`SHOW ALLOW_IP admin|proxy`
查看Admin或者Proxy的IP许可。
若列表为空,则代表没有任何限制。
### 增加IP许可
`ADD ALLOW_IP admin|proxy [[user@]IP]`
增加一个IP许可。(不要加引号)
* Admin: 仅能配置IP不能限制用户(Admin有效用户只有一个)
* Proxy: 仅配置IP代表允许该IP来源所有用户的访问配置User@IP代表允许该IP来源的特定用户访问。
>add allow_ip admin 127.0.0.1
>add allow_ip proxy test@127.0.0.1
### 删除IP许可
`DELETE ALLOW_IP admin|proxy [[user@]IP]`
删除一个IP许可。(不要加引号)
>delete allow_ip admin 127.0.0.1
>delete allow_ip proxy test@127.0.0.1
## 远程管理
### 重载分库配置
`reload shard`
需要"remote-config = true"和"disable-threads = false"启动选项。
从远端配置库中重载Shard配置。
### 保存配置到本地文件
`SAVE SETTINGS [FILE]`
保存当前配置到指定路径的本地文件中。
>save settings /tmp/proxy.cnf

369
doc/Configuration.md Normal file
View File

@ -0,0 +1,369 @@
# 配置文档
## 常规配置
### daemon
Default: false
通过守护进程启动。
> daemon = true
### user
Default: root
启动进程的用户
> user = cetus
### basedir
基础路径,其它配置可以以此为基准配置相对路径。(必须是绝对路径)
> basedir = /usr/lib/cetus
### pid-file
`必要`
PID文件路径
> pid-file = /var/log/cetus.pid
### log-file
`必要`
日志文件路径
> log-file = /var/log/cetus.log
### log-level
可选值: debug | info | message | warning | error | critical(default)
日志级别
> log-level = info
### plugins
`可多项`
加载模块名称。
> plugins = admin,proxy
### plugin-dir
库文件路径
> plugin-dir = /usr/lib/cetus/plugins
## Proxy配置
### proxy-address
Default: :4040
Proxy监听的IP和端口
> proxy-address = 127.0.0.1:4440
### proxy-allow-ip
`可在Admin模块中动态更改`
Proxy允许访问的"用户@IP"
参数未设置时,没有限制;"User@IP"限制特定的用户和IP组合访问"IP"允许该IP的所有用户访问
> proxy-allow-ip = root@127.0.0.1,10.238.7.6
### proxy-backend-addresses
Default: 127.0.0.1:3306
`可多项`
读写后端(主库)的IP和端口
> proxy-backend-addresses = 10.120.12.12:3306
若是分库模式需要同时指定group
> proxy-backend-addresses = 10.120.12.12:3306@data1
### proxy-read-only-backend-addresses
`可多项`
只读后端(从库)的IP和端口
> proxy-read-only-backend-addresses = 10.120.12.13:3307
若是分库模式需要同时指定group
> proxy-read-only-backend-addresses = 10.120.12.13:3307@data1
### default-username
默认用户名在Proxy启动时自动创建连接使用的用户名在*user-pwd*中需要有对应的配置
> default-username = default_user
### default-db
默认数据库当连接未指定db时使用的默认数据库名称
> default-db = test
### default-pool-size
Default: 100
当前连接数不足此值时,会自动创建连接
> default-pool-size = 200
### max-pool-size
Default: *default-pool-size * 2*
连接池的最大连接数,超过此数目的连接不会放入连接池
> default-pool-size = 300
### max-resp-size
Default: 10485760 (10MB)
每个后端返回结果集的最大数量
> max-resp-size = 1024
### user-pwd
`可在Admin模块动态更改`
Proxy连接后端时使用的用户名、密码。用户名应与*app-user-pwd*中一一对应
> user-pwd = user1@password1,user2@password2
### app-user-pwd
`可在Admin模块动态更改`
客户端连接Proxy时使用的用户名、密码。用户名应与*user-pwd*中一一对应
> app-user-pwd = user1@apppass1,user2@apppass2
### crypt-pwd
Default: false
使用加密格式保存后端密码(user-pwd)
> crypt-pwd = true
### crypt-app-pwd
Default: false
使用加密格式保存客户端访问Proxy的密码(app-user-pwd)
> crypt-app-pwd = true
### disable-sharding-mode
Default: false
不启用分库功能(默认启用),当只需要读写分离功能时设置为*true*
> disable-sharding-mode = true
### disable-auto-connect
Default: false
禁用自动创建连接,连接将在新请求到来时创建
> disable-auto-connect = false
## Admin配置
### admin-address
Default: :4041
管理模块的IP和端口
> admin-address = 127.0.0.1:4441
### admin-allow-ip
`可在Admin模块中动态更改`
参数未设置时不作限制仅能限制IP不区分用户
> admin-allow-ip = 127.0.0.1,10.238.7.6
### admin-lua-script
`必要`
管理模块对应的lua脚本路径
> admin-lua-script = /usr/lib/cetus/lua/admin.lua
### admin-username
`必要`
管理模块的用户名
> admin-username = admin
### admin-password
`必要`
管理模块的密码明文
> admin-password = admin_pass
## 远端配置中心
目前分库配置仅能通过配置中心获取因此要采用分库模式必须配置远端db
### config-remote
Default: false
是否启用远端配置中心
> config-remote = true
### config-host
Default: 127.0.0.1
配置中心host
> config-host = 127.0.0.1
### config-port
Default: 3306
配置中心端口
> config-port = 3310
### config-user
Default: root
配置中心用户
> config-user = test_user
### config-pass
配置中心密码
> config-pass = password
### config-db
Default: proxy_management
配置中心db名称
> config-db = proxy_management
### config-apply-interval
Default: 0 (seconds)
与配置中心同步配置的时间间隔。
若设置为*0*则配置更新不自动生效仅能手动生效且检测间隔为3秒。
> config-apply-interval = 0
### proxy-id
Proxy的标识在配置中心用来区分不同proxy的配置
> proxy-id = 001
## 辅助线程配置
### disable-threads
Default: false
禁用辅助线程,包括: 配置变更检测、后端存活检测和只读库延迟检测等
> disable-threads = true
### connect-timeout
Default: 2(seconds)
检测线程连接后端mysql时的超时时间(秒)
> connect-timeout = 10
### check-slave-delay
Default: false
是否检查从库延迟,需要配置`proxy-id`选项,否则不会生效
> check-slave-delay = true
### slave-delay-down
Default: 60 (seconds)
从库延迟超过该秒状态将被设置为DOWN
> slave-delay-down = 120
### slave-delay-recover
Default: *slave-delay-down / 2* (seconds)
从库延迟少于该秒数状态将恢复为UP
> slave-delay-recover = 30
## 其它
### verbose-shutdown
Default: false
程序退出时,记录下退出代码。
> verbose-shutdown = true
### keepalive
Default: false
当Proxy进程意外终止会自动启动一个新进程
> keepalive = true
### max-open-files
Default: 根据操作系统
最大打开的文件数目(ulimit -n)
> max-open-files = 10240

30
doc/Makefile.am Normal file
View File

@ -0,0 +1,30 @@
SUBDIRS=chapter
EXTRA_DIST = \
lua-classes.dot \
architecture.dot \
architecture-overview.dot \
architecture.txt \
core.txt \
chassis.txt \
plugins.txt \
protocol.txt \
tests.txt \
scripting.txt \
lifecycle.msc
clean-local:
rm -f *.html
html-local: book.html protocol.html scripting.html
## we use http://docutils.sourceforge.net/rst.html to generate the docs
book.html: book.txt chapter/scripting.txt chapter/protocol.txt
${RST2HTML} $< $@
protocol.html: protocol.txt chapter/protocol.txt
${RST2HTML} $< $@
scripting.html: scripting.txt chapter/scripting.txt
${RST2HTML} $< $@

21
doc/proxy.conf.example Normal file
View File

@ -0,0 +1,21 @@
[cetus]
# Loaded Plugins
plugins=proxy,admin
# Proxy Configuration
proxy-address=proxy-ip:proxy-port
proxy-backend-addresses=rw-ip:rw-port
proxy-read-only-backend-addresses=ro-ip:ro-port
# Admin Configuration
admin-address=admin-ip:admin-port
admin-username=admin
admin-password=admin
# Backend Configuration
default-db=test
default-username=dbtest
# File and Log Configuration
log-file=cetus.log
log-level=debug

20
doc/shard.conf.example Normal file
View File

@ -0,0 +1,20 @@
[cetus]
# Loaded Plugins
plugins=shard,admin
# Proxy Configuration
proxy-address=proxy-ip:proxy-port
proxy-backend-addresses=ip1:port1@data1,ip2:port2@data2,ip3:port3@data3,ip4:port4@data4
# Admin Configuration
admin-address=admin-ip:admin-port
admin-username=admin
admin-password=admin
# Backend Configuration
default-db=test
default-username=dbtest
# Log Configuration
log-file=cetus.log
log-level=debug

30
doc/sharding.json.example Normal file
View File

@ -0,0 +1,30 @@
{
"vdb": [
{
"id": 1,
"type": "int",
"method": "hash",
"num": 8,
"partitions": {"data1": [0,1], "data2": [2,3], "data3": [4,5], "data4": [6,7]}
},
{
"id": 2,
"type": "int",
"method": "range",
"num": 0,
"partitions": {"data1": 124999, "data2": 249999, "data3": 374999,"data4": 499999}
}
],
"table": [
{"vdb": 1, "db": "employees_hash", "table": "dept_emp", "pkey": "emp_no"},
{"vdb": 1, "db": "employees_hash", "table": "employees", "pkey": "emp_no"},
{"vdb": 1, "db": "employees_hash", "table": "titles", "pkey": "emp_no"},
{"vdb": 2, "db": "employees_range", "table": "dept_emp", "pkey": "emp_no"},
{"vdb": 2, "db": "employees_range", "table": "employees", "pkey": "emp_no"},
{"vdb": 2, "db": "employees_range", "table": "titles", "pkey": "emp_no"}
],
"single_tables": [
{"table": "regioncode", "db": "employees_hash", "group": "data1"},
{"table": "countries", "db": "employees_range", "group": "data2"}
]
}

11
doc/users.json.example Normal file
View File

@ -0,0 +1,11 @@
{
"users": [{
"user": "test1",
"client_pwd": "123",
"server_pwd": "123456"
}, {
"user": "test2",
"client_pwd": "123456",
"server_pwd": "123456"
}]
}

View File

@ -0,0 +1,17 @@
{
"variables": [
{
"name": "sql_mode",
"type": "string-csv",
"allowed_values": ["STRICT_TRANS_TABLES",
"NO_AUTO_CREATE_USER",
"NO_ENGINE_SUBSTITUTION"]
},
{
"name": "connect_timeout",
"type": "string",
"allowed_values": ["*"],
"silent_values": ["10", "100"]
}
]
}

90
lib/CMakeLists.txt Normal file
View File

@ -0,0 +1,90 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
INCLUDE_DIRECTORIES(${GLIB_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBINTL_LIBRARY_DIRS})
INCLUDE_DIRECTORIES(${LUA_INCLUDE_DIRS})
LINK_DIRECTORIES(${LUA_LIBRARY_DIRS})
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}) # for config.h
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR}) ## for the generated header file
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) ## for the packaged header file
#lemon bin
add_executable(lemon ${CETUS_TOOLS_DIR}/lemon.c)
option(SIMPLE_PARSER "use the simple parser")
if(SIMPLE_PARSER)
message("** Using simple parser [-DSIMPLE_PARSER=ON]")
set(LEMON_GRAMMAR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/simple-parser.y")
set(LEMON_RAW_REPORT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/simple-parser.out")
add_custom_command(TARGET lemon PRE_BUILD COMMAND echo "@@@ USING SIMPLE PARSER [-DSIMPLE_PARSER=ON]")
else(SIMPLE_PARSER)
message("** Using full parser [-DSIMPLE_PARSER=OFF(default)]")
set(LEMON_GRAMMAR_FILE "${CMAKE_CURRENT_SOURCE_DIR}/myparser.y")
set(LEMON_RAW_REPORT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/myparser.out")
add_custom_command(TARGET lemon PRE_BUILD COMMAND echo "@@@ USING FULL PARSER [-DSIMPLE_PARSER=OFF]")
endif(SIMPLE_PARSER)
#lemon parse
set(LEMON_PARSER_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/myparser.y.c)
set(LEMON_OUTPUT_HEADER ${CMAKE_CURRENT_BINARY_DIR}/myparser.y.h)
set(LEMON_TEMPATE_FILE ${CETUS_TOOLS_DIR}/lempar.c)
add_custom_command(
OUTPUT ${LEMON_PARSER_OUTPUT} ${SQL_TOKENS_HEADER}
DEPENDS ${LEMON_GRAMMAR_FILE} ${LEMON_TEMPATE_FILE}
${CMAKE_CURRENT_SOURCE_DIR}/extra_tokens.inc
COMMAND lemon -s -T${LEMON_TEMPATE_FILE} ${LEMON_GRAMMAR_FILE} -o${LEMON_PARSER_OUTPUT} -h${LEMON_OUTPUT_HEADER}
COMMAND cat ${CMAKE_CURRENT_SOURCE_DIR}/extra_tokens.inc >> ${LEMON_OUTPUT_HEADER}
)
set_source_files_properties(${LEMON_PARSER_OUTPUT} PROPERTIES GENERATED 1)
set_source_files_properties(${LEMON_OUTPUT_HEADER} PROPERTIES GENERATED 1)
#flex -- depends on parse.h generated by lemon
find_package(FLEX)
set(FLEX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/mylexer.l.c)
FLEX_TARGET(MyLexer mylexer.l ${FLEX_OUTPUT} COMPILE_FLAGS
"--header-file=${CMAKE_CURRENT_BINARY_DIR}/mylexer.l.h")
set_source_files_properties(${FLEX_OUTPUT}
PROPERTIES GENERATED 1)
set(SQL_PARSER_SOURCES
sql-expression.c
sql-operation.c
sql-property.c
sql-context.c
sql-construction.c
sql-filter-variables.c
${FLEX_MyLexer_OUTPUTS}
${LEMON_PARSER_OUTPUT}
)
ADD_LIBRARY(sqlparser SHARED ${SQL_PARSER_SOURCES})
add_dependencies(sqlparser lemon)
CHASSIS_INSTALL_TARGET(sqlparser)

12
lib/extra_tokens.inc Normal file
View File

@ -0,0 +1,12 @@
/* imported from extra_tokens.inc */
#define TK_CUSTOM 250 /*TODO: get the last number automatically */
#define TK_FUNCTION (TK_CUSTOM + 1)
#define TK_UMINUS (TK_CUSTOM + 2)
#define TK_UPLUS (TK_CUSTOM + 3)
#define TK_ISNOT (TK_CUSTOM + 4)
#define TK_PROPERTY (TK_CUSTOM + 8)
#define TK_PROPERTY_START (TK_CUSTOM + 9)
#define TK_PROPERTY_END (TK_CUSTOM + 10)
#define TK_MYSQL_HINT (TK_CUSTOM + 11)

298
lib/mylexer.l Normal file
View File

@ -0,0 +1,298 @@
%{
#include <stdio.h>
#include <glib.h>
#include "myparser.y.h"
%}
%option reentrant
%option noyywrap
%option never-interactive
/*yyin and yyout set to NULL*/
%option nostdinit
/*%option outfile="mylexer.c" header-file="mylexer.h"*/
%option nodefault
%option warn
%option case-insensitive
%x PROPERTY
%x COMMENT
%x MYSQL_HINT
HEX_DIGIT [0-9a-fA-F]
%%
[ \t\r\n]+ /* ignore whitespace */;
";" return TK_SEMI;
"(" return TK_LP ;
")" return TK_RP ;
"," return TK_COMMA ;
"<>" return TK_NE ;
"!=" return TK_NE ;
"=" return TK_EQ ;
"==" return TK_EQ ;
">" return TK_GT ;
"<=" return TK_LE ;
"<" return TK_LT ;
">=" return TK_GE ;
"&" return TK_BITAND ;
"|" return TK_BITOR ;
"~" return TK_BITNOT ;
"<<" return TK_LSHIFT ;
">>" return TK_RSHIFT ;
"+" return TK_PLUS ;
"-" return TK_MINUS ;
"*" return TK_STAR ;
"/" return TK_SLASH ;
"%" return TK_REM ; /*REMAIN */
"." return TK_DOT ;
"?" return TK_VARIABLE ;
"TABLE" return TK_TABLE ;
"CREATE" return TK_CREATE ;
"IF" return TK_IF ;
"NOT" return TK_NOT ;
"!" return TK_NOT;
"EXISTS" return TK_EXISTS ;
"TEMP" return TK_TEMP ;
"AS" return TK_AS ;
"WITHOUT" return TK_WITHOUT ;
"OR" return TK_OR ;
"||" return TK_OR;
"AND" return TK_AND ;
"&&" return TK_AND;
"IS" return TK_IS ;
"MATCH" return TK_MATCH ;
"LIKE" return TK_LIKE_KW ;
"REGEXP" return TK_LIKE_KW;
"BETWEEN" return TK_BETWEEN ;
"IN" return TK_IN ;
/*"ISNULL" return TK_ISNULL ;*/
/*"NOTNULL" return TK_NOTNULL ;*/
/*"||" return TK_CONCAT ;*/ /*for ansi sql_mode*/
"COLLATE" return TK_COLLATE ;
"ABORT" return TK_ABORT ;
"ACTION" return TK_ACTION ;
"AFTER" return TK_AFTER ;
"ANALYZE" return TK_ANALYZE ;
"ASC" return TK_ASC ;
"ATTACH" return TK_ATTACH ;
"BEFORE" return TK_BEFORE ;
"BEGIN" return TK_BEGIN;
"BY" return TK_BY ;
"CASCADE" return TK_CASCADE ;
"CAST" return TK_CAST ;
"COLUMN" return TK_COLUMNKW ;
"CONFLICT" return TK_CONFLICT ;
"DATABASE" return TK_DATABASE ;
"SCHEMA" return TK_SCHEMA;
"DESC" return TK_DESC ;
"DETACH" return TK_DETACH ;
"EACH" return TK_EACH ;
"END" return TK_END ;
"FAIL" return TK_FAIL ;
"FOR" return TK_FOR ;
"IGNORE" return TK_IGNORE ;
"INITIALLY" return TK_INITIALLY ;
"INSTEAD" return TK_INSTEAD ;
"NO" return TK_NO ;
"PLAN" return TK_PLAN ;
"QUERY" return TK_QUERY ;
"KEY" return TK_KEY ;
"OF" return TK_OF ;
"TO" return TK_TO;
"OFFSET" return TK_OFFSET ;
"PRAGMA" return TK_PRAGMA ;
"RAISE" return TK_RAISE ;
"RECURSIVE" return TK_RECURSIVE ;
"RELEASE" return TK_RELEASE ;
"REPLACE" return TK_REPLACE ;
"RESTRICT" return TK_RESTRICT ;
"ROW" return TK_ROW ;
"TRANSACTION" return TK_TRANSACTION;
"START" return TK_START;
"COMMIT" return TK_COMMIT;
"ROLLBACK" return TK_ROLLBACK ;
"SAVEPOINT" return TK_SAVEPOINT ;
"TRIGGER" return TK_TRIGGER ;
"VACUUM" return TK_VACUUM ;
"VIEW" return TK_VIEW ;
"VIRTUAL" return TK_VIRTUAL ;
"WITH" return TK_WITH ;
"INDEX" return TK_INDEX ;
"RENAME" return TK_RENAME ;
/*
"DATE" return TK_CTIME_KW ;
"TIME" return TK_CTIME_KW ;
"NOW" return TK_CTIME_KW ;
"TIMESTAMP" return TK_CTIME_KW ;
"TIMEDIFF" return TK_CTIME_KW ;
*/
"LEFT" return TK_JOIN_KW;
"RIGHT" return TK_JOIN_KW;
"INNER" return TK_JOIN_KW;
"OUTER" return TK_JOIN_KW;
"FULL" return TK_JOIN_KW;
"ANY" return TK_ANY ;
"CONSTRAINT" return TK_CONSTRAINT ;
"DEFAULT" return TK_DEFAULT ;
"CHECK" return TK_CHECK ;
"AUTO_INCREMENT" return TK_AUTO_INCREMENT ;
"PRIMARY" return TK_PRIMARY ;
"UNIQUE" return TK_UNIQUE ;
"FOREIGN" return TK_FOREIGN ;
"DROP" return TK_DROP ;
"SELECT" return TK_SELECT ;
"DISTINCT" return TK_DISTINCT ;
"FROM" return TK_FROM ;
"JOIN" return TK_JOIN ;
"ON" return TK_ON ;
"USING" return TK_USING ;
"ORDER" return TK_ORDER ;
"GROUP" return TK_GROUP ;
"HAVING" return TK_HAVING ;
"LIMIT" return TK_LIMIT ;
"DELETE" return TK_DELETE ;
"WHERE" return TK_WHERE ;
"UPDATE" return TK_UPDATE ;
"SET" return TK_SET ;
"INTO" return TK_INTO ;
"VALUES" return TK_VALUES ;
"INSERT" return TK_INSERT ;
"NULL" return TK_NULL ;
"BLOB" return TK_BLOB ;
"CASE" return TK_CASE ;
"WHEN" return TK_WHEN ;
"THEN" return TK_THEN ;
"ELSE" return TK_ELSE ;
"EXPLAIN" return TK_EXPLAIN;
"DESCRIBE" return TK_DESCRIBE;
"SHOW" return TK_SHOW;
"SQL_CALC_FOUND_ROWS" return TK_SQL_CALC_FOUND_ROWS;
"NAMES" return TK_NAMES;
"USE" return TK_USE;
"CALL" return TK_CALL;
"CHARACTER" return TK_CHARACTER;
"CHARSET" return TK_CHARSET;
"SESSION" return TK_SESSION;
"GLOBAL" return TK_GLOBAL;
"KILL" return TK_KILL;
"READ" return TK_READ;
"WRITE" return TK_WRITE;
"ONLY" return TK_ONLY;
"UNION" return TK_UNION;
"ALL" return TK_ALL;
"DUPLICATE" return TK_DUPLICATE;
"INTERVAL" return TK_INTERVAL;
"DAY" return TK_TIME_UNIT;
"MONTH" return TK_TIME_UNIT;
"YEAR" return TK_TIME_UNIT;
"HOUR" return TK_TIME_UNIT;
"MINUTE" return TK_TIME_UNIT;
"SECOND" return TK_TIME_UNIT;
"SHARD_EXPLAIN" return TK_SHARD_EXPLAIN;
"ALTER" return TK_ALTER;
"FORCE" return TK_FORCE;
"COLUMNS" return TK_COLUMNS;
"FIELDS" return TK_FIELDS;
"COMMENT" return TK_COMMENT_KW;
"LOCK" return TK_LOCK;/*R*/
"SHARE" return TK_SHARE;
"MODE" return TK_MODE;
"UNLOCK" return TK_UNLOCK;/*R*/
"TABLES" return TK_TABLES;
"LOCAL" return TK_LOCAL;
"LOW_PRIORITY" return TK_LOW_PRIORITY;/*R*/
"ISOLATION" return TK_ISOLATION;
"LEVEL" return TK_LEVEL;
"COMMITTED" return TK_COMMITTED;
"UNCOMMITTED" return TK_UNCOMMITTED;
"SERIALIZABLE" return TK_SERIALIZABLE;
"REPEATABLE" return TK_REPEATABLE;
"TRIM" return TK_TRIM;
"BOTH" return TK_TRIM_SPEC;
"LEADING" return TK_TRIM_SPEC;
"TRAILING" return TK_TRIM_SPEC;
"POSITION" return TK_POSITION;
"TRUNCATE" return TK_TRUNCATE;
"XA" return TK_XA;
"RECOVER" return TK_RECOVER;
"CURRENT_DATE" return TK_CURRENT_DATE;
"SIGNED" return TK_SIGNED;
"UNSIGNED" return TK_UNSIGNED;
"DECIMAL" return TK_DECIMAL;
"BINARY" return TK_BINARY;
"NCHAR" return TK_NCHAR;
"INT" return TK_INT_SYM;
"ESCAPE" return TK_ESCAPE;
"CETUS_SEQUENCE" return TK_CETUS_SEQUENCE;
"CETUS_VERSION" return TK_CETUS_VERSION;
"WARNINGS" return TK_WARNINGS;
[0-9]+ return TK_INTEGER; /*sign symbol is handled in parser*/
0[xX]{HEX_DIGIT}+ return TK_INTEGER;
[xX]'({HEX_DIGIT}{HEX_DIGIT})+' return TK_HEX_NUM; /* X'even num of digit' */
0b[01]+ return TK_BIN_NUM;
[bB]'[01]+' return TK_BIN_NUM;
[0-9]*\.[0-9]+([eE][-+]?[0-9]+)? return TK_FLOAT;
[a-zA-Z][a-zA-Z0-9_]* return TK_ID;
"@" return TK_AT_SIGN;
'([^'\\]|\\.|'')*' return TK_STRING;
\"([^"\\]|\"\"|\\.)*\" return TK_STRING;
`([^`]|``)*` return TK_ID;
"/*#" { BEGIN(PROPERTY); return TK_PROPERTY_START;}
<PROPERTY>[a-zA-Z0-9_.-]+ return TK_ID;
<PROPERTY>"=" return TK_EQ;
<PROPERTY>\"([^"\\]|\"\"|\\.)*\" return TK_STRING;
<PROPERTY>'([^'\\]|\\.|'')*' return TK_STRING;
<PROPERTY>[ \t\n\r,;]+ /* ignore */
<PROPERTY>. { g_warning("### bad char in property comment: %02x\n", yytext[0]);}
<PROPERTY>"*"+"/" { BEGIN(INITIAL); return TK_PROPERTY_END;}
"/*" { BEGIN(COMMENT); }
<COMMENT>[^*]*
<COMMENT>"*"+[^*/]*
<COMMENT>"*"+"/" { BEGIN(INITIAL); }
"/*!" { BEGIN(MYSQL_HINT); }
<MYSQL_HINT>[^*]*
<MYSQL_HINT>"*"+[^*/]*
<MYSQL_HINT>"*"+"/" { BEGIN(INITIAL); return TK_MYSQL_HINT;}
"#".* { /* useless comment */ }
"--"[ \t\n].* { /* useless comment */ }
. {g_warning("### flex: bad input character: %02x\n", yytext[0]);return 0;}
%%
/*hack function to restore user allocated string.
input string will be modified in the process of lexing
the string will be restored in the end of lexing, however,
if we break in the middle of lex process, the input
string will be left dirty, flex does not supply a
function to restore it. we use this function as a hack,
its declaration is in sql-expression.h
*/
void yylex_restore_buffer(void* yyscanner)
{
struct yyguts_t * yyg = (struct yyguts_t*)yyscanner;
if ( YY_CURRENT_BUFFER )
{
/* Flush out information for old buffer. */
*yyg->yy_c_buf_p = yyg->yy_hold_char;
YY_CURRENT_BUFFER_LVALUE->yy_buf_pos = yyg->yy_c_buf_p;
YY_CURRENT_BUFFER_LVALUE->yy_n_chars = yyg->yy_n_chars;
}
}

1299
lib/myparser.y Normal file

File diff suppressed because it is too large Load Diff

1032
lib/simple-parser.y Normal file

File diff suppressed because it is too large Load Diff

407
lib/sql-construction.c Normal file
View File

@ -0,0 +1,407 @@
#include "sql-construction.h"
#include <inttypes.h>
#include "myparser.y.h"
static void sql_expr_traverse(GString *s, sql_expr_t *expr);
static void string_append_quoted(GString *s, const char *p, char quote)
{
g_string_append_c(s, quote);
for (; *p != '\0'; ++p) {
if (*p == quote) { /* escape quote inside string */
g_string_append_c(s, '\\');
}
g_string_append_c(s, *p);
}
g_string_append_c(s, quote);
}
/* sql construction */
static void sql_append_expr(GString *s, sql_expr_t *p)
{
switch (p->op) {
case TK_ID:
g_string_append(s, " ");
g_string_append(s, p->token_text);
break;
case TK_EQ: g_string_append(s, "="); break;
case TK_LT: g_string_append(s, "<"); break;
case TK_GT: g_string_append(s, ">"); break;
case TK_LE: g_string_append(s, "<="); break;
case TK_GE: g_string_append(s, ">="); break;
case TK_NE: g_string_append(s, "<>"); break;
case TK_AND: g_string_append(s, " AND "); break;
case TK_OR: g_string_append(s, " OR "); break;
case TK_DOT:
g_string_append(s, " ");
g_string_append(s, p->left->token_text);
g_string_append(s, ".");
g_string_append(s, p->right->token_text);
break;
case TK_UPLUS:
case TK_UMINUS:
case TK_INTEGER: {
char valstr[32] = {0};
char *pstr = valstr;
if (p->op == TK_UMINUS) {*pstr='-'; ++pstr;}
sprintf(pstr, "%" PRIu64, p->num_value);
g_string_append(s, valstr);
break;
}
case TK_STRING:
if (sql_is_quoted_string(p->token_text)) {/* TODO: dequote all */
g_string_append(s, " ");
g_string_append(s, p->token_text);
} else {
g_string_append_c(s, ' ');
string_append_quoted(s, p->token_text, '\'');
}
break;
case TK_FUNCTION: {
g_string_append(s, " ");
g_string_append(s, p->token_text);
g_string_append(s, "(");
sql_expr_list_t *args = p->list;
if (args) {
int i = 0;
for (i = 0; i < args->len; ++i) {
sql_expr_t *arg = g_ptr_array_index(args, i);
sql_expr_traverse(s, arg);
if (i < args->len-1) {
g_string_append(s, ",");
}
}
}
g_string_append(s, ")");
break;
}
case TK_BETWEEN: {
sql_append_expr(s, p->left);
g_string_append(s, " BETWEEN ");
sql_expr_list_t *args = p->list;
if (args && args->len == 2) {
sql_expr_t *low = g_ptr_array_index(args, 0);
sql_expr_t *high = g_ptr_array_index(args, 1);
sql_expr_traverse(s, low);
g_string_append(s, " AND ");
sql_expr_traverse(s, high);
}
break;
}
case TK_IN: {
sql_append_expr(s, p->left);
g_string_append(s, " IN (");
if (p->list) {
sql_expr_list_t *args = p->list;
int i;
for (i = 0; args && i < args->len; ++i) {
sql_expr_t *arg = g_ptr_array_index(args, i);
sql_append_expr(s, arg);
if (i < args->len-1) {
g_string_append_c(s, ',');
}
}
} else if (p->select) {
GString *sel = sql_construct_select(p->select);
if (sel) {
g_string_append(s, sel->str);
g_string_free(sel, TRUE);
}
}
g_string_append(s, ")");
break;
}
case TK_EXISTS: {
g_string_append(s, " EXISTS (");
GString *sel = sql_construct_select(p->select);
if (sel) {
g_string_append(s, sel->str);
g_string_free(sel, TRUE);
}
g_string_append(s, ")");
break;
}
case TK_LIKE_KW:
if (p->list) {
sql_expr_list_t *args = p->list;
if (args->len > 0) {
sql_expr_t *arg = g_ptr_array_index(args, 0);
sql_expr_traverse(s, arg);
}
g_string_append(s, " LIKE ");
if (args->len > 1) {
sql_expr_t *arg = g_ptr_array_index(args, 1);
sql_append_expr(s, arg);
}
if (args->len > 2) {
sql_expr_t *arg = g_ptr_array_index(args, 2);
g_string_append(s, " ESCAPE ");
sql_append_expr(s, arg);
}
}
break;
case TK_NOT:
g_string_append(s, " NOT(");
sql_expr_traverse(s, p->left);
g_string_append_c(s, ')');
break;
case TK_SELECT: {/* subselect as an expression */
g_string_append(s, "(");
GString *sel = sql_construct_select(p->select);
if (sel) {
g_string_append(s, sel->str);
g_string_free(sel, TRUE);
}
g_string_append(s, ")");
break;
}
case TK_IS: g_string_append(s, " IS ");break;
case TK_ISNOT: g_string_append(s, " IS NOT ");break;
case TK_PLUS: g_string_append_c(s, '+');break;
case TK_MINUS: g_string_append_c(s, '-');break;
default:
g_string_append(s, " ");
g_string_append(s, p->token_text);
}
}
/* these expr will be processed as leaf node */
static gboolean sql_expr_is_leaf_node(sql_expr_t *expr)
{
return (expr->op == TK_DOT && expr->left && expr->right) /* db.table */
|| (expr->op == TK_UMINUS && expr->left) /* -3 */
|| (expr->op == TK_UPLUS && expr->left) /* +4 */
|| (expr->op == TK_BETWEEN)
|| (expr->op == TK_NOT)
|| (expr->op == TK_EXISTS)
|| (expr->op == TK_IN);
}
static void sql_expr_traverse(GString *s, sql_expr_t *expr)
{
if (!expr)
return;
if (sql_expr_is_leaf_node(expr)) {
sql_append_expr(s, expr);
return;
}
if (expr->left) {
gboolean parenth = (expr->op == TK_AND && expr->left->op == TK_OR);
if (parenth)
g_string_append_c(s, '(');
sql_expr_traverse(s, expr->left);
if (parenth)
g_string_append_c(s, ')');
}
sql_append_expr(s, expr);
if (expr->right) {
gboolean parenth = (expr->op == TK_AND && expr->right->op == TK_OR);
if (parenth)
g_string_append_c(s, '(');
sql_expr_traverse(s, expr->right);
if (parenth)
g_string_append_c(s, ')');
}
}
static void sql_construct_join(GString *s, int flag)
{
if (flag == JT_INNER) {
g_string_append(s, " JOIN ");
return;
}
if (flag & JT_INNER) {
g_string_append(s, "INNER ");
}
if (flag & JT_CROSS) {
g_string_append(s, "CROSS ");
}
if (flag & JT_NATURAL) {
g_string_append(s, "NATURAL ");
}
if (flag & JT_LEFT) {
g_string_append(s, "LEFT ");
}
if (flag & JT_RIGHT) {
g_string_append(s, "RIGHT ");
}
if (flag & JT_OUTER) {
g_string_append(s, "OUTER ");
}
g_string_append(s, "JOIN ");
}
static inline void append_sql_expr(GString *s, sql_expr_t *expr)
{
g_string_append_len(s, expr->start, expr->end - expr->start);
}
GString *sql_construct_select(sql_select_t *select)
{
int i = 0;
GString *s = g_string_new(NULL);
g_string_append(s, "SELECT ");
if (select->columns) {
if (select->flags & SF_DISTINCT) {
g_string_append(s, "DISTINCT ");
}
for (i = 0; i < select->columns->len; ++i) {
sql_expr_t *expr = g_ptr_array_index(select->columns, i);
append_sql_expr(s, expr);
if (expr->alias) {
g_string_append(s, " AS ");
g_string_append(s, expr->alias);
}
if (i != select->columns->len - 1)
g_string_append(s, ",");
}
}
if (select->from_src) {
g_string_append(s, " FROM ");
for (i = 0; i < select->from_src->len; ++i) {
sql_src_item_t *src = g_ptr_array_index(select->from_src, i);
if (src->table_name) {
if (src->dbname) {
g_string_append(s, src->dbname);
g_string_append(s, ".");
}
g_string_append(s, src->table_name);
g_string_append(s, " ");
} else if (src->select) {
GString *sub = sql_construct_select(src->select);
g_string_append_c(s, '(');
g_string_append_len(s, sub->str, sub->len);
g_string_append_c(s, ')');
g_string_free(sub, TRUE);
}
if (src->table_alias) {
g_string_append(s, " AS ");
g_string_append(s, src->table_alias);
g_string_append(s, " ");
}
if (src->on_clause) {
g_string_append(s, " ON ");
append_sql_expr(s, src->on_clause);
g_string_append_c(s, ' ');
}
if (src->jointype != 0) {
sql_construct_join(s, src->jointype);
}
}
}
if (select->where_clause) {
g_string_append(s, " WHERE ");
append_sql_expr(s, select->where_clause);
}
if (select->groupby_clause) {
sql_expr_list_t *groupby = select->groupby_clause;
g_string_append(s, " GROUP BY ");
for (i = 0; i < groupby->len; ++i) {
sql_expr_t *expr = g_ptr_array_index(groupby, i);
append_sql_expr(s, expr);
if (i < groupby->len - 1) {
g_string_append(s, ",");
}
}
}
if (select->having_clause) {
g_string_append(s, " HAVING ");
append_sql_expr(s, select->having_clause);
}
if (select->orderby_clause) {
sql_expr_list_t *orderby = select->orderby_clause;
g_string_append(s, " ORDER BY ");
for (i = 0; i < orderby->len; ++i) {
sql_column_t *col = g_ptr_array_index(orderby, i);
/* this might be duped from column, @see sql_modify_orderby
not using append_sql_expr() to reserve possible alias */
sql_expr_t *expr = col->expr;
sql_expr_traverse(s, expr);
if (col->sort_order && col->sort_order == SQL_SO_DESC) {
g_string_append(s, " DESC ");
}
if (i < orderby->len - 1) {
g_string_append(s, ",");
}
}
}
/* don't use append_sql_expr() for LIMIT/OFFSET, the expression has changed */
if (select->limit) {
g_string_append(s, " LIMIT ");
sql_expr_traverse(s, select->limit);
}
if (select->offset) {
g_string_append(s, " OFFSET ");
sql_expr_traverse(s, select->offset);
}
if (select->lock_read) {
g_string_append(s, " FOR UPDATE");
}
return s;
}
/* format as " expr1,expr2,expr3 "*/
void sql_append_expr_list(GString *s, sql_expr_list_t *exprlist)
{
int i = 0;
for (i = 0; i < exprlist->len; ++i) {
sql_expr_t *expr = g_ptr_array_index(exprlist, i);
append_sql_expr(s, expr);
if (i != exprlist->len -1) {
g_string_append_c(s, ',');
}
}
}
void sql_construct_insert(GString *s, sql_insert_t *p)
{
g_string_append(s, "INSERT INTO ");
if (p->table && p->table->len > 0) {
sql_src_item_t *src = g_ptr_array_index(p->table, 0);
if (src->dbname) {
g_string_append(s, src->dbname);
g_string_append_c(s, '.');
}
g_string_append(s, src->table_name);
g_string_append_c(s, ' ');
}
if (p->columns && p->columns->len > 0) {
g_string_append_c(s, '(');
int i = 0;
for (i =0; i < p->columns->len; ++i) {
const char *col = g_ptr_array_index(p->columns, i);
g_string_append(s, col);
if (i != p->columns->len - 1) {
g_string_append_c(s, ',');
}
}
g_string_append(s, ") ");
}
if (p->sel_val) {
if (p->sel_val->from_src) {
/* select as values */
GString *select = sql_construct_select(p->sel_val);
g_string_append(s, select->str);
g_string_free(select, TRUE);
} else {
/* expression values */
g_string_append(s, "VALUES");
sql_select_t *values = p->sel_val;
for (;values; values = values->prior) {
sql_expr_list_t *cols = values->columns;
g_string_append_c(s, '(');
sql_append_expr_list(s, cols);
g_string_append(s, "),");
}
s->str[s->len-1] = ' ';/* no comma at the end */
}
}
}

14
lib/sql-construction.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef _SQL_CONSTRUCTION_
#define _SQL_CONSTRUCTION_
#include "sql-expression.h"
GString *sql_construct_expr(sql_expr_t *expr);
/* TODO: don't alloc inside function */
GString *sql_construct_select(sql_select_t *);
void sql_construct_insert(GString *, sql_insert_t *);
#endif /*_SQL_CONSTRUCTION_*/

224
lib/sql-context.c Normal file
View File

@ -0,0 +1,224 @@
#include "sql-context.h"
#include "mylexer.l.h"
#include "sql-property.h"
void sqlParser(void *yyp,int yymajor, sql_token_t yyminor, sql_context_t *);
void sqlParserFree(void *p,void(*freeProc)(void *));
void *sqlParserAlloc(void *(*mallocProc)(size_t));
void sqlParserTrace(FILE *TraceFILE, char *zTracePrompt);
void yylex_restore_buffer(void *);
void sql_context_init(sql_context_t *p)
{
memset(p, 0, sizeof(sql_context_t));
}
void sql_context_destroy(sql_context_t *p)
{
if (p->sql_statement)
sql_statement_free(p->sql_statement, p->stmt_type);
if (p->message)
g_free(p->message);
if (p->property)
sql_property_free(p->property);
}
void sql_context_reset(sql_context_t *p)
{
sql_context_destroy(p);
sql_context_init(p);
}
void sql_context_append_msg(sql_context_t *p, char *msg)
{
int len = strlen(msg);
int orig_len = 0;
if (p->message == NULL) {
p->message = g_malloc0(len + 1);
} else {
char *orig_msg = p->message;
orig_len = strlen(orig_msg);
p->message = g_malloc0(orig_len + len + 1);
strcpy(p->message, orig_msg);
g_free(orig_msg);
}
strcpy(p->message + orig_len, msg);
}
void sql_context_set_error(sql_context_t *p, int err, char *msg)
{
p->rc = err;
sql_context_append_msg(p, msg);
}
void sql_context_add_stmt(sql_context_t *p,
enum sql_stmt_type_t type, void *clause)
{
if (p->stmt_count == 0) {
p->stmt_type = type;
p->sql_statement = clause;
} else {
if (clause) {
sql_statement_free(clause, type);
}
}
}
gboolean sql_context_has_sharding_property(sql_context_t *p)
{
return p && p->property && (p->property->table || p->property->group);
}
static void parse_token(sql_context_t *context, int code, sql_token_t token,
void *parser, sql_property_parser_t *prop_parser)
{
if (code == TK_PROPERTY_START) {
prop_parser->is_parsing = TRUE;
if (context->property == NULL) {
context->property = g_new0(sql_property_t, 1);
}
return;
} else if (code == TK_PROPERTY_END) {
prop_parser->is_parsing = FALSE;
sql_property_t *prop = context->property;
if (prop->mode == MODE_READWRITE) {
context->rw_flag |= CF_FORCE_MASTER;
} else if (prop->mode == MODE_READONLY) {
context->rw_flag |= CF_FORCE_SLAVE;
}
if (!sql_property_is_valid(context->property)) {
sql_property_free(context->property);
context->property = NULL;
g_message(G_STRLOC ":invalid comment");
sql_context_set_error(context, PARSE_SYNTAX_ERR, "comment error");
}
return;
} else if (code == TK_MYSQL_HINT) {
context->rw_flag |= CF_WRITE;
return;
}
if (prop_parser->is_parsing) {/*# K = V */
int rc = sql_property_parser_parse(prop_parser,
token.z, token.n, context->property);
if (!rc) {
sql_property_free(context->property);
context->property = NULL;
sql_context_set_error(context, PARSE_SYNTAX_ERR, "comment error");
}
} else {
sqlParser(parser, code, token, context);
}
}
#define PARSER_TRACE 0
/* Parse user allocated sql string
sql->str must be terminated with 2 NUL
sql->len is length including the 2 NUL */
void sql_context_parse_len(sql_context_t *context, GString *sql)
{
yyscan_t scanner;
yylex_init(&scanner);
YY_BUFFER_STATE buf_state = yy_scan_buffer(sql->str, sql->len, scanner);
#if PARSER_TRACE
sqlParserTrace(stdout, "---ParserTrace: ");
#endif
void *parser = sqlParserAlloc(malloc);
sql_context_reset(context);
static sql_property_parser_t comment_parser;
sql_property_parser_reset(&comment_parser);
int code;
int last_parsed_token;
sql_token_t token;
while ((code = yylex(scanner)) > 0) { /* 0 on EOF */
token.z = yyget_text(scanner);
token.n = yyget_leng(scanner);
#if PARSER_TRACE
printf("***LexerTrace: code: %d, yytext: %.*s\n", code, token.n, token.z);
printf("***LexerTrace: yytext addr: %p\n", token.z);
#endif
parse_token(context, code, token, parser, &comment_parser);
last_parsed_token = code;
if (context->rc != PARSE_OK) {/* break on PARSE_HEAD, other error */
yylex_restore_buffer(scanner);/* restore the input string */
break;
}
}
if (context->rc == PARSE_OK) {
/* grammar require semicolon as ending token */
if (last_parsed_token != TK_SEMI) {
sqlParser(parser, TK_SEMI, token, context);
}
sqlParser(parser, 0, token, context);
}
sqlParserFree(parser, free);
yy_delete_buffer(buf_state, scanner);
yylex_destroy(scanner);
}
gboolean sql_context_is_autocommit_on(sql_context_t *context)
{
if (context->stmt_type == STMT_SET) {
sql_expr_list_t *set_list = context->sql_statement;
if (set_list && set_list->len > 0) {
sql_expr_t *expr = g_ptr_array_index(set_list, 0);
if (expr->op == TK_EQ && sql_expr_is_id(expr->left, "AUTOCOMMIT")) {
gboolean on;
if (sql_expr_is_boolean(expr->right, &on)) {
return on;
}
}
}
}
return FALSE;
}
gboolean sql_context_is_autocommit_off(sql_context_t *context)
{
if (context->stmt_type == STMT_SET) {
sql_expr_list_t *set_list = context->sql_statement;
if (set_list && set_list->len > 0) {
sql_expr_t *expr = g_ptr_array_index(set_list, 0);
if (expr->op == TK_EQ && sql_expr_is_id(expr->left, "AUTOCOMMIT")) {
gboolean on;
if (sql_expr_is_boolean(expr->right, &on)) {
return on == FALSE;
}
}
}
}
return FALSE;
}
gboolean sql_context_is_single_node_trx(sql_context_t *context)
{
return context && context->property
&& context->property->transaction == TRX_SINGLE_NODE;
}
gboolean sql_context_is_cacheable(sql_context_t *context)
{
if (context->stmt_type != STMT_SELECT)
return FALSE;
if (context->clause_flags & CF_SUBQUERY)
return FALSE;
sql_select_t *select = context->sql_statement;
if (!select->from_src)
return FALSE;
if (select->lock_read)
return FALSE;
int i = 0;
for (i = 0; i < select->columns->len; ++i) { /* only allow aggreate functions */
sql_expr_t *col = g_ptr_array_index(select->columns, i);
if (col->flags & EP_FUNCTION && !(col->flags & EP_AGGREGATE))
return FALSE;
}
return TRUE;
}

71
lib/sql-context.h Normal file
View File

@ -0,0 +1,71 @@
#ifndef SQL_CONTEXT_H
#define SQL_CONTEXT_H
#include "sql-expression.h"
#include "myparser.y.h"
enum sql_parse_state_code_t {
PARSE_NOT_SUPPORT = -3,
PARSE_SYNTAX_ERR = -2,
PARSE_ERROR = -1,
PARSE_OK = 0,
PARSE_HEAD, /* only extract head, leave the tail unparsed */
PARSE_UNRECOGNIZED, /* confused, send to backend for further validation */
};
enum sql_parsing_place_t {
SELECT_OPTION,
SELECT_COLUMN,
SELECT_FROM,
SELECT_WHERE,
};
struct sql_property_t;
typedef struct sql_context_t {
enum sql_parse_state_code_t rc;
char *message;
int explain;
void *user_data;
void *sql_statement;/* opaque statement pointer */
sql_stmt_type_t stmt_type;
short stmt_count;
enum sql_clause_flag_t rw_flag;
enum sql_clause_flag_t clause_flags;
enum sql_expr_flags_t where_flags;
enum sql_parsing_place_t parsing_place;
struct sql_property_t *property;
} sql_context_t;
void sql_context_init(sql_context_t *);
void sql_context_reset(sql_context_t *);
void sql_context_destroy(sql_context_t *);
void sql_context_append_msg(sql_context_t *, char *msg);
void sql_context_set_error(sql_context_t *, int err, char *msg);
void sql_context_add_stmt(sql_context_t *,
enum sql_stmt_type_t, void *);
gboolean sql_context_using_property(sql_context_t *);
gboolean sql_context_has_sharding_property(sql_context_t *p);
void sql_context_parse_len(sql_context_t *, GString *sql);
gboolean sql_context_is_autocommit_on(sql_context_t *);
gboolean sql_context_is_autocommit_off(sql_context_t *);
gboolean sql_context_is_single_node_trx(sql_context_t *);
gboolean sql_context_is_cacheable(sql_context_t *);
#endif /* SQL_CONTEXT_H */

863
lib/sql-expression.c Normal file
View File

@ -0,0 +1,863 @@
#include "sql-expression.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <glib.h>
#include "myparser.y.h"
char *sql_token_dup(sql_token_t token)
{
if (token.n == 0)
return NULL;
char *s = g_malloc0(token.n + 1);
memcpy(s, token.z, token.n);
sql_string_dequote(s);
return s;
}
static int64_t sql_token_to_int(sql_token_t token)
{
/* TODO: HEX */
int64_t value = 0;
int sign = 1;
const char *c = token.z;
int i = 0;
if (*c == '+' || *c == '-' ) {
if (*c == '-' ) sign = -1;
c++;
i++;
}
while (isdigit(*c) && i++ < token.n) {
value *= 10;
value += (int) (*c - '0');
c++;
}
return (value *sign);
}
void sql_string_dequote(char *z)
{
int quote;
int i, j;
if (z == 0) return;
quote = z[0];
switch(quote) {
case '\'': break;
case '"': break;
case '`': break; /* For MySQL compatibility */
case '[': quote = ']'; break; /* For MS SqlServer compatibility */
default: return;
}
for (i = 1, j = 0; z[i]; i++) {
if (z[i] == quote) {
if (z[i+1] == quote) { /* quote escape */
z[j++] = quote;
i++;
} else {
z[j++] = 0;
break;
}
} else if (z[i] == '\\') { /* slash escape */
i++;
z[j++] = z[i];
} else {
z[j++] = z[i];
}
}
}
sql_expr_t *sql_expr_new(int op, const sql_token_t *token)
{
int extra = 0;
if (token && op != TK_INTEGER) {
extra = token->n + 1;
}
sql_expr_t *expr = g_malloc0(sizeof(sql_expr_t) + extra);
if (expr) {
expr->op = op;
if (token) {
if (extra == 0) {
expr->num_value = sql_token_to_int(*token);
} else {
expr->token_text = (char *)&expr[1];
assert(token != NULL);
if (token->n) {
strncpy(expr->token_text, token->z, token->n);
if (op == TK_STRING || op == TK_ID) {
sql_string_dequote(expr->token_text);
}
}
}
expr->start = token->z;
expr->end = &token->z[token->n];
}
expr->height = 1;
expr->var_scope = SCOPE_SESSION;
}
return expr;
}
/**
* Only duplicate the root node
*/
sql_expr_t *sql_expr_dup(const sql_expr_t *p)
{
int extra = 0;
if (p->token_text) {
extra = strlen(p->token_text) + 1;
}
int size = sizeof(sql_expr_t) + extra;
sql_expr_t *expr = g_malloc0(size);
if (expr) {
memcpy(expr, p, size);
if (p->op == TK_DOT) {
expr->left = sql_expr_dup(p->left);
expr->right = sql_expr_dup(p->right);
} else {
expr->left = 0;
expr->right = 0;
}
expr->list = 0;
expr->select = 0;
expr->alias = 0;
}
return expr;
}
static void height_of_expr(sql_expr_t *p, int *height)
{
if (p) {
if (p->height > *height) {
*height = p->height;
}
}
}
static void expr_set_height(sql_expr_t *p)
{
int height = 0;
height_of_expr(p->left, &height);
height_of_expr(p->right, &height);
p->height = height + 1;
}
void sql_expr_attach_subtrees(sql_expr_t *root, sql_expr_t *left, sql_expr_t *right)
{
if (root == NULL) {
sql_expr_free(left);
sql_expr_free(right);
} else {
if (left) {
root->left = left;
root->start = left->start;
}
if (right) {
root->right = right;
root->end = right->end;
}
expr_set_height(root);
}
}
void sql_expr_free(void *p)
{
if (p) {
sql_expr_t *exp = (sql_expr_t *)p;
if (exp->left)
sql_expr_free(exp->left);
if (exp->right)
sql_expr_free(exp->right);
if (exp->list)
g_ptr_array_free(exp->list, TRUE);
if (exp->select)
sql_select_free(exp->select);
if (exp->alias)
g_free(exp->alias);
g_free(exp);
}
}
gboolean sql_expr_get_int(sql_expr_t *p, gint64 *value)
{
gboolean rc = FALSE;
if (p == NULL)
return FALSE;
switch (p->op) {
case TK_INTEGER:
if (value) {
*value = p->num_value;
}
rc = TRUE;
break;
case TK_UMINUS:
rc = sql_expr_get_int(p->left, value);
*value = -(*value);
break;
case TK_UPLUS:
rc = sql_expr_get_int(p->left, value);
break;
default:
rc = FALSE;
}
return rc;
}
gboolean sql_expr_is_boolean(sql_expr_t *p, gboolean *value)
{
gint64 int_val = -1;
if (sql_expr_get_int(p, &int_val)) {
*value = (int_val != 0);
return TRUE;
} else if (sql_expr_is_id(p, "off") || sql_expr_is_id(p, "false")) {
*value = FALSE;
return TRUE;
} else if (sql_expr_is_id(p, "on") || sql_expr_is_id(p, "true")) {
*value = TRUE;
return TRUE;
} else {
return FALSE;
}
}
/**
* @param name NULL or expected token name
* @return
* if name is NULL:
* return whether p is of type TK_ID
* if name not NULL:
* return TRUE only if p is TK_ID and has same name
*/
gboolean sql_expr_is_id(const sql_expr_t *p, const char *name)
{
const char *id = sql_expr_id(p);
if (name == NULL) {
return id != NULL;
} else {
if (id) {
return strcasecmp(id, name) == 0;
}
return FALSE;
}
}
gboolean sql_expr_is_function(const sql_expr_t *p, const char *name)
{
const char *func = NULL;
if (p && p->op == TK_FUNCTION) {
func = p->token_text;
}
if (name == NULL) {
return func != NULL;
} else {
if (func) {
return strcasecmp(func, name) == 0;
}
return FALSE;
}
}
/**
* prefix and suffix is NULLable
* @return
* if prefix or suffix is NULL, return true if the expression is of type:
* TK_DOT
* |--TK_ID
* |--TK_ID
*
* if prefix or suffix not NULL, return ture only if expr is "preifx.suffix"
*/
gboolean sql_expr_is_dotted_name(const sql_expr_t *p, const char *prefix, const char *suffix)
{
if (p && p->op == TK_DOT) {
return sql_expr_is_id(p->left, prefix)
&& sql_expr_is_id(p->right, suffix);
}
return FALSE;
}
gboolean sql_expr_is_field_name(const sql_expr_t *p)
{
if (p) {
if (p->op == TK_ID) {
return TRUE;
} else if (p->op == TK_DOT) { /* qualified name */
if (p->right->op == TK_DOT) { /* db.table.col */
return sql_expr_is_id(p->left,0)
&& sql_expr_is_dotted_name(p->right,0,0);
} else { /* table.col */
return sql_expr_is_dotted_name(p,0,0);
}
}
}
return FALSE;
}
sql_expr_list_t *sql_expr_list_append(sql_expr_list_t *list, sql_expr_t *expr)
{
if (expr == NULL)
return list;
if (list == NULL) {
list = g_ptr_array_new_with_free_func(sql_expr_free);
}
g_ptr_array_add(list, expr);
return list;
}
sql_expr_t *sql_expr_list_find(sql_expr_list_t *list, const char *name)
{
int i = 0;
for (i = 0; i < list->len; ++i) {
sql_expr_t *col = g_ptr_array_index(list, i);
if (sql_expr_is_id(col, name)) {
return col;
}
}
return NULL;
}
sql_expr_t *sql_expr_list_find_fullname(sql_expr_list_t *list, const sql_expr_t *expr)
{
if (sql_expr_is_dotted_name(expr, NULL, NULL)) {
const char *prefix = expr->left->token_text;
const char *suffix = expr->right->token_text;
int i;
for (i = 0; i < list->len; ++i) {
sql_expr_t *col = g_ptr_array_index(list, i);
if (sql_expr_is_dotted_name(col, prefix, suffix)) {
return col;
}
}
}
return NULL;
}
int sql_expr_list_find_aggregate(sql_expr_list_t *list)
{
int i, index = 0;
enum sql_func_type_t type;
for (i = 0; i < list->len; ++i) {
sql_expr_t *p = g_ptr_array_index(list, i);
if (p->op == TK_FUNCTION) {
type = sql_func_type(p->token_text);
if (type != FT_UNKNOWN) {
return 1;
}
}
}
return 0;
}
int sql_expr_list_find_aggregates(sql_expr_list_t *list, GROUP_AGGR *aggr_array)
{
int i, index = 0;
enum sql_func_type_t type;
for (i = 0; i < list->len; ++i) {
sql_expr_t *p = g_ptr_array_index(list, i);
if (p->op == TK_FUNCTION) {
type = sql_func_type(p->token_text);
if (type != FT_UNKNOWN) {
if (index < MAX_AGGR_FUNS) {
aggr_array[index].pos = i;
aggr_array[index].fun_type = type;
index++;
}
}
}
}
return index;
}
void sql_expr_list_free(sql_expr_list_t *list)
{
if (list)
g_ptr_array_free(list, TRUE);
}
enum sql_func_type_t sql_func_type(const char *s)
{
if (strncasecmp(s, "count", 5) == 0) return FT_COUNT;
else if (strncasecmp(s, "sum", 3) == 0) return FT_SUM;
else if (strncasecmp(s, "avg", 3) == 0) return FT_AVG;
else if (strncasecmp(s, "max", 3) == 0) return FT_MAX;
else if (strncasecmp(s, "min", 3) == 0) return FT_MIN;
else return FT_UNKNOWN;
}
sql_column_t *sql_column_new()
{
return g_new0(struct sql_column_t, 1);
}
void sql_column_free(void *p)
{
if (!p)
return;
sql_column_t *col = (sql_column_t *)p;
if (col->expr)
sql_expr_free(col->expr);
if (col->alias)
g_free(col->alias);
if (col->type)
g_free(col->type);
g_free(col);
}
sql_column_list_t *sql_column_list_append(sql_column_list_t *list, sql_column_t *col)
{
if (col == NULL)
return list;
if (list == NULL) {
list = g_ptr_array_new_with_free_func(sql_column_free);
}
g_ptr_array_add(list, col);
return list;
}
void sql_column_list_free(sql_column_list_t *list)
{
if (list)
g_ptr_array_free(list, TRUE);
}
sql_select_t *sql_select_new()
{
sql_select_t *p = g_new0(sql_select_t, 1);
return p;
}
void sql_select_free(sql_select_t *p)
{
if (!p)
return;
if (p->columns) /* The fields of the result */
sql_expr_list_free(p->columns);
if (p->from_src) /* The FROM clause */
sql_src_list_free(p->from_src);
if (p->where_clause) /* The WHERE clause */
sql_expr_free(p->where_clause);
if (p->groupby_clause) /* The GROUP BY clause */
sql_expr_list_free(p->groupby_clause);
if (p->having_clause) /* The HAVING clause */
sql_expr_free(p->having_clause);
if (p->orderby_clause) /* The ORDER BY clause */
sql_expr_list_free(p->orderby_clause);
if (p->prior) /* Prior select in a compound select statement */
sql_select_free(p->prior);
/* sql_select_t *pNext; Next select to the left in a compound */
if (p->limit) /* LIMIT expression. NULL means not used. */
sql_expr_free(p->limit);
if (p->offset) /* OFFSET expression. NULL means not used. */
sql_expr_free(p->offset);
g_free(p);
}
sql_delete_t *sql_delete_new()
{
sql_delete_t *p = g_new0(sql_delete_t, 1);
return p;
}
void sql_delete_free(sql_delete_t *p)
{
if (!p)
return;
if (p->from_src) /* The FROM clause */
sql_src_list_free(p->from_src);
if (p->where_clause) /* The WHERE clause */
sql_expr_free(p->where_clause);
if (p->orderby_clause)
sql_expr_list_free(p->orderby_clause); /* The ORDER BY clause */
if (p->limit) /* LIMIT expression. NULL means not used. */
sql_expr_free(p->limit);
if (p->offset) /* OFFSET expression. NULL means not used. */
sql_expr_free(p->offset);
g_free(p);
}
sql_update_t *sql_update_new()
{
sql_update_t *p = g_new0(sql_update_t, 1);
return p;
}
void sql_update_free(sql_update_t *p)
{
if (!p)
return;
if (p->table)
sql_src_list_free(p->table);
if (p->set_list)
sql_expr_list_free(p->set_list);
if (p->where_clause) /* The WHERE clause */
sql_expr_free(p->where_clause);
if (p->orderby_clause)
sql_expr_list_free(p->orderby_clause); /* The ORDER BY clause */
if (p->limit) /* LIMIT expression. NULL means not used. */
sql_expr_free(p->limit);
if (p->offset) /* OFFSET expression. NULL means not used. */
sql_expr_free(p->offset);
g_free(p);
}
sql_insert_t *sql_insert_new()
{
sql_insert_t *p = g_new0(sql_insert_t, 1);
return p;
}
void sql_insert_free(sql_insert_t *p)
{
if (!p)
return;
if (p->table)
sql_src_list_free(p->table);
if (p->sel_val)
sql_select_free(p->sel_val);
if (p->columns)
sql_id_list_free(p->columns);
g_free(p);
}
void sql_src_item_free(void *p)
{
if (!p)
return;
struct sql_src_item_t *item = (struct sql_src_item_t *)p;
if (item->table_name)
g_free(item->table_name);
if (item->table_alias)
g_free(item->table_alias);
if (item->dbname)
g_free(item->dbname);
if (item->select)
sql_select_free(item->select);
if (item->on_clause)
sql_expr_free(item->on_clause);
if (item->pUsing)
sql_id_list_free(item->pUsing);
if (item->func_arg)
sql_expr_list_free(item->func_arg);
g_free(item);
}
sql_src_list_t *sql_src_list_append(sql_src_list_t *p, sql_token_t *tname,
sql_token_t *dbname, sql_token_t *alias, sql_select_t *subquery,
sql_expr_t *on_clause, sql_id_list_t *using_clause)
{
struct sql_src_item_t *item = g_new0(sql_src_item_t, 1);
if (item) {
item->table_name = tname ? sql_token_dup(*tname) : NULL;
item->table_alias = alias ? sql_token_dup(*alias) : NULL;
item->dbname = dbname ? sql_token_dup(*dbname) : NULL;
item->select = subquery;
item->on_clause = on_clause;
item->pUsing = using_clause;
}
if (!p) {
p = g_ptr_array_new_with_free_func(sql_src_item_free);
}
g_ptr_array_add(p, item);
return p;
}
void sql_src_list_free(sql_src_list_t *p)
{
if (p)
g_ptr_array_free(p, TRUE);
}
sql_id_list_t *sql_id_list_append(sql_id_list_t *p, sql_token_t *id_name)
{
if (!p) {
p = g_ptr_array_new_with_free_func(g_free);
}
if (id_name)
g_ptr_array_add(p, sql_token_dup(*id_name));
return p;
}
void sql_id_list_free(sql_id_list_t *p)
{
if (p)
g_ptr_array_free(p, TRUE);
}
char *sql_get_token_name(int op)
{
static struct token_list_s{
int code;
char *name;
} token_list[] = {
{TK_SEMI ,"TK_SEMI "},
{TK_CREATE ,"TK_CREATE "},
{TK_TABLE ,"TK_TABLE "},
{TK_IF ,"TK_IF "},
{TK_NOT ,"TK_NOT "},
{TK_EXISTS ,"TK_EXISTS "},
{TK_TEMP ,"TK_TEMP "},
{TK_LP ,"TK_LP "},
{TK_RP ,"TK_RP "},
{TK_AS ,"TK_AS "},
{TK_WITHOUT ,"TK_WITHOUT "},
{TK_COMMA ,"TK_COMMA "},
{TK_OR ,"TK_OR "},
{TK_AND ,"TK_AND "},
{TK_IS ,"TK_IS "},
{TK_MATCH ,"TK_MATCH "},
{TK_LIKE_KW ,"TK_LIKE_KW "},
{TK_BETWEEN ,"TK_BETWEEN "},
{TK_IN ,"TK_IN "},
/* {TK_ISNULL ,"TK_ISNULL "},*/
/* {TK_NOTNULL ,"TK_NOTNULL "},*/
{TK_NE ,"TK_NE "},
{TK_EQ ,"TK_EQ "},
{TK_GT ,"TK_GT "},
{TK_LE ,"TK_LE "},
{TK_LT ,"TK_LT "},
{TK_GE ,"TK_GE "},
{TK_ESCAPE ,"TK_ESCAPE "},
{TK_BITAND ,"TK_BITAND "},
{TK_BITOR ,"TK_BITOR "},
{TK_LSHIFT ,"TK_LSHIFT "},
{TK_RSHIFT ,"TK_RSHIFT "},
{TK_PLUS ,"TK_PLUS "},
{TK_MINUS ,"TK_MINUS "},
{TK_STAR ,"TK_STAR "},
{TK_SLASH ,"TK_SLASH "},
{TK_REM ,"TK_REM "},
{TK_CONCAT ,"TK_CONCAT "},
{TK_COLLATE ,"TK_COLLATE "},
{TK_BITNOT ,"TK_BITNOT "},
{TK_ID ,"TK_ID "},
{TK_ABORT ,"TK_ABORT "},
{TK_ACTION ,"TK_ACTION "},
{TK_AFTER ,"TK_AFTER "},
{TK_ANALYZE ,"TK_ANALYZE "},
{TK_ASC ,"TK_ASC "},
{TK_ATTACH ,"TK_ATTACH "},
{TK_BEFORE ,"TK_BEFORE "},
{TK_BEGIN ,"TK_BEGIN "},
{TK_BY ,"TK_BY "},
{TK_CASCADE ,"TK_CASCADE "},
{TK_CAST ,"TK_CAST "},
{TK_COLUMNKW ,"TK_COLUMNKW "},
{TK_CONFLICT ,"TK_CONFLICT "},
{TK_DATABASE ,"TK_DATABASE "},
{TK_DESC ,"TK_DESC "},
{TK_DETACH ,"TK_DETACH "},
{TK_EACH ,"TK_EACH "},
{TK_END ,"TK_END "},
{TK_FAIL ,"TK_FAIL "},
{TK_FOR ,"TK_FOR "},
{TK_IGNORE ,"TK_IGNORE "},
{TK_INITIALLY ,"TK_INITIALLY "},
{TK_INSTEAD ,"TK_INSTEAD "},
{TK_NO ,"TK_NO "},
{TK_PLAN ,"TK_PLAN "},
{TK_QUERY ,"TK_QUERY "},
{TK_KEY ,"TK_KEY "},
{TK_OF ,"TK_OF "},
{TK_TO,"TK_TO"},
{TK_OFFSET ,"TK_OFFSET "},
{TK_PRAGMA ,"TK_PRAGMA "},
{TK_RAISE ,"TK_RAISE "},
{TK_RECURSIVE ,"TK_RECURSIVE "},
{TK_RELEASE ,"TK_RELEASE "},
{TK_REPLACE ,"TK_REPLACE "},
{TK_RESTRICT ,"TK_RESTRICT "},
{TK_ROW ,"TK_ROW "},
{TK_TRANSACTION ,"TK_TRANSACTION"},
{TK_START ,"TK_START"},
{TK_COMMIT ,"TK_COMMIT"},
{TK_ROLLBACK ,"TK_ROLLBACK "},
{TK_SAVEPOINT ,"TK_SAVEPOINT "},
{TK_TRIGGER ,"TK_TRIGGER "},
{TK_VACUUM ,"TK_VACUUM "},
{TK_VIEW ,"TK_VIEW "},
{TK_VIRTUAL ,"TK_VIRTUAL "},
{TK_WITH ,"TK_WITH "},
{TK_RENAME ,"TK_RENAME "},
{TK_ANY ,"TK_ANY "},
{TK_STRING ,"TK_STRING "},
{TK_JOIN_KW ,"TK_JOIN_KW "},
{TK_INTEGER ,"TK_INTEGER "},
{TK_FLOAT ,"TK_FLOAT "},
{TK_CONSTRAINT ,"TK_CONSTRAINT"},
{TK_DEFAULT ,"TK_DEFAULT "},
{TK_CHECK ,"TK_CHECK "},
{TK_AUTO_INCREMENT ,"TK_AUTO_INCREMENT "},
{TK_PRIMARY ,"TK_PRIMARY "},
{TK_UNIQUE ,"TK_UNIQUE "},
{TK_FOREIGN ,"TK_FOREIGN "},
{TK_DROP ,"TK_DROP "},
{TK_SELECT ,"TK_SELECT "},
{TK_VALUES ,"TK_VALUES "},
{TK_DISTINCT ,"TK_DISTINCT "},
{TK_DOT ,"TK_DOT "},
{TK_FROM ,"TK_FROM "},
{TK_JOIN ,"TK_JOIN "},
{TK_ON ,"TK_ON "},
{TK_USING ,"TK_USING "},
{TK_ORDER ,"TK_ORDER "},
{TK_GROUP ,"TK_GROUP "},
{TK_HAVING ,"TK_HAVING "},
{TK_LIMIT ,"TK_LIMIT "},
{TK_DELETE ,"TK_DELETE "},
{TK_WHERE ,"TK_WHERE "},
{TK_UPDATE ,"TK_UPDATE "},
{TK_SET ,"TK_SET "},
{TK_INTO ,"TK_INTO "},
{TK_INSERT ,"TK_INSERT "},
{TK_NULL ,"TK_NULL "},
{TK_BLOB ,"TK_BLOB "},
{TK_CASE ,"TK_CASE "},
{TK_WHEN ,"TK_WHEN "},
{TK_THEN ,"TK_THEN "},
{TK_ELSE ,"TK_ELSE "}
};
int len = sizeof(token_list)/sizeof(struct token_list_s);
int i;
for (i = 0; i < len; ++i)
{
if (token_list[i].code == op)
return token_list[i].name;
}
return "NotFound";
}
/* print the syntax tree, used for debug */
void sql_expr_print(sql_expr_t *p, int depth)
{
if (p) {
int INDENT = 2;
static const char *spaces = " ";
printf("%.*s", depth *INDENT, spaces);
printf("%s ", sql_get_token_name(p->op));
if (p->op != TK_INTEGER && p->token_text)
printf("%s\n", p->token_text);
else if (p->op == TK_INTEGER)
printf("%" PRIu64 "\n", p->num_value);
else
printf("\n");
if (p->left)
sql_expr_print(p->left, depth+1);
else if (p->right)
printf("%.*s[nul]\n", (depth+1)*INDENT, spaces);
else
;
if (p->right)
sql_expr_print(p->right, depth+1);
else if (p->left)
printf("%.*s[nul]\n", (depth+1)*INDENT, spaces);
else
;
}
}
void sql_statement_free(void *clause, sql_stmt_type_t stmt_type)
{
if (!clause)
return;
switch(stmt_type) {
case STMT_SELECT:
sql_select_free(clause);
break;
case STMT_UPDATE:
sql_update_free(clause);
break;
case STMT_INSERT:
sql_insert_free(clause);
break;
case STMT_DELETE:
sql_delete_free(clause);
break;
case STMT_SHOW:
break;
case STMT_SHOW_COLUMNS:
case STMT_SHOW_CREATE:
case STMT_EXPLAIN_TABLE:
sql_src_list_free(clause);
break;
case STMT_SET:
sql_expr_list_free(clause);
break;
case STMT_SET_TRANSACTION:
case STMT_SET_NAMES:
case STMT_USE:
case STMT_SAVEPOINT:
g_free(clause);
break;
case STMT_START:
case STMT_COMMIT:
case STMT_ROLLBACK:
case STMT_COMMON_DDL:
break;
case STMT_CALL:
sql_expr_free(clause);
break;
default:
g_warning(G_STRLOC ":not supported clause type, caution mem leak");
}
}
gboolean sql_is_quoted_string(const char *s)
{
if (!s)
return FALSE;
int len = strlen(s);
if (len < 2)
return FALSE;
if (s[0] != s[len-1])
return FALSE;
return s[0] == '\'' || s[0] == '"' || s[0] == '`';
}
int sql_join_type(sql_token_t kw)
{
static struct {
char *name;
uint8_t code;
} _map[] = {
{"INNER", JT_INNER},
{"CROSS", JT_CROSS},
{"NATURAL", JT_NATURAL},
{"LEFT", JT_LEFT },
{"RIGHT", JT_RIGHT},
{"OUTER", JT_OUTER},
};
int ret = JT_ERROR;
char *kw_str = sql_token_dup(kw);
int i = 0;
while (i < 6) {
if (strcasecmp(_map[i].name, kw_str) == 0) {
ret = _map[i].code;
break;
}
++i;
}
g_free(kw_str);
return ret;
}
/**
* !! support only ID, INTGER and STRING
*/
gboolean sql_expr_equals(const sql_expr_t *p1, const sql_expr_t *p2)
{
if (p1 && p2 && p1->op == p2->op) {
if (p1->op == TK_ID || p1->op == TK_STRING) {
return strcmp(p1->token_text, p2->token_text) == 0;
} else if (p1->op == TK_INTEGER) {
return p1->num_value == p2->num_value;
}
}
return FALSE;
}

317
lib/sql-expression.h Normal file
View File

@ -0,0 +1,317 @@
#ifndef SQL_EXPRESSION_H
#define SQL_EXPRESSION_H
#include <stdint.h>
#include <stdbool.h>
#include <glib.h>
#include <stdio.h>
enum sql_join_type_t {
JT_INNER =0x01, /* Any kind of inner or cross join */
JT_CROSS =0x02, /* Explicit use of the CROSS keyword */
JT_NATURAL =0x04, /* True for a "natural" join */
JT_LEFT =0x08, /* Left outer join */
JT_RIGHT =0x10, /* Right outer join */
JT_OUTER =0x20, /* The "OUTER" keyword is present */
JT_ERROR =0x40, /* unknown or unsupported join type */
};
#define MAX_AGGR_FUNS 8
typedef struct GROUP_AGGR {
uint8_t type;
int pos;
unsigned int fun_type;
} GROUP_AGGR;
typedef struct sql_token_t sql_token_t;
typedef struct sql_expr_t sql_expr_t;
typedef GPtrArray sql_expr_list_t;
typedef GPtrArray sql_id_list_t;
typedef GPtrArray sql_src_list_t;
typedef struct sql_src_item_t sql_src_item_t;
typedef struct sql_expr_span_t sql_expr_span_t;
typedef struct sql_select_t sql_select_t;
typedef struct sql_delete_t sql_delete_t;
typedef struct sql_update_t sql_update_t;
typedef struct sql_insert_t sql_insert_t;
typedef enum sql_stmt_type_t sql_stmt_type_t;
typedef struct sql_column_t sql_column_t;
typedef GPtrArray sql_column_list_t;
enum sql_stmt_type_t {
STMT_UNKOWN,
STMT_SELECT,
STMT_INSERT,
STMT_UPDATE,
STMT_DELETE,
STMT_COMMON_DDL,
STMT_SHOW,
STMT_SET,
STMT_SET_NAMES,
STMT_SET_TRANSACTION,
STMT_ROLLBACK,
STMT_COMMIT,
STMT_CALL,
STMT_START,
STMT_EXPLAIN_TABLE,
STMT_USE,
STMT_SAVEPOINT,
STMT_SHOW_COLUMNS,
STMT_SHOW_CREATE,
STMT_SHOW_WARNINGS,
};
struct sql_token_t {
const char *z; /* pointer to token text, not NUL-terminated */
uint32_t n; /* length of token text in bytes */
};
enum sql_var_scope_t {
SCOPE_GLOBAL,
SCOPE_SESSION,
SCOPE_USER,
/* SET TRANSACTION ...without explicit scope keyword,
only affect next transaction, it's transient */
SCOPE_TRANSIENT,
SCOPE_NONE
};
enum sql_trx_feature_t {
TF_READ_ONLY = 1,
TF_READ_WRITE,
/* isolation levels */
TF_SERIALIZABLE,
TF_REPEATABLE_READ,
TF_READ_COMMITTED,
TF_READ_UNCOMMITTED,
};
enum sql_clause_flag_t {
CF_READ = 0x01,
CF_WRITE = 0x02,
CF_FORCE_MASTER = 0x04,
CF_FORCE_SLAVE = 0x08,
CF_DDL = 0x10,
CF_LOCAL_QUERY = 0x20,
CF_DISTINCT_AGGR = 0x40,
CF_SUBQUERY = 0x80,
};
enum sql_sort_order_t {
SQL_SO_ASC,
SQL_SO_DESC,
};
enum sql_expr_flags_t {
EP_FUNCTION = 0x01,
EP_INTERVAL = 0x02,
EP_EXISTS = 0x04,
EP_BETWEEN = 0x08,
EP_ATOMIC = 0x10,
EP_LAST_INSERT_ID = 0x20,
EP_DISTINCT = 0x40,
EP_SHARD_COND = 0x80,
EP_JOIN_LINK = 0x0100,
EP_NOT = 0x0200,
EP_CASE_WHEN = 0x0400,
EP_ORDER_BY = 0x0800,
EP_AGGREGATE = 0x1000,
};
enum sql_func_type_t {
FT_UNKNOWN = 0,
FT_COUNT,
FT_SUM,
FT_AVG,
FT_MAX,
FT_MIN,
};
struct sql_expr_t {
uint16_t op; /* Operation performed by this node */
char *token_text; /* Token value. Zero terminated and dequoted */
uint64_t num_value; /* Non-negative integer value */
sql_expr_t *left;
sql_expr_t *right;
sql_expr_list_t *list; /* op = IN, EXISTS, SELECT, CASE, FUNCTION, BETWEEN */
sql_select_t *select; /* EP_xIsSelect and op = IN, EXISTS, SELECT */
int height; /* Height of the tree headed by this node */
char *alias;
enum sql_expr_flags_t flags;
enum sql_var_scope_t var_scope; /* variable scope: SESSION(default) or GLOBAL */
const char *start; /* first char of expr in original sql */
const char *end; /* one char past the end of expr in orig sql */
};
enum select_flag_t{
SF_DISTINCT = 0x01,
SF_ALL = 0x02,
SF_CALC_FOUND_ROWS = 0x04,
SF_MULTI_VALUE = 0x08,
SF_REWRITE_ORDERBY = 0x10,
};
struct sql_select_t {
uint8_t op; /* One of: TK_UNION TK_ALL TK_INTERSECT TK_EXCEPT */
uint32_t flags; /* Various SF_* values */
sql_expr_list_t *columns; /* The fields of the result */
sql_src_list_t *from_src; /* The FROM clause */
sql_expr_t *where_clause;
sql_expr_list_t *groupby_clause;
sql_expr_t *having_clause;
sql_column_list_t *orderby_clause;
sql_select_t *prior; /* Prior select in a compound select statement */
sql_select_t *next; /* Next select to the left in a compound */
sql_expr_t *limit; /* LIMIT expression. NULL means not used. */
sql_expr_t *offset; /* OFFSET expression. NULL means not used. */
int lock_read;
};
struct sql_delete_t {
sql_src_list_t *from_src; /* The FROM clause */
sql_expr_t *where_clause;
sql_column_list_t *orderby_clause;
sql_expr_t *limit; /* LIMIT expression. NULL means not used. */
sql_expr_t *offset; /* OFFSET expression. NULL means not used. */
};
struct sql_update_t {
sql_src_list_t *table;
sql_expr_list_t *set_list;
sql_expr_t *where_clause;
sql_column_list_t *orderby_clause;
sql_expr_t *limit; /* LIMIT expression. NULL means not used. */
sql_expr_t *offset; /* OFFSET expression. NULL means not used. */
};
struct sql_insert_t {
int is_replace;
sql_src_list_t *table;
sql_select_t *sel_val; /* [1] select... [2] values(...)*/
sql_id_list_t *columns;
};
struct sql_column_t {
sql_expr_t *expr;
char *type;
enum sql_sort_order_t sort_order;
char *alias;
};
struct sql_src_item_t {
char *dbname; /* Name of database holding this table */
char *table_name; /* Name of the table */
char *table_alias; /* The "B" part of a "A AS B" phrase. zName is the "A" */
sql_select_t *select; /* A SELECT statement used in place of a table name */
uint8_t jointype; /* Type of join between this table and the previous */
sql_expr_t *on_clause; /* The ON clause of a join */
sql_id_list_t *pUsing; /* The USING clause of a join */
sql_expr_list_t *func_arg; /* Arguments to table-valued-function */
}; /* One entry for each identifier on the list */
typedef struct sql_set_transaction_t {
enum sql_var_scope_t scope;
enum sql_trx_feature_t rw_feature;
enum sql_trx_feature_t level;
} sql_set_transaction_t;
char *sql_token_dup(sql_token_t);
void sql_string_dequote(char *str);
sql_expr_t *sql_expr_new(int op, const sql_token_t *token);
sql_expr_t *sql_expr_dup(const sql_expr_t *);
void sql_expr_attach_subtrees(sql_expr_t *p, sql_expr_t *left, sql_expr_t *right);
void sql_expr_free(void *p);
void sql_expr_print(sql_expr_t *p, int depth);
gboolean sql_expr_get_int(sql_expr_t *p, gint64 *value);
gboolean sql_expr_is_boolean(sql_expr_t *p, gboolean *value);
#define sql_expr_id(A) \
(A ? ((A->op == TK_ID)?A->token_text:NULL) : NULL)
gboolean sql_expr_is_id(const sql_expr_t *p, const char *name);
gboolean sql_expr_is_function(const sql_expr_t *p, const char *func_name);
gboolean sql_expr_is_dotted_name(const sql_expr_t *p,
const char *prefix, const char *suffix);
gboolean sql_expr_is_field_name(const sql_expr_t *p);
sql_expr_list_t *sql_expr_list_append(sql_expr_list_t *list, sql_expr_t *expr);
sql_expr_t *sql_expr_list_find(sql_expr_list_t *list, const char *name);
sql_expr_t *sql_expr_list_find_fullname(sql_expr_list_t *list, const sql_expr_t *expr);
int sql_expr_list_find_aggregates(sql_expr_list_t *list, GROUP_AGGR *aggr_array);
int sql_expr_list_find_aggregate(sql_expr_list_t *list);
void sql_expr_list_free(sql_expr_list_t *list);
sql_src_list_t *sql_src_list_append(sql_src_list_t *, sql_token_t *tname,
sql_token_t *dbname, sql_token_t *alias, sql_select_t *subquery,
sql_expr_t *on_clause, sql_id_list_t *using_clause);
void sql_src_list_free(sql_src_list_t *p);
sql_id_list_t *sql_id_list_append(sql_id_list_t *p, sql_token_t *id_name);
void sql_id_list_free(sql_id_list_t *p);
sql_select_t *sql_select_new();
void sql_select_free(sql_select_t *);
sql_delete_t *sql_delete_new();
void sql_delete_free(sql_delete_t *);
sql_update_t *sql_update_new();
void sql_update_free(sql_update_t *);
sql_insert_t *sql_insert_new();
void sql_insert_free(sql_insert_t *);
char *sql_get_token_name(int op);
sql_column_list_t *sql_column_list_append(sql_column_list_t *, sql_column_t *);
void sql_column_list_free(sql_column_list_t *);
sql_column_t *sql_column_new();
void sql_column_free(void *);
int sql_join_type(sql_token_t kw);
void sql_statement_free(void *clause, sql_stmt_type_t stmt_type);
gboolean sql_is_quoted_string(const char *s);
enum sql_func_type_t sql_func_type(const char *s);
gboolean sql_expr_equals(const sql_expr_t *, const sql_expr_t *);
#endif /* SQL_EXPRESSION_H */

284
lib/sql-filter-variables.c Normal file
View File

@ -0,0 +1,284 @@
#include "sql-filter-variables.h"
#include "cetus-util.h"
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "cJSON.h"
enum _value_type_t {
VAL_UNKNOWN = -1,
VAL_INT = 1,
VAL_STRING,
VAL_STRING_CSV, /* comma seperated string value */
};
static enum _value_type_t value_type(const char *str)
{
if (strcasecmp(str, "int") == 0) return VAL_INT;
else if (strcasecmp(str, "string") == 0) return VAL_STRING;
else if (strcasecmp(str, "string-csv") == 0) return VAL_STRING_CSV;
else return VAL_UNKNOWN;
}
struct sql_variable_t {
char *name;
enum _value_type_t type;
GList *silent_values; /* GList<char *> */
GList *allowed_values; /* GList<char *> */
gboolean allow_all;
gboolean silence_all;
};
static void sql_variable_free(struct sql_variable_t *p)
{
if (p->name) g_free(p->name);
if (p->silent_values) g_list_free_full(p->silent_values, g_free);
if (p->allowed_values) g_list_free_full(p->allowed_values, g_free);
g_free(p);
}
static gboolean sql_variable_is_silent_value(struct sql_variable_t *p, const char *value)
{
GList *l;
for (l = p->silent_values; l; l = l->next) {
if (strcasecmp(l->data, value) == 0) {
return TRUE;
}
}
return FALSE;
}
static gboolean sql_variable_is_allowed_value(struct sql_variable_t *p, const char *value)
{
GList *l;
for (l = p->allowed_values; l; l = l->next) {
if (strcasecmp(l->data, value) == 0) {
return TRUE;
}
}
return FALSE;
}
static GHashTable *cetus_variables = NULL;
void sql_filter_vars_destroy()
{
if (cetus_variables) {
g_hash_table_destroy(cetus_variables);
}
}
gboolean sql_filter_vars_load_rules(char *filename)
{
char *buffer = NULL;
if (read_file_to_buffer(filename, &buffer) == FALSE) {
g_free(buffer);
return FALSE;
}
gboolean rc = sql_filter_vars_load_str_rules(buffer);
g_free(buffer);
return rc;
}
gboolean str_case_equal(gconstpointer v1, gconstpointer v2)
{
if (!v1 || !v2) return FALSE;
return strcasecmp((const char*)v1, (const char*)v2)==0;
}
guint str_case_hash(gconstpointer v)
{
char* lower = g_ascii_strdown((const char*)v, -1);
guint hash = g_str_hash(lower);
g_free(lower);
return hash;
}
gboolean sql_filter_vars_load_str_rules(const char *json_str)
{
if (!cetus_variables) {
cetus_variables = g_hash_table_new_full(str_case_hash, str_case_equal,
NULL, (GDestroyNotify)sql_variable_free);
}
cJSON *root = cJSON_Parse(json_str);
if (!root) {
g_warning(G_STRLOC ":rule file parse error");
return FALSE;
}
cJSON *var_node = cJSON_GetObjectItem(root, "variables");
if (!var_node) {
g_warning("cannot find \"variables\" json node");
return FALSE;
}
cJSON *cur = var_node->child;
for (; cur; cur = cur->next) {
cJSON *name = cJSON_GetObjectItem(cur, "name");
cJSON *type = cJSON_GetObjectItem(cur, "type");
if (!name || !type) {
return FALSE;
}
struct sql_variable_t *var = g_new0(struct sql_variable_t, 1);
var->name = g_strdup(name->valuestring);
var->type = value_type(type->valuestring);
cJSON *silent_array = cJSON_GetObjectItem(cur, "silent_values");
if (silent_array) {
cJSON *silent = silent_array->child;
for (; silent; silent = silent->next) {
if (strcmp(silent->valuestring, "*") == 0) {
var->silence_all = TRUE;
break;
}
var->silent_values = g_list_append(var->silent_values,
g_strdup(silent->valuestring));
}
}
cJSON *allowed_array = cJSON_GetObjectItem(cur, "allowed_values");
if (allowed_array) {
cJSON *allowed = allowed_array->child;
for (; allowed; allowed = allowed->next) {
if (strcmp(allowed->valuestring, "*") == 0) {
var->allow_all = TRUE;
break;
}
var->allowed_values = g_list_append(var->allowed_values,
g_strdup(allowed->valuestring));
}
}
/* if duplicated, replace and free the old (key & value) */
g_hash_table_replace(cetus_variables, var->name, var);
}
cJSON_Delete(root);
return TRUE;
}
gboolean sql_filter_vars_is_silent(const char *name, const char *val)
{
if (!name) {
return FALSE;
}
struct sql_variable_t *var = g_hash_table_lookup(cetus_variables, name);
if (!var) {
return FALSE;
}
if (var->silence_all) {
return TRUE;
}
if (!val) {
return FALSE;
}
switch (var->type) {
case VAL_STRING:
return sql_variable_is_silent_value(var, val);
case VAL_STRING_CSV: {
gchar **values = g_strsplit_set(val, ", ", -1);
int i = 0;
for (i = 0; values[i] != NULL; ++i) {
if (!sql_variable_is_silent_value(var, values[i])) {
g_strfreev(values);
return FALSE;
}
}
g_strfreev(values);
return TRUE;
}
default:
g_warning(G_STRLOC ":not implemented");
break;
}
return FALSE;
}
gboolean sql_filter_vars_is_allowed(const char *name, const char *val)
{
if (!name) {
return FALSE;
}
struct sql_variable_t *var = g_hash_table_lookup(cetus_variables, name);
if (!var) {
return FALSE;
}
if (var->allow_all) {
return TRUE;
}
if (!val) {
return FALSE;
}
switch (var->type) {
case VAL_STRING:
return sql_variable_is_allowed_value(var, val);
case VAL_STRING_CSV: {
gchar **values = g_strsplit_set(val, ", ", -1);
int i = 0;
for (i = 0; values[i] != NULL; ++i) {
if (!sql_variable_is_allowed_value(var, values[i])) {
g_strfreev(values);
return FALSE;
}
}
g_strfreev(values);
return TRUE;
}
default:
g_warning(G_STRLOC "not implemented");
break;
}
return FALSE;
}
void sql_filter_vars_load_default_rules()
{
static const char *default_var_rule = "{"
" \"variables\": ["
" {"
" \"name\": \"sql_mode\","
" \"type\": \"string-csv\","
" \"allowed_values\": ["
" \"STRICT_TRANS_TABLES\","
" \"NO_AUTO_CREATE_USER\","
" \"NO_ENGINE_SUBSTITUTION\""
" ]"
" },"
" {"
" \"name\": \"autocommit\","
" \"type\": \"string\","
" \"allowed_values\": [\"*\"]"
" },"
" {"
" \"name\": \"character_set_client\","
" \"type\": \"string\","
" \"allowed_values\": [\"latin1\",\"ascii\",\"gb2312\",\"gbk\",\"utf8\",\"utf8mb4\",\"binary\",\"big5\"]"
" },"
" {"
" \"name\": \"character_set_connection\","
" \"type\": \"string\","
" \"allowed_values\": [\"latin1\",\"ascii\",\"gb2312\",\"gbk\",\"utf8\",\"utf8mb4\",\"binary\",\"big5\"]"
" },"
" {"
" \"name\": \"character_set_results\","
" \"type\": \"string\","
" \"allowed_values\": [\"latin1\",\"ascii\",\"gb2312\",\"gbk\",\"utf8\",\"utf8mb4\",\"binary\",\"big5\",\"NULL\"]"
" }"
" ]"
"}";
gboolean rc = sql_filter_vars_load_str_rules(default_var_rule);
g_assert(rc);
}
void sql_filter_vars_shard_load_default_rules()
{
static const char *default_var_rule = "{"
" \"variables\": ["
" {"
" \"name\": \"autocommit\","
" \"type\": \"string\","
" \"allowed_values\": [\"*\"]"
" }"
" ]"
"}";
gboolean rc = sql_filter_vars_load_str_rules(default_var_rule);
g_assert(rc);
}

View File

@ -0,0 +1,20 @@
#ifndef _SQL_FILTER_VARIABLES_H_
#define _SQL_FILTER_VARIABLES_H_
#include <glib.h>
gboolean sql_filter_vars_load_rules(char *filename);
gboolean sql_filter_vars_load_str_rules(const char *json_str);
void sql_filter_vars_load_default_rules();
void sql_filter_vars_shard_load_default_rules();
void sql_filter_vars_destroy();
gboolean sql_filter_vars_is_silent(const char *name, const char *val);
gboolean sql_filter_vars_is_allowed(const char *name, const char *val);
#endif /*_SQL_FILTER_VARIABLES_H_*/

183
lib/sql-operation.c Normal file
View File

@ -0,0 +1,183 @@
#include "sql-operation.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <glib.h>
#include "sql-expression.h"
#include "sql-filter-variables.h"
#include "myparser.y.h"
void sql_select(sql_context_t *st, sql_select_t *select)
{
st->rw_flag |= CF_READ;
sql_context_add_stmt(st, STMT_SELECT, select);
}
void sql_delete(sql_context_t *st, sql_delete_t *del)
{
if (st->rc != PARSE_OK) {
g_warning("Delete Parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_DELETE, del);
}
void sql_update(sql_context_t *st, sql_update_t *update)
{
if (st->rc != PARSE_OK) {
g_warning("Update Parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_UPDATE, update);
}
void sql_insert(sql_context_t *st, sql_insert_t *insert)
{
if (st->rc != PARSE_OK) {
g_warning("Insert Parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_INSERT, insert);
}
void sql_start_transaction(sql_context_t *st)
{
if (st->rc != PARSE_OK) {
g_warning("Start transaction parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_START, NULL);
}
void sql_commit_transaction(sql_context_t *st)
{
if (st->rc != PARSE_OK) {
g_warning("COMMIT transaction parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_COMMIT, NULL);
}
void sql_rollback_transaction(sql_context_t *st)
{
if (st->rc != PARSE_OK) {
g_warning("ROLLBACK transaction parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_ROLLBACK, NULL);
}
void sql_savepoint(sql_context_t *st, int tk, char *name)
{
if (st->rc != PARSE_OK) {
g_warning("SAVE POINT parse Error");
}
st->rw_flag |= CF_WRITE;
sql_context_add_stmt(st, STMT_SAVEPOINT, name);
}
static gboolean string_array_contains(const char **sa, int size, const char *str)
{
int i = 0;
for (;i < size; ++i) {
if (strcasecmp(sa[i], str) == 0)
return TRUE;
}
return FALSE;
}
void sql_set_variable(sql_context_t *ps, sql_expr_list_t *exps)
{
if (ps->property) {
sql_context_set_error(ps, PARSE_NOT_SUPPORT,
"Commanding comment is not allowed in SET clause");
goto out;
}
g_assert(exps);
int i = 0;
for (i = 0; i < exps->len; ++i) {
sql_expr_t *p = g_ptr_array_index(exps, i);
if (!p || p->op != TK_EQ || !sql_expr_is_id(p->left, NULL)) {
sql_context_set_error(ps, PARSE_SYNTAX_ERR,
"syntax error in SET");
goto out;
}
if (p->left && p->left->var_scope == SCOPE_GLOBAL) {
sql_context_set_error(ps, PARSE_NOT_SUPPORT,
"Only session scope SET is supported now");
goto out;
}
const char *var_name = p->left->token_text;
const char *value = p->right->token_text;
if (strcasecmp(var_name, "sql_mode") == 0) {
gboolean supported = sql_filter_vars_is_allowed(var_name, value);
if (!supported) {
sql_context_set_error(ps, PARSE_NOT_SUPPORT,
"This sql_mode is not supported");
goto out;
}
} else if (!sql_filter_vars_is_allowed(var_name, value)) {
char msg[100];
snprintf(msg, 100, "SET of %s is not supported", var_name);
sql_context_set_error(ps, PARSE_NOT_SUPPORT, msg);
goto out;
}
}
out:
sql_context_add_stmt(ps, STMT_SET, exps);
}
void sql_set_names(sql_context_t *ps, char *val)
{
if (ps->property) {
sql_context_set_error(ps, PARSE_NOT_SUPPORT,
"Commanding comment is not allowed in SET clause");
g_free(val);
return;
}
const char *charsets[] = {"latin1", "ascii", "gb2312", "gbk", "utf8", "utf8mb4", "big5"};
int cs_size = sizeof(charsets) / sizeof(char *);
if (!string_array_contains(charsets, cs_size, val)) {
char msg[64] = {0};
snprintf(msg, 64, "Unknown character set: %s", val);
sql_context_set_error(ps, PARSE_NOT_SUPPORT, msg);
g_free(val);
return;
}
sql_context_add_stmt(ps, STMT_SET_NAMES, val);
}
void sql_set_transaction(sql_context_t *ps, int scope, int rw_feature, int level)
{
if (ps->property) {
sql_context_set_error(ps, PARSE_NOT_SUPPORT,
"Commanding comment is not allowed in SET clause");
return;
}
if (scope == SCOPE_GLOBAL) {
sql_context_set_error(ps, PARSE_NOT_SUPPORT,
"GLOBAL scope SET TRANSACTION is not supported now");
return;
}
sql_set_transaction_t *set_tran = g_new0(sql_set_transaction_t, 1);
set_tran->scope = scope;
set_tran->rw_feature = rw_feature;
set_tran->level = level;
sql_context_add_stmt(ps, STMT_SET_TRANSACTION, set_tran);
}
void sql_use_database(sql_context_t *ps, char *val)
{
ps->rw_flag |= CF_READ;
sql_context_add_stmt(ps, STMT_USE, val);
}
void sql_explain_table(sql_context_t *ps, sql_src_list_t *table)
{
ps->rw_flag |= CF_READ;
sql_context_add_stmt(ps, STMT_EXPLAIN_TABLE, table);
}

35
lib/sql-operation.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef SQL_OPERATION_H
#define SQL_OPERATION_H
#include "sql-context.h"
#include "sql-expression.h"
void sql_set_variable(sql_context_t *, sql_expr_list_t *);
void sql_set_names(sql_context_t *, char *);
void sql_set_transaction(sql_context_t *, int scope, int rwfeature, int level);
void sql_select(sql_context_t *st, sql_select_t *select);
void sql_delete(sql_context_t *st, sql_delete_t *del);
void sql_update(sql_context_t *st, sql_update_t *p);
void sql_insert(sql_context_t *st, sql_insert_t *p);
void sql_statement_free(void *stmt, sql_stmt_type_t stmt_type);
void sql_start_transaction(sql_context_t *st);
void sql_commit_transaction(sql_context_t *st);
void sql_rollback_transaction(sql_context_t *st);
void sql_savepoint(sql_context_t *st, int, char *);
void sql_explain_table(sql_context_t *st, sql_src_list_t *table);
void sql_use_database(sql_context_t *ps, char *val);
#endif /* SQL_OPERATION_H */

147
lib/sql-property.c Normal file
View File

@ -0,0 +1,147 @@
#include "sql-property.h"
#include <string.h>
enum property_parse_state_t{
// PARSE_STATE_INIT,
PARSE_STATE_KEY = 0,
PARSE_STATE_VALUE,
PARSE_STATE_EQ_SIGN,
PARSE_STATE_ERROR,
};
enum {
TYPE_INT,
TYPE_STRING,
};
#define MAX_VALUE_LEN 50
gboolean sql_property_is_valid(sql_property_t *p)
{
if (p->table && p->group)/* mutual exclusive */
return FALSE;
return TRUE;
}
void sql_property_free(sql_property_t *p)
{
if (p->group)
g_free(p->group);
if (p->table)
g_free(p->table);
if (p->key)
g_free(p->key);
g_free(p);
}
void sql_property_parser_reset(sql_property_parser_t *parser)
{
memset(parser, 0, sizeof(*parser));
}
static int string_to_code(const char *str)
{
struct code_map_t {
char *name;
int code;
} map[] = {
{"READWRITE", MODE_READWRITE},
{"READONLY", MODE_READONLY},
{"SCOPE_LOCAL", P_SCOPE_LOCAL},
{"SCOPE_GLOBAL", P_SCOPE_GLOBAL},
{"SINGLE_NODE", TRX_SINGLE_NODE},
};
int i;
for (i = 0; i < sizeof(map) / sizeof(*map); ++i) {
if (strcasecmp(map[i].name, str) == 0)
return map[i].code;
}
return ERROR_VALUE;
}
static gboolean parser_find_key(sql_property_parser_t *parser,
const char *token, int len)
{
static const struct property_desc_t {
const char *name;
size_t offset;
int type;
value_parse_func get_value;
} desc[] = {
{"mode", offsetof(struct sql_property_t, mode), TYPE_INT, string_to_code },
{"scope", offsetof(struct sql_property_t, scope), TYPE_INT, string_to_code },
{"transaction", offsetof(struct sql_property_t, transaction), TYPE_INT, string_to_code },
{"group", offsetof(struct sql_property_t, group), TYPE_STRING, NULL },
{"table", offsetof(struct sql_property_t, table), TYPE_STRING, NULL },
{"key", offsetof(struct sql_property_t, key), TYPE_STRING, NULL },
};
int i = 0;
for (i = 0; i < sizeof(desc)/sizeof(*desc); ++i) {
if (strcasecmp(token, desc[i].name) == 0) {
parser->key_offset = desc[i].offset;
parser->key_type = desc[i].type;
parser->get_value = desc[i].get_value;
parser->state = PARSE_STATE_EQ_SIGN;
return TRUE;
}
}
parser->state = PARSE_STATE_ERROR;
return FALSE;
}
static gboolean parser_set_value(sql_property_parser_t *parser,
void *object, const char *token, int len)
{
if (parser->key_type == TYPE_INT) {
int *p = (int *)((unsigned char *) object + parser->key_offset);
if (token[0] == '"' && token[len-1] == '"') {/* might be quoted */
char buf[MAX_VALUE_LEN] = {0};
memcpy(buf, token+1, len-2);
*p = parser->get_value(buf);
} else {
*p = parser->get_value(token);
}
parser->state = PARSE_STATE_KEY;
return TRUE;
} else if (parser->key_type == TYPE_STRING) {
char **p = (char **)((unsigned char *) object + parser->key_offset);
if (token[0] == '"' && token[len-1] == '"') {/* might be quoted */
*p = g_malloc0(len-1);
memcpy(*p, token+1, len-2);
} else {
*p = strndup(token, len);
}
parser->state = PARSE_STATE_KEY;
return TRUE;
}
parser->state = PARSE_STATE_ERROR;
return FALSE;
}
gboolean sql_property_parser_parse(sql_property_parser_t *parser,
const char *token, int len, sql_property_t *property)
{
switch (parser->state) {
case PARSE_STATE_KEY:
return parser_find_key(parser, token, len);
case PARSE_STATE_EQ_SIGN:
if (len == 1 && token[0] == '=') {
parser->state = PARSE_STATE_VALUE;
return TRUE;
} else {
return FALSE;
}
case PARSE_STATE_VALUE:
if (len > MAX_VALUE_LEN) {
return FALSE;
}
return parser_set_value(parser, property, token, len);
default:
return FALSE;
}
}

46
lib/sql-property.h Normal file
View File

@ -0,0 +1,46 @@
#ifndef SQL_PROPERTY_H
#define SQL_PROPERTY_H
#include <glib.h>
#define MAX_PROPERTY_LEN 64
enum {
ERROR_VALUE = -1,
MODE_READWRITE = 1,
MODE_READONLY,
P_SCOPE_LOCAL,
P_SCOPE_GLOBAL,
TRX_SINGLE_NODE,
};
typedef struct sql_property_t {
int mode;
int scope;
int transaction;
char *group;
char *table;
char *key;
} sql_property_t;
void sql_property_free(sql_property_t *);
gboolean sql_property_is_valid(sql_property_t *);
typedef int(*value_parse_func)(const char *);
typedef struct sql_property_parser_t {
int key_offset;
value_parse_func get_value;
int key_type;
int state;
gboolean is_parsing;
} sql_property_parser_t;
void sql_property_parser_reset(sql_property_parser_t *parser);
gboolean sql_property_parser_parse(sql_property_parser_t *parser,
const char *token, int len, sql_property_t *property);
#endif /* SQL_PROPERTY_H */

12
mysql-chassis.pc.cmake Normal file
View File

@ -0,0 +1,12 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=@CMAKE_INSTALL_PREFIX@
libdir=@CMAKE_INSTALL_PREFIX@/lib
includedir=@CMAKE_INSTALL_PREFIX@/include
Name: mysql-chassis
Version: @PACKAGE_VERSION_STRING@
Description: the Chassis of the MySQL Proxy
URL: http://forge.mysql.com/wiki/MySQL_Proxy
Requires: glib-2.0 >= 2.16
Libs: -L${libdir} -lmysql-chassis -lmysql-chassis-timing -lmysql-chassis-glibext
Cflags: -I${includedir}

24
plugins/CMakeLists.txt Normal file
View File

@ -0,0 +1,24 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
ADD_SUBDIRECTORY(proxy)
ADD_SUBDIRECTORY(admin)
ADD_SUBDIRECTORY(shard)
## needs readline
# ADD_SUBDIRECTORY(cli)

View File

@ -0,0 +1,36 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}) # for config.h
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
INCLUDE_DIRECTORIES(${GLIB_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${MYSQL_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${EVENT_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBINTL_LIBRARY_DIRS})
LINK_DIRECTORIES(${MYSQL_LIBRARY_DIRS})
SET(_plugin_name admin)
ADD_LIBRARY(${_plugin_name} SHARED "${_plugin_name}-plugin.c")
TARGET_LINK_LIBRARIES(${_plugin_name} mysql-chassis-proxy)
CHASSIS_PLUGIN_INSTALL(${_plugin_name})

2531
plugins/admin/admin-plugin.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,44 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}) # for config.h
include_directories(${PROJECT_SOURCE_DIR}/lib)
include_directories(${PROJECT_BINARY_DIR}/lib)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
INCLUDE_DIRECTORIES(${GLIB_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${MYSQL_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${EVENT_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBINTL_LIBRARY_DIRS})
LINK_DIRECTORIES(${MYSQL_LIBRARY_DIRS})
SET(_plugin_name proxy)
ADD_LIBRARY(${_plugin_name} SHARED "${_plugin_name}-plugin.c")
TARGET_LINK_LIBRARIES(${_plugin_name} mysql-chassis-proxy)
if(SIMPLE_PARSER)
target_compile_definitions(${_plugin_name} PRIVATE SIMPLE_PARSER=1)
endif(SIMPLE_PARSER)
CHASSIS_PLUGIN_INSTALL(${_plugin_name})

2481
plugins/proxy/proxy-plugin.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
# $%BEGINLICENSE%$
# Copyright (c) 2009, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
INCLUDE_DIRECTORIES(${CMAKE_SOURCE_DIR}/src/)
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}) # for config.h
include_directories(${PROJECT_SOURCE_DIR}/lib)
include_directories(${PROJECT_BINARY_DIR}/lib)
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
INCLUDE_DIRECTORIES(${GLIB_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${MYSQL_INCLUDE_DIRS})
INCLUDE_DIRECTORIES(${EVENT_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBINTL_LIBRARY_DIRS})
LINK_DIRECTORIES(${MYSQL_LIBRARY_DIRS})
SET(_plugin_name shard)
set(SHARD_SOURCES
${_plugin_name}-plugin.c
sharding-parser.c
)
ADD_LIBRARY(${_plugin_name} SHARED ${SHARD_SOURCES})
TARGET_LINK_LIBRARIES(${_plugin_name} mysql-chassis-proxy)
if(SIMPLE_PARSER)
target_compile_definitions(${_plugin_name} PRIVATE SIMPLE_PARSER=1)
endif(SIMPLE_PARSER)
CHASSIS_PLUGIN_INSTALL(${_plugin_name})

2357
plugins/shard/shard-plugin.c Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
#ifndef __SHARDING_PARSER_H__
#define __SHARDING_PARSER_H__
#include "network-mysqld.h"
#include "sharding-query-plan.h"
#include "sql-context.h"
#define USE_NON_SHARDING_TABLE 0 /* default */
#define USE_SHARDING 1 /* default */
#define USE_DIS_TRAN 2
#define USE_ANY_SHARDINGS 3 /* default */
#define USE_ALL_SHARDINGS 4 /* default */
#define USE_ALL 5 /* default */
#define USE_SAME 6
#define USE_PREVIOUS_WARNING_CONN 7
#define USE_NONE 8
#define USE_PREVIOUS_TRAN_CONNS 9
#define ERROR_UNPARSABLE -1
NETWORK_API int sharding_parse_groups(GString *, sql_context_t *, query_stats_t *,
unsigned int, sharding_plan_t *);
NETWORK_API GString *sharding_modify_sql(sql_context_t *, having_condition_t *);
NETWORK_API void sharding_filter_sql(sql_context_t *);
#endif //__SHARDING_PARSER_H__

View File

@ -0,0 +1,25 @@
#!/bin/sh
#
# wrapper script for self-contained packaging
#
# we assume that this script is located in
#
# {basedir}/bin/cetus
#
# and wants to call
#
# {basedir}/libexec/cetus
#
# You can inject a wrapper like
#
# $ MYSQL_PROXY_WRAPPER=valgrind ./bin/cetus
#
scriptdir=`dirname $0`
scriptname=`basename $0`
scriptdir=`(cd "$scriptdir/"; pwd)`
scriptdir=`dirname "$scriptdir"`
@DYNLIB_PATH_VAR@="$@DYNLIB_PATH_VAR@:$scriptdir/lib/"
export @DYNLIB_PATH_VAR@
exec $MYSQL_PROXY_WRAPPER "$scriptdir/libexec/$scriptname" "$@"

949
scripts/proxy-client Executable file
View File

@ -0,0 +1,949 @@
#!/usr/bin/env python
# coding: utf-8
###
# A script console for cetus.
# For changing proxy config on remote server.
###
import os
import sys
import re
import traceback
import MySQLdb
import json
###
# DB settings
#
# Please EDIT this before running
###
HOST = '10.238.7.6'
PORT = 3306
USER = 'root'
PASSWD = '123456'
DB = 'proxy_management'
conn = None
PROXY_ID = None
BACKENDS_NDX_MAP = {}
# a pattern for check IP format
IP_RE_PATTERN = ('^(?:[1-9]?\d|1\d{2}|2(?:[0-4]\d|5[0-5]))'
'(?:\.(?:[1-9]?\d|1\d{2}|2(?:[0-4]\d|5[0-5]))){3}$')
# Print msg to console with color
def print_console(msg):
print('{0}{1}{2}'.format('\033[1;32m', msg, '\033[0m'))
def strwide(msg):
return '\n{0}\n'.format(msg)
def print_console_wide(msg):
print_console(strwide(msg))
# The decoration for reconnecting db
def reconnect(f):
def deco(*args, **kwargs):
global conn
if not conn:
conn = connect_db(HOST, PORT, USER, PASSWD, DB)
return f(*args, **kwargs)
return deco
# Connect to database
def connect_db(host, port, user, passwd, db):
return MySQLdb.Connection(
host=host,
port=port,
user=user,
passwd=passwd,
db=db,
charset='utf8')
# Select data
@reconnect
def query_data(sql):
global conn
cur = conn.cursor()
cur.execute(sql)
data = cur.fetchall()
cur.close()
return data
@reconnect
def query(sql):
global conn
cur = conn.cursor()
ret = cur.execute(sql)
conn.commit()
cur.close()
return ret
@reconnect
def querymany(sql, data):
global conn
cur = conn.cursor()
ret = cur.executemany(sql, data)
conn.commit()
cur.close()
return ret
@reconnect
def insertone(sql):
global conn
cur = conn.cursor()
ret = 0
try:
cur.execute(sql)
ret = cur.lastrowid
except MySQLdb.Error:
pass
finally:
conn.commit()
cur.close()
return ret
def gen_backend_ndx():
global BACKENDS_NDX_MAP
BACKENDS_NDX_MAP = {}
# Process user's command
def run_cmd(cmd):
global PROXY_ID
global BACKENDS_NDX_MAP
###
# Show simple settings for proxy to start
###
if cmd == 'show':
cmap = {}
data = query_data('select proxy_id,s_key,s_value from settings')
max_key = 0
for line in data:
id, k, v = line
if not id in cmap:
cmap[id] = {}
if not k in cmap[id]:
cmap[id][k] = []
cmap[id][k].append(v)
if len(k) > max_key:
max_key = len(k)
msg_list = ['']
for ndx in cmap:
msg_list.append('-'*10)
msg_list.append('PROXY_ID: {0}'.format(ndx))
msg_list.append('')
for k in sorted(cmap[ndx].keys()):
i = 0
for v in cmap[ndx][k]:
if i is 0:
msg_list.append('{0}{1}'.format(k.ljust(max_key+3), v))
else:
msg_list.append('{0}{1}'.format(''.ljust(max_key+3), v))
i += 1
msg_list.append('-'*10)
msg_list.append('')
msg = '\n'.join(msg_list)
###
# Show backends info details
###
elif cmd == 'show backends':
data = query_data(
'select a.id, a.proxy_id, a.s_key, a.s_value, b.stat from '
'settings as a, backends as b where a.s_key in '
'("proxy-backend-addresses", "proxy-read-only-backend-addresses")'
' and a.id=b.settings_id')
cmap = {}
BACKENDS_NDX_MAP = {}
ndx_map = {}
for line in data:
id, pxy_id, k, v, st = line
if not pxy_id in BACKENDS_NDX_MAP:
BACKENDS_NDX_MAP[pxy_id] = {}
ndx_map[pxy_id] = 1
BACKENDS_NDX_MAP[pxy_id][str(ndx_map[pxy_id])] = id
if not pxy_id in cmap:
cmap[pxy_id] = []
bk_addr_list = re.split('[@:]', v)
addr = bk_addr_list[0]
port = bk_addr_list[1]
if k == 'proxy-backend-addresses':
tp = 'rw'
else:
tp = 'ro'
cmap[pxy_id].append({'ndx': ndx_map[pxy_id], 'addr':addr, 'port':port, 'type':tp, 'stat':st})
ndx_map[pxy_id] += 1
msg_list = ['']
for pxy_id in cmap:
msg_list.append('-'*10)
msg_list.append('PROXY_ID: {0}'.format(pxy_id))
msg_list.append('')
for backend in cmap[pxy_id]:
msg_list.append('{0}{1}'.format('NDX'.ljust(8), backend['ndx']))
msg_list.append('{0}{1}'.format('ADDR'.ljust(8), backend['addr']))
msg_list.append('{0}{1}'.format('PORT'.ljust(8), backend['port']))
msg_list.append('{0}{1}'.format('TYPE'.ljust(8), backend['type']))
msg_list.append('{0}{1}'.format('STAT'.ljust(8), backend['stat']))
msg_list.append('')
msg_list[-1] = '-'*10
msg_list.append('')
msg = '\n'.join(msg_list)
###
# Show sharding info details
###
elif cmd == 'show sharding':
data = query_data('select proxy_id, shard_db, shard_table, pkey, shard_key_type, '
'method, partitions, logic_shard_num from sharding ')
shard_map = {}
for line in data:
proxy_id = line[0]
if not proxy_id in shard_map:
shard_map[proxy_id] = []
shard_map[proxy_id].append({
'shard_db': line[1],
'shard_table': line[2],
'pkey': line[3],
'shard_key_type': line[4],
'method': line[5],
'partitions': line[6],
'logic_shard_num': line[7]
})
msg_list = ['']
for pid in shard_map:
msg_list.append('PROXY_ID: {0}'.format(pid))
msg_list.append('='*20)
for shard in shard_map[pid]:
if shard['shard_db'] == '_default_group':
msg_list.append('{0}{1}'.format('DEFAULT_GROUP'.ljust(18), shard['partitions']))
elif shard['shard_db'] == '_all_groups':
msg_list.append('{0}{1}'.format('ALL_GROUPS'.ljust(18), shard['partitions']))
else:
msg_list.append('{0}{1}'.format('DB'.ljust(18), shard['shard_db']))
msg_list.append('{0}{1}'.format('TABLE'.ljust(18), shard['shard_table']))
msg_list.append('{0}{1}'.format('KEY'.ljust(18), shard['pkey']))
msg_list.append('{0}{1}'.format('KEYTYPE'.ljust(18), shard['shard_key_type']))
msg_list.append('{0}{1}'.format('METHOD'.ljust(18), shard['method']))
msg_list.append('{0}{1}'.format('PARTITIONS'.ljust(18), shard['partitions']))
msg_list.append('{0}{1}'.format('SHARDNUM'.ljust(18), shard['logic_shard_num']))
msg_list.append('-'*10)
msg_list.append('')
msg = '\n'.join(msg_list)
###
# set global proxy_id, can ignore proxy_id in where clause below.
###
elif cmd.startswith('use '):
try:
PROXY_ID = cmd.split(' ', 1)[1]
msg = strwide('Proxy changed.')
except IndexError:
msg = strwide('Unknown Proxy.')
###
# Add or update settings
###
elif cmd.startswith('add ') or cmd.startswith('update '):
syntax_error = 0
msg = ''
###
# RE Pattern for add/update
# match `update (cap1) set (cap2) where (cap3)`
# Consider a JSON string may contains any character
###
up_pat = ('^\s*(add|update)\s+([a-zA-Z_]+)\s+'
'set\s+(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\4|\d+)\s*'
'(?:,\s*\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\5|\d+)\s*)*)'
'(?:\s+where\s+(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\7|\d+)'
'(?:\s+and\s+\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\8|\d+))*))?\s*$')
res = re.match(up_pat, cmd, re.IGNORECASE)
# Parse update command
if res is None:
syntax_error = 1
else:
c0 = res.group(1)
# settings/backend/sharding
c1 = res.group(2)
# set key/value
c2 = res.group(3)
# where clause, can be None if `use proxy_id` is set
c3 = res.group(6)
pxy = PROXY_ID
set_map = {}
cond_map = {}
# Parse set clause
set_pat = '(?:^|\s+)(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\2|\d+))(?:\s+|,|$)'
for set_group in re.findall(set_pat, c2):
set_item = set_group[0]
k, v = set_item.split('=', 1)
k = k.strip().lower()
v = v.strip()
# If string is in single/double quotation, remove them
if re.match(r'^(\'|").*?\1$', v):
v = v[1:-1]
set_map[k] = v
# Parse where clause if exists
if c3 is not None:
where_pat = '(?:^|\s+)(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\2|\d+))(?:\s+|$)'
for where_group in re.findall(where_pat, c3):
where_item = where_group[0]
k, v = where_item.split('=')
k = k.strip().lower()
v = v.strip()
if re.match(r'^(\'|").*?\1$', v):
v = v[1:-1]
cond_map[k] = v
if 'proxy_id' in cond_map:
pxy = cond_map['proxy_id']
# proxy_id should be set in where clause or use
if not pxy:
msg = strwide('`PROXY_ID` need to be specified')
elif c0 == 'add':
r = 0
if c1 == 'settings':
for k, v in set_map.items():
if not k in ['plugins', 'proxy-backend-addresses',
'proxy-read-only-backend-addresses']:
exist = query_data(
('select id from settings where proxy_id="{0}"'
' and s_key="{1}"').format(pxy, k))
if exist:
print_console('"{0}" currently exists, use `update` instead.')
continue
r += query(
('insert into settings set proxy_id="{0}", s_key="{1}",'
's_value="{2}"').format(pxy, k, v))
elif c1 == 'backend':
if not 'addr' in set_map:
return strwide('"addr" is needed.')
bk_addr = set_map['addr']
if not re.match(IP_RE_PATTERN, bk_addr):
return strwide('IP format error.')
bk_port = set_map.get('port', '3306')
if int(bk_port) > 65535 or int(bk_port) < 0:
return strwide('PORT format error.\nShould be integer in 0~65535')
bk_type = set_map.get('type', 'ro')
if not bk_type in ['ro', 'rw']:
return strwide("TYPE format error.\nShould be 'rw' or 'ro'.")
bk_stat = set_map.get('stat', 'unknown')
if not bk_stat in ['unknown', 'up', 'down', 'delete', 'maintaining']:
return strwide("STAT format error.\n"
"Should be 'unknown', 'up', 'down', 'delete' or 'maintaining'")
bk_shard = set_map.get('shard_group', None)
exist = query_data(
('select a.s_key, a.s_value, b.stat from settings as a, backends as b'
' where s_key in ("proxy-backend-addresses", "proxy-read-only-backend-addresses")'
' and a.id=b.settings_id and proxy_id="{0}"').format(pxy))
bk_full_addr = '{0}:{1}'.format(bk_addr, bk_port)
if bk_shard:
bk_full = '{0}@{1}'.format(bk_full_addr, bk_shard)
else:
bk_full = bk_addr_list
for bk in exist:
if bk[1] == bk_full_addr or bk[1].startswith('{0}@'.format(bk_full_addr)):
return strwide('backend "{0}:{1}" exists'.format(bk_addr, bk_port))
if bk_type == 'rw':
bk_key = 'proxy-backend-addresses'
else:
bk_key = 'proxy-read-only-backend-addresses'
last_id = insertone(
('insert into settings set proxy_id="{0}", '
's_key="{1}", s_value="{2}"').format(pxy, bk_key, bk_full))
if last_id:
r += 1
if insertone('insert into backends set settings_id={0}, stat="{1}"'.format(
last_id, bk_stat)):
r += 1
else:
query('delete from settings where id={0}'.format(last_id))
print_console('Insert failed. Dirty data exists in table backends.')
elif c1 == 'sharding':
shard_keys = set_map.keys()
for k in ['default_group', 'all_groups']:
if k in shard_keys:
shard_keys.remove(k)
real_k = '_' + k
v = set_map[k]
exist = query_data(
('select id from sharding where proxy_id="{0}" and '
'shard_db="{1}"').format(pxy, real_k))
try:
json.loads(v)
except ValueError:
print_console('"{0}" value should be in JSON format.'.format(real_k))
continue
if exist:
print_console('"{0}" exists, skip.'.format(real_k))
continue
in_sql = ('insert into sharding set proxy_id="{0}",'
'shard_db="{1}", shard_table="{1}", partitions="{2}"'
).format(pxy, real_k, v.replace('"', '\\"'))
r += query(in_sql)
# Check if all params is set.
if shard_keys:
for kk in ['db', 'table', 'key', 'keytype', 'method',
'partitions', 'shardnum']:
if not kk in set_map:
return strwide('Lack parameter "{0}".'.format(kk))
if not set_map['keytype'] in ['int', 'str', 'datetime', 'date']:
return strwide('"keytype" should be [int|str|date|datetime].')
if not set_map['method'] in ['range', 'hash']:
return strwide('"method" should be hash or range.')
try:
json.loads(set_map['partitions'])
except ValueError:
return strwide('"partitions" should be in JSON format.')
set_map['partitions'] = set_map['partitions'].replace('"', '\\"')
r += query(
('insert into sharding set proxy_id="{0}", shard_db="{1[db]}",'
'shard_table="{1[table]}", pkey="{1[key]}", shard_key_type='
'"{1[keytype]}", method="{1[method]}", partitions="{1[partitions]}",'
'logic_shard_num="{1[shardnum]}"').format(pxy, set_map))
else:
syntax_error = 1
print_console_wide('{0} rows added.'.format(r))
elif c0 == 'update':
r = 0
# Set normal start config
if c1 == 'settings':
for k, v in set_map.items():
exist = query_data(('select id from settings where proxy_id="{0}"'
' and s_key="{1}"').format(pxy, k))
if exist and exist[0]:
r += query('update settings set s_value="{0}" where id={1}'\
.format(v, exist[0][0]))
else:
print_console('"{0}" does not exist, use `add settings` instead.')
print_console_wide('{0} rows changed.'.format(r))
# Set backends config
elif c1 == 'backend':
# `show backends` should be run before update
# so that backend ndx will be create
if not BACKENDS_NDX_MAP:
msg = strwide('Please type `show backends` to confirm backend index.')
elif not 'ndx' in cond_map:
msg = strwide("'ndx' should be specified.")
else:
try:
backend = query_data((
'select a.s_key, a.s_value, b.stat from settings as a,'
'backends as b where a.id={0} and a.id=b.settings_id'
).format(BACKENDS_NDX_MAP[pxy][cond_map['ndx']]))
except IndexError:
return strwide('PROXY_ID or NDX error.')
if backend and backend[0]:
bk_spl = re.split('[@:]', backend[0][1])
bk_addr = bk_spl[0]
bk_port = bk_spl[1]
if len(bk_spl) is 3:
bk_shard = bk_spl[2]
else:
bk_shard = None
if backend[0][0] == 'proxy-backend-addresses':
bk_type = 'rw'
else:
bk_type = 'ro'
bk_stat = backend[0][2]
else:
return strwide('Backend info error.\n'
'Retry `show backends` and update again.')
for k, v in set_map.items():
if k == 'addr':
if not re.match(IP_RE_PATTERN, v):
return strwide('IP format error.')
bk_addr = v
elif k == 'port':
try:
new_port = int(v)
if new_port > 65535:
raise ValueError
except ValueError:
return strwide('PORT format error.\nShould be integer in 0~65535')
bk_port = v
elif k == 'type':
if v != 'rw' and v != 'ro':
print v
return strwide("TYPE format error.\nShould be 'rw' or 'ro'.")
bk_type = v
elif k == 'stat':
if not v in ['unknown', 'up', 'down', 'delete', 'maintaining']:
return strwide("STAT format error.\n"
"Should be 'unknown', 'up', 'down', 'delete' or 'maintaining'")
bk_stat = v
elif k == 'shard_group':
bk_shard = v
else:
return strwide("Backends setting key should be either 'addr', 'port', 'type' or 'stat'.")
if bk_shard:
new_bk = '{0}:{1}@{2}'.format(bk_addr, bk_port, bk_shard)
else:
new_bk = ':'.join([bk_addr, bk_port])
if bk_type == 'rw':
bk_key = 'proxy-backend-addresses'
else:
bk_key = 'proxy-read-only-backend-addresses'
r += query('update settings set s_key="{0}", s_value="{1}" where id={2}'.format(
bk_key, new_bk, BACKENDS_NDX_MAP[pxy][cond_map['ndx']]))
r += query('update backends set stat="{0}" where settings_id={1}'.format(
bk_stat, BACKENDS_NDX_MAP[pxy][cond_map['ndx']]))
print_console_wide('{0} rows changed.'.format(r))
msg_list = ['']
msg_list.append('PROXY_ID: {0}'.format(pxy))
msg_list.append('')
msg_list.append('{0}{1}'.format('NDX'.ljust(8), cond_map['ndx']))
msg_list.append('{0}{1}'.format('ADDR'.ljust(8), bk_addr))
msg_list.append('{0}{1}'.format('PORT'.ljust(8), bk_port))
msg_list.append('{0}{1}'.format('TYPE'.ljust(8), bk_type))
msg_list.append('{0}{1}'.format('STAT'.ljust(8), bk_stat))
msg_list.append('')
msg = '\n'.join(msg_list)
elif c1 == 'sharding':
# shard settings for table need 'db' and 'table'
shard_keys = set_map.keys()
try:
shard_keys.remove('default_group')
shard_keys.remove('all_groups')
except ValueError:
pass
if shard_keys and (not 'db' in cond_map or not 'table' in cond_map):
return strwide('"db" and "table" are needed in where clause')
# set 'default_group' and 'all_groups'
if 'default_group' in set_map:
r += query(('update sharding set partitions="{0}" where proxy_id="{1}"'
' and shard_db="_default_group"').format(
set_map['default_group'].replace('"', '\\"'), pxy))
if 'all_groups' in set_map:
r += query(('update sharding set partitions="{0}" where proxy_id="{1}"'
' and shard_db="_all_groups"').format(
set_map['all_groups'].replace('"', '\\"'), pxy))
# Normal shard settings
for k in shard_keys:
exist = query_data(('select id from sharding where proxy_id={0}'
' and shard_db="{1}" and shard_table="{2}"').format(
pxy, cond_map['db'], cond_map['table']))
if not exist or not exist[0]:
return strwide('shard rule does not exists, use "add sharding" instead')
rule_id = exist[0][0]
shard_set_list = []
for k, v in set_map.items():
if k == 'db':
shard_set_list.append('shard_db="{0}"'.format(v))
elif k == 'table':
shard_set_list.append('shard_table="{0}"'.format(v))
elif k == 'key':
shard_set_list.append('pkey="{0}"'.format(v))
elif k == 'keytype':
if not v in ['int', 'str', 'datetime', 'date']:
return strwide('"keytype" should be [int|str|date|datetime]')
shard_set_list.append('shard_key_type="{0}"'.format(v))
elif k == 'method':
if not v in ['range', 'hash']:
return strwide('"method" should be hash or range')
shard_set_list.append('method="{0}"'.format(v))
elif k == 'partitions':
try:
json.loads(v)
except ValueError:
return strwide('"partitions" should be a json string')
shard_set_list.append('partitions="{0}"'.format(v.replace('"', '\\"')))
r += query('update sharding set {0} where id={id}'.format(
','.join(shard_set_list), id=rule_id))
print_console_wide('{0} rows changed.'.format(r))
else:
syntax_error = 1
if syntax_error is 1:
msg = strwide('Syntax error with command `{0}`').format(cmd)
###
# delete settings, include sharding and backend
###
elif cmd.startswith('remove '):
syntax_error = 0
msg = ''
r = 0
rm_pat = ('^\s*remove\s+([a-zA-Z_]+)\s+'
'where\s+(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\3|\d+)'
'(?:\s+and\s+\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\4|\d+))*)\s*$')
res = re.match(rm_pat, cmd, re.IGNORECASE)
if res is None:
syntax_error = 1
else:
c1 = res.group(1)
c2 = res.group(2)
pxy = PROXY_ID
cond_map = {}
# Parse where clause
where_pat = '(?:^|\s+)(\S+\s*=\s*(?:(\'|").*?(?<!\\\\)\\2|\d+))(?:\s+|$)'
for where_group in re.findall(where_pat, c2):
where_item = where_group[0]
k, v = where_item.split('=')
k = k.strip().lower()
v = v.strip()
if re.match(r'^(\'|").*?\1$', v):
v = v[1:-1]
cond_map[k] = v
if 'proxy_id' in cond_map:
pxy = cond_map['proxy_id']
if not pxy:
msg = strwide('`PROXY_ID` need to be specified')
else:
if c1 == 'settings':
for k, v in cond_map.items():
del_sql = 'delete from settings where proxy_id="{0}"'.format(pxy)
if 'key' in cond_map:
del_sql += ' and s_key="{0}"'.format(cond_map['key'])
if 'value' in cond_map:
del_sql += ' and s_value="{0}"'.format(cond_map['value'])
r += query(del_sql)
elif c1 == 'backend':
if not BACKENDS_NDX_MAP:
msg = strwide('Please type `show backends` to confirm backend index.')
elif not 'ndx' in cond_map:
msg = strwide("'ndx' should be specified.")
else:
try:
bk_id = BACKENDS_NDX_MAP[pxy][cond_map['ndx']]
r += query('delete from settings where id={0}'.format(bk_id))
r += query('delete from backends where settings_id={0}'
.format(bk_id))
except IndexError:
return strwide('PROXY_ID or NDX error.')
elif c1 == 'sharding':
if not 'db' in cond_map or not 'table' in cond_map:
return strwide('"db" and "table" must be specified.')
del_sql = ('delete from sharding where proxy_id="{0}"'
' and shard_db="{1}" and shard_table="{2}"'.format(
pxy, cond_map['db'], cond_map['table']))
r += query(del_sql)
else:
syntax_error = 1
if syntax_error is 1:
msg = strwide('Syntax error with command `{0}`').format(cmd)
else:
print_console_wide('{0} rows deleted.'.format(r))
###
# Set a flag in mysql so that proxy will reload when detect
###
elif cmd.startswith('apply ') or cmd == 'apply':
pxy = PROXY_ID
try:
proxy_id = cmd.split()[1].strip('\'" ')
except IndexError:
proxy_id = None
if proxy_id:
pxy = proxy_id
if not pxy:
return strwide('proxy_id should be specified.')
query('update switch set apply_switch=1 where proxy_id="{0}"'.format(pxy))
msg = strwide('Apply config success.\nProxy will reload in a short while.')
###
# Initialize new proxy settings
###
elif cmd.startswith('init '):
proxy_id = cmd.split()[1]
if not proxy_id:
return strwide('No `proxy_id` specified.')
try:
r = init_new(proxy_id)
except EOFError:
print_console('User quit.')
return ''
if r is 0:
print_console('User quit.')
return ''
msg = strwide('Init complete.')
###
# Delete proxy settings
###
elif cmd.startswith('delete '):
proxy_id = cmd.split()[1]
r = delete_one(proxy_id)
if r:
msg = strwide('Delete done.')
else:
msg = ''
###
# Create tables which store settings if not exist
###
elif cmd == 'create tables':
if create_tables() is 0:
msg = 'Create tables DONE!'
else:
msg = 'Error!'
###
# Show help message
###
elif cmd == 'h' or cmd == 'help':
msg = show_help()
###
# Quit script
###
elif cmd == 'q' or cmd == 'quit':
#sys.exit()
raise EOFError
###
# Unrecognized command
###
else:
msg = strwide('Unkonwn command `{0}`.\n'
'Type `help` to see command list.').format(cmd)
return msg
# Init settings for a proxy instance
def init_new(id):
init = init_fetchone('Init {0}? [y/N]: '.format(id), 'n')
if not init:
return 0
settings = {}
settings['proxy-address'] = init_fetchone(
'proxy-address=? [127.0.0.1:4440]: ', '127.0.0.1:4440', False)
settings['proxy-backend-addresses'] = init_fetchmulti(
'proxy-backend-addresses=? [127.0.0.1:3306]: ', '127.0.0.1:3306')
settings['log-file'] = init_fetchone('log-file=? []: ', '', False)
settings['plugin-dir'] = init_fetchone('plugin-dir=? []: ', '', False)
admin = init_fetchone('Set admin? [Y/n]: ', 'y', )
if admin:
plugins = ['admin', 'proxy']
settings['admin-address'] = init_fetchone(
'admin-address=? [127.0.0.1:4441]: ', '127.0.0.1:4441', False)
settings['admin-lua-script'] = init_fetchone('admin-lua-script=? []: ', '', False)
settings['admin-username'] = init_fetchone(
'admin-username=? [admin]: ', 'admin', False)
settings['admin-password'] = init_fetchone(
'admin-password=? [admin_pass]: ', 'admin_pass', False)
else:
plugins = ['proxy']
settings['plugins'] = plugins
settings['default-username'] = init_fetchone('default-username=? [root]: ', 'root', False)
settings['default-db'] = init_fetchone('default-db=? [test]: ', 'test', False)
settings['default-pool-size'] = init_fetchone(
'default-pool-size=? [50]: ', '50', False, check_func=check_int)
settings['user-pwd'] = init_fetchone('user-pwd=? [root@123456]: ', 'root@123456', False)
settings['auto-connect'] = init_fetchone('auto-connect=? [Y/n]: ', 'y')
settings['app-user-pwd'] = init_fetchone('app-user-pwd=? [root@123]: ', 'root@123', False)
settings['sharding-mode'] = init_fetchone('sharding-mode=? [y/N]: ', 'n')
settings['log-level'] = init_fetchone('log-level=? [debug]: ', 'debug', False)
print_console('\n'+'-'*20)
print_console('Init confirmation:')
query_data = []
for k in sorted(settings.keys()):
v = settings[k]
print('{0}={1}'.format(k, v))
if isinstance(v, list):
for v_obj in v:
query_data.append((k, v_obj))
else:
query_data.append((k, v))
print_console('-'*20+'\n')
if not init_fetchone('Continue? [Y/n]: ', 'y'):
return 0
init_sql = 'insert into settings set proxy_id="{0}", s_key=%s, s_value=%s'.format(id)
querymany(init_sql, query_data)
# create switch
query('insert into switch set proxy_id="{0}", apply_switch=0'.format(id))
return
def init_fetchone(intro, default, yn=True, check_func=None):
while 1:
r = raw_input(intro)
if r == '':
r = default
if yn:
r = r.lower()
if r == 'y':
return 1
elif r == 'n':
return 0
else:
print('Input error, type `y` or `n`')
continue
else:
if check_func is not None and not check_func(r):
print('Value error.')
continue
return r
def init_fetchmulti(intro, default):
ret = []
while 1:
r = raw_input(intro)
if r == '':
r = default
ret.append(r)
r_next = init_fetchone('Another one? [y/N]: ', 'n')
if not r_next:
break
return ret
def check_int(n):
try:
int(n)
except ValueError:
print('Integer is needed')
return 0
return 1
# Delete settings for ONE proxy
def delete_one(id):
r = init_fetchone('Delete {0}? (Cannot recover) [y/N]: ', 'n')
if r:
ret = query('delete from settings where proxy_id={0}'.format(id))
print_console_wide('{0} rows changed.'.format(ret))
return r
# Show prompt message
def show_prompt():
print_console_wide('### Welcome to Mysql-Proxy Console. ###')
# Create db tables
def create_tables():
csql = []
csql.append(
'CREATE TABLE IF NOT EXISTS `settings` ('
'`id` int(11) NOT NULL AUTO_INCREMENT,'
'`proxy_id` varchar(64) NOT NULL,'
'`s_key` varchar(64) NOT NULL,'
'`s_value` varchar(255) NOT NULL,'
'PRIMARY KEY (`id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
csql.append(
'CREATE TABLE IF NOT EXISTS `sharding` ('
'`id` int(11) NOT NULL AUTO_INCREMENT,'
'`proxy_id` varchar(64) NOT NULL,'
'`shard_db` varchar(32) NOT NULL,'
'`shard_table` varchar(64) NOT NULL,'
'`pkey` varchar(32) DEFAULT NULL,'
"`shard_key_type` enum('int','str','datetime') DEFAULT NULL,"
"`method` enum('hash','range') DEFAULT NULL,"
'`partitions` varchar(255) NOT NULL,'
'`logic_shard_num` int(11) DEFAULT NULL,'
'PRIMARY KEY (`id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
csql.append(
'CREATE TABLE IF NOT EXISTS `backends` ('
'`id` int(11) NOT NULL AUTO_INCREMENT,'
'`settings_id` int(11) DEFAULT NULL,'
'`stat` varchar(32) NOT NULL,'
'PRIMARY KEY (`id`),'
'UNIQUE KEY `settings_id` (`settings_id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
csql.append(
'CREATE TABLE IF NOT EXISTS `switch` ('
'`proxy_id` varchar(64) NOT NULL,'
"`apply_switch` tinyint(4) NOT NULL DEFAULT '0',"
'UNIQUE KEY `proxy_id` (`proxy_id`)'
') ENGINE=InnoDB DEFAULT CHARSET=utf8')
# Ignore table exists warning
from warnings import filterwarnings
filterwarnings('ignore', category=MySQLdb.Warning)
for sql in csql:
query(sql)
return 0
# Show help message
def show_help():
return '''
List all commands:
show Show all config
show backends Show backends config
show sharding Show sharding config
add settings set [option]=[value], ... where proxy_id=[id]
update settings set [key]=[value] where proxy_id=[id]
remove settings where proxy_id=[id] and key=[value] and value=[value]
add backend set [option]=[value], ... where proxy_id=[id]
update backend set [option]=[value], ... where proxy_id=[id] and ndx=[index]
remove backend where proxy_id=[id] and ndx=[index]
add sharding set [option]=[value], ... where proxy_id=[id]
update sharding set [option]=[value], ... where proxy_id=[id] and db=[value] and table=[value]
remove sharding where proxy_id=[id] and db=[value] and table=[value]
init [proxy_id] Init config for `proxy_id`
use [proxy_id] Can ignore "where proxy_id=..." at follow when add/update/remove
delete [proxy_id] Delete config for `proxy_id`
apply [proxy_id] Apply config to a proxy
create tables Create tables if not exist on server
help Show this message
quit exit
--------
Backend option
addr IP address of mysql
port Port of mysql
type read only or read write [rw|ro]
stat status of backend [unknown|up|down|delete|maintaining]
shard_group sharding group name
----
Sharding option
db database name
table table name
key shard column
keytype type of key, [int|str|datetime|...]
method range or hash [range|hash]
partitions detail of sharding
shardnum total number of shard partitions
default_group default sharding group
all_groups all groups list
'''
if __name__ == '__main__':
show_prompt()
try:
while 1:
cmd = raw_input('\033[1;32m>\033[0m ')
cmd = cmd.strip().lower().rstrip(';')
if not cmd:
continue
print_console(run_cmd(cmd))
except EOFError:
print_console_wide('### Good Bye! ###')
except:
traceback.print_exc()
print_console_wide('Error detected! Exit.')
finally:
if conn:
conn.close()

188
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,188 @@
# $%BEGINLICENSE%$
# Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 of the
# License.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
# 02110-1301 USA
#
# $%ENDLICENSE%$
INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}) # for config.h
INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR})
include_directories(${PROJECT_SOURCE_DIR}/lib)
include_directories(${PROJECT_BINARY_DIR}/lib)
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR})
INCLUDE_DIRECTORIES(${GLIB_INCLUDE_DIRS})
LINK_DIRECTORIES(${GLIB_LIBRARY_DIRS})
INCLUDE_DIRECTORIES(${MYSQL_INCLUDE_DIRS})
LINK_DIRECTORIES(${MYSQL_LIBRARY_DIRS})
INCLUDE_DIRECTORIES(${EVENT_INCLUDE_DIRS})
LINK_DIRECTORIES(${EVENT_LIBRARY_DIRS})
LINK_DIRECTORIES(${LIBINTL_LIBRARY_DIRS})
STRING(REPLACE "." "" SHARED_LIBRARY_SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX})
ADD_DEFINITIONS(-DSHARED_LIBRARY_SUFFIX="${SHARED_LIBRARY_SUFFIX}")
SET(chassis_sources
chassis-plugin.c
chassis-event.c
chassis-log.c
chassis-mainloop.c
chassis-shutdown-hooks.c
chassis-keyfile.c
chassis-path.c
chassis-filemode.c
chassis-limits.c
chassis-frontend.c
chassis-options.c
chassis-unix-daemon.c
chassis-config.c
cJSON.c
)
SET(timing_sources
chassis-timings.c
)
SET(glibext_sources
glib-ext.c
)
SET(proxy_sources
network-mysqld.c
network-mysqld-proto.c
network-mysqld-packet.c
network-conn-pool.c
network-conn-pool-wrap.c
network-queue.c
network-socket.c
network-address.c
network-injection.c
resultset_merge.c
cetus-log.c
plugin-common.c
network-backend.c
sharding-config.c
sharding-query-plan.c
shard-plugin-con.c
character-set.c
server-session.c
network-compress.c
cetus-users.c
cetus-util.c
cetus-variable.c
cetus-monitor.c
)
if(NETWORK_DEBUG_TRACE_STATE_CHANGES)
list(APPEND proxy_sources cetus-query-queue.c)
endif(NETWORK_DEBUG_TRACE_STATE_CHANGES)
ADD_LIBRARY(mysql-chassis SHARED ${chassis_sources})
ADD_LIBRARY(mysql-chassis-proxy SHARED ${proxy_sources})
ADD_LIBRARY(mysql-chassis-glibext SHARED ${glibext_sources})
ADD_LIBRARY(mysql-chassis-timing SHARED ${timing_sources})
ADD_EXECUTABLE(cetus mysql-proxy-cli.c)
if(SIMPLE_PARSER)
target_compile_definitions(cetus PRIVATE DEFAULT_PLUGIN="proxy")
else(SIMPLE_PARSER)
target_compile_definitions(cetus PRIVATE DEFAULT_PLUGIN="shard")
endif(SIMPLE_PARSER)
TARGET_LINK_LIBRARIES(mysql-chassis-glibext
${GLIB_LIBRARIES}
${GMODULE_LIBRARIES}
${GTHREAD_LIBRARIES}
)
TARGET_LINK_LIBRARIES(mysql-chassis-timing
${GLIB_LIBRARIES}
${GMODULE_LIBRARIES}
${GTHREAD_LIBRARIES}
mysql-chassis-glibext
)
TARGET_LINK_LIBRARIES(mysql-chassis
${GLIB_LIBRARIES}
${GMODULE_LIBRARIES}
${GTHREAD_LIBRARIES}
${EVENT_LIBRARIES}
${MYSQL_LIBRARIES}
mysql-chassis-timing
mysql-chassis-glibext
)
TARGET_LINK_LIBRARIES(mysql-chassis-proxy
mysql-chassis
sqlparser
mysql-chassis-glibext
mysql-chassis-timing
${OPENSSL_LIBRARIES}
)
TARGET_LINK_LIBRARIES(cetus
mysql-chassis
mysql-chassis-proxy
mysql-chassis-glibext
mysql-chassis-timing
${GLIB_LIBRARIES}
${GMODULE_LIBRARIES}
${GTHREAD_LIBRARIES}
${EVENT_LIBRARIES}
${MYSQL_LIBRARIES}
)
# Unix platforms provide a wrapper script to avoid relinking at install time
# figure out the correct name of the shared linker lookup path for this system, default to LD_LIBRARY_PATH
SET(DYNLIB_PATH_VAR "LD_LIBRARY_PATH")
IF("AIX" STREQUAL ${CMAKE_SYSTEM_NAME})
SET(DYNLIB_PATH_VAR "LIBPATH")
ENDIF("AIX" STREQUAL ${CMAKE_SYSTEM_NAME})
IF("HP-UX" STREQUAL ${CMAKE_SYSTEM_NAME})
SET(DYNLIB_PATH_VAR "SHLIB_PATH")
ENDIF("HP-UX" STREQUAL ${CMAKE_SYSTEM_NAME})
IF(APPLE)
SET(DYNLIB_PATH_VAR "DYLD_LIBRARY_PATH")
ENDIF(APPLE)
# write the wrapper script, which uses DYNLIB_PATH_VAR
CONFIGURE_FILE(
${CMAKE_SOURCE_DIR}/scripts/cetus-binwrapper.in
${PROJECT_BINARY_DIR}/cetus.sh
@ONLY
)
INSTALL(FILES ${PROJECT_BINARY_DIR}/cetus.sh
DESTINATION bin/
PERMISSIONS OWNER_EXECUTE OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ
RENAME cetus
)
INSTALL(TARGETS cetus
RUNTIME DESTINATION libexec
)
CHASSIS_INSTALL_TARGET(mysql-chassis)
CHASSIS_INSTALL_TARGET(mysql-chassis-proxy)
CHASSIS_INSTALL_TARGET(mysql-chassis-glibext)
CHASSIS_INSTALL_TARGET(mysql-chassis-timing)

724
src/cJSON.c Normal file
View File

@ -0,0 +1,724 @@
#include <string.h>
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <float.h>
#include <limits.h>
#include <ctype.h>
#include "cJSON.h"
static const char *ep;
const char *cJSON_GetErrorPtr(void) {return ep;}
static int cJSON_strcasecmp(const char *s1,const char *s2)
{
if (!s1) return (s1==s2)?0:1;if (!s2) return 1;
for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if (*s1 == 0) return 0;
return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2);
}
static void *(*cJSON_malloc)(size_t sz) = malloc;
static void (*cJSON_free)(void *ptr) = free;
static char *cJSON_strdup(const char *str)
{
size_t len;
char *copy;
len = strlen(str) + 1;
if (!(copy = (char *)cJSON_malloc(len))) return 0;
memcpy(copy,str,len);
return copy;
}
void cJSON_InitHooks(cJSON_Hooks *hooks)
{
if (!hooks) { /* Reset hooks */
cJSON_malloc = malloc;
cJSON_free = free;
return;
}
cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc;
cJSON_free = (hooks->free_fn)?hooks->free_fn:free;
}
/* Internal constructor. */
static cJSON *cJSON_New_Item(void)
{
cJSON *node = (cJSON *)cJSON_malloc(sizeof(cJSON));
if (node) memset(node,0,sizeof(cJSON));
return node;
}
/* Delete a cJSON structure. */
void cJSON_Delete(cJSON *c)
{
cJSON *next;
while (c)
{
next=c->next;
if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child);
if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring);
if (!(c->type&cJSON_StringIsConst) && c->string) cJSON_free(c->string);
cJSON_free(c);
c=next;
}
}
/* Parse the input text to generate a number, and populate the result into item. */
static const char *parse_number(cJSON *item,const char *num)
{
double n=0,sign=1,scale=0;int subscale=0,signsubscale=1;
if (*num == '-') sign=-1,num++; /* Has sign? */
if (*num == '0') num++; /* is zero */
if (*num>='1' && *num<='9') do n=(n *10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */
if (*num == '.' && num[1]>='0' && num[1]<='9') {num++; do n=(n *10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */
if (*num == 'e' || *num == 'E') /* Exponent? */
{ num++;if (*num == '+') num++; else if (*num == '-') signsubscale=-1,num++; /* With sign? */
while (*num>='0' && *num<='9') subscale=(subscale *10)+(*num++ - '0'); /* Number? */
}
n=sign *n *pow(10.0,(scale+subscale *signsubscale)); /* number = +/- number.fraction *10^+/- exponent */
item->valuedouble=n;
item->valueint=(int)n;
item->type=cJSON_Number;
return num;
}
static int pow2gt (int x) { --x; x|=x>>1; x|=x>>2; x|=x>>4; x|=x>>8; x|=x>>16; return x+1; }
typedef struct {char *buffer; int length; int offset; } printbuffer;
static char *ensure(printbuffer *p,int needed)
{
char *newbuffer;int newsize;
if (!p || !p->buffer) return NULL;
needed += p->offset;
if (needed <= p->length) return p->buffer+p->offset;
newsize = pow2gt(needed);
newbuffer = (char *) cJSON_malloc(newsize);
if (!newbuffer) {cJSON_free(p->buffer); p->length=0, p->buffer=0; return NULL;}
if (newbuffer) memcpy(newbuffer, p->buffer, p->length);
cJSON_free(p->buffer);
p->length = newsize;
p->buffer = newbuffer;
return (char *) (newbuffer + p->offset);
}
static int update(printbuffer *p)
{
char *str;
if (!p || !p->buffer) return 0;
str=p->buffer+p->offset;
return p->offset+strlen(str);
}
/* Render the number nicely from the given item into a string. */
static char *print_number(cJSON *item,printbuffer *p)
{
char *str=0;
double d=item->valuedouble;
if (d == 0)
{
if (p) str=ensure(p,2);
else str=(char *)cJSON_malloc(2); /* special case for 0. */
if (str) strcpy(str,"0");
}
else if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN)
{
if (p) str=ensure(p,21);
else str=(char *)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */
if (str) sprintf(str,"%d",item->valueint);
}
else
{
if (p) str=ensure(p,64);
else str=(char *)cJSON_malloc(64); /* This is a nice tradeoff. */
if (str)
{
if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d);
else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d);
else sprintf(str,"%f",d);
}
}
return str;
}
static unsigned parse_hex4(const char *str)
{
unsigned h=0;
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
h=h<<4;str++;
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
h=h<<4;str++;
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
h=h<<4;str++;
if (*str>='0' && *str<='9') h+=(*str)-'0'; else if (*str>='A' && *str<='F') h+=10+(*str)-'A'; else if (*str>='a' && *str<='f') h+=10+(*str)-'a'; else return 0;
return h;
}
/* Parse the input text into an unescaped cstring, and populate item. */
static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC };
static const char *parse_string(cJSON *item,const char *str)
{
const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2;
if (*str!='\"') {ep=str;return 0;} /* not a string! */
while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */
out=(char *)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */
if (!out) return 0;
ptr=str+1;ptr2=out;
while (*ptr!='\"' && *ptr)
{
if (*ptr!='\\') *ptr2++=*ptr++;
else
{
ptr++;
switch (*ptr)
{
case 'b': *ptr2++='\b'; break;
case 'f': *ptr2++='\f'; break;
case 'n': *ptr2++='\n'; break;
case 'r': *ptr2++='\r'; break;
case 't': *ptr2++='\t'; break;
case 'u': /* transcode utf16 to utf8. */
uc=parse_hex4(ptr+1);ptr+=4; /* get the unicode char. */
if ((uc>=0xDC00 && uc<=0xDFFF) || uc == 0) break; /* check for invalid. */
if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */
{
if (ptr[1]!='\\' || ptr[2]!='u') break; /* missing second-half of surrogate. */
uc2=parse_hex4(ptr+3);ptr+=6;
if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */
uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF));
}
len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len;
switch (len) {
case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6;
case 1: *--ptr2 =(uc | firstByteMark[len]);
}
ptr2+=len;
break;
default: *ptr2++=*ptr; break;
}
ptr++;
}
}
*ptr2=0;
if (*ptr == '\"') ptr++;
item->valuestring=out;
item->type=cJSON_String;
return ptr;
}
/* Render the cstring provided to an escaped version that can be printed. */
static char *print_string_ptr(const char *str,printbuffer *p)
{
const char *ptr;char *ptr2,*out;int len=0,flag=0;unsigned char token;
if (!str)
{
if (p) out=ensure(p,3);
else out=(char *)cJSON_malloc(3);
if (!out) return 0;
strcpy(out,"\"\"");
return out;
}
for (ptr=str;*ptr;ptr++) flag|=((*ptr>0 && *ptr<32)||(*ptr == '\"')||(*ptr == '\\'))?1:0;
if (!flag)
{
len=ptr-str;
if (p) out=ensure(p,len+3);
else out=(char *)cJSON_malloc(len+3);
if (!out) return 0;
ptr2=out;*ptr2++='\"';
strcpy(ptr2,str);
ptr2[len]='\"';
ptr2[len+1]=0;
return out;
}
ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;}
if (p) out=ensure(p,len+3);
else out=(char *)cJSON_malloc(len+3);
if (!out) return 0;
ptr2=out;ptr=str;
*ptr2++='\"';
while (*ptr)
{
if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++;
else
{
*ptr2++='\\';
switch (token=*ptr++)
{
case '\\': *ptr2++='\\'; break;
case '\"': *ptr2++='\"'; break;
case '\b': *ptr2++='b'; break;
case '\f': *ptr2++='f'; break;
case '\n': *ptr2++='n'; break;
case '\r': *ptr2++='r'; break;
case '\t': *ptr2++='t'; break;
default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */
}
}
}
*ptr2++='\"';*ptr2++=0;
return out;
}
/* Invote print_string_ptr (which is useful) on an item. */
static char *print_string(cJSON *item,printbuffer *p) {return print_string_ptr(item->valuestring,p);}
/* Predeclare these prototypes. */
static const char *parse_value(cJSON *item,const char *value);
static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p);
static const char *parse_array(cJSON *item,const char *value);
static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p);
static const char *parse_object(cJSON *item,const char *value);
static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p);
/* Utility to jump whitespace and cr/lf */
static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;}
/* Parse an object - create a new root, and populate. */
cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated)
{
const char *end=0;
cJSON *c=cJSON_New_Item();
ep=0;
if (!c) return 0; /* memory fail */
end=parse_value(c,skip(value));
if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */
/* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */
if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}}
if (return_parse_end) *return_parse_end=end;
return c;
}
/* Default options for cJSON_Parse */
cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);}
/* Render a cJSON item/entity/structure to text. */
char *cJSON_Print(cJSON *item) {return print_value(item,0,1,0);}
char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0,0);}
char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt)
{
printbuffer p;
p.buffer=(char *)cJSON_malloc(prebuffer);
p.length=prebuffer;
p.offset=0;
return print_value(item,0,fmt,&p);
}
/* Parser core - when encountering text, process appropriately. */
static const char *parse_value(cJSON *item,const char *value)
{
if (!value) return 0; /* Fail on null. */
if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; }
if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; }
if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; }
if (*value == '\"') { return parse_string(item,value); }
if (*value == '-' || (*value>='0' && *value<='9')) { return parse_number(item,value); }
if (*value == '[') { return parse_array(item,value); }
if (*value == '{') { return parse_object(item,value); }
ep=value;return 0; /* failure. */
}
/* Render a value to text. */
static char *print_value(cJSON *item,int depth,int fmt,printbuffer *p)
{
char *out=0;
if (!item) return 0;
if (p)
{
switch ((item->type)&255)
{
case cJSON_NULL: {out=ensure(p,5); if (out) strcpy(out,"null"); break;}
case cJSON_False: {out=ensure(p,6); if (out) strcpy(out,"false"); break;}
case cJSON_True: {out=ensure(p,5); if (out) strcpy(out,"true"); break;}
case cJSON_Number: out=print_number(item,p);break;
case cJSON_String: out=print_string(item,p);break;
case cJSON_Array: out=print_array(item,depth,fmt,p);break;
case cJSON_Object: out=print_object(item,depth,fmt,p);break;
}
}
else
{
switch ((item->type)&255)
{
case cJSON_NULL: out=cJSON_strdup("null"); break;
case cJSON_False: out=cJSON_strdup("false");break;
case cJSON_True: out=cJSON_strdup("true"); break;
case cJSON_Number: out=print_number(item,0);break;
case cJSON_String: out=print_string(item,0);break;
case cJSON_Array: out=print_array(item,depth,fmt,0);break;
case cJSON_Object: out=print_object(item,depth,fmt,0);break;
}
}
return out;
}
/* Build an array from input text. */
static const char *parse_array(cJSON *item,const char *value)
{
cJSON *child;
if (*value!='[') {ep=value;return 0;} /* not an array! */
item->type=cJSON_Array;
value=skip(value+1);
if (*value == ']') return value+1; /* empty array. */
item->child=child=cJSON_New_Item();
if (!item->child) return 0; /* memory fail */
value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */
if (!value) return 0;
while (*value == ',')
{
cJSON *new_item;
if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */
child->next=new_item;new_item->prev=child;child=new_item;
value=skip(parse_value(child,skip(value+1)));
if (!value) return 0; /* memory fail */
}
if (*value == ']') return value+1; /* end of array */
ep=value;return 0; /* malformed. */
}
/* Render an array to text */
static char *print_array(cJSON *item,int depth,int fmt,printbuffer *p)
{
char *out=0,*ptr,*ret;int len=5;
cJSON *child=item->child;
int numentries=0,i = 0 ,fail=0;
/* How many entries in the array? */
while (child) numentries++,child=child->next;
/* Explicitly handle numentries == 0 */
if (!numentries)
{
if (p) out=ensure(p,3);
else out=(char *)cJSON_malloc(3);
if (out) strcpy(out,"[]");
return out;
}
if (p)
{
/* Compose the output array. */
i=p->offset;
ptr=ensure(p,1);if (!ptr) return 0; *ptr='['; p->offset++;
child=item->child;
while (child && !fail)
{
print_value(child,depth+1,fmt,p);
p->offset=update(p);
if (child->next) {len=fmt?2:1;ptr=ensure(p,len+1);if (!ptr) return 0;*ptr++=',';if (fmt)*ptr++=' ';*ptr=0;p->offset+=len;}
child=child->next;
}
ptr=ensure(p,2);if (!ptr) return 0; *ptr++=']';*ptr=0;
out=(p->buffer)+i;
}
else
{
/* Allocate an array to hold the values for each */
char **entries=(char **)cJSON_malloc(numentries *sizeof(char *));
if (!entries) return 0;
memset(entries,0,numentries *sizeof(char *));
/* Retrieve all the results: */
child=item->child;
while (child && !fail)
{
ret=print_value(child,depth+1,fmt,0);
entries[i++]=ret;
if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1;
child=child->next;
}
/* If we didn't fail, try to malloc the output string */
if (!fail) out=(char *)cJSON_malloc(len);
/* If that fails, we fail. */
if (!out) fail=1;
/* Handle failure. */
if (fail)
{
for (i = 0 ;i < numentries;i++) if (entries[i]) cJSON_free(entries[i]);
cJSON_free(entries);
return 0;
}
/* Compose the output array. */
*out='[';
ptr=out+1;*ptr=0;
for (i = 0 ;i < numentries;i++)
{
size_t tmplen=strlen(entries[i]);memcpy(ptr,entries[i],tmplen);ptr+=tmplen;
if (i!=numentries-1) {*ptr++=',';if (fmt)*ptr++=' ';*ptr=0;}
cJSON_free(entries[i]);
}
cJSON_free(entries);
*ptr++=']';*ptr++=0;
}
return out;
}
/* Build an object from the text. */
static const char *parse_object(cJSON *item,const char *value)
{
cJSON *child;
if (*value!='{') {ep=value;return 0;} /* not an object! */
item->type=cJSON_Object;
value=skip(value+1);
if (*value == '}') return value+1; /* empty array. */
item->child=child=cJSON_New_Item();
if (!item->child) return 0;
value=skip(parse_string(child,skip(value)));
if (!value) return 0;
child->string=child->valuestring;child->valuestring=0;
if (*value!=':') {ep=value;return 0;} /* fail! */
value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */
if (!value) return 0;
while (*value == ',')
{
cJSON *new_item;
if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */
child->next=new_item;new_item->prev=child;child=new_item;
value=skip(parse_string(child,skip(value+1)));
if (!value) return 0;
child->string=child->valuestring;child->valuestring=0;
if (*value!=':') {ep=value;return 0;} /* fail! */
value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */
if (!value) return 0;
}
if (*value == '}') return value+1; /* end of array */
ep=value;return 0; /* malformed. */
}
/* Render an object to text. */
static char *print_object(cJSON *item,int depth,int fmt,printbuffer *p)
{
char *out=0,*ptr,*ret,*str;int len=7,i = 0 ,j;
cJSON *child=item->child;
int numentries=0;
/* Count the number of entries. */
while (child) numentries++,child=child->next;
/* Explicitly handle empty object case */
if (!numentries)
{
if (p) out=ensure(p,fmt?depth+4:3);
else out=(char *)cJSON_malloc(fmt?depth+4:3);
if (!out) return 0;
ptr=out;*ptr++='{';
if (fmt) {*ptr++='\n';for (i = 0 ;i < depth-1;i++) *ptr++='\t';}
*ptr++='}';*ptr++=0;
return out;
}
if (p)
{
/* Compose the output: */
i=p->offset;
len=fmt?2:1; ptr=ensure(p,len+1); if (!ptr) return 0;
*ptr++='{'; if (fmt) *ptr++='\n'; *ptr=0; p->offset+=len;
child=item->child;depth++;
while (child)
{
if (fmt)
{
ptr=ensure(p,depth); if (!ptr) return 0;
for (j=0;j<depth;j++) *ptr++='\t';
p->offset+=depth;
}
print_string_ptr(child->string,p);
p->offset=update(p);
len=fmt?2:1;
ptr=ensure(p,len); if (!ptr) return 0;
*ptr++=':';if (fmt) *ptr++='\t';
p->offset+=len;
print_value(child,depth,fmt,p);
p->offset=update(p);
len=(fmt?1:0)+(child->next?1:0);
ptr=ensure(p,len+1); if (!ptr) return 0;
if (child->next) *ptr++=',';
if (fmt) *ptr++='\n';*ptr=0;
p->offset+=len;
child=child->next;
}
ptr=ensure(p,fmt?(depth+1):2); if (!ptr) return 0;
if (fmt) for (i = 0 ;i < depth-1;i++) *ptr++='\t';
*ptr++='}';*ptr=0;
out=(p->buffer)+i;
}
else
{
/* Allocate space for the names and the objects */
char **entries=(char **)cJSON_malloc(numentries *sizeof(char *));
if (!entries) return 0;
char **names=(char **)cJSON_malloc(numentries *sizeof(char *));
if (!names) {cJSON_free(entries);return 0;}
memset(entries,0,sizeof(char *)*numentries);
memset(names,0,sizeof(char *)*numentries);
/* Collect all the results into our arrays: */
child=item->child;depth++;if (fmt) len+=depth;
int fail = 0;
while (child)
{
names[i]=str=print_string_ptr(child->string,0);
entries[i++]=ret=print_value(child,depth,fmt,0);
if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1;
child=child->next;
}
/* Try to allocate the output string */
if (!fail) out=(char *)cJSON_malloc(len);
if (!out) fail=1;
/* Handle failure */
if (fail)
{
for (i = 0 ;i < numentries;i++) {if (names[i]) cJSON_free(names[i]);if (entries[i]) cJSON_free(entries[i]);}
cJSON_free(names);cJSON_free(entries);
return 0;
}
/* Compose the output: */
*out='{';ptr=out+1;if (fmt)*ptr++='\n';*ptr=0;
for (i = 0 ;i < numentries;i++)
{
if (fmt) for (j=0;j<depth;j++) *ptr++='\t';
size_t tmplen=strlen(names[i]);memcpy(ptr,names[i],tmplen);ptr+=tmplen;
*ptr++=':';if (fmt) *ptr++='\t';
strcpy(ptr,entries[i]);ptr+=strlen(entries[i]);
if (i!=numentries-1) *ptr++=',';
if (fmt) *ptr++='\n';*ptr=0;
cJSON_free(names[i]);cJSON_free(entries[i]);
}
cJSON_free(names);cJSON_free(entries);
if (fmt) for (i = 0 ;i < depth-1;i++) *ptr++='\t';
*ptr++='}';*ptr++=0;
}
return out;
}
/* Get Array size/item / object item. */
int cJSON_GetArraySize(cJSON *array) {cJSON *c=array->child;int i = 0 ;while(c)i++,c=c->next;return i;}
cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;}
cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;}
/* Utility for array list handling. */
static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;}
/* Utility for handling references. */
static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;}
/* Add item to array/object. */
void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}}
void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);}
void cJSON_AddItemToObjectCS(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (!(item->type&cJSON_StringIsConst) && item->string) cJSON_free(item->string);item->string=(char *)string;item->type|=cJSON_StringIsConst;cJSON_AddItemToArray(object,item);}
void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));}
void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));}
cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0;
if (c->prev) c->prev->next=c->next;if (c->next) c->next->prev=c->prev;if (c == array->child) array->child=c->next;c->prev=c->next=0;return c;}
void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));}
cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i = 0 ;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;}
void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));}
/* Replace array/object items with new ones. */
void cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) {cJSON_AddItemToArray(array,newitem);return;}
newitem->next=c;newitem->prev=c->prev;c->prev=newitem;if (c == array->child) array->child=newitem; else newitem->prev->next=newitem;}
void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return;
newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem;
if (c == array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);}
void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem) {int i = 0 ;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if (c) {newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}}
/* Create basic types: */
cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if (item)item->type=cJSON_NULL;return item;}
cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if (item)item->type=cJSON_True;return item;}
cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if (item)item->type=cJSON_False;return item;}
cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if (item)item->type=b?cJSON_True:cJSON_False;return item;}
cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if (item) {item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;}
cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if (item) {item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;}
cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if (item)item->type=cJSON_Array;return item;}
cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if (item)item->type=cJSON_Object;return item;}
/* Create Arrays: */
cJSON *cJSON_CreateIntArray(const int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i = 0 ;a && i < count;i++) {n=cJSON_CreateNumber(numbers[i]);if (!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
cJSON *cJSON_CreateFloatArray(const float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i = 0 ;a && i < count;i++) {n=cJSON_CreateNumber(numbers[i]);if (!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
cJSON *cJSON_CreateDoubleArray(const double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i = 0 ;a && i < count;i++) {n=cJSON_CreateNumber(numbers[i]);if (!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i = 0 ;a && i < count;i++) {n=cJSON_CreateString(strings[i]);if (!i)a->child=n;else suffix_object(p,n);p=n;}return a;}
/* Duplication */
cJSON *cJSON_Duplicate(cJSON *item,int recurse)
{
cJSON *newitem,*cptr,*nptr=0,*newchild;
/* Bail on bad ptr */
if (!item) return 0;
/* Create new item */
newitem=cJSON_New_Item();
if (!newitem) return 0;
/* Copy over all vars */
newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble;
if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}}
if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}}
/* If non-recursive, then we're done! */
if (!recurse) return newitem;
/* Walk the ->next chain for the child. */
cptr=item->child;
while (cptr)
{
newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */
if (!newchild) {cJSON_Delete(newitem);return 0;}
if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */
else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */
cptr=cptr->next;
}
return newitem;
}
void cJSON_Minify(char *json)
{
char *into=json;
while (*json)
{
if (*json == ' ') json++;
else if (*json == '\t') json++; /* Whitespace characters. */
else if (*json == '\r') json++;
else if (*json == '\n') json++;
else if (*json == '/' && json[1] == '/') while (*json && *json!='\n') json++; /* double-slash comments, to end of line. */
else if (*json == '/' && json[1] == '*') {while (*json && !(*json == '*' && json[1] == '/')) json++;json+=2;} /* multiline comments. */
else if (*json == '\"') {*into++=*json++;while (*json && *json!='\"') {if (*json == '\\') *into++=*json++;*into++=*json++;}*into++=*json++;} /* string literals, which are \" sensitive. */
else *into++=*json++; /* All other characters. */
}
*into=0; /* and null-terminate. */
}

127
src/cJSON.h Normal file
View File

@ -0,0 +1,127 @@
#ifndef cJSON__h
#define cJSON__h
#ifdef __cplusplus
extern "C"
{
#endif
/* cJSON Types: */
#define cJSON_False 0
#define cJSON_True 1
#define cJSON_NULL 2
#define cJSON_Number 3
#define cJSON_String 4
#define cJSON_Array 5
#define cJSON_Object 6
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
/* The cJSON structure: */
typedef struct cJSON {
struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */
struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */
int type; /* The type of the item, as above. */
char *valuestring; /* The item's string, if type == cJSON_String */
int valueint; /* The item's number, if type == cJSON_Number */
double valuedouble; /* The item's number, if type == cJSON_Number */
char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
} cJSON;
typedef struct cJSON_Hooks {
void *(*malloc_fn)(size_t sz);
void (*free_fn)(void *ptr);
} cJSON_Hooks;
/* Supply malloc, realloc and free functions to cJSON */
extern void cJSON_InitHooks(cJSON_Hooks *hooks);
/* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */
extern cJSON *cJSON_Parse(const char *value);
/* Render a cJSON entity to text for transfer/storage. Free the char *when finished. */
extern char *cJSON_Print(cJSON *item);
/* Render a cJSON entity to text for transfer/storage without any formatting. Free the char *when finished. */
extern char *cJSON_PrintUnformatted(cJSON *item);
/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */
extern char *cJSON_PrintBuffered(cJSON *item,int prebuffer,int fmt);
/* Delete a cJSON entity and all subentities. */
extern void cJSON_Delete(cJSON *c);
/* Returns the number of items in an array (or object). */
extern int cJSON_GetArraySize(cJSON *array);
/* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */
extern cJSON *cJSON_GetArrayItem(cJSON *array,int item);
/* Get item "string" from object. Case insensitive. */
extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string);
/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */
extern const char *cJSON_GetErrorPtr(void);
/* These calls create a cJSON item of the appropriate type. */
extern cJSON *cJSON_CreateNull(void);
extern cJSON *cJSON_CreateTrue(void);
extern cJSON *cJSON_CreateFalse(void);
extern cJSON *cJSON_CreateBool(int b);
extern cJSON *cJSON_CreateNumber(double num);
extern cJSON *cJSON_CreateString(const char *string);
extern cJSON *cJSON_CreateArray(void);
extern cJSON *cJSON_CreateObject(void);
/* These utilities create an Array of count items. */
extern cJSON *cJSON_CreateIntArray(const int *numbers,int count);
extern cJSON *cJSON_CreateFloatArray(const float *numbers,int count);
extern cJSON *cJSON_CreateDoubleArray(const double *numbers,int count);
extern cJSON *cJSON_CreateStringArray(const char **strings,int count);
/* Append item to the specified array/object. */
extern void cJSON_AddItemToArray(cJSON *array, cJSON *item);
extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item);
extern void cJSON_AddItemToObjectCS(cJSON *object,const char *string,cJSON *item); /* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object */
/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */
extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item);
extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item);
/* Remove/Detatch items from Arrays/Objects. */
extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which);
extern void cJSON_DeleteItemFromArray(cJSON *array,int which);
extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string);
extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string);
/* Update array items. */
extern void cJSON_InsertItemInArray(cJSON *array,int which,cJSON *newitem); /* Shifts pre-existing items to the right. */
extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem);
extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem);
/* Duplicate a cJSON item */
extern cJSON *cJSON_Duplicate(cJSON *item,int recurse);
/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will
need to be released. With recurse!=0, it will duplicate any children connected to the item.
The item->next and ->prev pointers are always zero on return from Duplicate. */
/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */
extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated);
extern void cJSON_Minify(char *json);
/* Macros for creating things quickly. */
#define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull())
#define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue())
#define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse())
#define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b))
#define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n))
#define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s))
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
#define cJSON_SetNumberValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val))
#ifdef __cplusplus
}
#endif
#endif

18
src/cetus-error.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef _CETUS_ERROR_H_
#define _CETUS_ERROR_H_
/**
* ERROR codes extends ER_xx from mysqld_error.h
*/
enum {
ER_CETUS_UNKNOWN = 5001,
ER_CETUS_RESULT_MERGE,
ER_CETUS_LONG_RESP,
ER_CETUS_PARSE_SHARDING,
ER_CETUS_NOT_SUPPORTED,
ER_CETUS_SINGLE_NODE_FAIL,
ER_CETUS_NO_GROUP,
};
#endif /*_CETUS_ERROR_H_*/

259
src/cetus-log.c Normal file
View File

@ -0,0 +1,259 @@
#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "cetus-log.h"
#define TC_PREFIX "/var/log/"
#define TC_ERROR_LOG_PATH "xa.log"
#define TC_ERR_LOG_TIME_LEN (sizeof("2012-07-31 12:35:00 +999") - 1)
#define TC_ERR_LOG_TIME_STR_LEN (TC_ERR_LOG_TIME_LEN + 1)
#define tc_cpymem(d, s, l) (((char *) memcpy(d, (void *) s, l)) + (l))
static int log_fd = -1;
static int last_hour = -1;
static const char *file_name_prefix = NULL;
static char tc_error_log_time[TC_ERR_LOG_TIME_STR_LEN];
static int update_time();
typedef struct {
char *level;
int len;
} tc_log_level_t;
static tc_log_level_t tc_log_levels[] = {
{ "[unknown]", 9 },
{ "[emerg]", 7 },
{ "[alert]", 7 },
{ "[crit]", 6 },
{ "[error]", 7 },
{ "[warn]", 6 },
{ "[notice]", 8},
{ "[info]", 6},
{ "[debug]", 7 }
};
static int
tc_vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
{
int i;
/*
* Attention for vsnprintf: http://lwn.net/Articles/69419/
*/
i = vsnprintf(buf, size, fmt, args);
if (i < (int) size) {
return i;
}
if (size >= 1) {
return size - 1;
} else {
return 0;
}
}
static void
tc_localtime(time_t sec, struct tm *tm)
{
#if (HAVE_LOCALTIME_R)
(void) localtime_r(&sec, tm);
#else
struct tm *t;
t = localtime(&sec);
*tm = *t;
#endif
tm->tm_mon++;
tm->tm_year += 1900;
}
static int
tc_scnprintf(char *buf, size_t size, const char *fmt, ...)
{
int i;
va_list args;
va_start(args, fmt);
i = tc_vscnprintf(buf, size, fmt, args);
va_end(args);
return i;
}
static int
tc_create_new_file(const char *file, int hour)
{
int len;
char new_file_path[512], *p;
if (file == NULL) {
len = strlen(TC_PREFIX);
if (len >= 256) {
fprintf(stderr, "file prefix too long: %s\n", TC_PREFIX);
return -1;
}
strncpy(new_file_path, TC_PREFIX, len);
p = new_file_path + len;
len += strlen(TC_ERROR_LOG_PATH);
if (len >= 256) {
fprintf(stderr, "file path too long: %s\n", TC_PREFIX);
return -1;
}
strcpy(p, TC_ERROR_LOG_PATH);
p = new_file_path + len;
sprintf(p, "_%2d", hour);
} else {
p = new_file_path;
len = strlen(file);
strcpy(p, file);
p = p + len;
sprintf(p, "_%02d", hour);
}
file = new_file_path;
#if (PROXY_O_SYNC)
log_fd = open(file, O_RDWR|O_CREAT|O_APPEND|O_SYNC, 0644);
#else
log_fd = open(file, O_RDWR|O_CREAT|O_APPEND, 0644);
#endif
if (log_fd == -1) {
fprintf(stderr, "Open log file:%s error\n", file);
}
return log_fd;
}
int
tc_log_init(const char *file)
{
file_name_prefix = file;
update_time();
tc_create_new_file(file, last_hour);
return log_fd;
}
int
tc_get_log_hour()
{
if (last_hour == -1) {
update_time();
}
return last_hour;
}
void
tc_log_end(void)
{
if (log_fd != -1) {
close(log_fd);
}
log_fd = -1;
}
static int
update_time() {
int status;
time_t sec;
struct tm tm;
struct timeval tv;
status = gettimeofday(&tv, NULL);
if (status >= 0) {
sec = tv.tv_sec;
long msec = tv.tv_usec / 1000;
tc_localtime(sec, &tm);
snprintf(tc_error_log_time, TC_ERR_LOG_TIME_STR_LEN,
"%4d/%02d/%02d %02d:%02d:%02d +%03d",
tm.tm_year, tm.tm_mon,
tm.tm_mday, tm.tm_hour,
tm.tm_min, tm.tm_sec,
(int) msec);
if (tm.tm_hour != last_hour) {
if (last_hour == -1) {
last_hour = tm.tm_hour;
} else {
last_hour = tm.tm_hour;
return 1;
}
}
}
return 0;
}
void
tc_log_info(int level, int err, const char *fmt, ...)
{
int n, len;
char buffer[LOG_MAX_LEN], *p;
va_list args;
tc_log_level_t *ll;
if (log_fd == -1) {
return;
}
if (update_time()) {
tc_log_end();
tc_create_new_file(file_name_prefix, last_hour);
}
ll = &tc_log_levels[level];
p = buffer;
p = tc_cpymem(p, tc_error_log_time, TC_ERR_LOG_TIME_LEN);
*p++ = ' ';
p = tc_cpymem(p, ll->level, ll->len);
*p++ = ' ';
n = len = TC_ERR_LOG_TIME_LEN + ll->len + 2;
va_start(args, fmt);
len += tc_vscnprintf(p, LOG_MAX_LEN - n, fmt, args);
va_end(args);
if (len < n) {
return;
}
p = buffer + len;
if (err > 0) {
len += tc_scnprintf(p, LOG_MAX_LEN - len, " (%s)", strerror(err));
if (len < (p - buffer)) {
return;
}
p = buffer + len;
}
*p++ = '\n';
write(log_fd, buffer, p - buffer);
}

70
src/cetus-log.h Normal file
View File

@ -0,0 +1,70 @@
#ifndef TC_LOG_INCLUDED
#define TC_LOG_INCLUDED
#include "chassis-exports.h"
#define LOG_STDERR 0
#define LOG_EMERG 1
#define LOG_ALERT 2
#define LOG_CRIT 3
#define LOG_ERR 4
#define LOG_WARN 5
#define LOG_NOTICE 6
#define LOG_INFO 7
#define LOG_DEBUG 8
#define LOG_MAX_LEN 2048
int tc_log_init(const char *);
int tc_get_log_hour();
void tc_log_end(void);
void tc_log_info(int level, int err, const char *fmt, ...);
#if (TC_DEBUG)
#define tc_log_debug0(level, err, fmt) \
tc_log_info(level, err, (const char *) fmt)
#define tc_log_debug1(level, err, fmt, a1) \
tc_log_info(level, err, (const char *) fmt, a1)
#define tc_log_debug2(level, err, fmt, a1, a2) \
tc_log_info(level, err, (const char *) fmt, a1, a2)
#define tc_log_debug3(level, err, fmt, a1, a2, a3) \
tc_log_info(level, err, (const char *) fmt, a1, a2, a3)
#define tc_log_debug4(level, err, fmt, a1, a2, a3, a4) \
tc_log_info(level, err, (const char *) fmt, a1, a2, a3, a4)
#define tc_log_debug5(level, err, fmt, a1, a2, a3, a4, a5) \
tc_log_info(level, err, (const char *) fmt, a1, a2, a3, a4, a5)
#define tc_log_debug6(level, err, fmt, a1, a2, a3, a4, a5, a6) \
tc_log_info(level, err, (const char *) fmt, a1, a2, a3, a4, a5, a6)
#define tc_log_debug7(level, err, fmt, a1, a2, a3, a4, a5, a6, a7) \
tc_log_info(level, err, (const char *) fmt, a1, a2, a3, a4, a5, a6, a7)
#define tc_log_debug8(level, err, fmt, a1, a2, a3, a4, a5, a6, a7, a8) \
tc_log_info(level, err, (const char *) fmt, a1, a2, a3, a4, a5, a6, a7, a8)
#else
#define tc_log_debug0(level, err, fmt)
#define tc_log_debug1(level, err, fmt, a1)
#define tc_log_debug2(level, err, fmt, a1, a2)
#define tc_log_debug3(level, err, fmt, a1, a2, a3)
#define tc_log_debug4(level, err, fmt, a1, a2, a3, a4)
#define tc_log_debug5(level, err, fmt, a1, a2, a3, a4, a5)
#define tc_log_debug6(level, err, fmt, a1, a2, a3, a4, a5, a6)
#define tc_log_debug7(level, err, fmt, a1, a2, a3, a4, a5, a6, a7)
#define tc_log_debug8(level, err, fmt, a1, a2, a3, a4, a5, a6, a7, a8)
#define tc_log_debug_trace(level, err, flag, ip, tcp)
#endif /* TC_DEBUG */
#endif /* TC_LOG_INCLUDED */

509
src/cetus-monitor.c Normal file
View File

@ -0,0 +1,509 @@
#include "cetus-monitor.h"
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <fcntl.h>
#include <limits.h>
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/time.h>
#include "cetus-users.h"
#include "cetus-util.h"
#include "chassis-timings.h"
#include "chassis-event.h"
#include "glib-ext.h"
#include "sharding-config.h"
#define CHECK_ALIVE_INTERVAL 3
#define CHECK_ALIVE_TIMES 2
#define CHECK_DELAY_INTERVAL 300 * 1000 /* 300ms */
/* Each backend should have db <proxy_heart_beat> and table <tb_heartbeat> */
#define HEARTBEAT_DB "proxy_heart_beat"
struct cetus_monitor_t {
struct chassis *chas;
GThread *thread;
chassis_event_loop_t *evloop;
struct event check_alive_timer;
struct event write_master_timer;
struct event read_slave_timer;
struct event check_config_timer;
GString *db_passwd;
GHashTable *backend_conns;
GList *registered_objects;
char *config_id;
};
static void mysql_conn_free(gpointer e)
{
MYSQL *conn = e;
if (conn) {
mysql_close(conn);
}
}
static char *get_current_sys_timestr(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
#define BUFSIZE 32
char *time_sec = g_malloc(BUFSIZE);
strftime(time_sec, BUFSIZE, "%Y-%m-%d %H:%M:%S", localtime(&tv.tv_sec));
char *time_micro = g_strdup_printf("%s.%06ld", time_sec, tv.tv_usec);
g_free(time_sec);
return time_micro;
}
static MYSQL *get_mysql_connection(cetus_monitor_t *monitor, char *addr)
{
MYSQL *conn = g_hash_table_lookup(monitor->backend_conns, addr);
if (conn) {
if (mysql_ping(conn) == 0) {
return conn;
} else {
g_hash_table_remove(monitor->backend_conns, addr);
g_message("monitor: remove dead mysql conn of backend: %s", addr);
}
}
conn = mysql_init(NULL);
if (!conn)
return NULL;
unsigned int timeout = 2 * SECONDS;
mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout);
mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout);
char **ip_port = g_strsplit(addr, ":", -1);
int port = atoi(ip_port[1]);
char *user = monitor->chas->default_username;
if (mysql_real_connect(conn, ip_port[0], user, monitor->db_passwd->str,
NULL, port, NULL, 0) == NULL)
{
g_critical("monitor thread cannot connect to backend: %s@%s",
monitor->chas->default_username, addr);
mysql_conn_free(conn);
g_strfreev(ip_port);
return NULL;
}
g_hash_table_insert(monitor->backend_conns, g_strdup(addr), conn);
g_message("monitor thread connected to backend: %s, cached %d conns",
addr, g_hash_table_size(monitor->backend_conns));
g_strfreev(ip_port);
return conn;
}
#define ADD_MONITOR_TIMER(ev_struct, ev_cb, timeout) \
evtimer_set(&(monitor->ev_struct), ev_cb, monitor);\
event_base_set(monitor->evloop, &(monitor->ev_struct));\
evtimer_add(&(monitor->ev_struct), &timeout);
static void check_backend_alive(int fd, short what, void *arg)
{
cetus_monitor_t *monitor = arg;
chassis *chas = monitor->chas;
int i;
network_backends_t *bs = chas->priv->backends;
for (i = 0; i < network_backends_count(bs); i++) {
network_backend_t *backend = network_backends_get(bs, i);
if (backend->state == BACKEND_STATE_DELETED ||
backend->state == BACKEND_STATE_MAINTAINING)
continue;
char *backend_addr = backend->addr->name->str;
int check_count = 0;
MYSQL *conn = NULL;
while (++check_count <= CHECK_ALIVE_TIMES) {
conn = get_mysql_connection(monitor, backend_addr);
if (conn) break;
}
if (conn == NULL) {
if (backend->state != BACKEND_STATE_DOWN) {
network_backends_modify(bs, i, backend->type, BACKEND_STATE_DOWN);
g_critical("Backend %s is set to DOWN.", backend_addr);
}
g_debug("Backend %s is not ALIVE!", backend_addr);
} else {
if (backend->state != BACKEND_STATE_UP) {
network_backends_modify(bs, i, backend->type, BACKEND_STATE_UP);
g_message("Backend %s is set to UP.", backend_addr);
}
g_debug("Backend %s is ALIVE!", backend_addr);
}
}
struct timeval timeout = {0};
timeout.tv_sec = CHECK_ALIVE_INTERVAL;
ADD_MONITOR_TIMER(check_alive_timer, check_backend_alive, timeout);
}
static void check_slave_timestamp(int fd, short what, void *arg);
static void update_master_timestamp(int fd, short what, void *arg)
{
cetus_monitor_t *monitor = arg;
chassis *chas = monitor->chas;
int i;
network_backends_t *bs = chas->priv->backends;
/* Catch RW time
* Need a table to write from master and read from slave.
* CREATE TABLE `tb_heartbeat` (
* `p_id` varchar(128) NOT NULL,
* `p_ts` timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
* PRIMARY KEY (`p_id`)
* ) ENGINE = InnoDB DEFAULT CHARSET = utf8;
*/
for (i = 0; i < network_backends_count(bs); i++) {
network_backend_t *backend = network_backends_get(bs, i);
if (backend->state == BACKEND_STATE_DELETED ||
backend->state == BACKEND_STATE_MAINTAINING)
continue;
if (backend->type == BACKEND_TYPE_RW) {
static char sql[1024];
char *cur_time_str = get_current_sys_timestr();
snprintf(sql, sizeof(sql), "INSERT INTO %s.tb_heartbeat (p_id, p_ts)"
" VALUES ('%s', '%s') ON DUPLICATE KEY UPDATE p_ts='%s'",
HEARTBEAT_DB, monitor->config_id, cur_time_str, cur_time_str);
char *backend_addr = backend->addr->name->str;
MYSQL *conn = get_mysql_connection(monitor, backend_addr);
if (conn == NULL) {
if (backend->state != BACKEND_STATE_DOWN) {
network_backends_modify(bs, i, backend->type, BACKEND_STATE_DOWN);
g_critical("Backend %s is set to DOWN.", backend_addr);
}
} else {
if (backend->state != BACKEND_STATE_UP) {
network_backends_modify(bs, i, backend->type, BACKEND_STATE_UP);
g_message("Backend %s is set to UP.", backend_addr);
}
static int previous_result[256] = {0}; /* for each backend group */
int result = mysql_real_query(conn, L(sql));
if (result != previous_result[i] && result != 0) {
g_critical("Update heartbeat error: %d, text: %s, backend: %s",
mysql_errno(conn), mysql_error(conn), backend_addr);
} else if (result != previous_result[i] && result == 0) {
g_message("Update heartbeat success. backend: %s", backend_addr);
}
previous_result[i] = result;
}
g_free(cur_time_str);
}
}
/* Wait 50ms for RO write data */
struct timeval timeout = {0};
timeout.tv_usec = 50 * 1000;
ADD_MONITOR_TIMER(read_slave_timer, check_slave_timestamp, timeout);
}
static void check_slave_timestamp(int fd, short what, void *arg)
{
cetus_monitor_t *monitor = arg;
chassis *chas = monitor->chas;
int i;
network_backends_t *bs = chas->priv->backends;
/* Read delay sec and set slave UP/DOWN according to delay_secs */
for (i = 0; i < network_backends_count(bs); i++) {
network_backend_t *backend = network_backends_get(bs, i);
if (backend->type == BACKEND_TYPE_RW ||backend->state == BACKEND_STATE_DELETED ||
backend->state == BACKEND_STATE_MAINTAINING)
continue;
char *backend_addr = backend->addr->name->str;
MYSQL *conn = get_mysql_connection(monitor, backend_addr);
if (conn == NULL) {
g_critical("Connection error when read delay from RO backend: %s", backend_addr);
if (backend->state != BACKEND_STATE_DOWN) {
network_backends_modify(bs, i, backend->type, BACKEND_STATE_DOWN);
g_critical("Backend %s is set to DOWN.", backend_addr);
}
continue;
}
static char sql[512];
snprintf(sql, sizeof(sql), "select p_ts from %s.tb_heartbeat where p_id='%s'",
HEARTBEAT_DB, monitor->config_id);
static int previous_result[256] = {0}; /* for each backend group */
int result = mysql_real_query(conn, L(sql));
if (result != previous_result[i] && result != 0) {
g_critical("Select heartbeat error: %d, text: %s, backend: %s",
mysql_errno(conn), mysql_error(conn), backend_addr);
} else if (result != previous_result[i] && result == 0) {
g_message("Select heartbeat success. backend: %s", backend_addr);
}
previous_result[i] = result;
if (result == 0) {
MYSQL_RES *rs_set = mysql_store_result(conn);
MYSQL_ROW row = mysql_fetch_row(rs_set);
double ts_slave;
if (row != NULL) {
if (strstr(row[0],".") != NULL) {
char **tms = g_strsplit(row[0], ".", -1);
glong ts_slave_sec = chassis_epoch_from_string(tms[0], NULL);
double ts_slave_msec = atof(tms[1]);
ts_slave = ts_slave_sec + ts_slave_msec/1000;
g_strfreev(tms);
} else {
ts_slave = chassis_epoch_from_string(row[0], NULL);
}
} else {
g_critical("Check slave delay no data:%s", sql);
ts_slave = (double)G_MAXINT32;
}
if (ts_slave != 0) {
struct timeval tv;
gettimeofday(&tv, NULL);
double ts_now = tv.tv_sec + ((double)tv.tv_usec)/1000000;
double delay_secs = ts_now - ts_slave;
backend->slave_delay_msec = (int)delay_secs * 1000;
if (delay_secs > chas->slave_delay_down_threshold_sec &&
backend->state != BACKEND_STATE_DOWN)
{
network_backends_modify(bs, i, backend->type, BACKEND_STATE_DOWN);
g_critical("Slave delay %.3f seconds. Set slave to DOWN.", delay_secs);
} else if (delay_secs <= chas->slave_delay_recover_threshold_sec &&
backend->state != BACKEND_STATE_UP)
{
network_backends_modify(bs, i, backend->type, BACKEND_STATE_UP);
g_message("Slave delay %.3f seconds. Recovered. Set slave to UP.", delay_secs);
}
}
mysql_free_result(rs_set);
}
}
struct timeval timeout = {0};
timeout.tv_usec = CHECK_DELAY_INTERVAL;
ADD_MONITOR_TIMER(write_master_timer, update_master_timestamp, timeout);
}
#define MON_MAX_NAME_LEN 128
struct monitored_object_t {
char name[MON_MAX_NAME_LEN];
monitor_callback_fn func;
void *arg;
};
struct event config_reload_timer;
static void check_config_worker(int fd, short what, void *arg)
{
cetus_monitor_t *monitor = arg;
chassis *chas = monitor->chas;
chassis_config_t *conf = chas->config_manager;
struct timeval timeout = {0};
GList *l;
for (l = monitor->registered_objects; l; l = l->next) {
struct monitored_object_t *ob = l->data;
if (chassis_config_is_object_outdated(conf, ob->name)) {
/* if (evtimer_pending(&config_reload_timer, NULL))
break; */
g_message("monitor: object `%s` is outdated, try updating...", ob->name);
/* first read in object from remote in monitor thread */
chassis_config_update_object_cache(conf, ob->name);
/* then switch to main thread */
evtimer_set(&config_reload_timer, ob->func, ob->arg);
event_base_set(chas->event_base, &config_reload_timer);
evtimer_add(&config_reload_timer, &timeout);
break; /* TODO: for now, only update one object each time */
}
}
timeout.tv_sec = 5;
ADD_MONITOR_TIMER(check_config_timer, check_config_worker, timeout);
}
void cetus_monitor_open(cetus_monitor_t *monitor, monitor_type_t monitor_type)
{
struct timeval timeout;
switch (monitor_type) {
case MONITOR_TYPE_CHECK_ALIVE:
timeout.tv_sec = CHECK_ALIVE_INTERVAL;
timeout.tv_usec = 0;
ADD_MONITOR_TIMER(check_alive_timer, check_backend_alive, timeout);
g_message("check_alive monitor open.");
break;
case MONITOR_TYPE_CHECK_DELAY:
timeout.tv_sec = 0;
timeout.tv_usec = CHECK_DELAY_INTERVAL;
ADD_MONITOR_TIMER(write_master_timer, update_master_timestamp, timeout);
g_message("check_slave monitor open.");
break;
case MONITOR_TYPE_CHECK_CONFIG:
timeout.tv_sec = 5;
timeout.tv_usec = 0;
ADD_MONITOR_TIMER(check_config_timer, check_config_worker, timeout);
g_message("check_config monitor open.");
break;
default:
break;
}
}
void cetus_monitor_close(cetus_monitor_t *monitor, monitor_type_t monitor_type)
{
switch (monitor_type) {
case MONITOR_TYPE_CHECK_ALIVE:
if (monitor->check_alive_timer.ev_base) {
evtimer_del(&monitor->check_alive_timer);
}
g_message("check_alive monitor close.");
break;
case MONITOR_TYPE_CHECK_DELAY:
if (monitor->write_master_timer.ev_base) {
evtimer_del(&monitor->write_master_timer);
}
if (monitor->read_slave_timer.ev_base) {
evtimer_del(&monitor->read_slave_timer);
}
g_message("check_slave monitor close.");
break;
case MONITOR_TYPE_CHECK_CONFIG:
if (monitor->check_config_timer.ev_base) {
evtimer_del(&monitor->check_config_timer);
}
g_message("check_config monitor close.");
break;
default:
break;
}
}
static void *cetus_monitor_mainloop(void *data)
{
cetus_monitor_t *monitor = data;
chassis_event_loop_t *loop = chassis_event_loop_new();
monitor->evloop = loop;
chassis *chas = monitor->chas;
monitor->config_id = chassis_config_get_id(chas->config_manager);
if (!chas->default_username) {
g_warning("default-username not set, monitor will not work");
return NULL;
}
cetus_users_get_server_pwd(chas->priv->users,
chas->default_username, monitor->db_passwd);
if (monitor->db_passwd->len == 0) { /* TODO: retry */
g_warning("no password for %s, monitor will not work", chas->default_username);
return NULL;
}
monitor->backend_conns = g_hash_table_new_full(
g_str_hash, g_str_equal, g_free, mysql_conn_free);
if (!chas->check_slave_delay) {
cetus_monitor_open(monitor, MONITOR_TYPE_CHECK_ALIVE);
}
if (chas->check_slave_delay) {
cetus_monitor_open(monitor, MONITOR_TYPE_CHECK_DELAY);
}
#if 0
cetus_monitor_open(monitor, MONITOR_TYPE_CHECK_CONFIG);
#endif
chassis_event_loop(loop);
g_message("monitor thread closing %d mysql conns",
g_hash_table_size(monitor->backend_conns));
g_hash_table_destroy(monitor->backend_conns);
mysql_thread_end();
g_debug("exiting monitor loop");
chassis_event_loop_free(loop);
return NULL;
}
void cetus_monitor_start_thread(cetus_monitor_t *monitor, chassis *chas)
{
monitor->chas = chas;
if (chas->disable_threads) {
g_message("monitor thread is disabled");
return;
}
g_assert(monitor->thread == 0);
GThread *new_thread = NULL;
#if !GLIB_CHECK_VERSION(2, 32, 0)
GError *error = NULL;
new_thread = g_thread_create(cetus_monitor_mainloop,
monitor, TRUE, &error);
if (new_thread == NULL && error != NULL) {
g_critical("Create thread error: %s", error->message);
}
#else
new_thread = g_thread_new("monitor-thread",
cetus_monitor_mainloop, monitor);
if (new_thread == NULL) {
g_critical("Create thread error.");
}
#endif
monitor->thread = new_thread;
g_message("monitor thread started");
}
void cetus_monitor_stop_thread(cetus_monitor_t *monitor)
{
if (monitor->thread) {
g_message("Waiting for monitor thread to quit ...");
g_thread_join(monitor->thread);
g_message("Monitor thread stopped");
}
}
cetus_monitor_t *cetus_monitor_new()
{
cetus_monitor_t *monitor = g_new0(cetus_monitor_t, 1);
monitor->db_passwd = g_string_new(0);
return monitor;
}
void cetus_monitor_free(cetus_monitor_t *monitor)
{
/* backend_conns should be freed in its own thread, not here */
g_string_free(monitor->db_passwd, TRUE);
g_list_free_full(monitor->registered_objects, g_free);
if (monitor->config_id) g_free(monitor->config_id);
g_free(monitor);
}
void cetus_monitor_register_object(cetus_monitor_t *monitor,
const char *name, monitor_callback_fn func, void *arg)
{
GList *l;
struct monitored_object_t *object = NULL;
for (l = monitor->registered_objects; l; l = l->next) {
struct monitored_object_t *ob = l->data;
if (strcmp(ob->name, name) == 0) {
object = ob;
break;
}
}
if (!object) {
object = g_new0(struct monitored_object_t, 1);
strncpy(object->name, name, MON_MAX_NAME_LEN - 1);
monitor->registered_objects = g_list_append(monitor->registered_objects, object);
}
object->func = func;
object->arg = arg;
}

34
src/cetus-monitor.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef _CHASSIS_REMOTE_CONFIGS_H_
#define _CHASSIS_REMOTE_CONFIGS_H_
#include <glib.h> /* GPtrArray */
#include "chassis-mainloop.h"
typedef struct cetus_monitor_t cetus_monitor_t;
typedef enum {
MONITOR_TYPE_CHECK_ALIVE,
MONITOR_TYPE_CHECK_DELAY,
MONITOR_TYPE_CHECK_CONFIG
} monitor_type_t;
typedef void (*monitor_callback_fn)(int, short, void *);
cetus_monitor_t *cetus_monitor_new();
void cetus_monitor_free(cetus_monitor_t *);
void cetus_monitor_open(cetus_monitor_t *, monitor_type_t);
void cetus_monitor_close(cetus_monitor_t *, monitor_type_t);
void cetus_monitor_open(cetus_monitor_t *, monitor_type_t);
void cetus_monitor_start_thread(cetus_monitor_t *, chassis *data);
void cetus_monitor_stop_thread(cetus_monitor_t *);
void cetus_monitor_register_object(cetus_monitor_t *,
const char *, monitor_callback_fn, void *);
#endif

100
src/cetus-query-queue.c Normal file
View File

@ -0,0 +1,100 @@
#include "cetus-query-queue.h"
#include <mysql.h>
#include <stdio.h>
#include <stdlib.h>
#include "glib-ext.h"
#include "network-mysqld-proto.h"
struct query_entry_t {
enum enum_server_command command;
GString *sql;
time_t recv_time;
};
static void query_entry_free(struct query_entry_t *entry)
{
if (entry->sql) {
g_string_free(entry->sql, TRUE);
}
g_free(entry);
}
query_queue_t *query_queue_new(int len)
{
query_queue_t *q = g_new0(struct query_queue_t, 1);
q->chunks = g_queue_new();
q->max_len = len;
return q;
}
void query_queue_free(query_queue_t *q)
{
g_queue_foreach(q->chunks, (GFunc)query_entry_free, NULL);
g_queue_free(q->chunks);
g_free(q);
}
void query_queue_append(query_queue_t *q, GString *data)
{
network_packet packet = {data, 0};
guint8 command = 0;
if (packet.data) {
network_mysqld_proto_skip_network_header(&packet);
if (network_mysqld_proto_get_int8(&packet, &command) != 0) {
return;
}
} else {
g_warning("%s: packet data is nill ", G_STRLOC);
return;
}
struct query_entry_t *entry = g_new0(struct query_entry_t, 1);
entry->command = command;
if (command == COM_QUERY) {
gsize sql_len = packet.data->len - packet.offset;
entry->sql = g_string_sized_new(sql_len + 1);
network_mysqld_proto_get_gstr_len(&packet, sql_len, entry->sql);
}
entry->recv_time = time(0);
if (g_queue_get_length(q->chunks) >= q->max_len) { /* TODO: slow */
struct query_entry_t *old = g_queue_pop_head(q->chunks);
query_entry_free(old);
}
g_queue_push_tail(q->chunks, entry);
}
const char *command_name(enum enum_server_command cmd)
{
static char number[8];
switch(cmd) {
case COM_QUERY:return "COM_QUERY";
case COM_QUIT:return "COM_QUIT";
case COM_INIT_DB:return "COM_INIT_DB";
case COM_FIELD_LIST:return "COM_FIELD_LIST";
default:
snprintf(number, sizeof(number), "COM_<%d>", cmd);/* TODO: only works for 1 cmd */
return number;
}
}
static void query_entry_dump(gpointer data, gpointer user_data)
{
struct query_entry_t *entry = (struct query_entry_t *)data;
struct tm *tm = localtime(&entry->recv_time);
char tm_str[16] = {0};
snprintf(tm_str, sizeof(tm_str), "%d:%d:%d", tm->tm_hour, tm->tm_min, tm->tm_sec);
g_message("%s %s %s", tm_str, command_name(entry->command),
entry->sql ? entry->sql->str:"");
}
void query_queue_dump(query_queue_t *q)
{
if (!g_queue_is_empty(q->chunks)) {
g_message("recent queries:");
g_queue_foreach(q->chunks, query_entry_dump, NULL);
}
}

16
src/cetus-query-queue.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef _CETUS_QUERY_QUEUE_H_
#define _CETUS_QUERY_QUEUE_H_
#include <glib.h>
typedef struct query_queue_t {
GQueue *chunks;
int max_len;
} query_queue_t;
query_queue_t *query_queue_new(int max_len);
void query_queue_free(query_queue_t *);
void query_queue_append(query_queue_t *, GString *);
void query_queue_dump(query_queue_t *);
#endif /* _CETUS_QUERY_QUEUE_H_*/

288
src/cetus-users.c Normal file
View File

@ -0,0 +1,288 @@
#include "cetus-users.h"
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "cJSON.h"
#include "cetus-util.h"
#include "chassis-config.h"
struct pwd_pair_t {
char *client;
char *server;
};
static struct pwd_pair_t *pwd_pair_new(const char *c, const char *s)
{
struct pwd_pair_t *pwd = g_new0(struct pwd_pair_t, 1);
pwd->client = g_strdup(c);
pwd->server = g_strdup(s);
return pwd;
}
static void pwd_pair_set_pwd(struct pwd_pair_t *pwd,
const char *new_pass, enum cetus_pwd_type t)
{
switch (t) {
case CETUS_CLIENT_PWD:
g_free(pwd->client);
pwd->client = g_strdup(new_pass);
break;
case CETUS_SERVER_PWD:
g_free(pwd->server);
pwd->server = g_strdup(new_pass);
break;
default:
g_assert(0);
}
}
static gboolean pwd_pair_same_pwd(struct pwd_pair_t *pwd,
const char *new_pass, enum cetus_pwd_type t)
{
switch (t) {
case CETUS_CLIENT_PWD:
return strcmp(pwd->client, new_pass) == 0;
case CETUS_SERVER_PWD:
return strcmp(pwd->server, new_pass) == 0;
default:
return FALSE;
}
}
static void pwd_pair_free(struct pwd_pair_t *pwd)
{
if (pwd) {
g_free(pwd->client);
g_free(pwd->server);
g_free(pwd);
}
}
cetus_users_t *cetus_users_new()
{
cetus_users_t *users = g_new0(cetus_users_t, 1);
users->records = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
(GDestroyNotify)pwd_pair_free);
return users;
}
void cetus_users_free(cetus_users_t *users)
{
if (users) {
if (users->records)
g_hash_table_destroy(users->records);
g_free(users);
}
}
static void cetus_users_set_records(cetus_users_t *users, GHashTable *new_records)
{
if (users->records) {
g_hash_table_destroy(users->records);
}
users->records = new_records;
}
static gboolean cetus_users_parse_json(cetus_users_t *users, char *buffer)
{
cJSON *root = cJSON_Parse(buffer);
if (!root) {
g_critical(G_STRLOC ":json syntax error");
return FALSE;
}
gboolean success = FALSE;
GHashTable *user_records = g_hash_table_new_full(g_str_hash, g_str_equal,
g_free, (GDestroyNotify)pwd_pair_free);
cJSON *users_node = cJSON_GetObjectItem(root, "users");
cJSON *user_node = users_node ? users_node->child : NULL;
for (; user_node; user_node = user_node->next) {
cJSON *username = cJSON_GetObjectItem(user_node, "user");
if (!username) {
g_critical(G_STRLOC ":user conf error, no username");
goto out;
}
cJSON *client_pwd = cJSON_GetObjectItem(user_node, "client_pwd");
cJSON *server_pwd = cJSON_GetObjectItem(user_node, "server_pwd");
if (!client_pwd && !server_pwd) {
g_critical(G_STRLOC ":user conf error, at least one of client/server is needed");
goto out;
}
if (client_pwd && !server_pwd) server_pwd = client_pwd;
if (!client_pwd && server_pwd) client_pwd = server_pwd;
g_hash_table_insert(user_records, g_strdup(username->valuestring),
pwd_pair_new(client_pwd->valuestring, server_pwd->valuestring));
}
success = TRUE;
out:
cJSON_Delete(root);
if (success) {
cetus_users_set_records(users, user_records);
} else {
g_hash_table_destroy(user_records);
}
return success;
}
gboolean cetus_users_read_json(cetus_users_t *users, chassis_config_t *conf)
{
users->conf_manager = conf;
char *buffer = NULL;
chassis_config_query_object(conf, "users", &buffer);
if (!buffer)
return FALSE;
gboolean success = cetus_users_parse_json(users, buffer);
if (success) {
g_message("read %d users", g_hash_table_size(users->records));
}
g_free(buffer);
return success;
}
gboolean cetus_users_update_record(cetus_users_t *users, const char *user,
const char *pass, enum cetus_pwd_type type)
{
struct pwd_pair_t *pwd = g_hash_table_lookup(users->records, user);
if (pwd) {
if (pwd_pair_same_pwd(pwd, pass, type)) {
return FALSE;
}
pwd_pair_set_pwd(pwd, pass, type);
} else {
g_hash_table_insert(users->records, g_strdup(user), pwd_pair_new(pass, pass));
}
g_message("update user: %s", user);
return TRUE;
}
gboolean cetus_users_delete_record(cetus_users_t *users, const char *user)
{
gboolean found = g_hash_table_remove(users->records, user);
if (found)
g_message("delete user: %s", user);
return found;
}
gboolean cetus_users_write_json(cetus_users_t *users)
{
cJSON *users_node = cJSON_CreateArray();
if (users_node == NULL) {
g_warning(G_STRLOC ":users_node is nil");
return FALSE;
}
GHashTableIter iter;
char *username = NULL;
struct pwd_pair_t *pwd = NULL;
g_hash_table_iter_init(&iter, users->records);
while (g_hash_table_iter_next(&iter, (gpointer *)&username, (gpointer *)&pwd)) {
cJSON *node = cJSON_CreateObject();
cJSON_AddStringToObject(node, "user", username);
cJSON_AddStringToObject(node, "client_pwd", pwd->client);
cJSON_AddStringToObject(node, "server_pwd", pwd->server);
cJSON_AddItemToArray(users_node, node);
}
cJSON *root = cJSON_CreateObject();
cJSON_AddItemToObject(root, "users", users_node);
char *json_str = cJSON_Print(root);
chassis_config_write_object(users->conf_manager, "users", json_str);
cJSON_Delete(root);
g_free(json_str);
return TRUE;
}
gboolean cetus_users_authenticate_client(cetus_users_t *users,
network_mysqld_auth_challenge *challenge,
network_mysqld_auth_response *response)
{
char *user_name = response->username->str;
struct pwd_pair_t *pwd = g_hash_table_lookup(users->records, user_name);
if (pwd == NULL) {
return FALSE;
}
/* term 2: user_name and password must in frontend users/passwords */
if (pwd->client) {
GString *sha1_pwd = g_string_new(NULL);
network_mysqld_proto_password_hash(sha1_pwd, pwd->client, strlen(pwd->client));
GString *expected_response = g_string_new(NULL);
network_mysqld_proto_password_scramble(expected_response,
S(challenge->auth_plugin_data), S(sha1_pwd));
if (g_string_equal(response->auth_plugin_data, expected_response)) {
g_string_free(expected_response, TRUE);
g_string_free(sha1_pwd, TRUE);
return TRUE;
}
g_string_free(expected_response, TRUE);
g_string_free(sha1_pwd, TRUE);
}
return FALSE;
}
void cetus_users_get_hashed_pwd(cetus_users_t *users, const char *user_name,
enum cetus_pwd_type type, GString *sha1pwd)
{
if (type == CETUS_CLIENT_PWD) {
cetus_users_get_hashed_client_pwd(users, user_name, sha1pwd);
} else if (type == CETUS_SERVER_PWD) {
cetus_users_get_hashed_server_pwd(users, user_name, sha1pwd);
}
}
void cetus_users_get_hashed_client_pwd(cetus_users_t *users,
const char *user_name, GString *sha1_pwd)
{
struct pwd_pair_t *pwd = g_hash_table_lookup(users->records, user_name);
if (pwd == NULL) {
return;
}
if (pwd->client) {
network_mysqld_proto_password_hash(sha1_pwd, pwd->client, strlen(pwd->client));
}
}
void cetus_users_get_hashed_server_pwd(cetus_users_t *users,
const char *user_name, GString *sha1_pwd)
{
struct pwd_pair_t *pwd = g_hash_table_lookup(users->records, user_name);
if (pwd == NULL) {
return;
}
if (pwd->server) {
network_mysqld_proto_password_hash(sha1_pwd, pwd->server, strlen(pwd->server));
}
}
void cetus_users_get_server_pwd(cetus_users_t *users, const char *user_name,
GString *res_pwd)
{
struct pwd_pair_t *pwd = g_hash_table_lookup(users->records, user_name);
if (pwd == NULL) {
return;
}
if (pwd->server) {
g_string_assign(res_pwd, pwd->server);
}
}
gboolean cetus_users_contains(cetus_users_t *users, const char *user_name)
{
return g_hash_table_lookup(users->records, user_name) ? TRUE : FALSE;
}
void cetus_users_reload_callback(int fd, short what, void *arg)
{
cetus_users_t *users = arg;
gboolean ok = cetus_users_read_json(users, users->conf_manager);
if (!ok)
g_warning("reloading users failed");
}

48
src/cetus-users.h Normal file
View File

@ -0,0 +1,48 @@
#ifndef _CETUS_USERS_H_
#define _CETUS_USERS_H_
#include "glib-ext.h"
#include "network-mysqld-packet.h"
typedef struct cetus_users_t {
chassis_config_t *conf_manager;
GHashTable *records; /* <char *, pwd_pair_t *> */
} cetus_users_t;
enum cetus_pwd_type {
CETUS_UNKNOWN_PWD,
CETUS_CLIENT_PWD,
CETUS_SERVER_PWD,
};
cetus_users_t *cetus_users_new();
void cetus_users_free(cetus_users_t *users);
gboolean cetus_users_read_json(cetus_users_t *users, chassis_config_t *);
gboolean cetus_users_update_record(cetus_users_t *users, const char *user,
const char *pass, enum cetus_pwd_type type);
gboolean cetus_users_delete_record(cetus_users_t *users, const char *user);
/* write current users to disk */
gboolean cetus_users_write_json(cetus_users_t *users);
gboolean cetus_users_authenticate_client(cetus_users_t *users, network_mysqld_auth_challenge *,
network_mysqld_auth_response *);
void cetus_users_get_hashed_pwd(cetus_users_t *, const char *user,
enum cetus_pwd_type, GString *sha1pwd);
void cetus_users_get_hashed_client_pwd(cetus_users_t *, const char *user, GString *sha1pwd);
void cetus_users_get_hashed_server_pwd(cetus_users_t *, const char *user, GString *sha1pwd);
void cetus_users_get_server_pwd(cetus_users_t *, const char *user, GString *pwd);
gboolean cetus_users_contains(cetus_users_t *, const char *user);
void cetus_users_reload_callback(int fd, short what, void *arg);
#endif /*_CETUS_USERS_H_*/

79
src/cetus-util.c Normal file
View File

@ -0,0 +1,79 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include <glib.h>
#include "glib-ext.h"
#include "cetus-util.h"
void cetus_string_dequote(char *z)
{
int quote;
int i, j;
if (z == 0) return;
quote = z[0];
switch (quote) {
case '\'': break;
case '"': break;
case '`': break; /* For MySQL compatibility */
default: return;
}
for (i = 1, j = 0; z[i]; i++) {
if (z[i] == quote) {
if (z[i+1] == quote) { /* quote escape */
z[j++] = quote;
i++;
} else {
z[j++] = 0;
break;
}
} else if (z[i] == '\\') { /* slash escape */
i++;
z[j++] = z[i];
} else {
z[j++] = z[i];
}
}
}
gboolean read_file_to_buffer(const char *filename, char **buffer)
{
FILE *fp = fopen(filename, "r");
if (!fp) {
g_critical(G_STRLOC ":cannot open user conf: %s", filename);
return FALSE;
}
const int MAX_FILE_SIZE = 1024 * 1024; /* 1M */
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
if (len < 0) {
g_warning(G_STRLOC ":%s", g_strerror(errno));
fclose(fp);
return FALSE;
}
if (len > MAX_FILE_SIZE) {
g_warning(G_STRLOC ":file too large");
fclose(fp);
return FALSE;
}
rewind(fp);
*buffer = g_new(char, len + 1);
if (fread((*buffer), 1, len, fp) != len) {
g_warning(G_STRLOC ":len is not consistant");
fclose(fp);
return FALSE;
}
(*buffer)[len] = 0;
fclose(fp);
return TRUE;
}

29
src/cetus-util.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef _GLIB_EXT_STRING_LEN_H_
#define _GLIB_EXT_STRING_LEN_H_
#include <inttypes.h>
/**
* simple macros to get the data and length of a "string"
*
* C() is for constant strings like "foo"
* S() is for GString's
*/
#define C(x) x, sizeof(x) - 1
#define S(x) (x) ? (x)->str : NULL, (x) ? (x)->len : 0
#define L(x) x, strlen(x)
typedef int32_t BitArray;
#define SetBit(A,k) (A[(k/32)] |= (1 << (k%32)))
#define ClearBit(A,k) (A[(k/32)] &= ~(1 << (k%32)))
#define TestBit(A,k) (A[(k/32)] & (1 << (k%32)))
void cetus_string_dequote(char *z);
#define KB 1024
#define MB 1024 * KB
#define GB 1024 * MB
gboolean read_file_to_buffer(const char *filename, char **buffer);
#endif

50
src/cetus-variable.c Normal file
View File

@ -0,0 +1,50 @@
#include "cetus-variable.h"
#include "chassis-mainloop.h"
#include <stdlib.h>
#include <string.h>
void cetus_variables_init_stats(cetus_variable_t **vars, chassis *chas)
{
query_stats_t *stats = &(chas->query_stats);
cetus_variable_t stats_variables[] = {
{"Com_select", &stats->com_select, VAR_INT64},
{"Com_insert", &stats->com_insert, VAR_INT64},
{"Com_update", &stats->com_update, VAR_INT64},
{"Com_delete", &stats->com_delete, VAR_INT64},
{"Com_select_shard", &stats->com_select_shard, VAR_INT64},
{"Com_insert_shard", &stats->com_insert_shard, VAR_INT64},
{"Com_update_shard", &stats->com_update_shard, VAR_INT64},
{"Com_delete_shard", &stats->com_delete_shard, VAR_INT64},
{"Com_select_global", &stats->com_select_global, VAR_INT64},
{"Com_select_bad_key", &stats->com_select_bad_key, VAR_INT64},
{NULL, NULL, 0}
};
int length = sizeof(stats_variables);
int count = length / sizeof(cetus_variable_t);
*vars = calloc(count, sizeof(cetus_variable_t));
memcpy(*vars, stats_variables, length);
}
char *cetus_variable_get_value_str(cetus_variable_t *var)
{
char *value = NULL;
switch (var->type) {
case VAR_INT:
value = g_strdup_printf("%u", *(gint *)(var->value));
break;
case VAR_INT64:
value = g_strdup_printf("%lu", *(guint64 *)(var->value));
break;
case VAR_FLOAT:
value = g_strdup_printf("%f", *(double *)(var->value));
break;
case VAR_STRING:
value = g_strdup(var->value);
break;
default:
value = g_strdup("error value");
}
return value;
}

27
src/cetus-variable.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef CETUS_VARIABLE_H
#define CETUS_VARIABLE_H
#include <inttypes.h>
enum cetus_variable_type_t {
VAR_INT,
VAR_INT64,
VAR_FLOAT,
VAR_STRING,
};
struct chassis;
typedef struct cetus_variable_t {
char *name;
void *value;
enum cetus_variable_type_t type;
} cetus_variable_t;
void cetus_variables_init_stats(/* out */ cetus_variable_t **vars, struct chassis *);
/**
@return newly allocated string, must be freed
*/
char *cetus_variable_get_value_str(cetus_variable_t *var);
#endif /* CETUS_VARIABLES_H */

45
src/character-set.c Normal file
View File

@ -0,0 +1,45 @@
#include "character-set.h"
#include <string.h>
#include <assert.h>
#define DEFAULT_CHARSET '\x21'
int charset_get_number(const char *name)
{
if (!name)
return DEFAULT_CHARSET;
static struct _charset_number_t {
const char *name;
int number;
} map[] = {
{"latin1", 0x08},
{"big5", 0x01},
{"gb2312", 0x18},
{"gbk", 0x1c},
{"utf8", 0x21},
{"utf8mb4", 0x2d},
{"binary", 0x3f}
};
int map_len = sizeof(map)/sizeof(struct _charset_number_t);
int i = 0;
while (i < map_len) {
if (strcmp(name, map[i].name) == 0) {
return map[i].number;
}
++i;
}
return DEFAULT_CHARSET;
}
const char *charset_get_name(int number)
{
assert(number < 64);
static const char *charset[64] = {
0, "big5", 0, 0, 0, 0, 0, 0, "latin1", 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, "gb2312", 0, 0, 0, "gbk", 0, 0, 0,
0, "utf8", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "utf8mb4", 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, "binary"
};
return charset[number];
}

9
src/character-set.h Normal file
View File

@ -0,0 +1,9 @@
#ifndef CHARACTER_SET_H
#define CHARACTER_SET_H
#include "network-exports.h"
NETWORK_API int charset_get_number(const char *name);
NETWORK_API const char *charset_get_name(int number);
#endif // CHARACTER_SET_H

688
src/chassis-config.c Normal file
View File

@ -0,0 +1,688 @@
#include "chassis-config.h"
#include "chassis-timings.h"
#include "chassis-options.h"
#include "cetus-util.h"
#include <mysql.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib/gstdio.h> /* for g_stat */
enum chassis_config_type_t {
CHASSIS_CONF_SSH,
CHASSIS_CONF_FTP,
CHASSIS_CONF_MYSQL,
CHASSIS_CONF_SQLITE,
CHASSIS_CONF_LOCAL, /* maybe unify local directory? */
};
#define RF_MAX_NAME_LEN 128
typedef struct chassis_config_object_t {
char name[RF_MAX_NAME_LEN];
/* TODO: cache lock */
char *cache;
time_t mtime;
} config_object_t;
static void config_object_free(config_object_t *ob)
{
if (ob->cache) g_free(ob->cache);
g_free(ob);
}
struct chassis_config_t
{
enum chassis_config_type_t type;
char *user;
char *password;
char *host;
int port;
char *schema;
/* this mysql conn might be used in 2 threads,
* on startup, the main thread use it to load config
* while running, the monitor thread use it periodically
* for now, it is guaranteed not used simutaneously, so it's not locked
*/
MYSQL *mysql_conn;
char *options_table;
char *options_filter;
GHashTable *options;
GList *objects;
};
static gboolean url_parse_user_pass(chassis_config_t *rconf, const char *userpass, int len)
{
char *p = strndup(userpass, len);
char *sep = strchr(p, ':');
if (sep) {
rconf->user = strndup(p, sep-p);
rconf->password = strdup(sep+1);
g_free(p);
} else {
rconf->user = p;
}
return TRUE;
}
static gboolean url_parse_host_port(chassis_config_t *rconf, const char *hostport, int len)
{
char *p = strndup(hostport, len);
char *sep = strchr(p, ':');
if (sep) {
rconf->host = strndup(p, sep-p);
rconf->port = atoi(sep+1);
g_free(p);
} else {
rconf->host = p;
rconf->port = 3306;
}
return TRUE;
}
static gboolean url_parse_parameter(chassis_config_t *rconf, const char *param, int len)
{
char **params = g_strsplit(param, "&", -1);
int i = 0;
GString *filter = g_string_new(0);
for (i = 0; params[i]; ++i) {
if (strncasecmp(params[i], "table=", 6) == 0) {
rconf->options_table = strdup(params[i]+6);
} else {
g_string_append(filter, params[i]);
g_string_append(filter, " and ");
}
}
if (filter->len >= 4) { /* remove last 'and' */
g_string_truncate(filter, filter->len - 4);
rconf->options_filter = strdup(filter->str);
}
g_string_free(filter, TRUE);
g_strfreev(params);
return TRUE;
}
/* example mysql://user:pass@host:port/schema?table=xx&id=xx */
static gboolean chassis_config_parse_mysql_url(chassis_config_t *rconf,
const char *url, int len)
{
/* only "host" is required -> [dbuser[:[dbpassword]]@]host[:port][/schema] */
char *param = strchr(url, '?');
char *schema = strchr(url, '/');
char *at = strchr(url, '@');
if (schema) {
if (param && param < schema)
return FALSE;
rconf->schema = param ? strndup(schema+1, param-schema-1) : strdup(schema+1);
}
if (param)
url_parse_parameter(rconf, param+1, url+len - param);
if (!rconf->options_table)
rconf->options_table = g_strdup("settings");
const char *hostend = schema ? schema : (param ? param : url+len);
gboolean ok = FALSE;
if (at) {
ok = url_parse_user_pass(rconf, url, at-url);
ok = ok && url_parse_host_port(rconf, at+1, hostend-at-1);
} else {
ok = url_parse_host_port(rconf, url, hostend-url);
}
return ok;
}
static MYSQL *chassis_config_get_mysql_connection(chassis_config_t *conf)
{
/* first try the cached connection */
if (conf->mysql_conn) {
if (mysql_ping(conf->mysql_conn) == 0) {
return conf->mysql_conn;
} else {
mysql_close(conf->mysql_conn);
conf->mysql_conn = NULL;
}
}
MYSQL *conn = mysql_init(NULL);
if (!conn)
return NULL;
unsigned int timeout = 2 * SECONDS;
mysql_options(conn, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
mysql_options(conn, MYSQL_OPT_READ_TIMEOUT, &timeout);
mysql_options(conn, MYSQL_OPT_WRITE_TIMEOUT, &timeout);
if (mysql_real_connect(conn, conf->host, conf->user, conf->password,
NULL, conf->port, NULL, 0) == NULL)
{
g_critical("%s", mysql_error(conn));
mysql_close(conn);
return NULL;
}
conf->mysql_conn = conn; /* cache the connection */
return conn;
}
static gboolean chassis_config_mysql_init_tables(chassis_config_t *conf)
{
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
char sql[256] = {0};
snprintf(sql, sizeof(sql), "CREATE DATABASE IF NOT EXISTS %s", conf->schema);
if (mysql_query(conn, sql)) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS %s.objects("
"object_name varchar(64) NOT NULL,"
"object_value text NOT NULL,"
"mtime timestamp NOT NULL,"
"PRIMARY KEY(object_name))", conf->schema);
if (mysql_query(conn, sql)) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS %s.%s("
"option_key varchar(64) NOT NULL,"
"option_value varchar(1024) NOT NULL,"
"PRIMARY KEY(option_key))", conf->schema, conf->options_table);
if (mysql_query(conn, sql)) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
return TRUE;
}
chassis_config_t *chassis_config_from_url(char *url)
{
chassis_config_t *rconf = g_new0(chassis_config_t, 1);
gboolean ok = FALSE;
if (strncasecmp(url, "mysql://", 8) == 0) {
rconf->type = CHASSIS_CONF_MYSQL;
ok = chassis_config_parse_mysql_url(rconf, url+8, strlen(url)-8);
} else if (strncasecmp(url, "sqlite://", 9) == 0) {
ok = chassis_config_parse_mysql_url(rconf, url+8, strlen(url)-8);
} else {
g_warning("url not supported: %s", url);
}
if (ok)
ok = chassis_config_mysql_init_tables(rconf);
if (!ok) {
chassis_config_free(rconf);
return NULL;
}
return rconf;
}
/* TODO: */
chassis_config_t *chassis_config_from_local_dir(char *conf_dir, char *conf_file)
{
chassis_config_t *conf = g_new0(chassis_config_t, 1);
conf->type = CHASSIS_CONF_LOCAL;
conf->schema = g_strdup(conf_dir);
conf->options_table = g_strdup(conf_file);
return conf;
}
void chassis_config_free(chassis_config_t *p)
{
if (!p) return;
if (p->user) g_free(p->user);
if (p->password) g_free(p->password);
if (p->host) g_free(p->host);
if (p->schema) g_free(p->schema);
if (p->options_table) g_free(p->options_table);
if (p->options_filter) g_free(p->options_filter);
if (p->options) g_hash_table_destroy(p->options);
if (p->objects) g_list_free_full(p->objects, (GDestroyNotify)config_object_free);
if (p->mysql_conn) {
mysql_close(p->mysql_conn);
}
mysql_thread_end();
g_free(p);
}
static gboolean chassis_config_load_options_mysql(chassis_config_t *conf)
{
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn) {
g_warning("chassis_config can't get mysql conn");
goto mysql_error;
}
char sql[1024] = {0};
snprintf(sql, sizeof(sql), "SELECT option_key,option_value FROM %s.%s",
conf->schema, conf->options_table);
if (mysql_query(conn, sql)) {
g_warning("sql failed: %s", sql);
goto mysql_error;
}
MYSQL_RES *result = mysql_store_result(conn);
if (!result) goto mysql_error;
if (!conf->options)
conf->options = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
else
g_hash_table_remove_all(conf->options);
MYSQL_ROW row;
while ((row = mysql_fetch_row(result))) {
g_hash_table_insert(conf->options, g_strdup(row[0]), g_strdup(row[1]));
}
mysql_free_result(result);
return TRUE;
mysql_error:
return FALSE;
}
static gboolean chassis_config_load_options(chassis_config_t *conf)
{
switch (conf->type) {
case CHASSIS_CONF_MYSQL:
return chassis_config_load_options_mysql(conf);
default:
/* TODO g_critical(G_STRLOC " not implemented"); */
return FALSE;
}
}
GHashTable *chassis_config_get_options(chassis_config_t *conf)
{
if (!conf->options)
chassis_config_load_options(conf);
return conf->options;
}
static void chassis_config_object_set_cache(config_object_t *ob,
const char *str, time_t mt)
{
if (ob->cache) {
g_free(ob->cache);
}
ob->cache = g_strdup(str);
ob->mtime = mt;
}
static config_object_t *chassis_config_get_object(chassis_config_t *conf,
const char *name)
{
GList *l = NULL;
for (l = conf->objects; l; l = l->next) {
config_object_t *ob = l->data;
if (strcmp(ob->name, name) == 0)
return ob;
}
return NULL;
}
static gboolean chassis_config_mysql_query_object(chassis_config_t *conf,
config_object_t *object, const char *name, char **json_res)
{
g_assert(conf->type == CHASSIS_CONF_MYSQL);
if (object->cache) {
*json_res = g_strdup(object->cache);
return TRUE;
}
gboolean status = FALSE;
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn) {
g_warning("Cannot connect to mysql server.");
goto mysql_error;
}
char sql[256] = {0};
snprintf(sql, sizeof(sql),
"SELECT object_value,mtime FROM %s.objects where object_name='%s'",
conf->schema, name);
if (mysql_query(conn, sql)) {
g_warning("sql failed: %s", sql);
goto mysql_error;
}
MYSQL_RES *result = mysql_store_result(conn);
if (!result)
goto mysql_error;
MYSQL_ROW row;
row = mysql_fetch_row(result);
if (!row) {
mysql_free_result(result);
goto mysql_error;
}
*json_res = g_strdup(row[0]);
time_t mt = chassis_epoch_from_string(row[1], NULL);
chassis_config_object_set_cache(object, row[0], mt);
mysql_free_result(result);
status = TRUE;
mysql_error:
return status;
}
static gboolean chassis_config_local_query_object(chassis_config_t *conf,
config_object_t *object, const char *name, char **json_res)
{
if (object->cache) {
*json_res = g_strdup(object->cache);
return TRUE;
}
char basename[128] = {0};
snprintf(basename, sizeof(basename), "%s.%s", name, "json");
char *object_file = g_build_filename(conf->schema, basename, NULL);
char *buffer = NULL;
if (read_file_to_buffer(object_file, &buffer) == FALSE) {
g_free(object_file);
g_free(buffer);
return FALSE;
}
*json_res = buffer;
GStatBuf sta;
if (g_stat(object_file, &sta) == 0) {
chassis_config_object_set_cache(object, buffer, sta.st_mtime);
}
g_free(object_file);
return TRUE;
}
/* select a table, make it into a json */
gboolean chassis_config_query_object(chassis_config_t *conf,
const char *name, char **json_res)
{
config_object_t *object = chassis_config_get_object(conf, name);
if (!object) {
object = g_new0(config_object_t, 1);
strncpy(object->name, name, RF_MAX_NAME_LEN - 1);
conf->objects = g_list_append(conf->objects, object);
}
switch (conf->type) {
case CHASSIS_CONF_MYSQL:
return chassis_config_mysql_query_object(conf, object, name, json_res);
case CHASSIS_CONF_LOCAL:
return chassis_config_local_query_object(conf, object, name, json_res);
default:
return FALSE;
}
}
static gboolean chassis_config_mysql_write_object(chassis_config_t *conf,
config_object_t *object, const char *name, const char *json)
{
g_assert(conf->type == CHASSIS_CONF_MYSQL);
time_t now = time(0);
GString *sql = g_string_new(0);
g_string_printf(sql, "REPLACE INTO %s.objects(object_name,object_value,mtime)"
" VALUES('%s','%s', FROM_UNIXTIME(%ld))", conf->schema, name, json, now);
gboolean status = TRUE;
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn) {
g_warning("Cannot connect to mysql server.");
status = FALSE;
}
if (mysql_query(conn, sql->str)) {
g_warning("sql failed: %s", sql->str);
status = FALSE;
}
chassis_config_object_set_cache(object, json, now);
g_string_free(sql, TRUE);
return status;
}
static gboolean chassis_config_local_write_object(chassis_config_t *conf,
config_object_t *object, const char *name, const char *json_str)
{
/* conf->schema is an absolute path for local config */
char basename[128] = {0};
snprintf(basename, sizeof(basename), "%s.%s", name, "json");
char *object_file = g_build_filename(conf->schema, basename, NULL);
FILE *fp = fopen(object_file, "w"); /* truncate and write */
if (!fp) {
g_critical(G_STRLOC "can't open file: %s for write", object_file);
g_free(object_file);
return FALSE;
}
fwrite(json_str, 1, strlen(json_str), fp);
fclose(fp);
GStatBuf sta;
if (g_stat(object_file, &sta) == 0) {
chassis_config_object_set_cache(object, json_str, sta.st_mtime);
}
g_free(object_file);
return TRUE;
}
gboolean chassis_config_write_object(chassis_config_t *conf,
const char *name, const char *json)
{
config_object_t *object = chassis_config_get_object(conf, name);
if (!object) {
object = g_new0(config_object_t, 1);
strncpy(object->name, name, RF_MAX_NAME_LEN - 1);
conf->objects = g_list_append(conf->objects, object);
}
switch (conf->type) {
case CHASSIS_CONF_MYSQL:
return chassis_config_mysql_write_object(conf, object, name, json);
case CHASSIS_CONF_LOCAL:
return chassis_config_local_write_object(conf, object, name, json);
default:
return FALSE;
}
}
gboolean chassis_config_parse_options(chassis_config_t *conf, GList *entries)
{
GHashTable *opts_table = chassis_config_get_options(conf);
if (!opts_table)
return FALSE;
GList *l;
for (l = entries; l ; l = l->next) {
chassis_option_t *entry = l->data;
/* already set by cmdline or config file */
if ((entry->flags & OPTION_FLAG_CMDLINE) ||
(entry->flags & OPTION_FLAG_CONF_FILE))
continue;
char *entry_value = g_hash_table_lookup(opts_table, entry->long_name);
if (entry_value) {
switch (entry->arg) {
case OPTION_ARG_NONE:
case OPTION_ARG_INT:
if (entry->arg_data == NULL) break;
*(int *)(entry->arg_data) = atoi(entry_value);
break;
case OPTION_ARG_STRING: {
if (entry->arg_data == NULL || *(char **)entry->arg_data != NULL) break;
char *value = g_strdup(entry_value);
*(char **)(entry->arg_data) = value;
break;
}
case OPTION_ARG_STRING_ARRAY: {
if (entry->arg_data == NULL || *(char **)entry->arg_data != NULL) break;
char **values = g_strsplit(entry_value, ",", -1);
*(char ***)(entry->arg_data) = values;
break;
}
case OPTION_ARG_DOUBLE:
*(double *)(entry->arg_data) = atof(entry_value);
break;
default:
g_warning("Unhandled option arg type: %d", entry->arg);
break;
}
}
}
return TRUE;
}
gboolean chassis_config_mysql_is_object_outdated(chassis_config_t *conf,
config_object_t *object,
const char *name)
{
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn)
return FALSE;
static char sql[128] = {0};
snprintf(sql, sizeof(sql), "SELECT mtime FROM %s.objects where object_name='%s'",
conf->schema, name);
if (mysql_query(conn, sql)) {
g_warning("sql failed: %s", sql);
return FALSE;
}
MYSQL_RES *result = mysql_store_result(conn);
if (!result)
return FALSE;
MYSQL_ROW row;
row = mysql_fetch_row(result);
if (!row)
return FALSE;
time_t mt = chassis_epoch_from_string(row[0], NULL);
mysql_free_result(result);
return object->mtime < mt;
}
gboolean chassis_config_local_is_object_outdated(chassis_config_t *conf,
config_object_t *object,
const char *name)
{
GStatBuf sta;
char basename[128] = {0};
snprintf(basename, sizeof(basename), "%s.%s", name, "json");
char *object_file = g_build_filename(conf->schema, basename, NULL);
if (g_stat(object_file, &sta)) {
g_free(object_file);
return FALSE;
}
g_free(object_file);
return object->mtime < sta.st_mtime;
}
gboolean chassis_config_is_object_outdated(chassis_config_t *conf, const char *name)
{
config_object_t *object = chassis_config_get_object(conf, name);
if (!object) {
return FALSE;
}
switch (conf->type) {
case CHASSIS_CONF_MYSQL:
return chassis_config_mysql_is_object_outdated(conf, object, name);
case CHASSIS_CONF_LOCAL:
return chassis_config_local_is_object_outdated(conf, object, name);
default:
return FALSE;
}
}
void chassis_config_update_object_cache(chassis_config_t *conf, const char *name)
{
config_object_t *object = chassis_config_get_object(conf, name);
if (!object)
return;
if (object->cache) {
g_free(object->cache);
object->cache = NULL;
}
char *str;
chassis_config_query_object(conf, name, &str);
if (str) { /* we just want to trigger query&caching, result is not needed */
g_free(str);
}
}
#define MAX_ID_SIZE 127
char *chassis_config_get_id(chassis_config_t *conf)
{
GString *id = g_string_new(0);
switch (conf->type) {
case CHASSIS_CONF_MYSQL:
g_string_printf(id, "%s_%d_%s", conf->host, conf->port, conf->schema);
break;
case CHASSIS_CONF_LOCAL:
g_string_assign(id, conf->schema);
break;
default:
g_string_assign(id, "error-config-id");
g_assert(0);
break;
}
g_string_append_printf(id, "_%u_%u", g_random_int(), g_random_int());
if (id->len >= MAX_ID_SIZE) {
g_string_erase(id, 0, id->len - MAX_ID_SIZE);
g_warning("id truncated to: %s", id->str);
}
char *id_str = id->str;
g_string_free(id, FALSE);
return id_str;
}
gboolean chassis_config_register_service(chassis_config_t *conf, char *id, char *data)
{
if (conf->type != CHASSIS_CONF_MYSQL)
return FALSE;
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
char sql[512] = {0};
snprintf(sql, sizeof(sql), "CREATE TABLE IF NOT EXISTS %s.services("
"id varchar(64) NOT NULL,"
"data varchar(64) NOT NULL,"
"start_time timestamp, PRIMARY KEY(id))", conf->schema);
if (mysql_query(conn, sql)) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
time_t now = time(0);
snprintf(sql, sizeof(sql), "INSERT INTO %s.services(id, data, start_time)"
" VALUES('%s','%s',FROM_UNIXTIME(%ld)) ON DUPLICATE KEY UPDATE"
" start_time=FROM_UNIXTIME(%ld)",
conf->schema, id, data, now, now);
if (mysql_query(conn, sql)) {
g_critical("%s", mysql_error(conn));
return FALSE;
}
return TRUE;
}
void chassis_config_unregister_service(chassis_config_t *conf, char *id)
{
if (conf->type != CHASSIS_CONF_MYSQL)
return;
MYSQL *conn = chassis_config_get_mysql_connection(conf);
if (!conn) {
g_critical("%s", mysql_error(conn));
return;
}
char sql[512] = {0};
snprintf(sql, sizeof(sql), "DELETE FROM %s.services WHERE id='%s'",
conf->schema, id);
if (mysql_query(conn, sql)) {
g_critical("%s", mysql_error(conn));
return;
}
}

39
src/chassis-config.h Normal file
View File

@ -0,0 +1,39 @@
#ifndef CHASSIS_CONFIG_H
#define CHASSIS_CONFIG_H
#include "glib-ext.h"
/**
* The config manager module, it manages `options` and `object`
* currently support `mysql` and `local` media type
*/
typedef struct chassis_config_t chassis_config_t;
chassis_config_t *chassis_config_from_url(char *url);
chassis_config_t *chassis_config_from_local_dir(char *dir, char *conf_file);
void chassis_config_free(chassis_config_t *);
GHashTable *chassis_config_get_options(chassis_config_t *);
gboolean chassis_config_parse_options(chassis_config_t *, GList *entries);
gboolean chassis_config_query_object(chassis_config_t *,
const char *name, char **json);
gboolean chassis_config_write_object(chassis_config_t *,
const char *name, const char *json);
gboolean chassis_config_is_object_outdated(chassis_config_t *, const char *name);
void chassis_config_update_object_cache(chassis_config_t *, const char *name);
char *chassis_config_get_id(chassis_config_t *);
gboolean chassis_config_register_service(chassis_config_t *conf, char *id, char *data);
void chassis_config_unregister_service(chassis_config_t *conf, char *id);
#endif /* CHASSIS_CONFIG_H */

92
src/chassis-event.c Normal file
View File

@ -0,0 +1,92 @@
#include <glib.h>
#include <errno.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h> /* for write() */
#endif
#include <sys/socket.h> /* for SOCK_STREAM and AF_UNIX/AF_INET */
#include <event.h>
#include "chassis-event.h"
#include "glib-ext.h"
#include "cetus-util.h"
#define E_NET_CONNRESET ECONNRESET
#define E_NET_CONNABORTED ECONNABORTED
#define E_NET_INPROGRESS EINPROGRESS
#if EWOULDBLOCK == EAGAIN
/**
* some system make EAGAIN == EWOULDBLOCK which would lead to a
* error in the case handling
*
* set it to -1 as this error should never happen
*/
#define E_NET_WOULDBLOCK -1
#else
#define E_NET_WOULDBLOCK EWOULDBLOCK
#endif
void chassis_event_add_with_timeout(chassis *chas, struct event *ev, struct timeval *tv) {
event_base_set(chas->event_base, ev);
#if NETWORK_DEBUG_TRACE_EVENT
CHECK_PENDING_EVENT(ev);
#endif
event_add(ev, tv);
g_debug("%s:event add ev:%p",G_STRLOC, ev);
}
/**
* add a event asynchronously
*
* @see network_mysqld_con_handle()
*/
void chassis_event_add(chassis *chas, struct event *ev) {
chassis_event_add_with_timeout(chas, ev, NULL);
}
chassis_event_loop_t *chassis_event_loop_new() {
return event_base_new();
}
void chassis_event_loop_free(chassis_event_loop_t *event) {
event_base_free(event);
}
void *chassis_event_loop(chassis_event_loop_t *loop) {
/**
* check once a second if we shall shutdown the proxy
*/
while (!chassis_is_shutdown()) {
struct timeval timeout;
int r;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
r = event_base_loopexit(loop, &timeout);
if (r == -1) {
g_critical("%s: leaving chassis_event_loop early. failed", G_STRLOC);
break;
}
r = event_base_dispatch(loop);
if (r == -1) {
if (errno == EINTR) continue;
g_critical("%s: leaving chassis_event_loop early, errno != EINTR was: %s (%d)",
G_STRLOC, g_strerror(errno), errno);
break;
}
}
return NULL;
}

26
src/chassis-event.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef _CHASSIS_EVENT_H_
#define _CHASSIS_EVENT_H_
#include <glib.h> /* GPtrArray */
#include "chassis-exports.h"
#include "chassis-mainloop.h"
#define CHECK_PENDING_EVENT(ev) \
if (event_pending((ev), EV_READ|EV_WRITE|EV_TIMEOUT, NULL)) { \
g_warning(G_STRLOC ": pending ev:%p, flags:%d", (ev), (ev)->ev_flags); \
event_del(ev); \
}
CHASSIS_API void chassis_event_add(chassis *chas, struct event *ev);
CHASSIS_API void chassis_event_add_with_timeout(chassis *chas,
struct event *ev, struct timeval *tv);
typedef struct event_base chassis_event_loop_t;
CHASSIS_API chassis_event_loop_t *chassis_event_loop_new();
CHASSIS_API void chassis_event_loop_free(chassis_event_loop_t *e);
CHASSIS_API void chassis_event_set_event_base(chassis_event_loop_t *e, struct event_base *event_base);
CHASSIS_API void *chassis_event_loop(chassis_event_loop_t *);
#endif

7
src/chassis-exports.h Normal file
View File

@ -0,0 +1,7 @@
#ifndef _CHASSIS_EXPORTS_
#define _CHASSIS_EXPORTS_
#define CHASSIS_API extern
#endif

47
src/chassis-filemode.c Normal file
View File

@ -0,0 +1,47 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <string.h>
#include <gmodule.h>
#include "chassis-filemode.h"
/*
* check whether the given filename points to a file the permissions
* of which are 0 for group and other (ie read/writable only by owner).
* return 0 for "OK", -1 of the file cannot be accessed or is the wrong
* type of file, and 1 if permissions are wrong
*
*
* FIXME? this function currently ignores ACLs
*/
int chassis_filemode_check_full(const gchar *filename, int required_filemask, GError **gerr) {
struct stat stbuf;
mode_t fmode;
if (stat(filename, &stbuf) == -1) {
g_set_error(gerr, G_FILE_ERROR, g_file_error_from_errno(errno),
"cannot stat(%s): %s", filename,
g_strerror(errno));
return -1;
}
fmode = stbuf.st_mode;
if ((fmode & S_IFMT) != S_IFREG) {
g_set_error(gerr, G_FILE_ERROR, G_FILE_ERROR_INVAL,
"%s isn't a regular file", filename);
return -1;
}
if ((fmode & required_filemask) != 0) {
g_set_error(gerr, G_FILE_ERROR, G_FILE_ERROR_PERM,
"permissions of %s aren't secure (0660 or stricter required)", filename);
return 1;
}
#undef MASK
return 0;
}

12
src/chassis-filemode.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef _CHASSIS_PERM_H_
#define _CHASSIS_PERM_H_
#include <glib.h>
#include "chassis-exports.h"
#include <sys/stat.h>
#define CHASSIS_FILEMODE_SECURE_MASK (S_IROTH|S_IWOTH|S_IXOTH)
CHASSIS_API int chassis_filemode_check_full(const gchar *, int , GError **);
#endif

334
src/chassis-frontend.c Normal file
View File

@ -0,0 +1,334 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <glib.h>
#include <gmodule.h>
#include <event.h>
#include "glib-ext.h"
#include "chassis-config.h"
#include "chassis-frontend.h"
#include "chassis-path.h"
#include "chassis-plugin.h"
#include "chassis-keyfile.h"
#include "chassis-filemode.h"
#include "chassis-options.h"
#include "cetus-util.h"
/**
* initialize the basic components of the chassis
*/
int chassis_frontend_init_glib() {
const gchar *check_str = NULL;
#if 0
g_mem_set_vtable(glib_mem_profiler_table);
#endif
if (!GLIB_CHECK_VERSION(2, 6, 0)) {
g_critical("the glib header is too old, need at least 2.6.0, got: %d.%d.%d",
GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION);
return -1;
}
check_str = glib_check_version(GLIB_MAJOR_VERSION,
GLIB_MINOR_VERSION, GLIB_MICRO_VERSION);
if (check_str) {
g_critical("%s, got: lib=%d.%d.%d, headers=%d.%d.%d",
check_str,
glib_major_version, glib_minor_version, glib_micro_version,
GLIB_MAJOR_VERSION, GLIB_MINOR_VERSION, GLIB_MICRO_VERSION);
return -1;
}
if (!g_module_supported()) {
g_critical("loading modules is not supported on this platform");
return -1;
}
#if !GLIB_CHECK_VERSION(2, 32, 0)
/* GLIB below 2.32 must call thread_init */
g_thread_init(NULL);
#endif
return 0;
}
/**
* setup and check the basedir if nessesary
*/
int chassis_frontend_init_basedir(const char *prg_name, char **_base_dir) {
char *base_dir = *_base_dir;
if (base_dir) { /* basedir is already known, check if it is absolute */
if (!g_path_is_absolute(base_dir)) {
g_critical("%s: --basedir option must be an absolute path, but was %s",
G_STRLOC,
base_dir);
return -1;
} else {
return 0;
}
}
/* find our installation directory if no basedir was given
* this is necessary for finding files when we daemonize
*/
base_dir = chassis_get_basedir(prg_name);
if (!base_dir) {
g_critical("%s: Failed to get base directory",
G_STRLOC);
return -1;
}
*_base_dir = base_dir;
return 0;
}
int chassis_frontend_init_plugin_dir(char **_plugin_dir, const char *base_dir) {
char *plugin_dir = *_plugin_dir;
if (plugin_dir) return 0;
plugin_dir = g_build_filename(base_dir, "lib", PACKAGE, "plugins", NULL);
*_plugin_dir = plugin_dir;
return 0;
}
int chassis_frontend_load_plugins(GPtrArray *plugins,
const gchar *plugin_dir, gchar **plugin_names)
{
int i;
/* load the plugins */
for (i = 0; plugin_names && plugin_names[i]; i++) {
chassis_plugin *p;
#define G_MODULE_PREFIX "lib"
/* we have to hack around some glib distributions that
* don't set the correct G_MODULE_SUFFIX, notably MacPorts
*/
#ifndef SHARED_LIBRARY_SUFFIX
#define SHARED_LIBRARY_SUFFIX G_MODULE_SUFFIX
#endif
char *plugin_filename;
/* skip trying to load a plugin when the parameter was --plugins=
that will never work...
*/
if (!g_strcmp0("", plugin_names[i])) {
continue;
}
plugin_filename = g_strdup_printf("%s%c%s%s.%s",
plugin_dir,
G_DIR_SEPARATOR,
G_MODULE_PREFIX,
plugin_names[i],
SHARED_LIBRARY_SUFFIX);
p = chassis_plugin_load(plugin_filename);
g_free(plugin_filename);
if (NULL == p) {
g_critical("setting --plugin-dir=<dir> might help");
return -1;
}
p->option_grp_name = g_strdup(plugin_names[i]);
g_ptr_array_add(plugins, p);
}
return 0;
}
int chassis_frontend_init_plugins(GPtrArray *plugins,
chassis_options_t *opts, chassis_config_t *config_manager,
int *argc_p, char ***argv_p,
GKeyFile *keyfile,
const char *keyfile_section_name,
GError **gerr)
{
guint i;
for (i = 0; i < plugins->len; i++) {
GList *config_entries = NULL;
chassis_plugin *p = g_ptr_array_index(plugins, i);
if (NULL != (config_entries = chassis_plugin_get_options(p))) {
chassis_options_add_options(opts, config_entries);
if (FALSE == chassis_options_parse_cmdline(opts, argc_p, argv_p, gerr)) {
return -1;
}
/* parse the new options */
if (keyfile) {
if (FALSE == chassis_keyfile_to_options_with_error(keyfile,
keyfile_section_name, config_entries, gerr))
{
return -1;
}
}
/* Load from remote config first */
if (config_manager) {
chassis_config_parse_options(config_manager, config_entries);
}
}
}
return 0;
}
int chassis_frontend_init_base_options(int *argc_p, char ***argv_p,
int *print_version,
char **config_file,
GError **gerr)
{
int ret = 0;
chassis_options_t *opts = chassis_options_new();
chassis_options_set_cmdline_only_options(opts, print_version, config_file);
opts->ignore_unknown = TRUE;
if (FALSE == chassis_options_parse_cmdline(opts, argc_p, argv_p, gerr)) {
ret = -1;
}
chassis_options_free(opts);
return ret;
}
GKeyFile *chassis_frontend_open_config_file(const char *filename, GError **gerr) {
GKeyFile *keyfile;
if (chassis_filemode_check_full(filename, CHASSIS_FILEMODE_SECURE_MASK, gerr) != 0) {
return NULL;
}
keyfile = g_key_file_new();
g_key_file_set_list_separator(keyfile, ',');
if (FALSE == g_key_file_load_from_file(keyfile, filename, G_KEY_FILE_NONE, gerr)) {
g_key_file_free(keyfile);
return NULL;
}
return keyfile;
}
/**
* setup the options that can only appear on the command-line
*/
int chassis_options_set_cmdline_only_options(chassis_options_t *opts,
int *print_version,
char **config_file) {
chassis_options_add(opts,
"version", 'V', 0, OPTION_ARG_NONE, print_version,
"Show version", NULL);
chassis_options_add(opts,
"defaults-file", 0, 0, OPTION_ARG_STRING, config_file,
"configuration file", "<file>");
return 0;
}
int chassis_frontend_print_version() {
/*
* allow to pass down a build-tag at build-time
* which gets hard-coded into the binary
*/
g_print(" %s\n", PACKAGE_STRING);
#ifdef CHASSIS_BUILD_TAG
g_print(" build: %s\n", CHASSIS_BUILD_TAG);
#endif
g_print(" glib2: %d.%d.%d\n", GLIB_MAJOR_VERSION,GLIB_MINOR_VERSION,GLIB_MICRO_VERSION);
g_print(" libevent: %s\n", event_get_version());
return 0;
}
int chassis_frontend_print_plugin_versions(GPtrArray *plugins) {
guint i;
g_print("-- modules\n");
for (i = 0; i < plugins->len; i++) {
chassis_plugin *p = plugins->pdata[i];
g_print(" %s: %s\n", p->name, p->version);
}
return 0;
}
/**
* log the versions of the initialized plugins
*/
int chassis_frontend_log_plugin_versions(GPtrArray *plugins) {
guint i;
for (i = 0; i < plugins->len; i++) {
chassis_plugin *p = plugins->pdata[i];
g_message("plugin %s %s started", p->name, p->version);
}
return 0;
}
int chassis_frontend_write_pidfile(const char *pid_file, GError **gerr) {
int fd;
int ret = 0;
gchar *pid_str;
/**
* write the PID file
*/
if (-1 == (fd = open(pid_file, O_WRONLY|O_TRUNC|O_CREAT, 0600))) {
g_set_error(gerr,
G_FILE_ERROR,
g_file_error_from_errno(errno),
"%s: open(%s) failed: %s",
G_STRLOC,
pid_file,
g_strerror(errno));
return -1;
}
pid_str = g_strdup_printf("%d", getpid());
if (write(fd, pid_str, strlen(pid_str)) < 0) {
g_set_error(gerr,
G_FILE_ERROR,
g_file_error_from_errno(errno),
"%s: write(%s) of %s failed: %s",
G_STRLOC,
pid_file,
pid_str,
g_strerror(errno));
ret = -1;
}
g_free(pid_str);
close(fd);
return ret;
}

134
src/chassis-frontend.h Normal file
View File

@ -0,0 +1,134 @@
#ifndef __CHASSIS_FRONTEND_H__
#define __CHASSIS_FRONTEND_H__
#include <glib.h>
#include "chassis-exports.h"
#include "chassis-options.h"
/**
* @file
* a collections of common functions used by chassis frontends
*
* take a look at mysql-proxy-cli.c on what sequence to call these functions
*/
/**
* setup glib and gmodule
*
* may abort of glib headers and glib libs don't match
*
* @return 0 on success, -1 on error
*/
CHASSIS_API int chassis_frontend_init_glib(void);
/**
* detect the basedir
*
* if *_basedir is not NULL, don't change it
* otherwise extract the basedir from the prg_name
*
* @param prg_name program-name (usually argv[0])
* @param _basedir user-supplied basedir
* @return 0 on success, -1 on error
*/
CHASSIS_API int chassis_frontend_init_basedir(const char *prg_name, char **_base_dir);
/**
* open the configfile
*
* @param filename name of the configfile
* @param gerr a pointer to a clean GError *or
* NULL in case the possible error should be ignored
*
* @see g_key_file_free
*/
CHASSIS_API GKeyFile *chassis_frontend_open_config_file(const char *filename, GError **gerr);
CHASSIS_API int chassis_frontend_init_plugin_dir(char **, const char *);
/**
* extract --version and --defaults-file from comandline options
*
* @param argc_p pointer to the number of args in argv_p
* @param argv_p pointer to arguments to parse
* @param print_version pointer to int to set if --version is specified
* @param config_file pointer to char *to store if --defaults-file is specified
* @param gerr a pointer to a clean GError *or
* NULL in case the possible error should be ignored
*/
CHASSIS_API int chassis_frontend_init_base_options(int *argc_p, char ***argv_p,
int *print_version,
char **config_file,
GError **gerr);
/**
* load the plugins
*
* loads the plugins from 'plugin_names' from the 'plugin_dir' and
* store their chassis_plugin structs in 'plugins'
*
* the filename of the plugin is constructed based depending on the platform
*
* @param plugins empty array
* @param plugin_dir directory to load the plugins from
* @param plugin_names NULL terminated list of plugin names
*
* @see chassis_frontend_init_plugins
*/
CHASSIS_API int chassis_frontend_load_plugins(GPtrArray *plugins,
const gchar *plugin_dir,
gchar **plugin_names);
/**
* init the loaded plugins and setup their config
*
* @param plugins array of chassis_plugin structs
* @param option_ctx a fresh GOptionContext
* @param argc_p pointer to the number of args in argv_p
* @param argv_p pointer to arguments to parse
* @param keyfile the configfile
* @param base_dir base directory
* @param gerr a pointer to a clean GError *or
* NULL in case the possible error should be ignored
*
* @see chassis_frontend_init_basedir, chassis_frontend_init_plugins
*/
struct remote_config_t;
CHASSIS_API int chassis_frontend_init_plugins(GPtrArray *plugins,
chassis_options_t *option_ctx,
struct chassis_config_t *config_manager,
int *argc_p, char ***argv_p,
GKeyFile *keyfile,
const char *keyfile_section_name,
GError **gerr);
/**
* print the version of the program
*/
CHASSIS_API int chassis_frontend_print_version(void);
/**
* print the versions of the initialized plugins
*/
CHASSIS_API int chassis_frontend_print_plugin_versions(GPtrArray *plugins);
/**
* log the versions of the initialized plugins
*/
CHASSIS_API int chassis_frontend_log_plugin_versions(GPtrArray *plugins);
/**
* write the PID to a file
*
* @param pid_file name of the PID file
* @param gerr GError
* @return 0 on success, -1 on error
*/
CHASSIS_API int chassis_frontend_write_pidfile(const char *pid_file, GError **gerr);
CHASSIS_API int chassis_options_set_cmdline_only_options(chassis_options_t *opts,
int *print_version,
char **config_file);
#endif

115
src/chassis-keyfile.c Normal file
View File

@ -0,0 +1,115 @@
#include "chassis-path.h"
#include "chassis-keyfile.h"
#include "chassis-options.h"
/**
* map options from the keyfile to the config-options
*
* @returns FALSE on error, TRUE on success
* @added in 0.8.3
*/
gboolean chassis_keyfile_to_options_with_error(GKeyFile *keyfile,
const gchar *ini_group_name, GList *config_entries,
GError **_gerr)
{
GError *gerr = NULL;
gboolean ret = TRUE;
int j;
if (NULL == keyfile) {
g_set_error(_gerr, G_FILE_ERROR, G_FILE_ERROR_INVAL,
"keyfile has to be set");
return FALSE;
}
if (!g_key_file_has_group(keyfile, ini_group_name)) {
/* the group doesn't exist, no config-entries to map */
g_warning("(keyfile) has no group [%s]", ini_group_name);
return TRUE;
}
GList *l;
/* set the defaults */
for (l = config_entries; l; l = l->next) {
chassis_option_t *entry = l->data;
gchar *arg_string;
gchar **arg_string_array;
gboolean arg_bool = 0;
gint arg_int = 0;
gdouble arg_double = 0;
gsize len = 0;
/* already set by command line */
if (entry->flags & OPTION_FLAG_CMDLINE)
continue;
switch (entry->arg) {
case OPTION_ARG_STRING:
/* is this option set already */
if (entry->arg_data == NULL || *(char **)entry->arg_data != NULL) break;
arg_string = g_key_file_get_string(keyfile, ini_group_name,
entry->long_name, &gerr);
if (!gerr) {
/* strip trailing spaces */
*(gchar **)(entry->arg_data) = g_strchomp(arg_string);
}
break;
case OPTION_ARG_STRING_ARRAY:
/* is this option set already */
if (entry->arg_data == NULL || *(char **)entry->arg_data != NULL) break;
arg_string_array = g_key_file_get_string_list(keyfile,
ini_group_name, entry->long_name, &len, &gerr);
if (!gerr) {
for (j = 0; arg_string_array[j]; j++) {
arg_string_array[j] = g_strstrip(arg_string_array[j]);
}
*(gchar ***)(entry->arg_data) = arg_string_array;
}
break;
case OPTION_ARG_NONE:
arg_bool = g_key_file_get_boolean(keyfile, ini_group_name,
entry->long_name, &gerr);
if (!gerr) {
*(gboolean *)(entry->arg_data) = arg_bool;
}
break;
case OPTION_ARG_INT:
arg_int = g_key_file_get_integer(keyfile, ini_group_name,
entry->long_name, &gerr);
if (!gerr) {
*(gint *)(entry->arg_data) = arg_int;
}
break;
case OPTION_ARG_DOUBLE:
arg_double = g_key_file_get_double(keyfile, ini_group_name,
entry->long_name, &gerr);
if (!gerr) {
*(gdouble *)(entry->arg_data) = arg_double;
}
break;
default:
g_error("%s: (keyfile) the option %d can't be handled",
G_STRLOC, entry->arg);
break;
}
if (gerr) {
if (gerr->code == G_KEY_FILE_ERROR_KEY_NOT_FOUND) {
/* ignore if this key isn't set in the config-file */
g_error_free(gerr);
} else {
/* otherwise propage the error the higher level */
g_propagate_error(_gerr, gerr);
ret = FALSE;
break;
}
gerr = NULL;
} else {
entry->flags |= OPTION_FLAG_CONF_FILE;
}
}
return ret;
}

21
src/chassis-keyfile.h Normal file
View File

@ -0,0 +1,21 @@
#ifndef _CHASSIS_KEYFILE_H_
#define _CHASSIS_KEYFILE_H_
#include <glib.h>
#include "chassis-exports.h"
/** @addtogroup chassis */
/*@{*/
/**
* parse the configfile options into option entries
*
*/
gboolean chassis_keyfile_to_options_with_error(GKeyFile *keyfile,
const gchar *groupname, GList *config_entries, GError **gerr);
/*@}*/
#endif

60
src/chassis-limits.c Normal file
View File

@ -0,0 +1,60 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <glib.h>
#include <sys/types.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h>
#endif
#include <errno.h>
#include <stdlib.h>
#include "chassis-limits.h"
gint64 chassis_fdlimit_get() {
struct rlimit max_files_rlimit;
if (-1 == getrlimit(RLIMIT_NOFILE, &max_files_rlimit)) {
return -1;
} else {
return max_files_rlimit.rlim_cur;
}
}
/**
* set the upper limit of open files
*
* @return -1 on error, 0 on success
*/
int chassis_fdlimit_set(gint64 max_files_number) {
struct rlimit max_files_rlimit;
rlim_t hard_limit;
if (-1 == getrlimit(RLIMIT_NOFILE, &max_files_rlimit)) {
return -1;
}
hard_limit = max_files_rlimit.rlim_max;
max_files_rlimit.rlim_cur = max_files_number;
/*
* raise the hard-limit too in case it is smaller
* than the soft-limit, otherwise we get a EINVAL
*/
if (hard_limit < max_files_number) {
max_files_rlimit.rlim_max = max_files_number;
}
if (-1 == setrlimit(RLIMIT_NOFILE, &max_files_rlimit)) {
return -1;
}
return 0;
}

15
src/chassis-limits.h Normal file
View File

@ -0,0 +1,15 @@
#ifndef _CHASSIS_LIMITS_H_
#define _CHASSIS_LIMITS_H_
#include <glib.h> /* GPtrArray */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "chassis-exports.h"
CHASSIS_API int chassis_fdlimit_set(gint64 max_files_number);
CHASSIS_API gint64 chassis_fdlimit_get(void);
#endif

400
src/chassis-log.c Normal file
View File

@ -0,0 +1,400 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h> /* close */
#define EVENTLOG_ERROR_TYPE 0x0001
#define EVENTLOG_WARNING_TYPE 0x0002
#define EVENTLOG_INFORMATION_TYPE 0x0004
#include <glib.h>
#ifdef HAVE_SYSLOG_H
#include <syslog.h>
#else
/* placeholder values for platforms not having syslog support */
#define LOG_USER 0 /* placeholder for user-level syslog facility */
#define LOG_CRIT 0
#define LOG_ERR 0
#define LOG_WARNING 0
#define LOG_NOTICE 0
#define LOG_INFO 0
#define LOG_DEBUG 0
#endif
#include "sys-pedantic.h"
#include "chassis-log.h"
#define S(x) x->str, x->len
/**
* the mapping of our internal log levels various log systems
*
* Attention: this needs to be adjusted should glib ever
* change its log level ordering
*/
#define G_LOG_ERROR_POSITION 3
const struct {
char *name;
GLogLevelFlags lvl;
int syslog_lvl;
int win_evtype;
} log_lvl_map[] = { /* syslog levels are different to the glib ones */
{ "error", G_LOG_LEVEL_ERROR, LOG_CRIT, EVENTLOG_ERROR_TYPE},
{ "critical", G_LOG_LEVEL_CRITICAL, LOG_ERR, EVENTLOG_ERROR_TYPE},
{ "warning", G_LOG_LEVEL_WARNING, LOG_WARNING, EVENTLOG_WARNING_TYPE},
{ "message", G_LOG_LEVEL_MESSAGE, LOG_NOTICE, EVENTLOG_INFORMATION_TYPE},
{ "info", G_LOG_LEVEL_INFO, LOG_INFO, EVENTLOG_INFORMATION_TYPE},
{ "debug", G_LOG_LEVEL_DEBUG, LOG_DEBUG, EVENTLOG_INFORMATION_TYPE},
{ NULL, 0, 0, 0 }
};
static gboolean
chassis_log_rotate_reopen(chassis_log *log, gpointer userdata, GError **gerr);
chassis_log *chassis_log_new(void) {
chassis_log *log;
log = g_new0(chassis_log, 1);
log->log_file_fd = -1;
log->log_ts_str = g_string_sized_new(sizeof("2004-01-01T00:00:00.000Z"));
log->log_ts_resolution = CHASSIS_RESOLUTION_DEFAULT;
log->min_lvl = G_LOG_LEVEL_CRITICAL;
log->last_msg = g_string_new(NULL);
log->last_msg_ts = 0;
log->last_msg_count = 0;
log->rotate_func = NULL;
chassis_log_set_rotate_func(log, chassis_log_rotate_reopen, NULL, NULL);
return log;
}
int chassis_log_set_level(chassis_log *log, const gchar *level) {
gint i;
for (i = 0; log_lvl_map[i].name; i++) {
if (0 == strcmp(log_lvl_map[i].name, level)) {
log->min_lvl = log_lvl_map[i].lvl;
return 0;
}
}
return -1;
}
/**
* open the log-file
*
* open the log-file set in log->log_filename
* if no log-filename is set, returns TRUE
*
* FIXME: the return value is not following 'unix'-style (0 on success, -1 on error),
* nor does it say it is a gboolean. Has to be fixed in 0.9.0
*
* @return TRUE on success, FALSE on error
*/
int chassis_log_open(chassis_log *log) {
if (!log->log_filename) return TRUE;
log->log_file_fd = open(log->log_filename, O_RDWR | O_CREAT | O_APPEND, 0660);
return (log->log_file_fd != -1);
}
/**
* close the log-file
*
* @return 0 on success
*
* @see chassis_log_open
*/
int chassis_log_close(chassis_log *log) {
if (log->log_file_fd == -1) return 0;
close(log->log_file_fd);
log->log_file_fd = -1;
return 0;
}
void chassis_log_free(chassis_log *log) {
if (!log) return;
chassis_log_close(log);
g_string_free(log->log_ts_str, TRUE);
g_string_free(log->last_msg, TRUE);
if (log->log_filename) g_free(log->log_filename);
if (log->rotate_func_data_destroy && log->rotate_func_data) {
log->rotate_func_data_destroy(log->rotate_func_data);
}
g_free(log);
}
static int chassis_log_update_timestamp(chassis_log *log) {
struct tm *tm;
GTimeVal tv;
time_t t;
GString *s = log->log_ts_str;
g_get_current_time(&tv);
t = (time_t) tv.tv_sec;
tm = localtime(&t);
s->len = strftime(s->str, s->allocated_len, "%Y-%m-%d %H:%M:%S", tm);
if (log->log_ts_resolution == CHASSIS_RESOLUTION_MS)
g_string_append_printf(s, ".%.3d", (int) tv.tv_usec/1000);
return 0;
}
static int chassis_log_write(chassis_log *log, int log_level, GString *str) {
if (-1 != log->log_file_fd) {
/* prepend a timestamp */
if (-1 == write(log->log_file_fd, S(str))) {
/* writing to the file failed (Disk Full, what ever ... */
if (write(STDERR_FILENO, S(str)) >= 0) {
if (write(STDERR_FILENO, "\n", 1) >= 0) {
}
}
} else {
if (write(log->log_file_fd, "\n", 1) >= 0) {
}
}
} else {
if (write(STDERR_FILENO, S(str)) >=0) {
if (write(STDERR_FILENO, "\n", 1) >= 0) {
}
}
}
return 0;
}
/**
* skip the 'top_srcdir' from a string starting with G_STRLOC or
* __FILE__ if it is absolute
*
* <absolute-path>/src/chassis-log.c will become src/chassis-log.c
*
* NOTE: the code assumes it is located in src/ or src\. i
* If it gets moves somewhere else
* it won't crash, but strip too much of pathname
*/
const char *chassis_log_skip_topsrcdir(const char *message) {
const char *my_filename = __FILE__;
int ndx;
/*
* we want to strip everything that is before the src/
* in the above example. If we don't get the srcdir name passed down
* as part of the __FILE__, don't try to parse it out
*/
if (!g_path_is_absolute(__FILE__)) {
return message;
}
/* usually the message start with G_STRLOC which may contain a rather
* long, absolute path. If
* it matches the TOP_SRCDIR, we filter it out
*
* - strip what is the same as our __FILE__
* - don't strip our own sub-path 'src/'
*/
for (ndx = 0; message[ndx]; ndx++) {
if (0 == strncmp(message + ndx, "src" G_DIR_SEPARATOR_S,
sizeof("src" G_DIR_SEPARATOR_S) - 1))
{
break;
}
if (message[ndx] != my_filename[ndx]) break;
}
if (message[ndx] != '\0') {
message += ndx;
}
return message;
}
/*
* chassis_log_rotate_reopen:
* @log: a #chassis_log
* @userdata: ...
* @gerr: ...
*
* default log-rotate function
*
* assumes that:
* - log-file is moved to new name
* - SIGHUP is sent to process to announce log-file rotation
* - we reopen the log-file
*
* Returns: %TRUE
*/
static gboolean
chassis_log_rotate_reopen(chassis_log *log, gpointer userdata, GError **gerr) {
chassis_log_close(log);
chassis_log_open(log);
return TRUE;
}
/**
* chassis_log_rotate:
* @log: a #chassis_log
*
* rotate the logfile
*
* calls the rotation function provided by the user
* (or the default log-rotation function)
*
* Returns: %TRUE if the log-file was rotated, %FALSE on error
*/
gboolean
chassis_log_rotate(chassis_log *log, GError **gerr) {
return log->rotate_func(log, log->rotate_func_data, gerr);
}
/**
* chassis_log_set_rotate_func:
* @log: a #chassis_log
* @rotate_func: (allow-none): log-file rotation function
* @userdata: (allow-none): userdata that is passed to @rotate_func
* @userdata_free: (allow-none): a #GDestroyNotify that is called
* when the rotate-function gets unset
*
* set a rotate function that is called when logfile rotate is requested
*
* if @rotate_func is %NULL, the default log-rotation function is set
*/
void
chassis_log_set_rotate_func(chassis_log *log, chassis_log_rotate_func rotate_func,
gpointer userdata, GDestroyNotify userdata_free) {
if (log->rotate_func_data && log->rotate_func_data_destroy) {
log->rotate_func_data_destroy(log->rotate_func_data);
log->rotate_func_data = NULL;
}
if (NULL == rotate_func) {
log->rotate_func = chassis_log_rotate_reopen;
} else {
log->rotate_func = rotate_func;
}
log->rotate_func_data = userdata;
log->rotate_func_data_destroy = userdata_free;
return;
}
static void
chassis_log_func_locked(const gchar G_GNUC_UNUSED *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer user_data) {
chassis_log *log = user_data;
int i;
gchar *log_lvl_name = "(error)";
gboolean is_duplicate = FALSE;
const char *stripped_message = chassis_log_skip_topsrcdir(message);
/**
* rotate logs straight away if log->rotate_logs is true
* we do this before ignoring any log levels, so that rotation
* happens straight away - see Bug#55711
*/
if (-1 != log->log_file_fd) {
if (log->rotate_logs) {
gboolean is_rotated;
is_rotated = chassis_log_rotate(log, NULL);
log->rotate_logs = FALSE;
/* ->is_rotated stays set until the first message is logged
* again to make sure we don't hide it as duplicate
*/
if (log->is_rotated == FALSE && is_rotated == TRUE) {
log->is_rotated = TRUE;
}
}
}
/* ignore the verbose log-levels */
if (log_level > log->min_lvl) {
return;
}
for (i = 0; log_lvl_map[i].name; i++) {
if (log_lvl_map[i].lvl == log_level) {
log_lvl_name = log_lvl_map[i].name;
break;
}
}
if (log->last_msg->len > 0 &&
0 == strcmp(log->last_msg->str, stripped_message)) {
is_duplicate = TRUE;
}
/**
* if the log has been rotated, we always dump the last message even if it
* was a duplicate. Otherwise, do not print duplicates unless they have been
* ignored at least 100 times, or they were last printed greater than
* 30 seconds ago.
*/
if (log->is_rotated ||
!is_duplicate ||
log->last_msg_count > 100 ||
time(NULL) - log->last_msg_ts > 30) {
/* if we lave the last message repeating, log it */
if (log->last_msg_count) {
chassis_log_update_timestamp(log);
g_string_append_printf(log->log_ts_str, ": (%s) last message repeated %d times",
log_lvl_name,
log->last_msg_count);
chassis_log_write(log, log_level, log->log_ts_str);
}
chassis_log_update_timestamp(log);
g_string_append(log->log_ts_str, ": (");
g_string_append(log->log_ts_str, log_lvl_name);
g_string_append(log->log_ts_str, ") ");
g_string_append(log->log_ts_str, stripped_message);
/* reset the last-logged message */
g_string_assign(log->last_msg, stripped_message);
log->last_msg_count = 0;
log->last_msg_ts = time(NULL);
chassis_log_write(log, log_level, log->log_ts_str);
} else {
log->last_msg_count++;
}
log->is_rotated = FALSE;
}
void chassis_log_func(const gchar *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer user_data)
{
chassis_log_func_locked(log_domain, log_level, message, user_data);
}
void chassis_log_set_logrotate(chassis_log *log) {
log->rotate_logs = TRUE;
}

75
src/chassis-log.h Normal file
View File

@ -0,0 +1,75 @@
#ifndef _CHASSIS_LOG_H_
#define _CHASSIS_LOG_H_
#include <glib.h>
#include "chassis-exports.h"
#define CHASSIS_RESOLUTION_SEC 0x0
#define CHASSIS_RESOLUTION_MS 0x1
#define CHASSIS_RESOLUTION_DEFAULT CHASSIS_RESOLUTION_SEC
/** @addtogroup chassis */
/*@{*/
typedef struct _chassis_log chassis_log;
/**
* chassis_log_rotate_func:
*
* prototype of the log rotation function
*
* used by #chassis_log_set_rotate_func and #chassis_log_rotate()
*
* the function has to
* - return %TRUE if log-file rotation was successful,
* %FALSE otherwise and set the @gerr accordingly
* - @user_data is passed through from #chassis_log_set_rotate_func()
* - @gerr is passed in from #chassis_log_rotate()
*
*/
typedef gboolean (*chassis_log_rotate_func)(chassis_log *log, gpointer user_data, GError **gerr);
struct _chassis_log {
GLogLevelFlags min_lvl;
gchar *log_filename;
gint log_file_fd;
gboolean rotate_logs;
GString *log_ts_str;
gint log_ts_resolution; /*<< timestamp resolution (sec, ms) */
GString *last_msg;
time_t last_msg_ts;
guint last_msg_count;
/* private */
chassis_log_rotate_func rotate_func;
gpointer rotate_func_data;
GDestroyNotify rotate_func_data_destroy;
gboolean is_rotated;
};
CHASSIS_API chassis_log *chassis_log_new(void);
CHASSIS_API int chassis_log_set_level(chassis_log *log, const gchar *level);
CHASSIS_API void chassis_log_free(chassis_log *log);
CHASSIS_API int chassis_log_open(chassis_log *log);
CHASSIS_API int chassis_log_close(chassis_log *log);
CHASSIS_API void chassis_log_func(const gchar *log_domain, GLogLevelFlags log_level,
const gchar *message, gpointer user_data);
CHASSIS_API void chassis_log_set_logrotate(chassis_log *log);
CHASSIS_API const char *chassis_log_skip_topsrcdir(const char *message);
CHASSIS_API void
chassis_log_set_rotate_func(chassis_log *log, chassis_log_rotate_func rotate_func,
gpointer userdata, GDestroyNotify userdata_free);
/*@}*/
#endif

395
src/chassis-mainloop.c Normal file
View File

@ -0,0 +1,395 @@
#include <sys/types.h>
#include <signal.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h> /* event.h need struct timeval */
#endif
#ifdef HAVE_PWD_H
#include <pwd.h> /* getpwnam() */
#endif
#include <sys/socket.h> /* for SOCK_STREAM and AF_UNIX/AF_INET */
#include <glib.h>
#include "glib-ext.h"
#include "network-mysqld.h"
#include "chassis-plugin.h"
#include "chassis-mainloop.h"
#include "chassis-event.h"
#include "chassis-log.h"
#include "chassis-timings.h"
static volatile sig_atomic_t signal_shutdown;
/**
* check if the libevent headers we built against match the
* library we run against
*/
int chassis_check_version(const char *lib_version, const char *hdr_version) {
int lib_maj, lib_min, lib_pat;
int hdr_maj, hdr_min, hdr_pat;
int scanned_fields;
if (3 != (scanned_fields = sscanf(lib_version, "%d.%d.%d%*s", &lib_maj,
&lib_min, &lib_pat)))
{
g_critical("%s: library version %s failed to parse: %d",
G_STRLOC, lib_version, scanned_fields);
return -1;
}
if (3 != (scanned_fields = sscanf(hdr_version, "%d.%d.%d%*s", &hdr_maj,
&hdr_min, &hdr_pat)))
{
g_critical("%s: header version %s failed to parse: %d",
G_STRLOC, hdr_version, scanned_fields);
return -1;
}
if (lib_maj == hdr_maj &&
lib_min == hdr_min &&
lib_pat >= hdr_pat) {
return 0;
}
return -1;
}
/**
* create a global context
*/
chassis *chassis_new() {
chassis *chas;
if (0 != chassis_check_version(event_get_version(), _EVENT_VERSION)) {
g_critical("%s: chassis is built against libevent %s, but now runs against %s",
G_STRLOC, _EVENT_VERSION, event_get_version());
return NULL;
}
chas = g_new0(chassis, 1);
chas->modules = g_ptr_array_new();
chas->event_hdr_version = g_strdup(_EVENT_VERSION);
chas->shutdown_hooks = chassis_shutdown_hooks_new();
incremental_guid_init(&(chas->guid_state));
chas->startup_time = time(0);
return chas;
}
static void g_queue_free_cache_index(gpointer q) {
GQueue *queue = q;
query_cache_index_item *index;
while ((index = g_queue_pop_head(queue))) {
g_free(index->key);
g_free(index);
}
g_queue_free(queue);
}
/**
* free the global scope
*
* closes all open connections, cleans up all plugins
*
* @param chas global context
*/
void chassis_free(chassis *chas) {
guint i;
#ifdef HAVE_EVENT_BASE_FREE
const char *version;
#endif
if (!chas) return;
/* init the shutdown, without freeing share structures */
if (chas->priv_shutdown) chas->priv_shutdown(chas, chas->priv);
/* call the destructor for all plugins */
for (i = 0; i < chas->modules->len; i++) {
chassis_plugin *p = g_ptr_array_index(chas->modules, i);
g_assert(p->destroy);
p->destroy(chas, p->config);
}
/* cleanup the global 3rd party stuff before we unload the modules */
chassis_shutdown_hooks_call(chas->shutdown_hooks);
for (i = 0; i < chas->modules->len; i++) {
chassis_plugin *p = chas->modules->pdata[i];
chassis_plugin_free(p);
}
g_ptr_array_free(chas->modules, TRUE);
if (chas->base_dir) g_free(chas->base_dir);
if (chas->conf_dir) g_free(chas->conf_dir);
if (chas->plugin_dir) g_free(chas->plugin_dir);
if (chas->user) g_free(chas->user);
if (chas->default_db) g_free(chas->default_db);
if (chas->default_username) g_free(chas->default_username);
if (chas->default_hashed_pwd) g_free(chas->default_hashed_pwd);
if (chas->query_cache_table) g_hash_table_destroy(chas->query_cache_table);
if (chas->cache_index) g_queue_free_cache_index(chas->cache_index);
g_free(chas->event_hdr_version);
chassis_shutdown_hooks_free(chas->shutdown_hooks);
if (chas->priv_finally_free_shared) chas->priv_finally_free_shared(chas, chas->priv);
/* free the pointers _AFTER_ the modules are shutdown */
if (chas->priv_free) chas->priv_free(chas, chas->priv);
#ifdef HAVE_EVENT_BASE_FREE
/* only recent versions have this call */
version = event_get_version();
/* libevent < 1.3e doesn't cleanup its own fds from the event-queue in signal_init()
* calling event_base_free() would cause a assert() on shutdown
*/
if (version && (strcmp(version, "1.3e") >= 0)) {
if (chas->event_base) {
event_base_free(chas->event_base);
}
}
#endif
if (chas->config_manager) chassis_config_free(chas->config_manager);
g_free(chas);
}
void chassis_set_shutdown_location(const gchar *location) {
if (signal_shutdown == 0) {
g_message("Initiating shutdown, requested from %s",
(location != NULL ? location : "signal handler"));
}
signal_shutdown = 1;
}
gboolean chassis_is_shutdown() {
return signal_shutdown == 1;
}
static void
sigterm_handler(int G_GNUC_UNUSED fd, short G_GNUC_UNUSED event_type,
void G_GNUC_UNUSED *_data)
{
chassis_set_shutdown_location(NULL);
}
static void
sighup_handler(int G_GNUC_UNUSED fd, short G_GNUC_UNUSED event_type,
void *_data)
{
chassis *chas = _data;
/* this should go into the old logfile */
g_message("received a SIGHUP, closing log file");
chassis_log_set_logrotate(chas->log);
/* ... and this into the new one */
g_message("re-opened log file after SIGHUP");
}
/**
* forward libevent messages to the glib error log
*/
static void event_log_use_glib(int libevent_log_level, const char *msg) {
/* map libevent to glib log-levels */
GLogLevelFlags glib_log_level = G_LOG_LEVEL_DEBUG;
if (libevent_log_level == _EVENT_LOG_DEBUG) glib_log_level = G_LOG_LEVEL_DEBUG;
else if (libevent_log_level == _EVENT_LOG_MSG) glib_log_level = G_LOG_LEVEL_MESSAGE;
else if (libevent_log_level == _EVENT_LOG_WARN) glib_log_level = G_LOG_LEVEL_WARNING;
else if (libevent_log_level == _EVENT_LOG_ERR) glib_log_level = G_LOG_LEVEL_CRITICAL;
g_log(G_LOG_DOMAIN, glib_log_level, "(libevent) %s", msg);
}
int chassis_mainloop(void *_chas) {
chassis *chas = _chas;
guint i;
struct event ev_sigterm, ev_sigint;
#ifdef SIGHUP
struct event ev_sighup;
#endif
/* redirect logging from libevent to glib */
event_set_log_callback(event_log_use_glib);
/* add a event-handler for the "main" events */
chassis_event_loop_t *mainloop = chassis_event_loop_new();
chas->event_base = mainloop;
g_assert(chas->event_base);
/* setup all plugins */
for (i = 0; i < chas->modules->len; i++) {
chassis_plugin *p = chas->modules->pdata[i];
g_assert(p->apply_config);
if (0 != p->apply_config(chas, p->config)) {
g_critical("%s: applying config of plugin %s failed",
G_STRLOC, p->name);
return -1;
}
}
chas->dist_tran_id = g_random_int_range(0, 100000000);
int srv_id = g_random_int_range(0, 10000);
if (chas->proxy_address) {
snprintf(chas->dist_tran_prefix, MAX_DIST_TRAN_PREFIX,
"clt-%s-%d", chas->proxy_address, srv_id);
} else {
snprintf(chas->dist_tran_prefix, MAX_DIST_TRAN_PREFIX,
"clt-%d", srv_id);
}
g_message("Initial dist_tran_id:%llu", chas->dist_tran_id);
g_message("dist_tran_prefix:%s", chas->dist_tran_prefix);
/*
* drop root privileges if requested
*/
if (chas->user) {
struct passwd *user_info;
uid_t user_id = geteuid();
/* Don't bother if we aren't superuser */
if (user_id) {
g_critical("can only use the --user switch if running as root");
return -1;
}
if (NULL == (user_info = getpwnam(chas->user))) {
g_critical("unknown user: %s", chas->user);
return -1;
}
if (chas->log->log_filename) {
/* chown logfile */
if (-1 == chown(chas->log->log_filename, user_info->pw_uid,
user_info->pw_gid))
{
g_critical("%s: chown(%s) failed: %s",
G_STRLOC,
chas->log->log_filename,
g_strerror(errno));
return -1;
}
}
if (setgid(user_info->pw_gid) == 0) {
if (setuid(user_info->pw_uid) == 0) {
}
}
g_debug("now running as user: %s (%d/%d)",
chas->user,
user_info->pw_uid,
user_info->pw_gid);
}
signal_set(&ev_sigterm, SIGTERM, sigterm_handler, NULL);
event_base_set(chas->event_base, &ev_sigterm);
signal_add(&ev_sigterm, NULL);
signal_set(&ev_sigint, SIGINT, sigterm_handler, NULL);
event_base_set(chas->event_base, &ev_sigint);
signal_add(&ev_sigint, NULL);
#ifdef SIGHUP
signal_set(&ev_sighup, SIGHUP, sighup_handler, chas);
event_base_set(chas->event_base, &ev_sighup);
if (signal_add(&ev_sighup, NULL)) {
g_critical("%s: signal_add(SIGHUP) failed", G_STRLOC);
}
#endif
#if !GLIB_CHECK_VERSION(2, 32, 0)
/* GLIB below 2.32 must call thread_init if multi threads */
if (!chas->disable_threads) {
g_thread_init(NULL);
} else {
g_debug("Disable threads creation.");
}
#endif
/**
* block until we are asked to shutdown
*/
chassis_event_loop(mainloop);
signal_del(&ev_sigterm);
signal_del(&ev_sigint);
#ifdef SIGHUP
signal_del(&ev_sighup);
#endif
return 0;
}
uint64_t incremental_guid_get_next(struct incremental_guid_state_t *s)
{
uint64_t uniq_id = 0;
uint64_t cur_time = time(0);
static uint64_t SEQ_MASK = (-1L ^ (-1L << 16L));
uniq_id = cur_time << 32;
uniq_id |= (s->worker_id & 0xff) << 24;
if (cur_time == s->last_sec) {
s->seq_id = (s->seq_id + 1) & SEQ_MASK;
if (s->seq_id == 0) {
s->rand_id = (s->rand_id + 1) & 0x3ff;
g_message("%s:rand id changed:%llu", G_STRLOC,
(unsigned long long) s->rand_id);
}
} else {
s->seq_id = 0;
s->rand_id = s->init_rand_id;
}
s->last_sec = cur_time;
uniq_id |= s->rand_id << 16;
uniq_id |= s->seq_id;
return uniq_id;
}
void incremental_guid_init(struct incremental_guid_state_t *s)
{
struct timeval tp;
gettimeofday(&tp, NULL);
unsigned int seed = tp.tv_usec;
if (s->worker_id == 0) {
s->worker_id = (int) ((rand_r(&seed) / (RAND_MAX + 1.0)) * 64);
}
s->rand_id = (int) ((rand_r(&seed) / (RAND_MAX + 1.0)) * 1024);
s->init_rand_id = s->rand_id;
s->last_sec = time(0);
s->seq_id = 0;
}

186
src/chassis-mainloop.h Normal file
View File

@ -0,0 +1,186 @@
#ifndef _CHASSIS_MAINLOOP_H_
#define _CHASSIS_MAINLOOP_H_
#include <glib.h> /* GPtrArray */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h> /* event.h needs struct tm */
#endif
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#include <stdio.h>
#include <event.h> /* struct event_base */
#include "chassis-exports.h"
#include "chassis-log.h"
#include "chassis-shutdown-hooks.h"
#include "cetus-util.h"
#include "chassis-config.h"
/** @defgroup chassis Chassis
*
* the chassis contains the set of functions that are used by all programs
*
* */
/*@{*/
typedef struct chassis_private chassis_private;
typedef struct chassis chassis;
#define MAX_SERVER_NUM 64
#define MAX_QUERY_TIME 1000
#define MAX_WAIT_TIME 1024
#define MAX_DIST_TRAN_PREFIX 32
#define MAX_ALLOWED_PACKET_CEIL (1 * GB)
#define MAX_ALLOWED_PACKET_DEFAULT (32 * MB)
#define MAX_ALLOWED_PACKET_FLOOR (1 * KB)
typedef struct cached_sql_info_t {
time_t last_visit_time;
int visited_cnt;
int parsed_txLevel;
int parsed_rv;
int parsed_join_op_unsupported;
GPtrArray *groups;
} cached_sql_info_t;
typedef struct rw_op_t {
uint64_t ro;
uint64_t rw;
} rw_op_t;
typedef struct query_stats_t {
rw_op_t client_query;
rw_op_t proxyed_query;
uint64_t query_time_table[MAX_QUERY_TIME];
uint64_t query_wait_table[MAX_WAIT_TIME];
rw_op_t server_query_details[MAX_SERVER_NUM];
uint64_t com_select;
uint64_t com_insert;
uint64_t com_update;
uint64_t com_delete;
uint64_t com_select_shard;
uint64_t com_insert_shard;
uint64_t com_update_shard;
uint64_t com_delete_shard;
uint64_t com_select_global;
uint64_t com_select_bad_key;
uint64_t xa_count;
} query_stats_t;
/* For generating unique global ids for MySQL */
struct incremental_guid_state_t {
unsigned int last_sec;
int worker_id;
int rand_id;
int init_rand_id;
int seq_id;
};
void incremental_guid_init(struct incremental_guid_state_t *s);
uint64_t incremental_guid_get_next(struct incremental_guid_state_t *s);
struct chassis {
struct event_base *event_base;
gchar *event_hdr_version;
/**< array(chassis_plugin) */
GPtrArray *modules;
/**< base directory for all relative paths referenced */
gchar *base_dir;
/**< plugin dir for save settings */
gchar *plugin_dir;
gchar *conf_dir;
/**< user to run as */
gchar *user;
char *proxy_address;
char *default_db;
char *default_username;
char *default_charset;
char *default_hashed_pwd;
unsigned int auto_key;
unsigned int sess_key;
unsigned int maintain_close_mode;
unsigned int sharding_mode;
unsigned int config_remote;
unsigned int disable_threads;
unsigned int is_tcp_stream_enabled;
unsigned int query_cache_enabled;
unsigned int is_back_compressed;
unsigned int compress_support;
unsigned int client_found_rows;
unsigned int master_preferred;
unsigned int is_reduce_conns;
unsigned int xa_log_detailed;
unsigned int is_reset_conn_enabled;
unsigned int sharding_reload;
unsigned int check_slave_delay;
int complement_conn_cnt;
int default_query_cache_timeout;
double slave_delay_down_threshold_sec;
double slave_delay_recover_threshold_sec;
unsigned int long_query_time;
unsigned int min_req_time_for_cache;
int cetus_max_allowed_packet;
int disable_dns_cache;
int max_resp_len;
int merged_output_size;
int max_header_size;
int compressed_merged_output_size;
/* Conn-pool initialize settings */
int max_idle_connections;
int mid_idle_connections;
unsigned long long dist_tran_id;
char dist_tran_prefix[MAX_DIST_TRAN_PREFIX];
chassis_private *priv;
void (*priv_shutdown)(chassis *chas, chassis_private *priv);
void (*priv_finally_free_shared)(chassis *chas, chassis_private *priv);
void (*priv_free)(chassis *chas, chassis_private *priv);
chassis_log *log;
chassis_shutdown_hooks_t *shutdown_hooks;
query_stats_t query_stats;
struct incremental_guid_state_t guid_state;
time_t startup_time;
struct chassis_options_t *options;
chassis_config_t *config_manager;
GHashTable *query_cache_table;
GQueue *cache_index;
unsigned long long last_cache_purge_time;
gboolean allow_new_conns;
};
CHASSIS_API chassis *chassis_new(void);
CHASSIS_API void chassis_free(chassis *chas);
CHASSIS_API int chassis_check_version(const char *lib_version, const char *hdr_version);
/**
* the mainloop for all chassis apps
*
*/
CHASSIS_API int chassis_mainloop(void *user_data);
CHASSIS_API void chassis_set_shutdown_location(const gchar *location);
CHASSIS_API gboolean chassis_is_shutdown(void);
#define chassis_set_shutdown() chassis_set_shutdown_location(G_STRLOC)
/*@}*/
#endif

874
src/chassis-options.c Normal file
View File

@ -0,0 +1,874 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <glib.h>
#include "chassis-options.h"
/**
* create a command-line option
*/
chassis_option_t *chassis_option_new() {
chassis_option_t *opt;
opt = g_slice_new0(chassis_option_t);
return opt;
}
/**
* free the option
*/
void chassis_option_free(chassis_option_t *opt) {
if (!opt) return;
g_slice_free(chassis_option_t, opt);
}
/**
* add a option
*
* GOptionEntry
*/
int chassis_option_set(chassis_option_t *opt,
const char *long_name,
gchar short_name,
gint flags,
enum option_type arg,
gpointer arg_data,
const char *description,
const char *arg_desc) {
opt->long_name = long_name;
opt->short_name = short_name;
opt->flags = flags;
opt->arg = arg;
opt->arg_data = arg_data;
opt->description = description;
opt->arg_description = arg_desc;
return 0;
}
char *chassis_option_get_value_str(chassis_option_t *opt)
{
gchar *value = NULL;
switch (opt->arg) {
case OPTION_ARG_NONE:
value = g_strdup((*(gint *)opt->arg_data) ? "true" : "false");
break;
case OPTION_ARG_INT:
value = g_strdup_printf("%u", *(gint *)(opt->arg_data));
break;
case OPTION_ARG_INT64:
value = g_strdup_printf("%lu", *(gint64 *)(opt->arg_data));
break;
case OPTION_ARG_DOUBLE:
value = g_strdup_printf("%f", *(double *)(opt->arg_data));
break;
case OPTION_ARG_STRING:
value = g_strdup(*(char **)opt->arg_data);
break;
default:
value = g_strdup("error value");
}
return value;
}
gboolean chassis_option_set_value(chassis_option_t *opt, const char *value)
{
switch (opt->arg) {
case OPTION_ARG_NONE:
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
*(gint *)opt->arg_data = 1;
return TRUE;
} else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
*(gint *)opt->arg_data = 0;
return TRUE;
} else {
return FALSE;
}
case OPTION_ARG_INT: {
char *endptr = NULL;
int num = strtol(value, &endptr, 0);
if (endptr[0] == '\0') {
*(gint *)opt->arg_data = num; /* TODO: apply lower/upper bounds */
return TRUE;
} else {
return FALSE;
}
}
case OPTION_ARG_INT64: {
char *endptr = NULL;
gint64 num = strtoll(value, &endptr, 0);
if (endptr[0] == '\0') {
*(gint64 *)opt->arg_data = num; /* TODO: apply lower/upper bounds */
return TRUE;
} else {
return FALSE;
}
}
default:
return FALSE;
}
}
/**
* create a command-line option
*/
chassis_options_t *chassis_options_new() {
chassis_options_t *opt;
opt = g_slice_new0(chassis_options_t);
return opt;
}
/**
* add a option
*
* GOptionEntry
*/
int chassis_options_add_option(chassis_options_t *opts,
chassis_option_t *opt) {
opts->options = g_list_append(opts->options, opt);
return 0;
}
void chassis_options_add_options(chassis_options_t *opts, GList *list)
{
opts->options = g_list_concat(opts->options, list);
}
int chassis_options_add(chassis_options_t *opts,
const char *long_name,
gchar short_name,
int flags,
enum option_type arg,
gpointer arg_data,
const char *description,
const char *arg_desc)
{
chassis_option_t *opt = chassis_option_new();
if (0 != chassis_option_set(opt,
long_name,
short_name,
flags,
arg,
arg_data,
description,
arg_desc) ||
0 != chassis_options_add_option(opts, opt)) {
chassis_option_free(opt);
return -1;
} else {
return 0;
}
}
chassis_option_t *chassis_options_get(GList *opts, const char *long_name)
{
GList *l = opts;
for (l = opts; l; l = l->next) {
chassis_option_t *opt = l->data;
if (strcmp(opt->long_name, long_name) == 0) {
return opt;
}
}
return NULL;
}
#define NO_ARG(entry) ((entry)->arg == OPTION_ARG_NONE)
#define OPTIONAL_ARG(entry) FALSE
typedef struct
{
enum option_type arg_type;
gpointer arg_data;
union
{
gboolean bool;
gint integer;
gchar *str;
gchar **array;
gdouble dbl;
gint64 int64;
} prev;
union
{
gchar *str;
struct
{
gint len;
gchar **data;
} array;
} allocated;
} Change;
typedef struct
{
gchar **ptr;
gchar *value;
} PendingNull;
static gboolean context_has_h_entry(chassis_options_t *context)
{
GList *l;
for (l = context->options; l; l = l->next) {
chassis_option_t *entry = l->data;
if (entry->short_name == 'h')
return TRUE;
}
return FALSE;
}
static void print_help(chassis_options_t *context)
{
int max_length = 50;
g_print("%s\n %s", "Usage:", g_get_prgname());
if (context->options)
g_print(" %s", "[OPTION...]");
g_print("\n\nHelp Options:\n");
char token = context_has_h_entry (context) ? '?' : 'h';
g_print(" -%c, --%-*s %s\n", token, max_length-12, "help", "Show help options");
g_print("\n\nApplication Options:\n");
GList *l;
for (l = context->options; l; l = l->next) {
chassis_option_t *entry = l->data;
int len = 0;
if (entry->short_name) {
g_print(" -%c, --%s", entry->short_name, entry->long_name);
len = 8 + strlen(entry->long_name);
} else {
g_print(" --%s", entry->long_name);
len = 4 + strlen(entry->long_name);
}
if (entry->arg_description) {
g_print("=%s", entry->arg_description);
len += 1+strlen(entry->arg_description);
}
g_print("%*s %s\n", max_length - len, "",
entry->description ? entry->description : "");
}
exit(0);
}
static gboolean
parse_int(const gchar *arg_name,
const gchar *arg,
gint *result,
GError **error)
{
gchar *end;
glong tmp;
errno = 0;
tmp = strtol(arg, &end, 0);
if (*arg == '\0' || *end != '\0') {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Cannot parse integer value '%s' for %s",
arg, arg_name);
return FALSE;
}
*result = tmp;
if (*result != tmp || errno == ERANGE) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Integer value '%s' for %s out of range",
arg, arg_name);
return FALSE;
}
return TRUE;
}
static gboolean
parse_double(const gchar *arg_name,
const gchar *arg,
gdouble *result,
GError **error)
{
gchar *end;
gdouble tmp;
errno = 0;
tmp = g_strtod(arg, &end);
if (*arg == '\0' || *end != '\0') {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Cannot parse double value '%s' for %s",
arg, arg_name);
return FALSE;
}
if (errno == ERANGE) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Double value '%s' for %s out of range",
arg, arg_name);
return FALSE;
}
*result = tmp;
return TRUE;
}
static gboolean
parse_int64(const gchar *arg_name,
const gchar *arg,
gint64 *result,
GError **error)
{
gchar *end;
gint64 tmp;
errno = 0;
tmp = g_ascii_strtoll(arg, &end, 0);
if (*arg == '\0' || *end != '\0') {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Cannot parse integer value '%s' for %s",
arg, arg_name);
return FALSE;
}
if (errno == ERANGE) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Integer value '%s' for %s out of range",
arg, arg_name);
return FALSE;
}
*result = tmp;
return TRUE;
}
static Change *
get_change(chassis_options_t *context,
enum option_type arg_type,
gpointer arg_data)
{
GList *list;
Change *change = NULL;
for(list = context->changes; list != NULL; list = list->next) {
change = list->data;
if (change->arg_data == arg_data)
goto found;
}
change = g_new0(Change, 1);
change->arg_type = arg_type;
change->arg_data = arg_data;
context->changes = g_list_prepend(context->changes, change);
found:
return change;
}
static void
add_pending_null(chassis_options_t *context,
gchar **ptr,
gchar *value)
{
PendingNull *n;
n = g_new0(PendingNull, 1);
n->ptr = ptr;
n->value = value;
context->pending_nulls = g_list_prepend(context->pending_nulls, n);
}
static gboolean
parse_arg(chassis_options_t *context,
chassis_option_t *entry,
const gchar *value,
const gchar *option_name,
GError **error)
{
Change *change;
g_assert(value || OPTIONAL_ARG(entry) || NO_ARG(entry));
switch (entry->arg) {
case OPTION_ARG_NONE: {
(void) get_change(context, OPTION_ARG_NONE, entry->arg_data);
*(gboolean *)entry->arg_data = !(entry->flags & OPTION_FLAG_REVERSE);
break;
}
case OPTION_ARG_STRING: {
gchar *data = g_locale_to_utf8(value, -1, NULL, NULL, error);
if (!data)
return FALSE;
change = get_change(context, OPTION_ARG_STRING, entry->arg_data);
g_free(change->allocated.str);
change->prev.str = *(gchar **)entry->arg_data;
change->allocated.str = data;
*(gchar **)entry->arg_data = data;
break;
}
case OPTION_ARG_STRING_ARRAY: {
gchar *data = g_locale_to_utf8(value, -1, NULL, NULL, error);
if (!data)
return FALSE;
change = get_change(context, OPTION_ARG_STRING_ARRAY, entry->arg_data);
if (change->allocated.array.len == 0) {
change->prev.array = *(gchar ***)entry->arg_data;
change->allocated.array.data = g_new(gchar *, 2);
} else {
change->allocated.array.data =
g_renew(gchar *, change->allocated.array.data,
change->allocated.array.len + 2);
}
change->allocated.array.data[change->allocated.array.len] = data;
change->allocated.array.data[change->allocated.array.len + 1] = NULL;
change->allocated.array.len ++;
*(gchar ***)entry->arg_data = change->allocated.array.data;
break;
}
case OPTION_ARG_INT: {
gint data;
if (!parse_int(option_name, value, &data, error))
return FALSE;
change = get_change(context, OPTION_ARG_INT, entry->arg_data);
change->prev.integer = *(gint *)entry->arg_data;
*(gint *)entry->arg_data = data;
break;
}
case OPTION_ARG_DOUBLE: {
gdouble data;
if (!parse_double(option_name, value, &data, error)) {
return FALSE;
}
change = get_change(context, OPTION_ARG_DOUBLE, entry->arg_data);
change->prev.dbl = *(gdouble *)entry->arg_data;
*(gdouble *)entry->arg_data = data;
break;
}
case OPTION_ARG_INT64: {
gint64 data;
if (!parse_int64(option_name, value, &data, error)) {
return FALSE;
}
change = get_change(context, OPTION_ARG_INT64, entry->arg_data);
change->prev.int64 = *(gint64 *)entry->arg_data;
*(gint64 *)entry->arg_data = data;
break;
}
default:
g_assert_not_reached();
}
entry->flags |= OPTION_FLAG_CMDLINE;
return TRUE;
}
static gboolean
parse_short_option(chassis_options_t *context,
gint idx,
gint *new_idx,
gchar arg,
gint *argc,
gchar ***argv,
GError **error,
gboolean *parsed)
{
GList *l;
for (l = context->options; l; l = l->next) {
chassis_option_t *entry = l->data;
if (arg == entry->short_name) {
gchar *option_name = g_strdup_printf ("-%c", entry->short_name);
gchar *value = NULL;
if (NO_ARG(entry)) {
value = NULL;
} else {
if (*new_idx > idx) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_FAILED,
"Error parsing option %s", option_name);
g_free(option_name);
return FALSE;
}
if (idx < *argc - 1) {
if (!OPTIONAL_ARG(entry)) {
value = (*argv)[idx + 1];
add_pending_null(context, &((*argv)[idx + 1]), NULL);
*new_idx = idx + 1;
} else {
if ((*argv)[idx + 1][0] == '-') {
value = NULL;
} else {
value = (*argv)[idx + 1];
add_pending_null(context, &((*argv)[idx + 1]), NULL);
*new_idx = idx + 1;
}
}
} else if (idx >= *argc - 1 && OPTIONAL_ARG(entry)) {
value = NULL;
} else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Missing argument for %s", option_name);
g_free(option_name);
return FALSE;
}
}
if (!parse_arg(context, entry, value, option_name, error)) {
g_free(option_name);
return FALSE;
}
g_free(option_name);
*parsed = TRUE;
}
}
return TRUE;
}
static gboolean
parse_long_option(chassis_options_t *context,
gint *idx,
gchar *arg,
gboolean aliased,
gint *argc,
gchar ***argv,
GError **error,
gboolean *parsed)
{
GList *l;
for (l = context->options; l; l = l->next) {
chassis_option_t *entry = l->data;
if (*idx >= *argc)
return TRUE;
if (NO_ARG(entry) && strcmp(arg, entry->long_name) == 0) {
gchar *option_name = g_strconcat("--", entry->long_name, NULL);
gboolean retval = parse_arg(context, entry, NULL, option_name, error);
g_free(option_name);
add_pending_null(context, &((*argv)[*idx]), NULL);
*parsed = TRUE;
return retval;
} else {
gint len = strlen(entry->long_name);
if (strncmp(arg, entry->long_name, len) == 0 &&
(arg[len] == '=' || arg[len] == 0))
{
gchar *value = NULL;
gchar *option_name;
add_pending_null(context, &((*argv)[*idx]), NULL);
option_name = g_strconcat("--", entry->long_name, NULL);
if (arg[len] == '=') {
value = arg + len + 1;
} else if (*idx < *argc - 1) {
if (!OPTIONAL_ARG(entry)) {
value = (*argv)[*idx + 1];
add_pending_null(context, &((*argv)[*idx + 1]), NULL);
(*idx)++;
} else {
if ((*argv)[*idx + 1][0] == '-') {
gboolean retval = parse_arg(context, entry,
NULL, option_name, error);
*parsed = TRUE;
g_free(option_name);
return retval;
} else {
value = (*argv)[*idx + 1];
add_pending_null(context, &((*argv)[*idx + 1]), NULL);
(*idx)++;
}
}
} else if (*idx >= *argc - 1 && OPTIONAL_ARG(entry)) {
gboolean retval = parse_arg(context, entry, NULL, option_name, error);
*parsed = TRUE;
g_free(option_name);
return retval;
} else {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_BAD_VALUE,
"Missing argument for %s", option_name);
g_free(option_name);
return FALSE;
}
if (!parse_arg(context, entry, value, option_name, error)) {
g_free(option_name);
return FALSE;
}
g_free(option_name);
*parsed = TRUE;
}
}
}
return TRUE;
}
static gboolean
parse_remaining_arg(chassis_options_t *context,
gint *idx,
gint *argc,
gchar ***argv,
GError **error,
gboolean *parsed)
{
GList *l;
for (l = context->options; l; l = l->next) {
chassis_option_t *entry = l->data;
if (*idx >= *argc)
return TRUE;
if (entry->long_name[0])
continue;
g_return_val_if_fail(entry->arg == OPTION_ARG_STRING_ARRAY, FALSE);
add_pending_null(context, &((*argv)[*idx]), NULL);
if (!parse_arg(context, entry, (*argv)[*idx], "", error))
return FALSE;
*parsed = TRUE;
return TRUE;
}
return TRUE;
}
static void free_changes_list(chassis_options_t *context, gboolean revert)
{
GList *list;
for (list = context->changes; list != NULL; list = list->next) {
Change *change = list->data;
if (revert) {
switch (change->arg_type) {
case OPTION_ARG_NONE:
*(gboolean *)change->arg_data = change->prev.bool;
break;
case OPTION_ARG_INT:
*(gint *)change->arg_data = change->prev.integer;
break;
case OPTION_ARG_STRING:
g_free(change->allocated.str);
*(gchar **)change->arg_data = change->prev.str;
break;
case OPTION_ARG_STRING_ARRAY:
g_strfreev (change->allocated.array.data);
*(gchar ***)change->arg_data = change->prev.array;
break;
case OPTION_ARG_DOUBLE:
*(gdouble *)change->arg_data = change->prev.dbl;
break;
case OPTION_ARG_INT64:
*(gint64 *)change->arg_data = change->prev.int64;
break;
default:
g_assert_not_reached();
}
}
g_free(change);
}
g_list_free(context->changes);
context->changes = NULL;
}
static void free_pending_nulls(chassis_options_t *context, gboolean perform_nulls)
{
GList *list;
for (list = context->pending_nulls; list != NULL; list = list->next) {
PendingNull *n = list->data;
if (perform_nulls) {
if (n->value) {
/* Copy back the short options */
*(n->ptr)[0] = '-';
strcpy(*n->ptr + 1, n->value);
} else {
*n->ptr = NULL;
}
}
g_free(n->value);
g_free(n);
}
g_list_free(context->pending_nulls);
context->pending_nulls = NULL;
}
/**
* free the option context
*/
void chassis_options_free(chassis_options_t *context)
{
if (!context) return;
g_list_free_full(context->options, (GDestroyNotify)chassis_option_free);
free_changes_list(context, FALSE);
free_pending_nulls(context, FALSE);
g_slice_free(chassis_options_t, context);
}
gboolean chassis_options_parse_cmdline(chassis_options_t *context,
int *argc, char ***argv, GError **error)
{
if (!argc || !argv)
return FALSE;
/* Set program name */
if (!g_get_prgname()) {
if (*argc) {
gchar *prgname = g_path_get_basename((*argv)[0]);
g_set_prgname(prgname?prgname:"<unknown>");
g_free(prgname);
}
}
gboolean stop_parsing = FALSE;
gboolean has_unknown = FALSE;
gint separator_pos = 0;
int i, j, k;
for (i = 1; i < *argc; i++) {
gchar *arg;
gboolean parsed = FALSE;
if ((*argv)[i][0] == '-' && (*argv)[i][1] != '\0' && !stop_parsing) {
if ((*argv)[i][1] == '-') {
/* -- option */
arg = (*argv)[i] + 2;
/* '--' terminates list of arguments */
if (*arg == 0) {
separator_pos = i;
stop_parsing = TRUE;
continue;
}
/* Handle help options */
if (context->help_enabled && strcmp(arg, "help") == 0)
print_help(context);
if (!parse_long_option(context, &i, arg,
FALSE, argc, argv, error, &parsed))
goto fail;
if (parsed)
continue;
if (context->ignore_unknown)
continue;
} else { /* short option */
gint new_i = i, arg_length;
gboolean *nulled_out = NULL;
gboolean has_h_entry = context_has_h_entry(context);
arg = (*argv)[i] + 1;
arg_length = strlen(arg);
nulled_out = g_newa (gboolean, arg_length);
memset(nulled_out, 0, arg_length * sizeof (gboolean));
for (j = 0; j < arg_length; j++) {
if (context->help_enabled &&
(arg[j] == '?' || (arg[j] == 'h' && !has_h_entry)))
print_help(context);
parsed = FALSE;
if (!parse_short_option(context, i, &new_i, arg[j],
argc, argv, error, &parsed))
goto fail;
if (context->ignore_unknown && parsed)
nulled_out[j] = TRUE;
else if (context->ignore_unknown)
continue;
else if (!parsed)
break;
/* !context->ignore_unknown && parsed */
}
if (context->ignore_unknown) {
gchar *new_arg = NULL;
gint arg_index = 0;
for (j = 0; j < arg_length; j++) {
if (!nulled_out[j]) {
if (!new_arg)
new_arg = g_malloc(arg_length + 1);
new_arg[arg_index++] = arg[j];
}
}
if (new_arg)
new_arg[arg_index] = '\0';
add_pending_null(context, &((*argv)[i]), new_arg);
i = new_i;
} else if (parsed) {
add_pending_null(context, &((*argv)[i]), NULL);
i = new_i;
}
}
if (!parsed)
has_unknown = TRUE;
if (!parsed && !context->ignore_unknown) {
g_set_error(error, G_OPTION_ERROR, G_OPTION_ERROR_UNKNOWN_OPTION,
"Unknown option %s", (*argv)[i]);
goto fail;
}
} else {
//if (context->strict_posix)
stop_parsing = TRUE;
/* Collect remaining args */
if (!parse_remaining_arg(context, &i, argc, argv, error, &parsed))
goto fail;
if (!parsed && (has_unknown || (*argv)[i][0] == '-'))
separator_pos = 0;
}
}
if (separator_pos > 0)
add_pending_null(context, &((*argv)[separator_pos]), NULL);
if (argc && argv) {
free_pending_nulls(context, TRUE);
for (i = 1; i < *argc; i++) {
for (k = i; k < *argc; k++)
if ((*argv)[k] != NULL)
break;
if (k > i) {
k -= i;
for (j = i + k; j < *argc; j++) {
(*argv)[j-k] = (*argv)[j];
(*argv)[j] = NULL;
}
*argc -= k;
}
}
}
return TRUE;
fail:
free_changes_list(context, TRUE);
free_pending_nulls(context, FALSE);
return FALSE;
}

94
src/chassis-options.h Normal file
View File

@ -0,0 +1,94 @@
#ifndef __CHASSIS_OPTIONS_H__
#define __CHASSIS_OPTIONS_H__
#include <glib.h>
enum option_type { // arg_data type
OPTION_ARG_NONE, // bool *
OPTION_ARG_INT, // int *
OPTION_ARG_INT64, // int64 *
OPTION_ARG_DOUBLE, // double *
OPTION_ARG_STRING, // char **
OPTION_ARG_STRING_ARRAY, // char **
};
enum option_flags {
OPTION_FLAG_REVERSE = 0x04,
OPTION_FLAG_CMDLINE = 0x10,
OPTION_FLAG_CONF_FILE = 0x20,
OPTION_FLAG_REMOTE_CONF = 0x40,
};
/**
* @file
*
* a _new()/_free()-able version of GOptionEntry
*/
/**
* 'const'-free version of GOptionEntry
*/
typedef struct {
const char *long_name;
const char *description;
const char *arg_description;
gpointer arg_data;
enum option_type arg;
enum option_flags flags;
gchar short_name;
} chassis_option_t;
/**
* create a chassis_option_t
*/
chassis_option_t *chassis_option_new(void);
void chassis_option_free(chassis_option_t *opt);
int chassis_option_set(chassis_option_t *opt,
const char *long_name,
gchar short_name,
gint flags,
enum option_type arg,
gpointer arg_data,
const char *description,
const char *arg_desc);
/**
* @return newly allocated string, need to be freed
*/
char *chassis_option_get_value_str(chassis_option_t *opt);
gboolean chassis_option_set_value(chassis_option_t *opt, const char *);
typedef struct chassis_options_t{
GList *options; /* List of chassis_option_t */
/* We keep a list of change so we can revert them */
GList *changes;
/* We also keep track of all argv elements
* that should be NULLed or modified.
*/
GList *pending_nulls;
gboolean ignore_unknown;
gboolean help_enabled;
} chassis_options_t;
chassis_options_t *chassis_options_new(void);
void chassis_options_free(chassis_options_t *opts);
int chassis_options_add_option(chassis_options_t *opts, chassis_option_t *opt);
void chassis_options_add_options(chassis_options_t *opts, GList *list);
int chassis_options_add(chassis_options_t *opts,
const char *long_name,
gchar short_name,
int flags,
enum option_type arg,
gpointer arg_data,
const char *description,
const char *arg_desc);
chassis_option_t *chassis_options_get(GList *opts, const char *long_name);
gboolean chassis_options_parse_cmdline(chassis_options_t *context,
int *argc, char ***argv, GError **error);
#endif

78
src/chassis-path.c Normal file
View File

@ -0,0 +1,78 @@
#include <glib.h>
#include <errno.h>
#include <stdlib.h> /* for realpath */
#include <string.h>
#include "glib-ext.h"
#include "chassis-path.h"
gchar *chassis_get_basedir(const gchar *prgname) {
gchar *absolute_path;
gchar *bin_dir;
gchar r_path[PATH_MAX];
gchar *base_dir;
if (g_path_is_absolute(prgname)) {
/* No need to dup, just to get free right */
absolute_path = g_strdup(prgname);
} else {
/**
* the path wasn't absolute
*
* Either it is
* - in the $PATH
* - relative like ./bin/... or
*/
absolute_path = g_find_program_in_path(prgname);
if (absolute_path == NULL) {
g_critical("can't find myself (%s) in PATH", prgname);
return NULL;
}
if (!g_path_is_absolute(absolute_path)) {
gchar *cwd = g_get_current_dir();
g_free(absolute_path);
absolute_path = g_build_filename(cwd, prgname, NULL);
g_free(cwd);
}
}
/* assume that the binary is in ./s?bin/ and
* that the the basedir is right above it
*
* to get this working we need a "clean" basedir, no .../foo/./bin/
*/
if (NULL == realpath(absolute_path, r_path)) {
g_critical("%s: realpath(%s) failed: %s",
G_STRLOC,
absolute_path,
g_strerror(errno));
return NULL;
}
bin_dir = g_path_get_dirname(r_path);
base_dir = g_path_get_dirname(bin_dir);
/* don't free base_dir, because we need it later */
g_free(absolute_path);
g_free(bin_dir);
return base_dir;
}
gchar *chassis_resolve_path(const char *base_dir, gchar *filename) {
if (!base_dir || !filename)
return NULL;
if (g_path_is_absolute(filename)) return filename;
return g_build_filename(base_dir, G_DIR_SEPARATOR_S, filename, NULL);
}

12
src/chassis-path.h Normal file
View File

@ -0,0 +1,12 @@
#ifndef __CHASSIS_PATH_H__
#define __CHASSIS_PATH_H__
#include <glib.h>
#include "chassis-exports.h"
CHASSIS_API gchar *chassis_resolve_path(const char *base_dir, gchar *filename);
CHASSIS_API gchar *chassis_get_basedir(const gchar *prgname);
#endif

108
src/chassis-plugin.c Normal file
View File

@ -0,0 +1,108 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#include <glib.h>
#include <gmodule.h>
#ifdef HAVE_SIGNAL_H
#include <signal.h>
#endif
#include "chassis-plugin.h"
#include "chassis-options.h"
chassis_plugin *chassis_plugin_new(void) {
chassis_plugin *p;
p = g_new0(chassis_plugin, 1);
return p;
}
void chassis_plugin_free(chassis_plugin *p) {
if (p->option_grp_name) g_free(p->option_grp_name);
if (p->name) g_free(p->name);
if (p->version) g_free(p->version);
if (p->stats && p->free_stats) p->free_stats(p->stats);
/* closing the plugin has to be the last call to make sure
* we don't free/call/... stuff that is already unmapped
* from memory
*/
#if (!VALGRIND_SUPPORT)
if (p->module) g_module_close(p->module);
#endif
g_free(p);
}
chassis_plugin *chassis_plugin_load(const gchar *name) {
int (*plugin_init)(chassis_plugin *p);
chassis_plugin *p = chassis_plugin_new();
p->module = g_module_open(name, G_MODULE_BIND_LOCAL);
if (!p->module) {
g_critical("loading module '%s' failed: %s", name, g_module_error());
chassis_plugin_free(p);
return NULL;
}
/* each module has to have a plugin_init function */
if (!g_module_symbol(p->module, "plugin_init", (gpointer) &plugin_init)) {
g_critical("module '%s' doesn't have a init-function: %s", name, g_module_error());
chassis_plugin_free(p);
return NULL;
}
if (0 != plugin_init(p)) {
g_critical("init-function for module '%s' failed", name);
chassis_plugin_free(p);
return NULL;
}
if (p->magic != CHASSIS_PLUGIN_MAGIC) {
g_critical("'%s' doesn't match the current interface (plugin is %lx, chassis is %lx)",
name, p->magic, CHASSIS_PLUGIN_MAGIC);
chassis_plugin_free(p);
return NULL;
}
if (p->init) {
p->config = p->init();
}
/* if the plugins haven't set p->name provide our own name */
if (!p->name) p->name = g_strdup(name);
/* set dummy version number if the plugin doesn't provide a real one */
if (!p->version) {
g_critical("plugin '%s' doesn't set a version num, refusing to load this plugin",
name);
chassis_plugin_free(p);
return NULL;
}
if (p->new_stats) {
p->stats = p->new_stats();
}
return p;
}
GList *chassis_plugin_get_options(chassis_plugin *p) {
GList *options = NULL;
if (!p->get_options) return NULL;
if (NULL == (options = p->get_options(p->config))) {
g_critical("adding config options for module '%s' failed", p->name);
}
return options;
}

102
src/chassis-plugin.h Normal file
View File

@ -0,0 +1,102 @@
#ifndef _CHASSIS_PLUGIN_H_
#define _CHASSIS_PLUGIN_H_
#include <glib.h>
#include <gmodule.h>
#include "chassis-mainloop.h"
#include "chassis-exports.h"
/* current magic is 0.8.0-1 */
#define CHASSIS_PLUGIN_MAGIC 0x00080001L
/**
* The private stats structure of a plugin. This is opaque to the rest of the code,
* we can only get a copy of it in a hash.
* @see chassis_plugin_stats.get_stats()
*/
typedef struct chassis_plugin_stats chassis_plugin_stats_t;
typedef struct chassis_plugin_config chassis_plugin_config;
typedef struct chassis_plugin {
/**< a magic token to verify that the plugin API matches */
long magic;
/**< name of the option group (used in --help-<option-grp> */
gchar *option_grp_name;
/**< user visible name of this plugin */
gchar *name;
/**< the plugin's version number */
gchar *version;
/**< the plugin handle when loaded */
GModule *module;
/**< contains the plugin-specific statistics */
chassis_plugin_stats_t *stats;
/**< handler function to initialize the plugin-specific stats */
chassis_plugin_stats_t *(*new_stats)(void);
/**< handler function to dealloc the plugin-specific stats */
void (*free_stats)(chassis_plugin_stats_t *user_data);
/**< handler function to retrieve the plugin-specific stats */
GHashTable *(*get_stats)(chassis_plugin_stats_t *user_data);
/**< contains the plugin-specific config data */
chassis_plugin_config *config;
/**< handler function to allocate/initialize a chassis_plugin_config struct */
chassis_plugin_config *(*init)(void);
/**< handler function used to deallocate the chassis_plugin_config */
void (*destroy)(chassis *chas, chassis_plugin_config *user_data);
/**< handler function to obtain the command line argument information */
GList *(*get_options)(chassis_plugin_config *user_data);
/**< handler function to set the argument values in the plugin's config */
int (*apply_config)(chassis *chas, chassis_plugin_config *user_data);
/**< handler function to retrieve the plugin's global state */
void *(*get_global_state)(chassis_plugin_config *user_data, const char *member);
/**< handler function used to get allow ip list */
GList *(*allow_ip_get)(chassis_plugin_config *user_data);
/**< handler function used to add IP addr to allow_ip_table */
gboolean (*allow_ip_add)(chassis_plugin_config *user_data, char *addr);
/**< handler function used to delete IP addr to allow_ip_table */
gboolean (*allow_ip_del)(chassis_plugin_config *user_data, char *addr);
/**< handler function used to get deny ip list */
GList *(*deny_ip_get)(chassis_plugin_config *user_data);
/**< handler function used to add IP addr to deny_ip_table */
gboolean (*deny_ip_add)(chassis_plugin_config *user_data, char *addr);
/**< handler function used to delete IP addr to deny_ip_table */
gboolean (*deny_ip_del)(chassis_plugin_config *user_data, char *addr);
} chassis_plugin;
CHASSIS_API chassis_plugin *chassis_plugin_new(void);
CHASSIS_API chassis_plugin *chassis_plugin_load(const gchar *name);
CHASSIS_API void chassis_plugin_free(chassis_plugin *p);
CHASSIS_API GList *chassis_plugin_get_options(chassis_plugin *p);
/**
* Retrieve the chassis plugin for a particular name.
*
* @param chas a pointer to the chassis
* @param plugin_name The name of the plugin to look up.
* @return A pointer to a chassis_plugin structure
* @retval NULL if there is no loaded chassis with this name
*/
CHASSIS_API chassis_plugin *chassis_plugin_for_name(chassis *chas, gchar *plugin_name);
#endif

View File

@ -0,0 +1,42 @@
#include <stdlib.h>
#include <string.h>
#include "chassis-shutdown-hooks.h"
#include "glib-ext.h"
void chassis_shutdown_hook_free(chassis_shutdown_hook_t *hook) {
g_slice_free(chassis_shutdown_hook_t, hook);
}
chassis_shutdown_hooks_t *chassis_shutdown_hooks_new() {
chassis_shutdown_hooks_t *hooks;
hooks = g_slice_new0(chassis_shutdown_hooks_t);
hooks->hooks = g_hash_table_new_full(
(GHashFunc)g_string_hash,
(GEqualFunc)g_string_equal,
g_string_true_free,
(GDestroyNotify)chassis_shutdown_hook_free);
return hooks;
}
void chassis_shutdown_hooks_free(chassis_shutdown_hooks_t *hooks) {
g_hash_table_destroy(hooks->hooks);
g_slice_free(chassis_shutdown_hooks_t, hooks);
}
void chassis_shutdown_hooks_call(chassis_shutdown_hooks_t *hooks) {
GHashTableIter iter;
GString *key;
chassis_shutdown_hook_t *hook;
g_hash_table_iter_init(&iter, hooks->hooks);
while (g_hash_table_iter_next(&iter, (void **)&key, (void **)&hook)) {
if (hook->func && !hook->is_called) hook->func(hook->udata);
hook->is_called = TRUE;
}
}

View File

@ -0,0 +1,28 @@
#ifndef _CHASSIS_SHUTDOWN_HOOKS_H_
#define _CHASSIS_SHUTDOWN_HOOKS_H_
#include <glib.h> /* GPtrArray */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "chassis-exports.h"
typedef struct {
void (*func)(gpointer _udata);
gpointer udata;
gboolean is_called;
} chassis_shutdown_hook_t;
CHASSIS_API void chassis_shutdown_hook_free(chassis_shutdown_hook_t *);
typedef struct {
GHashTable *hooks;
} chassis_shutdown_hooks_t;
CHASSIS_API chassis_shutdown_hooks_t *chassis_shutdown_hooks_new(void);
CHASSIS_API void chassis_shutdown_hooks_free(chassis_shutdown_hooks_t *);
CHASSIS_API void chassis_shutdown_hooks_call(chassis_shutdown_hooks_t *hooks);
#endif

48
src/chassis-timings.c Normal file
View File

@ -0,0 +1,48 @@
#include <glib.h>
#include <time.h>
#include <math.h>
#include "chassis-timings.h"
#include "glib-ext.h"
int chassis_epoch_from_string(const char *str, gboolean *ok)
{
if (!str) {
*ok = FALSE;
return 0;
}
struct tm t = {0};
if (strptime(str, "%Y-%m-%d %H:%M:%S", &t)) {/* %Y-%m-%d will fail */
if (ok)
*ok = TRUE;
return mktime(&t);
}
struct tm d = {0};
if (strptime(str, "%Y-%m-%d", &d)) { /* %Y-%m-%d %H:%M:%S will also pass, with 'd' set wrong */
if (ok)
*ok = TRUE;
return mktime(&d);
}
if (ok)
*ok = FALSE;
return 0;
}
gboolean chassis_timeval_from_double(struct timeval *dst, double t)
{
g_return_val_if_fail(dst != NULL, FALSE);
g_return_val_if_fail(t >= 0, FALSE);
dst->tv_sec = floor(t);
dst->tv_usec = floor((t - dst->tv_sec) * 1000000);
return TRUE;
}
void chassis_epoch_to_string(time_t *epoch, char *str, int len)
{
struct tm *local = localtime(epoch);
if (local) {
strftime(str, len, "%Y-%m-%d %H:%M:%S", local);
}
}

16
src/chassis-timings.h Normal file
View File

@ -0,0 +1,16 @@
#ifndef __CHASSIS_TIMINGS_H__
#define __CHASSIS_TIMINGS_H__
#include <sys/time.h> /* struct timeval */
#include <glib.h>
#include "chassis-exports.h"
#define SECONDS ( 1)
#define MINUTES ( 60 * SECONDS)
#define HOURS ( 60 * MINUTES)
CHASSIS_API int chassis_epoch_from_string(const char *str, gboolean *ok);
CHASSIS_API gboolean chassis_timeval_from_double(struct timeval *dst, double t);
void chassis_epoch_to_string(time_t *epoch, char *str, int len);
#endif

216
src/chassis-unix-daemon.c Normal file
View File

@ -0,0 +1,216 @@
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#ifdef HAVE_SYS_WAIT_H
#include <sys/wait.h> /* wait4 */
#endif
#include <sys/stat.h>
#ifdef HAVE_SYS_RESOURCE_H
#include <sys/resource.h> /* getrusage */
#endif
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <glib.h>
#include "glib-ext.h"
#include "chassis-unix-daemon.h"
/**
* start the app in the background
*
* UNIX-version
*/
void chassis_unix_daemonize(void) {
#ifdef SIGTTOU
signal(SIGTTOU, SIG_IGN);
#endif
#ifdef SIGTTIN
signal(SIGTTIN, SIG_IGN);
#endif
#ifdef SIGTSTP
signal(SIGTSTP, SIG_IGN);
#endif
if (fork() != 0) exit(0);
if (setsid() == -1) exit(0);
signal(SIGHUP, SIG_IGN);
if (fork() != 0) exit(0);
umask(0);
}
/**
* forward the signal to the process group, but not us
*/
static void chassis_unix_signal_forward(int sig) {
signal(sig, SIG_IGN); /* we don't want to create a loop here */
kill(0, sig);
}
/**
* keep the ourself alive
*
* if we or the child gets a SIGTERM, we quit too
* on everything else we restart it
*/
int chassis_unix_proc_keepalive(int *child_exit_status) {
int nprocs = 0;
pid_t child_pid = -1;
/*
* we ignore SIGINT and SIGTERM and
* just let it be forwarded to the child instead
* as we want to collect its PID before we shutdown too
*
* the child will have to set its own signal handlers for this
*/
for (;;) {
/* try to start the children */
while (nprocs < 1) {
pid_t pid = fork();
if (pid == 0) {
/* child */
g_debug("%s: we are the child: %d, nprocs:%d",
G_STRLOC,
getpid(),
nprocs);
return 0;
} else if (pid < 0) {
/* fork() failed */
g_critical("%s: fork() failed: %s (%d), nprocs:%d",
G_STRLOC,
g_strerror(errno),
errno,
nprocs);
return -1;
} else {
/* we are the angel, let's see what the child did */
g_message("%s: [angel] we try to keep PID=%d alive, nprocs:%d",
G_STRLOC,
pid,
nprocs);
/* forward a few signals that are sent to us to the child instead */
signal(SIGINT, chassis_unix_signal_forward);
signal(SIGTERM, chassis_unix_signal_forward);
signal(SIGHUP, chassis_unix_signal_forward);
signal(SIGUSR1, chassis_unix_signal_forward);
signal(SIGUSR2, chassis_unix_signal_forward);
child_pid = pid;
nprocs++;
}
}
if (child_pid != -1) {
struct rusage rusage;
int exit_status;
pid_t exit_pid;
g_debug("%s: waiting for %d",
G_STRLOC,
child_pid);
#ifdef HAVE_WAIT4
exit_pid = wait4(child_pid, &exit_status, 0, &rusage);
#else
/* make sure everything is zero'ed out */
memset(&rusage, 0, sizeof(rusage));
exit_pid = waitpid(child_pid, &exit_status, 0);
#endif
g_debug("%s: %d returned: %d",
G_STRLOC,
child_pid,
exit_pid);
if (exit_pid == child_pid) {
/* our child returned, let's see how it went */
if (WIFEXITED(exit_status)) {
g_message("%s: PID=%d exited with %d (%ld kBytes max)",
G_STRLOC,
child_pid,
WEXITSTATUS(exit_status),
rusage.ru_maxrss / 1024);
if (child_exit_status) {
*child_exit_status = WEXITSTATUS(exit_status);
}
if (WEXITSTATUS(exit_status) != EXIT_SUCCESS &&
WEXITSTATUS(exit_status) != EXIT_FAILURE)
{
int time_towait = 2;
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGHUP, SIG_DFL);
while (time_towait > 0) time_towait = sleep(time_towait);
nprocs--;
child_pid = -1;
} else {
return 1;
}
return 1;
} else if (WIFSIGNALED(exit_status)) {
int time_towait = 2;
/* our child died on a signal
*
* log it and restart */
g_critical("%s: PID=%d died on signal=%d (%ld kBytes max)",
G_STRLOC,
child_pid,
WTERMSIG(exit_status),
rusage.ru_maxrss / 1024);
/**
* to make sure we don't loop as fast as we can,
* sleep a bit between restarts
*/
signal(SIGINT, SIG_DFL);
signal(SIGTERM, SIG_DFL);
signal(SIGHUP, SIG_DFL);
while (time_towait > 0) time_towait = sleep(time_towait);
nprocs--;
child_pid = -1;
} else if (WIFSTOPPED(exit_status)) {
} else {
g_assert_not_reached();
}
} else if (-1 == exit_pid) {
/* EINTR is ok, all others bad */
if (EINTR != errno) {
/* how can this happen ? */
g_critical("%s: wait4(%d, ...) failed: %s (%d)",
G_STRLOC,
child_pid,
g_strerror(errno),
errno);
return -1;
}
} else {
g_assert_not_reached();
}
}
}
}

View File

@ -0,0 +1,7 @@
#ifndef __CHASSIS_UNIX_DAEMON_H__
#define __CHASSIS_UNIX_DAEMON_H__
int chassis_unix_proc_keepalive(int *child_exit_status);
void chassis_unix_daemonize(void);
#endif

130
src/glib-ext.c Normal file
View File

@ -0,0 +1,130 @@
#include <glib.h>
#include "glib-ext.h"
#include "sys-pedantic.h"
#include <string.h>
#include "cetus-util.h"
/**
* free function for GStrings in a GHashTable
*/
void g_hash_table_string_free(gpointer data) {
g_string_free(data, TRUE);
}
/**
* common GString free func as a hook
*/
void g_string_true_free(gpointer data) {
g_string_free(data, TRUE);
}
/**
* hash function for GString
*/
guint g_hash_table_string_hash(gconstpointer _key) {
return g_string_hash(_key);
}
/**
* compare function for GString
*/
gboolean g_hash_table_string_equal(gconstpointer _a, gconstpointer _b) {
return g_string_equal(_a, _b);
}
/**
* true-function for g_hash_table_foreach
*/
gboolean
g_hash_table_true(gpointer UNUSED_PARAM(key), gpointer UNUSED_PARAM(value),
gpointer UNUSED_PARAM(u))
{
return TRUE;
}
/**
* duplicate a GString
*/
GString *g_string_dup(GString *src) {
GString *dst = g_string_sized_new(src->len+1);
g_string_assign(dst, src->str);
return dst;
}
/**
* compare two strings (gchar arrays), whose lengths are known
*/
gboolean strleq(const gchar *a, gsize a_len, const gchar *b, gsize b_len) {
if (a_len != b_len) return FALSE;
return (0 == memcmp(a, b, a_len));
}
/**
* calculate the difference between two GTimeVal values, in usec
* positive return value in *tdiff means *told is indeed "earlier" than *tnew,
* negative return value means the reverse
* Caller is responsible for passing valid pointers
*/
void ge_gtimeval_diff(GTimeVal *told, GTimeVal *tnew, gint64 *tdiff) {
*tdiff = (gint64) tnew->tv_sec - told->tv_sec;
*tdiff *= G_USEC_PER_SEC;
*tdiff += (gint64) tnew->tv_usec - told->tv_usec;
}
GString *g_string_assign_len(GString *s, const char *str, gsize str_len) {
g_string_truncate(s, 0);
return g_string_append_len(s, str, str_len);
}
void g_debug_hexdump(const char *msg, const void *_s, size_t len) {
GString *hex;
size_t i;
const unsigned char *s = _s;
hex = g_string_new(NULL);
for (i = 0; i < len; i++) {
if (i % 16 == 0) {
g_string_append_printf(hex, "[%04"G_GSIZE_MODIFIER"x] ", i);
}
g_string_append_printf(hex, "%02x", s[i]);
if ((i + 1) % 16 == 0) {
size_t j;
g_string_append_len(hex, C(" "));
for (j = i - 15; j <= i; j++) {
g_string_append_c(hex, g_ascii_isprint(s[j]) ? s[j] : '.');
}
g_string_append_len(hex, C("\n "));
} else {
g_string_append_c(hex, ' ');
}
}
if (i % 16 != 0) {
/* fill up the line */
size_t j;
for (j = 0; j < 16 - (i % 16); j++) {
g_string_append_len(hex, C(" "));
}
g_string_append_len(hex, C(" "));
for (j = i - (len % 16); j < i; j++) {
g_string_append_c(hex, g_ascii_isprint(s[j]) ? s[j] : '.');
}
}
g_debug("(%s) %"G_GSIZE_FORMAT" bytes:\n %s",
msg,
len,
hex->str);
g_string_free(hex, TRUE);
}

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