mirror of
https://gitee.com/wangbin579/cetus.git
synced 2024-12-01 19:37:38 +08:00
Add files to cetus project
This commit is contained in:
parent
d2b2d5a7ba
commit
0af842272e
282
CMakeLists.txt
Normal file
282
CMakeLists.txt
Normal 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
339
COPYING
Normal 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
343
COPYING.rtf
Normal 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
|
||||
}
|
12
cetus.pc.cmake
Normal file
12
cetus.pc.cmake
Normal 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
|
79
cmake/ChassisInstall.cmake
Normal file
79
cmake/ChassisInstall.cmake
Normal 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
43
cmake/ChassisPlugin.cmake
Normal 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
73
cmake/Tar.cmake
Normal 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
48
config.h.cmake
Normal 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
1
deps/CMakeLists.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
# INCLUDE(deps/libevent.cmake)
|
25
deps/libevent.cmake
vendored
Normal file
25
deps/libevent.cmake
vendored
Normal 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
35
deps/libevent.config.h.cmake
vendored
Normal 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
35
deps/libevent.event-config.h.cmake
vendored
Normal 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
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
191
deps/libevent.evutil.h.cmake
vendored
Normal 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
284
doc/Admin.md
Normal 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
369
doc/Configuration.md
Normal 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
30
doc/Makefile.am
Normal 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
21
doc/proxy.conf.example
Normal 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
20
doc/shard.conf.example
Normal 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
30
doc/sharding.json.example
Normal 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
11
doc/users.json.example
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"users": [{
|
||||
"user": "test1",
|
||||
"client_pwd": "123",
|
||||
"server_pwd": "123456"
|
||||
}, {
|
||||
"user": "test2",
|
||||
"client_pwd": "123456",
|
||||
"server_pwd": "123456"
|
||||
}]
|
||||
}
|
17
doc/variables.json.example
Normal file
17
doc/variables.json.example
Normal 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
90
lib/CMakeLists.txt
Normal 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
12
lib/extra_tokens.inc
Normal 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
298
lib/mylexer.l
Normal 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
1299
lib/myparser.y
Normal file
File diff suppressed because it is too large
Load Diff
1032
lib/simple-parser.y
Normal file
1032
lib/simple-parser.y
Normal file
File diff suppressed because it is too large
Load Diff
407
lib/sql-construction.c
Normal file
407
lib/sql-construction.c
Normal 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
14
lib/sql-construction.h
Normal 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
224
lib/sql-context.c
Normal 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
71
lib/sql-context.h
Normal 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
863
lib/sql-expression.c
Normal 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
317
lib/sql-expression.h
Normal 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
284
lib/sql-filter-variables.c
Normal 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);
|
||||
}
|
20
lib/sql-filter-variables.h
Normal file
20
lib/sql-filter-variables.h
Normal 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
183
lib/sql-operation.c
Normal 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
35
lib/sql-operation.h
Normal 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
147
lib/sql-property.c
Normal 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
46
lib/sql-property.h
Normal 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
12
mysql-chassis.pc.cmake
Normal 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
24
plugins/CMakeLists.txt
Normal 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)
|
36
plugins/admin/CMakeLists.txt
Normal file
36
plugins/admin/CMakeLists.txt
Normal 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
2531
plugins/admin/admin-plugin.c
Normal file
File diff suppressed because it is too large
Load Diff
44
plugins/proxy/CMakeLists.txt
Normal file
44
plugins/proxy/CMakeLists.txt
Normal 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
2481
plugins/proxy/proxy-plugin.c
Normal file
File diff suppressed because it is too large
Load Diff
49
plugins/shard/CMakeLists.txt
Normal file
49
plugins/shard/CMakeLists.txt
Normal 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
2357
plugins/shard/shard-plugin.c
Normal file
File diff suppressed because it is too large
Load Diff
1708
plugins/shard/sharding-parser.c
Normal file
1708
plugins/shard/sharding-parser.c
Normal file
File diff suppressed because it is too large
Load Diff
28
plugins/shard/sharding-parser.h
Normal file
28
plugins/shard/sharding-parser.h
Normal 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__
|
25
scripts/cetus-binwrapper.in
Normal file
25
scripts/cetus-binwrapper.in
Normal 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
949
scripts/proxy-client
Executable 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
188
src/CMakeLists.txt
Normal 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
724
src/cJSON.c
Normal 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
127
src/cJSON.h
Normal 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
18
src/cetus-error.h
Normal 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
259
src/cetus-log.c
Normal 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
70
src/cetus-log.h
Normal 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
509
src/cetus-monitor.c
Normal 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
34
src/cetus-monitor.h
Normal 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
100
src/cetus-query-queue.c
Normal 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
16
src/cetus-query-queue.h
Normal 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
288
src/cetus-users.c
Normal 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
48
src/cetus-users.h
Normal 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
79
src/cetus-util.c
Normal 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
29
src/cetus-util.h
Normal 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
50
src/cetus-variable.c
Normal 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
27
src/cetus-variable.h
Normal 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
45
src/character-set.c
Normal 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
9
src/character-set.h
Normal 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
688
src/chassis-config.c
Normal 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
39
src/chassis-config.h
Normal 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
92
src/chassis-event.c
Normal 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
26
src/chassis-event.h
Normal 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
7
src/chassis-exports.h
Normal file
@ -0,0 +1,7 @@
|
||||
#ifndef _CHASSIS_EXPORTS_
|
||||
#define _CHASSIS_EXPORTS_
|
||||
|
||||
#define CHASSIS_API extern
|
||||
|
||||
#endif
|
||||
|
47
src/chassis-filemode.c
Normal file
47
src/chassis-filemode.c
Normal 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
12
src/chassis-filemode.h
Normal 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
334
src/chassis-frontend.c
Normal 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
134
src/chassis-frontend.h
Normal 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
115
src/chassis-keyfile.c
Normal 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
21
src/chassis-keyfile.h
Normal 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
60
src/chassis-limits.c
Normal 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
15
src/chassis-limits.h
Normal 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
400
src/chassis-log.c
Normal 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
75
src/chassis-log.h
Normal 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
395
src/chassis-mainloop.c
Normal 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
186
src/chassis-mainloop.h
Normal 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
874
src/chassis-options.c
Normal 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
94
src/chassis-options.h
Normal 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
78
src/chassis-path.c
Normal 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
12
src/chassis-path.h
Normal 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
108
src/chassis-plugin.c
Normal 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
102
src/chassis-plugin.h
Normal 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
|
42
src/chassis-shutdown-hooks.c
Normal file
42
src/chassis-shutdown-hooks.c
Normal 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;
|
||||
}
|
||||
}
|
||||
|
28
src/chassis-shutdown-hooks.h
Normal file
28
src/chassis-shutdown-hooks.h
Normal 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
48
src/chassis-timings.c
Normal 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
16
src/chassis-timings.h
Normal 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
216
src/chassis-unix-daemon.c
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
7
src/chassis-unix-daemon.h
Normal file
7
src/chassis-unix-daemon.h
Normal 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
130
src/glib-ext.c
Normal 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
Loading…
Reference in New Issue
Block a user